提示:demo示例中所涉及到的three.js安装插件方法这里就不单个说明了哈,有需要的网上有很多教程
three.js-example_demo1
提示:这里涉及到的插件:
demo中所有用到都是从网上找的类型为*.stl文件,大家可以从在网站或者three.js的官网(官网three.js-dev下载地址在最后)中扒模型尝试一下。注意:模型文件需要放在vue项目的public或者static文件夹中,否则会找不到文件,vue项目中的public、static、assets文件夹的区别
以下是three.js支持的模型文件类型:
- *.obj
- *.mtl
- *.dae
- *.ctm
- *.ply
- *.stl
- *.wrl
- *.vtk
提示:以下是本篇文章正文内容
<div style="width:100%;height:100%;">
<canvas id="workshop" width="1200px" height="935px">canvas>
<el-dialog title="人员信息" :visible.sync="messageVisible" width="40%" style="z-index: 5000">
<span slot="footer" class="dialog-footer">
<el-button @click="messageVisible = false" style="padding: 12px 20px;">取 消el-button>
span>
el-dialog>
div>
import * as THREE from 'three';
// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";//引入(轨道控制器)可以使得相机围绕目标进行轨道运动
import gsap from 'gsap';//导入动画库
import * as dat from 'dat.gui'// 导入dat.gui
demo为了方便用的是全局定义属性
var condition8 = false;
var renderer = null;
var canvas = null;
let group = new THREE.Group();//用来存放外部引入的模型
const mouse = new THREE.Vector2();
let selectObject, INTERSECTED;
// #region 模型部分
/**工作台 */
var material1 = null;
var mesh1 = null;
var loader = new STLLoader();
/**传送带 */
var material2 = null;
var mesh2 = null;
var loader2 = new STLLoader();
/**传送带2 */
var material3 = null;
var mesh3 = null;
var loader3 = new STLLoader();
/**称重 */
var material4 = null;
var mesh4 = null;
var loader4 = new STLLoader();
/**产品 */
var material5 = null;
var mesh5 = null;
var loader5 = new STLLoader();
/**料架 */
var material6 = null;
var mesh6 = null;
var loader6 = new STLLoader();
/**人员 */
var material7 = null;
var mesh7 = null;
var loader7 = new STLLoader();
/**LED绿 */
var material8 = null;
var mesh8 = null;
var loader8 = new STLLoader();
/**摄像头 */
var material11 = null;
var mesh11 = null;
var loader11 = new STLLoader();
// #endregion
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// const camera = new CinematicCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 设置相机位置
camera.position.set(-10, 5, 10);
// lookAt设置必须统一设置
camera.lookAt(new THREE.Vector3(10,10,20));
// 在场景中添加相机
scene.add(camera);
需要添加监听事件,监听鼠标的状态
/**监听鼠标点击的对象 */
document.addEventListener('dblclick', this.onMouseDblclick, false);
/**监听鼠标移入对象 */
document.addEventListener('mousemove', this.onMouseenter, false);
这里可以举一反三,通过鼠标监听获取当前的mesh对象,并对应做出一些逻辑方法,譬如双击可改变颜色、鼠标移入可使对应的对象变色功能等
/**鼠标双击时 */
onMouseDblclick(event) {
//获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
let intersects = this.getIntersects(event);
console.log('intersects', intersects);
//获取选中最近的Mesh对象
//instance坐标是对象,右边是类,判断对象是不是属于这个类的
if (intersects.length !== 0 && intersects[0].object.type === 'Mesh' && intersects[0].object.name === 'peple01') {
intersects[0].object.material.color.set(0x00FF00);//给选中的模型换成绿色
// console.log(intersects[0].object.material.color);
selectObject = intersects[0].object;
console.log("selectObject", selectObject);//后期可根据position位置判断人的信息
this.messageVisible = true;//显示弹框
} else {
console.log('未选中 人员Mesh!');
}
},
getIntersects(event) {
event.preventDefault();// 阻止默认的点击事件执行, https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault
//console.log("event.clientX:" + event.clientX);
//console.log("event.clientY:" + event.clientY);
//声明 rayCaster 和 mouse 变量
let rayCaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
//通过鼠标点击位置,计算出raycaster所需点的位置,以屏幕为中心点,范围-1到1
mouse.x = ((event.clientX - canvas.getBoundingClientRect().left) / canvas.offsetWidth) * 2 - 1;
mouse.y = -((event.clientY - canvas.getBoundingClientRect().top) / canvas.offsetHeight) * 2 + 1; //这里为什么是-号,没有就无法点中
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
rayCaster.setFromCamera(mouse, camera);
//获取与射线相交的对象数组, 其中的元素按照距离排序,越近的越靠前。
//+true,是对其后代进行查找,这个在这里必须加,因为模型是由很多部分组成的,后代非常多。
let intersects = rayCaster.intersectObjects(group.children, true);
//返回选中的对象
return intersects;
},
/**鼠标移入时 */
onMouseenter(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
},
这里使用watch监听弹框,根据弹框的状态给人物赋值状态
// 监听弹框,若弹框关闭 则返回原来的颜色
messageVisible: {
handler(newName, oldName) {
if (!newName) {
if (selectObject.name == 'peple01') {
material7.color = new THREE.Color(0xe0b884)
}
}
},
deep: true, //为true,表示深度监听,这时候就能监测到a值变化
}
这里以工作台workbench.stl模型为例子,将所有模型都添加至group中,为了方便后期获取物体的材质信息
// 工作台
loadFun1() {
loader= new STLLoader();//用于实现加载器的基类
loader.load('static/stls/workshop/workbench.stl', geometry => {
// 创建材质
material1 = new THREE.MeshLambertMaterial({//材质,一种非光泽表面的材质,没有镜面高光
color: 0xc1c1c1,
// opacity: 1,//设置透明度,默认为1
});
mesh1 = new THREE.Mesh(geometry, material1);//将模型物体和材质加载至mesh中
mesh1.scale.set(-0.004, 0.004, 0.004);//设置模型的大小缩放
mesh1.translateZ(-3)//设置z轴移动位置
// scene.add(mesh1);
group.add(mesh1);//将mesh网格添加至group中
scene.add(group);//将group添加至场景中
});
},
推荐博客:gui工具的使用
这里给了五种常用的功能,如下代码所示。
demo示例中用到了使用dat.gui工具改变物体颜色(是下面代码中的2.)
const gui = new dat.GUI();//创建一个
/**1.移动动画*/
// 0-5范围内,间隔0.01
gui.add(cube.position, "x")
.min(0).max(5)
.step(0.01)
.name("移动x轴")
.onChange((value) => { //移动时会被触发,值被改变时
console.log("值被修改:", value);
}).onFinishChange((value) => {//鼠标抬起时
console.log("完全停下来:", value);
});
/* 2.修改物体的颜色*/
const params = {
color: "#ffff00",
fn: () => {
//让立方体运动起来
gsap.to(cube.position, { x: 5, duration: 2, yoyo: true, repeat: -1 })
}
}
gui.addColor(params, "color").onChange((value) => {
console.log("值被修改:", value);
cube.material.color.set(value); //cube是mesh对象
})
/**3.置选项框*/
gui.add(cube, "visible").name("是否显示");
/**4.设置文件夹 */
var folder = gui.addFolder("设置立方体");
folder.add(cube.material,"wireframe");
/**5.设置按钮点击触发某个事件 */
folder.add(params, "fn").name("让物体运动")
动画库可以使物体自主运动,让页面看着不那么干涩,以下是对gsap.to(此方法用于创建一个从当前属性到指定目标属性的动画对象)对象常用属性介绍。
// 设置动画
/**duration: 5 移到位置需要5秒的时间 ; ease:控制速度(inOut:交叉轨道;out:外部轨道;in:内部轨道) */
// gsap.to(cube.position, { x: 5, duration: 5, ease: "power1.inOut" });
// gsap.to(cube.rotation, { x: 2 * Math.PI, duration: 5, ease: "power1.inOut" })//旋转
/**动画回调函数 */
var animate1 = gsap.to(cube.position, {
x: 5,
duration: 5,//动画时长
ease: "power1.inOut",
repeat: 2, //设置重复次数,无限循环为-1
yoyo: true,//设置往返
delay: 2,//设置延迟2s
onComplete: () => {
console.log("动画完成");
},
onStart: () => {
console.log("动画开始");
}
});
/**鼠标双击监听控制动画的暂停/开始*/
window.addEventListener("dblclick", () => {//监听鼠标双击
console.log("animate1", animate1);
if (animate1.isActive()) {
animate1.pause();//暂停
} else {
animate1.resume();//恢复
}
})
demo中用到的动画库:mesh5.position对象里有x、y、z,这里仅对x和y做了设置
gsap.to(mesh5.position, {
keyframes: [
{
delay: 0,
x: -2,
y: 2.85,
duration: 5,
},
{
delay: 1,
x: 6,
y: 2.85,
duration: 5,
}
],
repeat: 1,//动画执行次数
onComplete: this.myFunction
});
gsap.defaultOverwrite = "auto"
<!-- 工站的例子 -->
<template>
<div style="width:100%;height:100%;">
<canvas id="workshop" width="1200px" height="935px"></canvas>
<!-- 新增车间、产线、工站部分 -->
<el-dialog title="人员信息" :visible.sync="messageVisible" width="40%" style="z-index: 5000">
<span slot="footer" class="dialog-footer">
<el-button @click="messageVisible = false" style="padding: 12px 20px;">取 消</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
// import './js/workStation'
import * as THREE from 'three';
// import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";//引入(轨道控制器)可以使得相机围绕目标进行轨道运动
import gsap from 'gsap';//导入动画库
import * as dat from 'dat.gui'// 导入dat.gui
import { CinematicCamera } from 'three/examples/jsm/cameras/CinematicCamera.js';
// export { Color } from '@/src/assets/math/Color.js';
var condition8 = false;
var renderer = null;
var canvas = null;
let group = new THREE.Group();//用来存外部引入的模型
const mouse = new THREE.Vector2();
let selectObject, INTERSECTED;
// #region
/**工作台 */
var material1 = null;
var mesh1 = null;
var loader = new STLLoader();
/**传送带 */
var material2 = null;
var mesh2 = null;
var loader2 = new STLLoader();
/**传送带2 */
var material3 = null;
var mesh3 = null;
var loader3 = new STLLoader();
/**称重 */
var material4 = null;
var mesh4 = null;
var loader4 = new STLLoader();
/**产品 */
var material5 = null;
var mesh5 = null;
var loader5 = new STLLoader();
/**料架 */
var material6 = null;
var mesh6 = null;
var loader6 = new STLLoader();
/**人员 */
var material7 = null;
var mesh7 = null;
var loader7 = new STLLoader();
/**LED绿 */
var material8 = null;
var mesh8 = null;
var loader8 = new STLLoader();
/**摄像头 */
var material11 = null;
var mesh11 = null;
var loader11 = new STLLoader();
// #endregion
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// const camera = new CinematicCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 设置相机位置
camera.position.set(-10, 5, 10);
// lookAt设置必须统一设置
camera.lookAt(new THREE.Vector3(10,10,20));
// 在场景中添加相机
scene.add(camera);
export default {
components: {},
data() {
return {
messageVisible: false,
};
},
props: {},
computed: {},
mounted() {
/**监听鼠标点击的对象 */
document.addEventListener('dblclick', this.onMouseDblclick, false);
/**监听鼠标移入对象 */
document.addEventListener('mousemove', this.onMouseenter, false);
this.initFun();
this.render();
this.loadFun1();// 工作台
this.loadFun2();// 传送带
this.loadFun3();// 传送带2
this.loadFun4();// 称重
this.loadFun5();// 产品
this.loadFun6();// 料架
this.loadFun7();// 人员
this.loadFun8();// LED
this.loadFun11();// 摄像头
// 监听画面变化,更新渲染画面
window.addEventListener("resize", () => {
console.log("画面变化了");
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影举证
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比
renderer.setPixelRatio(window.devicePixelRatio);
})
},
methods: {
initFun() {
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6) // 创建环境光
scene.add(ambientLight) // 将环境光添加到场景
const spotLight = new THREE.SpotLight(0xffffff) // 创建聚光灯
spotLight.position.set(15, 15, 15)
// spotLight.castShadow = true
scene.add(spotLight);
/**右上角gui配置 */
const gui = new dat.GUI();
/* 修改物体的颜色 == 红色led灯*/
const params = {
color: "#118fdd",
}
gui.addColor(params, "color").onChange((value) => {
mesh8.material.color.set(value);
})
console.log("mesh8", mesh8);
// 初始化渲染器
console.log("document.getElementById('workshop')", document.getElementById('workshop'));
// const renderer = new THREE.WebGLRenderer();
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('workshop')
});
canvas = renderer.domElement;
// // 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);
// // 使用渲染器,通过相机将场景渲染进去
// renderer.render(scene,camera);
/**创建轨道控制器*/
const controls = new OrbitControls(camera, renderer.domElement);//renderer.domElement 指的是场景将被渲染的
// controls.target = new THREE.Vector3(5, 5, 20);
// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5); //用于简单模拟3个坐标轴的对象 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
scene.add(axesHelper);
//网格线绘制
var grid = new THREE.GridHelper(24, 24, 0x444444, 0x888888);
grid.material.opacity = 0.4;
grid.material.transparent = true;
grid.rotation.x = Math.PI;
grid.position.y = -2.5;
scene.add(grid);
let clock = new THREE.Clock();
},
render() {
renderer.render(scene, camera);
// // 渲染下一帧的时候就会渲染render函数
requestAnimationFrame(this.render);//请求动画帧
},
/**鼠标双击时 */
onMouseDblclick(event) {
//获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
let intersects = this.getIntersects(event);
console.log('intersects', intersects);
//获取选中最近的Mesh对象
//instance坐标是对象,右边是类,判断对象是不是属于这个类的
if (intersects.length !== 0 && intersects[0].object.type === 'Mesh' && intersects[0].object.name === 'peple01') {
intersects[0].object.material.color.set(0x00FF00);//给选中的模型换成绿色
// console.log(intersects[0].object.material.color);
selectObject = intersects[0].object;
console.log("selectObject", selectObject);//后期可根据position位置判断人的信息
this.messageVisible = true;//显示弹框
} else {
console.log('未选中 人员Mesh!');
}
},
getIntersects(event) {
event.preventDefault();// 阻止默认的点击事件执行, https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault
//console.log("event.clientX:" + event.clientX);
//console.log("event.clientY:" + event.clientY);
//声明 rayCaster 和 mouse 变量
let rayCaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
//通过鼠标点击位置,计算出raycaster所需点的位置,以屏幕为中心点,范围-1到1
mouse.x = ((event.clientX - canvas.getBoundingClientRect().left) / canvas.offsetWidth) * 2 - 1;
mouse.y = -((event.clientY - canvas.getBoundingClientRect().top) / canvas.offsetHeight) * 2 + 1; //这里为什么是-号,没有就无法点中
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
rayCaster.setFromCamera(mouse, camera);
//获取与射线相交的对象数组, 其中的元素按照距离排序,越近的越靠前。
//+true,是对其后代进行查找,这个在这里必须加,因为模型是由很多部分组成的,后代非常多。
let intersects = rayCaster.intersectObjects(group.children, true);
//返回选中的对象
return intersects;
},
/**鼠标移入时 */
onMouseenter(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
},
// 工作台
loadFun1() {
loader.load('static/stls/workshop/workbench.stl', geometry => {
// 创建材质
material1 = new THREE.MeshLambertMaterial({
color: 0xc1c1c1,
// opacity: 1,//设置透明度
});
mesh1 = new THREE.Mesh(geometry, material1);
mesh1.scale.set(-0.004, 0.004, 0.004);
mesh1.translateZ(-3)
// scene.add(mesh1);
group.add(mesh1)
scene.add(group);
});
},
// 传送带
loadFun2() {
loader2.load('static/stls/workshop/FB15.stl', geometry => {
// 创建材质
material2 = new THREE.MeshLambertMaterial({
color: 0x002b37,
// opacity:1,//设置透明度
});
mesh2 = new THREE.Mesh(geometry, material2);
mesh2.scale.set(-0.02, 0.004, 0.004);
mesh2.translateZ(1.5)
mesh2.translateY(2.5)
// scene.add(mesh2);
group.add(mesh2)
scene.add(group);
});
},
// 传送带2
loadFun3() {
loader3.load('static/stls/workshop/FB15.stl', geometry => {
// 创建材质
material3 = new THREE.MeshLambertMaterial({ color: 0x002b37 });
mesh3 = new THREE.Mesh(geometry, material3);
mesh3.scale.set(-0.02, 0.004, 0.004);
mesh3.translateZ(3.5)
mesh3.translateY(2.5)
// scene.add(mesh3);
group.add(mesh3)
scene.add(group);
});
},
// 称重
loadFun4() {
loader4.load('static/stls/workshop/SCALE.stl', geometry => {
// 创建材质
material4 = new THREE.MeshLambertMaterial({ color: 0x00d6e1 });
mesh4 = new THREE.Mesh(geometry, material4);
mesh4.rotation.y = 0.5 * Math.PI;
mesh4.scale.set(-0.009, 0.02, 0.009);
mesh4.translateY(1)
mesh4.translateX(-2.3)
// scene.add(mesh4);
// material4.visible =false;
console.log("material4", material4);
group.add(mesh4)
scene.add(group);
});
},
// 产品
loadFun5() {
loader5.load('static/stls/workshop/product.stl', geometry => {
// 创建材质
material5 = new THREE.MeshLambertMaterial({
color: 0x28496f,
opacity: 0.4,
transparent: true,
});
console.log("material5",material5);
mesh5 = new THREE.Mesh(geometry, material5);
// mesh.rotation.y = 0.5 * Math.PI;
mesh5.scale.set(-0.02, 0.009, 0.009);
mesh5.translateZ(2.5)
mesh5.translateY(2.85)
mesh5.translateX(-5)
// scene.add(mesh5);
group.add(mesh5)
scene.add(group);
// 添加动画
console.log("mesh5===", mesh5);
console.log("mesh5.position", mesh5.position);
gsap.to(mesh5.position, {
keyframes: [
{
delay: 0,
x: -2,
y: 2.85,
duration: 5,
},
{
delay: 1,
x: 6,
y: 2.85,
duration: 5,
}
],
repeat: 1,//动画执行次数
onComplete: this.myFunction
});
gsap.defaultOverwrite = "auto"
});
},
myFunction() {
console.log("运动结束啦");
},
// 料架
loadFun6() {
loader6.load('static/stls/workshop/Warehouse.stl', geometry => {
// 创建材质
material6 = new THREE.MeshLambertMaterial({ color: 0x91b9f7 });
mesh6 = new THREE.Mesh(geometry, material6);
mesh6.rotation.x = 1.5 * Math.PI;
mesh6.scale.set(-0.002, 0.002, 0.002);
mesh6.translateZ(-2)
mesh6.translateY(-62.5) //z轴
mesh6.translateX(-10.65)//x轴
// scene.add(mesh6);
group.add(mesh6)
scene.add(group);
});
},
// 人员
loadFun7() {
loader7.load('static/stls/workshop/man.stl', geometry => {
// 创建材质
material7 = new THREE.MeshLambertMaterial({ color: 0xe0b884 });
mesh7 = new THREE.Mesh(geometry, material7);
mesh7.rotation.y = Math.PI;
mesh7.scale.set(-0.003, 0.003, 0.003);
mesh7.translateZ(-5)
mesh7.translateY(1)
mesh7.name = "peple01"
console.log("mesh7", mesh7);
// scene.add(mesh7);
group.add(mesh7)
scene.add(group);
});
},
// LED
loadFun8() {
loader8 = new STLLoader();
loader8.load('static/stls/workshop/led.stl', geometry => {
// 创建材质
// if (condition8) {
// material8 = new THREE.MeshPhongMaterial({ color: 0x66ff65 });
// } else {
// material8 = new THREE.MeshPhongMaterial({ color: 0x2ece06 });
// }
material8 = new THREE.MeshLambertMaterial({ color: 0x29db6f });
mesh8 = new THREE.Mesh(geometry, material8);
console.log("mesh8",mesh8);
mesh8.rotation.y = 0.5 * Math.PI;
mesh8.scale.set(-0.08, 0.08, 0.08);
mesh8.translateZ(3.3)//x
mesh8.translateY(6.2) //y
mesh8.translateX(0.5)//z
setTimeout(() => {
this.getMeshFun(material8);
}, 2000);
scene.add(mesh8);
group.add(mesh8)
scene.add(group);
});
},
getMeshFun(data) {
// material8.color = "0x118fdd"
// 创建颜色对象
const color = new THREE.Color(0x118fdd) //默认是纯白色0xffffff
material8.color = color;
// return 0x118fdd
},
// 摄像头
loadFun11() {
loader11.load('static/stls/workshop/camera.stl', geometry => {
// 创建材质
material11 = new THREE.MeshLambertMaterial({ color: 0x858585 });
mesh11 = new THREE.Mesh(geometry, material11);
mesh11.rotation.x = -0.25 * Math.PI;
mesh11.scale.set(-0.009, 0.009, 0.009);
mesh11.translateZ(4)
mesh11.translateY(3.5)
mesh11.translateX(0)
// scene.add(mesh11);
group.add(mesh11)
scene.add(group);
});
},
},
watch: {
// 监听弹框,若弹框关闭 则返回原来的颜色
messageVisible: {
handler(newName, oldName) {
if (!newName) {
if (selectObject.name == 'peple01') {
material7.color = new THREE.Color(0xe0b884)
}
}
},
deep: true, //为true,表示深度监听,这时候就能监测到a值变化
}
}
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
}
</style>
到此本篇正文就结束啦,内容太多,我已经尽量在代码里注释了,讲的不详细之处请多多包涵
我喜欢研究three.js官网给的一些demo,觉得很有意思,而且可以直接下载项目下来琢磨,看他们封装的代码都很高级,也会学到不少,我的这个demo看着太low了,之后若是有时间写出超级酷的3D页面,就再花一下午时间写个博客分享记录吧。
附上three.js的官网源码:three.js-dev