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 [email protected] my-vue-app --template vue

npm 7+, 需要额外的双横线:

npm init [email protected] 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 [email protected] 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

参考包/支持