好久没有学习three.js了,现在再重新巩固并深入学习。荒废学习一年多了,希望现在为时未晚💪
包围盒
包围盒按照字面理解就是包围一个物体的盒子,那就是一个长方形空间。
一般来说,包围盒可以用于:
- 物体之间的碰撞检测。因为物体大多是不规则的型体,如果按照uv坐标去检测,太过耗费性能,所以直接检测两个物体包围盒是否碰撞。
- 物体选择:通过鼠标的点击位置和包围盒来对场景中物体进行选择
- 可视化剔除:在渲染场景时,可以根据相机的位置和视锥体来判断哪些物体在视野内,从而提高渲染性能。
下面以我导入的3D模型为例子,模型的格式为gltf。
这是一个我从爱给网下载的3D模型,可以通过ThreeJs 中的GLTFLoader加载器将此3D模型导入进项目当中。
本项目我使用的是Vue3
+ Vite
的模式,在main.ts
中导入ThreeJs的各个需要的模块,然后设置为全局挂载。
具体到vue组件中,先将需要的渲染器、场景、相机等元素加载进来。根据自己的需求还可以将鼠标控件以及辅助观察的坐标轴添加到场景当中。
以下代码为基础的ThreeJs场景布置效果以及实现代码如下
- 效果:
- 代码:
html
<template>
<!-- <h1>{{ $route.query.name }}</h1> -->
<div class="ThreeCanvas" id="canvas" ref="canvas"></div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance } from "vue";
import { setCameraPos } from "@/store";
const { proxy } = getCurrentInstance() as any;
const { $THREE : THREE, $OrbitControls, $GLTFLoader: GLTFLoader } = proxy;
const scene: THREE.Scene = new THREE.Scene(); // 场景
const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(105, window.innerWidth / window.innerHeight, 0.1, 1000); //相机
const render: THREE.WebGLRenderer = new THREE.WebGLRenderer({ antialias: true }); // 渲染器
let canvas = ref<any>(null);
const controls = new $OrbitControls(camera, render.domElement); // 鼠标控件
// AxesHelper:辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(40);
scene.add(axesHelper);
/** 后续对3D模型导入以及包围盒设置区域
......
**/
function animate() {
controls.update(); //鼠标控制
render.render(scene, camera);
window.requestAnimationFrame(animate);
}
onMounted(()=>{
let { width: canvasW, height: canvasH } = window.getComputedStyle(canvas.value);
render.setSize(parseFloat(canvasW), parseFloat(canvasH));
render.setClearColor('rgb(135,206,250)',1.0);
canvas.value.appendChild(render.domElement);
setCameraPos(camera, 0, 0, 10);
animate();
})
</script>
<style scoped lang="less">
</style>
3D模型导入
然后开始导入3D模型,通过GLTFLoader 加载器创建一个实例。 关于引入文件,在vite
中可以动态化引入资源,使用URL
进行引用,参考文档链接:静态资源处理 | Vite 官方中文文档 (vitejs.cn)
PS:关于资源路径中使用了别名
@
,并且有中文导致中文被new URL
处理后转码,无法找到资源这一问题,我查找了半天都没有找到解决方法。最后只能将路径里的中文名称修改为了英文。let url = new URL('@/assets/img/module/shaolindashi/少林大师.gltf', import.meta.url);
, 提前使用了encodeURI
转换也不成功。
动态引入模型代码:
ts
// 实例化加载器
const gltfLoader = new GLTFLoader();
let url = new URL(`@/assets/img/module/shaolindashi/person.gltf`, import.meta.url);
gltfLoader.load(
// 模型路径
url.href,
(gltf:any)=>{
let module = gltf.scene;
scene.add( module );
module.name = 'shaolin';
}
)
- 效果:
包围盒计算和获取
接下来需要获取包围盒了,可以先打印查看gltf
中module
,会发现并没有geometry
这个属性。这是因为此3D模型是层级结构。
所以接下来需要用到递归遍历方法traverse
,可以递归遍历gltf
所有的模型节点。
- 代码:
ts
// 递归遍历层级模型
module.traverse((child:any)=>{
if (child.isMesh){
const geometry = child.geometry;
// 计算包围盒
geometry.computeBoundingBox();
// 获取包围盒
let personBox = geometry.boundingBox;
console.log(personBox);
}
})
这里需要注意,在获取包围盒之前,需要先计算一次包围盒属性。方法API地址:BufferGeometry -- three.js docs (threejs.org)
可以看到包围盒有max
和min
两个属性,通过这两个属性可以得到3维坐标中两个点,然后得到长方形的盒子模型。
那现在就需要将包围盒展示在眼前了,这里使用辅助对象Box3Helper
。地址:Box3Helper -- three.js docs (threejs.org)
代码:
ts
// 显示包围盒
let boxHelper = new THREE.Box3Helper(personBox, 0xff0000);
scene.add(boxHelper);
效果:
由此,我们就可以看到模型各块集合体的包围盒位置和大小了。
世界矩阵
但是,当你将3D模型的位置和大小改变之后,会发现生成的包围盒位置还是停留在原地,这是就需要通过世界矩阵 进行处理了。在Object3D
中有一个matrixWorld
属性,API地址:Object3D#matrixWorld -- three.js docs (threejs.org)
设置世界矩阵:(这里有一点,此步骤需要在模型位置和大小发生改变之后设置)
ts
// 更新世界矩阵
child.updateWorldMatrix(true, true);
// 更新包围盒
personBox.applyMatrix4(child.matrixWorld);
// 显示包围盒
let boxHelper = new THREE.Box3Helper(personBox, 0xff0000);
scene.add(boxHelper);
当前效果:
几何体居中
在geometry
中,存在center
方法,可以让几何体直接居中。但是因为我的模型是一个组,所以直接让所有geometry
居中是不行的,会让模型错乱。
错乱效果:
这里就只需要一个包围盒,那本身模型是一个
group
组,所以可以自己创建一个new Box3()
实例对象,接下来通过union
合并方法,将内部的child包围盒
计算到大盒子中。API地址:Box3#union -- three.js docs (threejs.org)
实现代码:
ts
// 创建一个大盒子
let bigBox = new THREE.Box3();
// 递归遍历层级模型
module.traverse((child:any)=>{
if (child.isMesh){
const geometry = child.geometry;
// 计算包围盒
geometry.computeBoundingBox();
// 获取包围盒
let personBox = geometry.boundingBox;
// 将包围盒放到大盒子中计算
bigBox.union(personBox);
}
})
console.log(bigBox);
// 显示包围盒
let boxHelper = new THREE.Box3Helper(bigBox, 0xff0000);
scene.add(boxHelper);
接下来可以使用getCenter
方法,获取中心点地址。然后通过设置3D模型位置,调整世界矩阵,就可以让物体居中了。
ts
// 获取大盒子中心点
let centerV3 = bigBox.getCenter(new THREE.Vector3());
console.log(centerV3);
module.position.set(-centerV3.x, -centerV3.y, -centerV3.z);
// 更新世界矩阵
module.updateWorldMatrix(true, true);
bigBox.applyMatrix4(module.matrixWorld);
以上就是当前关于包围盒的基础学习,当然还有碰撞等一系列东西还没学。