Three.js 开发3D智慧楼宇(Vue)

在VUE项目里使用three.js搭建智慧园区和楼宇,控制空调、灯光、窗帘的开关等设备,实现智能化楼宇。

环境搭建

除了vue项目所需要的基本依赖,可用vue-cli脚手架快速搭建不多介绍,我们还需要再额外安装three.js的一些依赖来开发:

  1. 这边用到了这2两个依赖,一个是整个three.js 的所有资源,一个是three的控件,控制选择缩放的,在package.json内,安装方法:cnpm i -D three three-orbit-controls即可。(这里我用的淘宝镜像,没有cnpm 的可直接npm安装,就是速度很慢。。。)
"three": "^0.122.0",
"three-orbit-controls": "^82.1.0",
  1. three.js 的依赖里面基本包含了three的所有资源,我们直接全部引入即可。因为是ES6模块化形式的开发,我们引入的包都是module形式的。新建一个vue页面,基本代码如下:
<template>
	<div id="box">
		<div id="my-3d"></div>
	</div>
</template>
<script>
	import * as THREE from 'three/build/three.module'
	let OrbitControls = require('three-orbit-controls')(THREE)// 控件,控制缩放旋转的
	import Stats from 'three/examples/jsm/libs/stats.module'// 性能监测,类似游戏的FPS
		export default {
			data() {
				return {
	
				};
			},
		methods: {
			init3D(){
				// 场景
				let scene = new THREE.Scene();

				// 相机设置
				let camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
				camera.position.set(5, 25, 30);
				camera.lookAt(0, 0, 0);

				// 环境光 能保持整体都是亮点
				let ambientLight = new THREE.AmbientLight(0x404040)
				// 点光源 就像灯泡一样的效果  白色灯光 亮度0.6
				let pointLight = new THREE.PointLight(0xffffff, 0.6);

				// 将灯光加入到场景中
				scene.add(ambientLight)
				// 将灯光加到摄像机中 点光源跟随摄像机移动
				// 为什么这样做  因为这样可以让后期处理时的辉光效果更漂亮
				camera.add(pointLight);
				scene.add(camera);

				// 初始化渲染器
				let renderer = new THREE.WebGLRenderer({
					antialias: true,// 开启抗锯齿
					alpha: true
				});

				// 把自动清除颜色缓存关闭 这个如果不关闭 后期处理这块会不能有效显示
				renderer.autoClear = false;
				// 背景透明 配合 alpha
				renderer.setClearColor(0xffffff, 0);
				renderer.setPixelRatio(window.devicePixelRatio);
				renderer.setSize(window.innerWidth, window.innerHeight);
				// 伽马值启动 更像人眼观察的场景
				renderer.gammaInput = true;
				renderer.gammaOutput = true;
				// 坐标轴
				var helper = new THREE.AxesHelper(100);
				scene.add(helper);
				// 容器
				let container = document.getElementById('my-3d');
				container.appendChild(renderer.domElement);
				// 性能监测
				let stats = new Stats();
				container.appendChild(stats.dom);
				
				// 控制器的控件初始化
				let controls;
				const initControls = ()=> {
					controls = new OrbitControls(camera, renderer.domElement);
					controls.enableDamping = false;//是否有惯性
					controls.target.x = 0;
					controls.target.y = 0;
					controls.target.z = 0;
					controls.enableZoom = true;//缩放
					controls.autoRotate = false;//自动旋转
					//设置相机距离原点的最远距离
					controls.minDistance = 4;
					//设置相机距离原点的最远距离
					controls.maxDistance = 400;
					this.minPolarAngle = 0; 
					this.maxPolarAngle = Math.PI / 2;
					controls.enablePan = true;//右键拖拽
				}
				initControls()

				let clock = new THREE.Clock();//声明一个时钟对象
				function render() {
					renderer.render(scene, camera);
					requestAnimationFrame(render);
					controls.update(clock.getDelta())
					stats.update();
				}
				render();
			},
		},
		mounted() {
			this.init3D()
		},
	};
	</script>

3.这样基本就OK了,打开页面只有一个三维坐标轴,这个坐标应该是遵循右手法则,x轴朝右,y轴朝上,z轴朝屏幕前的你。我们没有放其他玩意,放一个坐标轴做辅助参考的。此外, 尽量不要在data return里面定义sceen,camera之类的初始值,这样vue会对值做绑定遍历之类的操作,造成不必要的内存开销。直接在init3D这个函数内定义,变量直接保存在函数内;

加载外部模型

  1. 因为公司项目,肯定是要单独建模,设计师这边建模完毕,用3ds max工具导出指定格式,比如:FBX、OBJ等。这边我们需要解析然后在页面展示出来,在做其他的交互。下面以OBJ格式为例:
  2. 使用3ds max导出来的时候,设计师会贴好材质图片,加好颜色,导出则会这些文件一起导出来,文件大概如下:lu.obj,lu.mtl,maps文件夹里面存放的是图片,也就是贴图。
  3. 在导出来图片这里插入图片描述
  4. 接下来就是模型解析了。我们的obj格式的模型需要使用objloader和mtlloader两个解析器来解析,同时搭配一个ddsloader来配合使用,这些loader不需要额外安装,只需要从node_modules安装包里面引入即可。也就是在three的包,three的包里面基本包括了所有需要的依赖。
	import {OBJLoader} from 'three/examples/jsm/loaders/OBJLoader'
	import {MTLLoader} from 'three/examples/jsm/loaders/MTLLoader'

使用的时候,先解析mtl,在解析obj,我的mtl和obj及贴图都放在/static/model目录下,需要注意一点:
1、所有的文件都不能用中文命名,不然会出现乱码;
2、贴图得图片引用路径一定要正确,不然解析不到。可以在编辑器打开obj/mtl文件查看下路径和名称是不是正确的,路径不支持中文!。

在这里插入图片描述
在这里插入图片描述

即可拿得到解析后的文件:

// 加载外部模型
				function loadBuildings(url){
					let mtlLoader = new MTLLoader();
					mtlLoader.load(`/static/model/${url}.mtl`, function(materials) {
						materials.preload();
						let objLoader = new OBJLoader();
						objLoader.setMaterials(materials);
						objLoader.load(`/static/model/${url}.obj`, function(obj) {
							console.log(obj) // 这个obj就是解析后的模型,类型是group
							scene.add(obj);// 把解析后的模型加入到场景里面

这样检查完毕,就可以看到页面效果了。

OBJ格式转GLTF,并且压缩

网页中最适合的外部引入的格式是GLTF,我们使用3ds max导出来的是obj格式,可以用插件来转换,并且实现超高压缩比。600k => 20k !
1、首先安装obj2gltf;该插件的作用是把obj格式转为gltf格式。下面开始安装,建议全局安装,这样下次在其他文件夹或者目录下都可以用命令行来实现格式转换。

cnpm i -g obj2gltf

2、接着安装gltf-pipeline;该插件的作用是把现有的gltf格式模型进行压缩,跟图片压缩一样。减少大小,更快的加载。

cnpm i -g gltf-pipeline

3、两个都安装成功后,接下来就可以使用了。
在这里插入图片描述
如图:我要对这个lu.obj进行转格式,然后再压缩。(maps文件夹里面是材质图片,隶属于mtl)
输入命令:(参数:-i 是输入路径,-o 是输出路径,其他参数可参考插件官方文档,根据自己的需要添加即可,输入输出的路径一定要正确,不然会报找不到directory之类的错误)

obj2gltf  -i ./static/model/park/lu.obj -o ./static/model/park/after/lu.gltf -u

得到结果:
在这里插入图片描述
4、下面来进行压缩,输入命令:

gltf-pipeline  -i ./static/model/park/after/lu.gltf -o ./static/model/park/after/lu_mini.gltf -d

最后在after文件内会多出lu_mini.gltf 和lu_mini.bin文件。此时lu_mini.gltf是我们最终想要的压缩后的模型文件了。一般文件越大,压缩效果越好。(参数-d 是使用draco压缩算法)
在这里插入图片描述

5、解析GLTF格式模型:
因为刚才转换格式并且压缩了,所以解析要新加一个DRACO压缩算法模型解析器。在顶部解析器再引入两个解析器

	import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'
	import {DRACOLoader} from 'three/examples/jsm/loaders/DRACOLoader'

这里把压缩算法和gltf两个解析器绑定在一起,即可解析压缩后的gltf模型,从而可将模型添加至scene中:

![draco算法所依赖的文件](https://img-blog.csdnimg.cn/20210226140733576.png#pic_center)

// 加载外部模型,转GLTF压缩后的
				function loadMiniModel(index,url) {
					let gltfLoader = new GLTFLoader();
					let dracoLoader = new DRACOLoader();
					dracoLoader.setDecoderPath( '/static/libs/draco/gltf/' );// 这个是加载draco算法,这样才能解析压缩后的gltf模型格式.
					gltfLoader.setDRACOLoader( dracoLoader );
					gltfLoader.load(`/static/model/obj/compress/${url}.gltf`, function(obj) {
						console.log(obj)// 这个obj就是解析后的模型,可添加到scene内。
						scene.add(obj.scene);

这样,格式转换和压缩也做好了。

使用tween.js实现相机视角切换动画、mesh位置改变动画

1、安装tween动画库:

cnpm i -D @tweenjs/tween.js

页面引入:

const TWEEN = require('@tweenjs/tween.js')

在业务代码里调用:(某个mesh挪到参数带来的位置)

function animateCamera(obj,end){
					let option = {// 执行动画的物体的初始位置
						x: obj.position.x,
						y: obj.position.y,
						z: obj.position.z,
					};
					let tween = new TWEEN.Tween(option).to({
							x: end.x,// 结束位置,相机固定cx,cy,cz三个位置,mesh通过传参
							y: end.y,
							z: end.z,
					},2000).onUpdate(function(e){
							obj.position.x = e.x// 这里面是一直在变动的,所以在这里设置位置值,e是参数对象,里面保存着动态的值
							obj.position.y = e.y
							obj.position.z = e.z
					}).delay(100)
					tween.onComplete(function(){// 动画执行完毕的回调
						// this.orbitControl.enabled = true;
						// loadBuildings(3,15,0,0.05, 'new/fan-1','fan')
					})
					tween.easing(TWEEN.Easing.Cubic.InOut);// 动画形式,
					tween.start();// 开始
				}

这样,在其他地方调用animateCamera()这个函数,传入一个mesh对象和一组x,y,z的坐标值即可。

To be continued…

Logo

前往低代码交流专区

更多推荐