赞
踩
提示: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>
<!-- element组件实现弹框效果 -->
<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 指的是场景将被渲染的<canvas> 元素 // 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。