Vue+Three.js視覺化實戰

語言: CN / TW / HK

theme: geek-black

實戰目的

根據不同的產品配合介面展示相應的描述。根據選擇的場景及其物品實現視覺化的產品展示效果。效果展示

支援不同位置展示不同描述:配合資料配置渲染不同楨的效果

Aug-26-2022 13-32-33.gif

根據選中的產品,切換相應產品效果

Aug-26-2022 13-35-32.gif

根據選中場景,切換相應的場景

Aug-26-2022 13-37-41.gif

實現思路

封裝一個Three的函式,支援設定相機、場景、渲染函式,新增模型解析器,新增物品,整合渲染效果,新增事件監聽,完善模型動畫展示

three.png

具體實現

使用vite搭建一個專案,後安裝Three支援,進行具體實現

準備vue專案

npm 6.x

npm init vite@latest my-vue-app --template vue

npm 7+, 需要額外的雙橫線:

npm init vite@latest my-vue-app -- --template vue

yarn

yarn create vite my-vue-app --template vue

pnpm

pnpm create vite my-vue-app -- --template vue - 根據自己的環境選擇自己的搭建程式碼 npm init vite@latest my-vue-app -- --template vue ``` - 根據提示建立專案

image.png image.png - 確認專案正常訪問

image.png

安裝 Three

npm install --save three

image.png

刪除無用程式碼,新增渲染節點

增加一個場景展示的div,用於渲染3D資訊 image.png

Three 實戰

載入場景及控制器

初始化場景HDR圖片

// 初始化場景 initScene() { this.scene = new THREE.Scene(); this.setEnvMap("001"); } // 場景設定 setEnvMap(hdr) { new RGBELoader().setPath("./hdr/").load(`${hdr}.hdr`, (texture) => { texture.mapping = THREE.EquirectangularRefractionMapping; this.scene.background = texture; this.scene.environment = texture; }); }

確定相機位置

initCamera() { this.camera = new THREE.PerspectiveCamera( 45, // 角度 window.innerWidth / window.innerHeight, // 比例 0.25, // 近 200 // 遠 ); // 相機位置 this.camera.position.set(-1.8, 0.6, 2.7); }

渲染:根據相機位置和場景圖渲染初始畫面

js render() { this.renderer.render(this.scene, this.camera); }

動畫:渲染初始畫面

js animate() { this.renderer.setAnimationLoop(this.render.bind(this)); }

此時,這個頁面只能展示出部分的靜態畫面,要想通過滑鼠控制相機的位置,則需要增加控制器

image.png

引入控制器

// 控制器 initControls() { this.controls = new OrbitControls(this.camera, this.renderer.domElement); }

加入控制器後,則可以通過滑鼠的滑動控制相機的角度

Aug-28-2022 16-42-18.gif

增加產品模型

引入模型解析器

js import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

新增模型到場景裡

js setModel(modelName) { const loader = new GLTFLoader().setPath("/gltf/"); loader.load(modelName, (gltf) => { this.model = gltf.scene.children[0]; this.scene.add(gltf.scene); }); } // 新增模型 async addMesh() { let res = await this.setModel("bag2.glb"); }

模型已經加入到場景裡,但是模型不在場景中間🤔️,模型比較亮,真實物品看不清楚

image.png

列印一下模型解析後的資料,我們可以看到模型有自己的相機場景動畫等資訊,我們可以把當前相應的設定調整成模型的設定

image.png

模型調整

調整相機為模型相機

js setModel(modelName) { ... // 修改相機為模型相機 this.camera = gltf.cameras[0]; ... }

調整後模型位置在畫面中間

image.png

調整場景其他配置

```js // 設定模型 setModel(modelName) { return new Promise((resolve, reject) => { const loader = new GLTFLoader().setPath("./gltf/"); loader.load(modelName, (gltf) => { console.log(gltf); this.model && this.model.removeFromParent(); this.model = gltf.scene.children[0]; if (modelName === "bag2.glb" && !this.dish) { this.dish = gltf.scene.children[5]; // 修改相機為模型相機 this.camera = gltf.cameras[0]; // 呼叫動畫 this.mixer = new THREE.AnimationMixer(gltf.scene.children[1]); this.animateAction = this.mixer.clipAction(gltf.animations[0]); // 設定動畫播放時長 this.animateAction.setDuration(20).setLoop(THREE.LoopOnce); // 設定播放後停止 this.animateAction.clampWhenFinished = true; // 設定燈光 this.spotlight1 = gltf.scene.children[2].children[0]; this.spotlight1.intensity = 1; this.spotlight2 = gltf.scene.children[3].children[0]; this.spotlight2.intensity = 1; this.spotlight3 = gltf.scene.children[4].children[0]; this.spotlight3.intensity = 1;

      // this.scene.add(this.dish);
    }
    this.scene.add(gltf.scene);
    resolve(`${this.modelName}模型新增成功`);
  });
});

} ```

調整引數後的產品展示效果

image.png

定時器和滾輪監聽動畫

```js // 新增定時器 render() { var delta = this.clock.getDelta(); this.mixer && this.mixer.update(delta); this.renderer.render(this.scene, this.camera); }

// 監聽滾輪事件 window.addEventListener("mousewheel", this.onMouseWheel.bind(this));

// 監聽滾輪事件 onMouseWheel(e) { let timeScale = e.deltaY > 0 ? 1 : -1; this.animateAction.setEffectiveTimeScale(timeScale); this.animateAction.paused = false; this.animateAction.play(); if (this.timeoutId) { clearTimeout(this.timeoutId); } this.timeoutId = setTimeout(() => { this.animateAction.halt(0.3); }, 300); } ```

場景和產品模型都新增成功,結合動畫,可以進行產品的預覽

Aug-29-2022 11-26-20.gif

新增視窗監聽事件

調整頁面視窗時,保證場景的全屏展示 // 監聽場景大小改變,調整渲染尺寸 window.addEventListener("resize", this.onWindowResize.bind(this)); // 監聽尺寸 onWindowResize() { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); }

優化載入

模型載入成功後在展示 js constructor(selector, onFinish) { this.onFinish = onFinish; } // 新增物品增加回調函式 async addMesh() { let res = await this.setModel("bag2.glb"); this.onFinish(res); }

增加商品的介紹

根據duration和time 計算當前處於哪部門節點

image.png

image.png js window.addEventListener("mousewheel", (e) => { let duration = data.base3d.animateAction._clip.duration; let time = data.base3d.animateAction.time; let index = Math.floor((time / duration) * 4); data.descIndex = index; });

Aug-29-2022 14-30-12.gif

增加選擇場景和產品

建立資料增加操作的dom

```js

``` image.png

增加操作事件

js function changeModel(prod, pI) { data.pIndex = pI; data.base3d.setModel(prod.modelName); } function changeHdr(scene, index) { data.sceneIndex = index; data.base3d.setEnvMap(scene); }

大功告成

支援不同位置展示不同描述:配合資料配置渲染不同楨的效果

Aug-26-2022 13-32-33.gif

根據選中的產品,切換相應產品效果

Aug-26-2022 13-35-32.gif

根據選中場景,切換相應的場景

Aug-26-2022 13-37-41.gif

總結

  • 通過類的方式建立的方法,能夠很好的儲存了建立過程中3D模型的所具備的屬性和功能,在例項化後,可以很便捷的獲取到相應的屬性
  • 在建立場景/模型時,可以根據要突出的效果調整相應的引數,我們可以認真觀察創建出來的例項物件中包含的屬性和方法,方便我們渲染使用 image.png

參考包/支援