简介
最近新入职一家主要做智慧城市的公司,也是我第一次实战使用 three.js ,目前要开发智慧社区类型的项目,学习使用相关 three 的效果,网上资料还不是很全面,该文章简单介绍了我的开发历程,后续会逐步更新实战效果,供大家参考。
开发环境
vue3.2 + vite4.0 + node16.16.0 + three0.145.0
最终效果图
three学习资料
目前最火的 three 教学博主有两位,一位是郭隆邦,一位是老陈打码,两位的课程目前在B站上还是不错的,讲一些基础的视频和一些练习适合入门。两位博主视频网址如下:
- three 资料:threejs.org/manual/#zh/…
- three 工具-在线编辑网址:codesandbox.io/s/three-jsc…
加载天空图
设置天空图还可以加载视频的格式作为背景,也可以准备6张图片按顺序摆好,创建盒子作为6个面贴上,还可以创建一个圆球帖上图片,下面是最简单的一种方法。
const loader = new THREE.TextureLoader();
const texture = loader.load('./skyImg/skys.jpg', () => {
scene.background = texture;
});
设置灯光
灯光可以重复设置多组,也可叠加,创建灯光的参数分别是(参数1:灯光颜色,参数2:颜色深度)
// 环境光
let AmbientLight = new THREE.AmbientLight(0xffffff, 2);
AmbientLight.position.set(0, 10, 0);
scene.add(AmbientLight);
// 平行光
let dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(-5, 10, 20);
scene.add(dirLight)
设置相机
相机的位置可以移动,也可以转换镜头,跟随物体的视角移动,PerspectiveCamera(参数1:摄像机看向物体的倾斜度,参数2:可视区域横纵比,参数3:看到的最近距离,参数4:看到的最远距离)
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 10, 10)
camera.lookAt(new THREE.Vector3(0, 0, 0));
加载精灵图
const Sprite = new THREE.Sprite(
new THREE.SpriteMaterial({
map: new THREE.TextureLoader().load(img),
})
);
// 控制精灵图的缩放比例
Sprite.scale.set(0.3, 0.3, 0.3);
// 控制精灵图的位置( x,y,z), 把某个物体的坐标轴赋值给精灵图,就可以加载到物体上,一起控制
sprite.position.set(10, 10, 0);
scene.add(sprite);
加载视频
const geom = new THREE.BoxGeometry(22, 16, 0.6)
const video = document.createElement("video");
video.src = new URL(
`xxx.mp4`,
import.meta.url
).href;
video.muted = true;
video.autoplay = "autoplay";
video.loop = true;
video.play();
const videoTexture = new THREE.VideoTexture(video);
const videoMaterial = new THREE.MeshBasicMaterial({
map: videoTexture,
});
videoMaterial.position.set(10, 10, 10);
scene.add(new THREE.Mesh(geom, videoMaterial));
在3D场景中添加标签
// 引入
import { CSS2DRenderer, CSS2DObject, } from "three/examples/jsm/renderers/CSS2DRenderer.js";
// 创建标签
const earthDiv = document.createElement('div');
earthDiv.className = 'label';
earthDiv.innerHTML = '地球'
const earthLabel = new CSS2DObject(earthDiv);
earthLabel.position.set(0,1,0);
// 将2d对象添加到地球上
物体模型.add(earthLabel)
// 设置渲染器,这部分代码和renderer放到一起
labelRenderer = new CSS2DRenderer()
labelRenderer.setSize(window.innerWidth,innerHeight);
document.body.appendChild(labelRenderer.domElement);
//设置2d渲染器布局
labelRenderer.domElement.style.position = 'fixed';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.left = '0px';
labelRenderer.domElement.style.zIndex = '0';
//这部分代码和animate放一块
labelRenderer.render(scene,camera);
模型压缩、加载进度
模型过大的话,可以进行压缩,设置loaidng效果,压缩插件推荐2种。
- gltf-pipeline 插件
-
准备工作和 obj 转 gltf 插件一样,切换插件目录下下载依赖,下载 npm install -g gltf-pipeline,准备工作完成,执行命令: gltf-pipeline -i model.gltf -o modelDraco.gltf -d 路径和 obj 转 gltf 一样
-
gltf-pipeline 压缩完,可能会造成材质丢失模糊。
-
结尾使用-d压缩完,需要 draco 解码器编译,改动如下
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';//引入 const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('./public/draco-master/draco/')//存放draco的目录 loader.setDRACOLoader(dracoLoader) loader.load('./public/models/xxx.gltf', (gltf) => { scene.add(gltf.scene); }) -
需要下载 draco, 下载完放在 public文件下,就可以了。
-
draco下载网址:gitee.com/fan_jialong…
- gltfpack插件
- 插件网址:github.com/zeux/meshop…
- 下载完插件,切换到目录下执行 npm install -g gltfpack
- 压缩命令 gltfpack -i male.glb -o male-processed.glb -cc
- 压缩完会产生两个文件,xxx.bin和xxx.gltf,这俩要放到一起。
- 这个不需要插件,改动如下
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js'; loader.setMeshoptDecoder(MeshoptDecoder) // 正常使用..... - 个人使用情况:模型60mb,这俩压缩完大概都在30mb左右,gltfpack插件压缩后的材质要比gltf-pipeline插件好些
- 模型进度
-
所有模型进度条
const manager = new THREE.LoadingManager() manager.onProgress = function (item, loaded, total) { console.log('百分比' + loaded / total * 100); }; const loader = new GLTFLoader(manager); loader.load('url',(gltf)=>{ scene.add(gltf.scene) }) -
单个模型进度条
manager.onProgress = function (item, loaded, total) { console.log('百分比' + loaded / total * 100); }; const loader = new GLTFLoader(manager); loader.load('url',(gltf)=>{ scene.add(gltf.scene) })
-
three销毁
beforeDestroy() {
try {
scene.clear();
renderer.dispose();
renderer.forceContextLoss();
} catch (e) {
console.log(e);
}
},
three 点击模型事件
modelClick(event) {
event.stopPropagation();
event.preventDefault();
let Sx = event.clientX;
let Sy = event.clientY;
let x = (Sx / window.innerWidth) * 2 - 1;
let y = -(Sy / window.innerHeight) * 2 + 1;
let raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera);
let intersects = raycaster.intersectObjects(this.scene.children, true);
let clickMesh = intersects[0] ? intersects[0].object : "";//获取到元素对象,改变点击的模型材质、颜色、位置等
// clickMesh.material.color.set(0x444444),颜色值
// clickMesh.material.opacity = 0,透明度
// 点击模型弹框思路 创建一个盒子,改变盒子的left,top值,Sx对应left,Sy对应top
},
窗口自适应
mounted(){
window.addEventListener("resize", this.onWindowResize, false);
}
onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); //更新投影矩阵
renderer.setSize(window.innerWidth, window.innerHeight);
},
可能会踩的坑
- 模型存放路径,可以放在 public 文件下,如果放在src下面,vue2使用require方法,vue3使用new URL(url, import.meta.url)
- 尽量避开使用obj格式,贴图容易丢失,转动相机,模型忽隐忽现,有可能是需要设置双面材质,或者设置下blog.csdn.net/eevee_1/art…
- 在 vue 里开发的时候,那些场景实例和相机实例等相关实例最好不要放在 data 里,放在 vue 实例外面声明使用,性能会好点,或者可以封装成一个 class 类调用。
- 设置模型透明属性(opacity),需要先设置transparent为true。
- 在项目中发现,使用visible属性隐藏模型后,看不见了,但是点击那片区域还能获取到,很奇怪。
- 后续踩到坑,会持续更新....