Three.js杂记(十三)—— 包围盒

好久没有学习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';
	}
)
  • 效果:

包围盒计算和获取

接下来需要获取包围盒了,可以先打印查看gltfmodule,会发现并没有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)

可以看到包围盒有maxmin两个属性,通过这两个属性可以得到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);

以上就是当前关于包围盒的基础学习,当然还有碰撞等一系列东西还没学。

相关推荐
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈3 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水4 小时前
简洁之道 - React Hook Form
前端
正小安6 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch7 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光7 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   7 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   7 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web7 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery