目录
一、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 three
npm install @tweenjs/tween.js
2.template模板
<template>
<div class="container">
<div id="model">
</div>
</div>
</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,
}); //材质对象Material
let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
mesh.position.set(0, index * 4, 0); //设置mesh3模型对象的xyz坐标为120,0,0
this.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,
}); //材质对象Material
let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
mesh.position.set(-long / 2 + (index - 1) * 4, tall / 2, 0); //设置mesh3模型对象的xyz坐标为120,0,0
this.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样式
<style scoped>
.container {
width: 100%;
height: 100%;
position: relative;
}
/*模型样式*/
#model {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
</style>
呈现效果,如下图。
但是这样仅仅只是将自制的模型创建并渲染了出来,无法满足我们大多数情况的使用场景,例如旋转,点击,拖拽平移,相机视角跟随移动等,接下来就实现这些效果。
三、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模板
<template>
<div class="container">
<div id="model"></div>
<button
id="btns"
:style="{ cursor: isDisabled ? '' : 'pointer' }"
:disabled="isDisabled"
type="isDisabled:"
@click="toHomeView(1)"
>
主视角
</button>
</div>
</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.25
this.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 = true
this.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;
//z<0为背面格子,z>0为正面格子,并设置相机的位置
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到1
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / (window.innerHeight-50)) * 2 + 1;
// 通过鼠标点的位置和当前相机的矩阵计算出raycaster
this.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样式
<style scoped>
.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;
}
</style>
呈现效果如下图,如下图。
四、源码地址
建议认真看每一部分的代码,实在不清楚可以看源码,毕竟最近才做这方面的项目,写的不好的地方,还请各位嘴下留情。
货架三维模型: 简单的货架三维模型实现点击交互,相机旋转等功能https://gitee.com/halsixsixsix/3d-model-of-shelf.git