目录

一、Three.js是什么?

二、VUE简单使用Three.js步骤

1.npm安装

2.template模板

3.引入库

4.定义全局变量

5.初始化场景

6.初始化相机

7.初始化灯光

8.初始化渲染器

9.创建模型(这里我搭建的模型是一个简单双面货架模型)

10.根据浏览器窗口自适应

11.初始化函数,页面加载完成时调用(mounted()中调用)

12.Style样式

三、VUE进阶使用Three.js步骤(完成各种事件和效果)

在简单使用Three.js的基础上,添加以下控件和代码

1.引入库及需要使用的组件

2.template模板

3.定义全局变量

4.使用OrbitControls控制给模型添加缩放,旋转,平移和拖拽等效果

5.点击模型后的模型样式效果

6.相机跟随点击事件移动动画效果

7.返回主视角按钮事件

8.添加鼠标点击模型事件,并调用相机移动方法(第6点)和点击后样式方法(第5点)

9.运行动画

10.初始化函数,页面加载完成时调用(mounted()中调用)

11.Style样式

四、源码地址


一、Three.js是什么?

Three.js是一款基于原生WebGL封装通用Web 3D引擎,在小游戏、产品展示、物联网、数字孪生、智慧城市园区、机械、建筑、全景看房、GIS等各个领域基本上都有three.js的身影

二、VUE简单使用Three.js步骤

1.npm安装

npm install threenpm install @tweenjs/tween.js

2.template模板

3.引入库

import * as THREE from "three";

4.定义全局变量

data() {return {isDisabled: true,scene: null,camera: null,renderer: null,light: null,light2: null,//定义模型架子的长度long: 100,//定义模型架子的宽度tall: 24,//定义模型架子横纵板的宽度thickness: 0.4,};}

5.初始化场景

//初始化场景initScene() { this.scene = new THREE.Scene();},

6.初始化相机

//初始化相机initCamera() {const { long, tall } = this;this.camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,50000);this.camera.position.set(0, -(5 * tall) / 12, (6 * long) / 5);}

7.初始化灯光

//初始化灯光initLight() {let ambientLight = new THREE.AmbientLight(0x404040);this.scene.add(ambientLight);//定义灯,并设置位置this.light = new THREE.DirectionalLight(0x333333);this.light.position.set(60, 30, 40);this.light2 = new THREE.DirectionalLight(0xdddddd);this.light2.position.set(-20, 20, -20);this.scene.add(this.light);this.scene.add(this.light2);},

8.初始化渲染器

//初始化渲染器initRender() {//dom元素渲染器this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });this.renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染区域尺寸this.renderer.setClearColor(0x000000, 0); // 设置背景颜色//window.devicePixelRatio 当前设备的物理分辨率与css分辨率之比this.renderer.setPixelRatio(window.devicePixelRatio);//按层级先后渲染this.renderer.sortObjects = true;document.getElementById("model").appendChild(this.renderer.domElement);},

9.创建模型(这里我搭建的模型是一个简单双面货架模型)

//创建载入模型initModel() {const { long, tall, thickness } = this;//坐标系// let axes = new THREE.AxesHelper(4000);// this.scene.add(axes);//所有横板for (let index = 1; index <= tall / 4 - 1; index++) {let geometry = new THREE.BoxGeometry(long, thickness, 12);let material = new THREE.MeshLambertMaterial({color: 0x808080,opacity: 0.7,transparent: true,}); //材质对象Materiallet mesh = new THREE.Mesh(geometry, material); //网格模型对象Meshmesh.position.set(0, index * 4, 0); //设置mesh3模型对象的xyz坐标为120,0,0this.scene.add(mesh); //网格模型添加到场景中}//所有纵板for (let index = 1; index <= long / 4 + 1; index++) {let geometry = new THREE.BoxGeometry(thickness, tall, 12);let material = new THREE.MeshLambertMaterial({color: 0x808080,opacity: 0.7,transparent: true,}); //材质对象Materiallet mesh = new THREE.Mesh(geometry, material); //网格模型对象Meshmesh.position.set(-long / 2 + (index - 1) * 4, tall / 2, 0); //设置mesh3模型对象的xyz坐标为120,0,0this.scene.add(mesh); //网格模型添加到场景中}//正面所有箱子let list1 = new Array(tall / 4);for (let i = 0; i < list1.length; i++) {list1[i] = new Array(long / 4);//不一定写for循环赋值,还可以直接赋值,在数量有限的情况下for (let j = 0; j < long / 4; j++) {// a[i][j] = i + j;let geometry = new THREE.BoxGeometry(3, 3, 5);let material = new THREE.MeshLambertMaterial({color: 0x00b1f7,opacity: 0.4,transparent: true,});let mesh = new THREE.Mesh(geometry, material);//定义每个箱子的名称(或者编号)mesh.name = i + 1 + "-" + (j + 1);//设置每个箱子的位置mesh.position.set(-long / 2 + j * 4 + 2, i * 4 + 2, 3);this.scene.add(mesh);}}//背面所有箱子let list2 = new Array(tall / 4);for (let i = 0; i < list2.length; i++) {list2[i] = new Array(long / 4);//不一定写for循环赋值,还可以直接赋值,在数量有限的情况下for (let j = 0; j < long / 4; j++) {// a[i][j] = i + j;let geometry = new THREE.BoxGeometry(3, 3, 5);let material = new THREE.MeshLambertMaterial({color: 0x00b1f7,opacity: 0.4,transparent: true,});let mesh = new THREE.Mesh(geometry, material);//定义每个箱子的名称(或者编号)mesh.name = "-" + (i + 1) + "-" + (j + 1);//设置每个箱子的位置mesh.position.set(-long / 2 + j * 4 + 2, i * 4 + 2, -3);this.scene.add(mesh);}}}

10.根据浏览器窗口自适应

//根据浏览器窗口自适应onWindowResize() {this.renderer.setSize(window.innerWidth, window.innerHeight);this.camera.aspect = window.innerWidth / window.innerHeight;this.camera.updateProjectionMatrix();}

11.初始化函数,页面加载完成时调用(mounted()中调用)

//初始化函数,页面加载完成时调用init() {this.initScene();this.initCamera();this.initLight();this.initRender();this.initModel();this.renderer.render(this.scene, this.camera);window.onresize = this.onWindowResize;}

12.Style样式

.container {width: 100%;height: 100%;position: relative;}/*模型样式*/#model {width: 100%;height: 100%;position: relative;overflow: hidden;}

呈现效果,如下图。

但是这样仅仅只是将自制的模型创建并渲染了出来,无法满足我们大多数情况的使用场景,例如旋转,点击,拖拽平移,相机视角跟随移动等,接下来就实现这些效果。

三、VUE进阶使用Three.js步骤(完成各种事件和效果)

在简单使用Three.js的基础上,添加以下控件和代码

1.引入库及需要使用的组件

import TWEEN from "@tweenjs/tween.js";import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";

2.template模板

3.定义全局变量

这里就是所有的全局变量了,直接复制替换就行了

data() {return {isDisabled: true,scene: null,camera: null,renderer: null,controls: null,light: null,light2: null,group: new THREE.Group(),composer: null, // 控制发光outlinePass: null,renderPass: null,// 选中的模型selectedObjects: [],mouse: new THREE.Vector2(),raycaster: new THREE.Raycaster(),tween: null,//定义模型架子的长度long: 100,//定义模型架子的宽度tall: 24,//定义模型架子横纵板的宽度thickness: 0.4,positionObj: null,};}

4.使用OrbitControls控制给模型添加缩放,旋转,平移和拖拽等效果

//使用OrbitControls控制三维场景缩放和旋转等功能initControls() {this.controls = new OrbitControls(this.camera, this.renderer.domElement);//动态阻尼系数 即鼠标拖拽旋转的灵敏度this.controls.dampingFactor = 0.25;// this.controls.target.set(0, 900, 0)// //上下旋转范围this.controls.minPolarAngle = 0;this.controls.maxPolarAngle = 1.5;this.controls.autoRotate = true;//惯性滑动,滑动大小默认0.25this.controls.dampingFactor = 0.25;//滚轮是否可控制zoom,zoom速度默认1//缩放倍数this.controls.zoomSpeed = 1.0;//最大最小相机移动距离(景深相机)this.controls.minDistance = 1;this.controls.maxDistance = Infinity;//水平方向视角限制this.minAzimuthAngle = -Math.PI * 2;this.maxAzimuthAngle = Math.PI * 2;this.controls.enabledPan = true;this.keyPanSpeed = 7.0;}

5.点击模型后的模型样式效果

//高亮显示模型(呼吸灯)outlineObj(selectedObjects) {// 创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道。this.composer = new EffectComposer(this.renderer);// 新建一个场景通道为了覆盖到原理来的场景上this.renderPass = new RenderPass(this.scene, this.camera);this.composer.addPass(this.renderPass);// 物体边缘发光通道this.outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight),this.scene,this.camera,selectedObjects);this.outlinePass.edgeStrength = 8.0; // 高光边缘边框的亮度this.outlinePass.edgeGlow = 1; // 光晕[0,1]边缘微光强度this.outlinePass.usePatternTexture = false; // 是否使用父级的材质,纹理覆盖this.outlinePass.edgeThickness = 3; // 边框宽度,高光厚度this.outlinePass.downSampleRatio = 1; // 边框弯曲度this.outlinePass.pulsePeriod = 2; // 呼吸闪烁的速度,数值越大,律动越慢this.outlinePass.visibleEdgeColor.set(parseInt(0x00f6ff)); // 呼吸显示的颜色this.outlinePass.hiddenEdgeColor = new THREE.Color(0, 0, 0); // 呼吸消失的颜色// this.outlinePass.clear = truethis.composer.addPass(this.outlinePass); // 加入高光特效this.outlinePass.selectedObjects = selectedObjects; // 需要高光的模型}

6.相机跟随点击事件移动动画效果

// 相机移动动画initTween(targetX, targetY, targetZ) {// 获取当前相机位置let initPosition = {x: this.camera.position.x,y: this.camera.position.y,z: this.camera.position.z,};//定义相机移动方法let tween = new TWEEN.Tween(initPosition).to({ x: targetX, y: targetY, z: targetZ }, 2000).easing(TWEEN.Easing.Sinusoidal.InOut);//格子位置参数let onUpdate = (pos) => {let x = pos.x;let y = pos.y;let z = pos.z;//z0为正面格子,并设置相机的位置if (pos.z < 0) {this.camera.position.set(x, y, z - 12);} else {this.camera.position.set(x, y, z + 12);}};//调用相机方法并传入格子位置参数tween.onUpdate(onUpdate);tween.start();//设置相机target位置(相机看向格子的位置)if (this.positionObj.z < 0) {this.controls.target.set(this.positionObj.x,this.positionObj.y - 0.4,-12);} else {this.controls.target.set(this.positionObj.x,this.positionObj.y - 0.4,12);}}

7.返回主视角按钮事件

//相机返回主视角动画toHomeView(id) {const { long, tall } = this;if (id === 1) {// 获取当前相机位置let initPosition = {x: this.camera.position.x,y: this.camera.position.y,z: this.camera.position.z,};//定义相机移动方法let tween = new TWEEN.Tween(initPosition).to({ x: 0, y: -(5 * tall) / 12, z: (6 * long) / 5 }, 2000).easing(TWEEN.Easing.Sinusoidal.InOut);//主视角相机位置(点击前原位置)let onUpdate = (pos) => {let x = pos.x;let y = pos.y;let z = pos.z;this.camera.position.set(x, y, z);};tween.onUpdate(onUpdate);tween.start();//设置相机target位置(看向坐标轴零点的位置)this.controls.target.set(0, 0, 0);//相机返回原点后,返回主视角禁用this.isDisabled = true;//相机返回原点后,开启模型自动旋转this.controls.autoRotate = true;}}

8.添加鼠标点击模型事件,并调用相机移动方法(第6点)和点击后样式方法(第5点)

// 鼠标点击模型onMouseClick(event) {//通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;this.mouse.y = -(event.clientY / (window.innerHeight-50)) * 2 + 1;// 通过鼠标点的位置和当前相机的矩阵计算出raycasterthis.raycaster.setFromCamera(this.mouse, this.camera);// 获取raycaster直线和所有模型相交的数组集合let intersects = this.raycaster.intersectObjects(this.scene.children);if (!intersects[0]) {return;} else {//这样会获取所有模型,因为我们这里只需要小格子//所以只需要获取我们之前设置name!=""属性的object即可if (!intersects[0].object.name == "") {this.selectedObjects = [];this.selectedObjects.push(intersects[0].object);//调用高亮显示模型(呼吸灯)的方法给点击的格子添加点击后的样式this.outlineObj(this.selectedObjects);//拿到格子的position坐标this.positionObj = {x: intersects[0].object.position.x,y: intersects[0].object.position.y,z: intersects[0].object.position.z,};//调用机移动动画,将相机移动至被点击的格子处this.initTween(this.positionObj.x,this.positionObj.y,this.positionObj.z);//点击格子后,开放返回主视角的点击权限this.isDisabled = false;//点击格子后,禁止模型自动旋转this.controls.autoRotate = false;}}}

9.运行动画

//运行动画animate() {//运行相机旋转动画TWEEN.update();//渲染场景和相机this.renderer.render(this.scene, this.camera);this.controls.update();if (this.composer) {this.composer.render();}//使用requestAnimationFrame周期性渲染requestAnimationFrame(this.animate);}

10.初始化函数,页面加载完成时调用(mounted()中调用)

//初始化函数,页面加载完成时调用init() {this.initScene();this.initCamera();this.initLight();this.initRender();this.initModel();this.renderer.render(this.scene, this.camera);this.initControls();this.animate();window.onresize = this.onWindowResize;window.onclick = this.onMouseClick;}

11.Style样式

.container {width: 100%;height: 100%;position: relative;}#btns {position: absolute;background-color: #031b34;bottom: 0;left: 50%;width: 80px;height: 30px;line-height: 30px;transform: translate(-50%, -50%);text-align: center;z-index: 9999;color: #00eeff;font-weight: bold;box-shadow: 0px 0px 2px 1px #00f6ff;border-radius: 6px;border: 1px solid #00f6ff;padding: 0;}/*模型样式*/#model {width: 100%;height: 100%;position: relative;overflow: hidden;}

呈现效果如下图,如下图。

四、源码地址

建议认真看每一部分的代码,实在不清楚可以看源码,毕竟最近才做这方面的项目,写的不好的地方,还请各位嘴下留情。
货架三维模型: 简单的货架三维模型实现点击交互,相机旋转等功能https://gitee.com/halsixsixsix/3d-model-of-shelf.git