Three.js Box3 完全指南:从基础到高级应用

280 阅读4分钟

一、Box3 基础概念

1. 什么是 Box3?

Box3 是 Three.js 中用于表示三维空间中轴对齐边界框(Axis-Aligned Bounding Box, AABB)的类。它由最小点(min)和最大点(max)两个三维向量定义,形成一个与坐标轴对齐的立方体空间。

2. 核心作用

  • 碰撞检测:快速判断物体是否相交
  • 视锥体剔除:优化渲染性能,只渲染可见物体
  • 空间分区:在八叉树等数据结构中作为基本单元
  • 计算包围盒:获取物体的空间范围

二、Box3 基本操作

1. 创建 Box3 对象

import * as THREE from 'three';
// 方法一:创建空的 Box3,后续再设置范围
const box1 = new THREE.Box3();
// 方法二:通过最小点和最大点直接定义
const min = new THREE.Vector3(0, 0, 0);
const max = new THREE.Vector3(10, 10, 10);
const box2 = new THREE.Box3(min, max);
// 方法三:从一组点计算包围盒
const points = [
  new THREE.Vector3(0, 0, 0),
  new THREE.Vector3(5, 10, 5),
  new THREE.Vector3(-5, 5, 5)
];
const box3 = new THREE.Box3().setFromPoints(points);

2. 常用属性和方法

属性

  • min:边界框的最小点坐标(Vector3)
  • max:边界框的最大点坐标(Vector3)

基础方法

// 1. 设置包围盒范围
box.set(min, max);
// 2. 从点集合计算包围盒
box.setFromPoints(points);
// 3. 从对象计算包围盒(需要先计算几何体的边界)
mesh.geometry.computeBoundingBox();
box.setFromObject(mesh);
// 4. 扩展包围盒以包含某个点
box.expandByPoint(new THREE.Vector3(20, 20, 20));
// 5. 扩展包围盒大小
box.expandByScalar(5); // 各方向扩展5个单位
// 6. 收缩包围盒
box.expandByScalar(-2); // 各方向收缩2个单位(注意不要导致min > max)

三、Box3 高级应用

1. 碰撞检测

// 检测两个 Box3 是否相交
const boxA = new THREE.Box3(
  new THREE.Vector3(0, 0, 0),
  new THREE.Vector3(10, 10, 10)
);
const boxB = new THREE.Box3(
  new THREE.Vector3(5, 5, 5),
  new THREE.Vector3(15, 15, 15)
);
if (boxA.intersectsBox(boxB)) {
  console.log("BoxA 与 BoxB 相交");
}
// 检测点是否在 Box3 内
const point = new THREE.Vector3(8, 8, 8);
if (boxA.containsPoint(point)) {
  console.log("点在 BoxA 内部");
}

2. 视锥体剔除优化

// 创建相机视锥体
const frustum = new THREE.Frustum();
const cameraMatrixWorldInverse = new THREE.Matrix4();
// 每一帧更新视锥体
function update() {
  // 更新相机矩阵
  camera.updateMatrixWorld();
  cameraMatrixWorldInverse.copy(camera.matrixWorld).invert();
  
  // 从投影矩阵和相机矩阵计算视锥体
  frustum.setFromProjectionMatrix(
    new THREE.Matrix4().multiplyMatrices(
      camera.projectionMatrix, 
      cameraMatrixWorldInverse
    )
  );
  
  // 检查物体是否在视锥体内
  if (frustum.intersectsBox(mesh.geometry.boundingBox)) {
    // 物体在视锥体内,渲染
    renderer.render(scene, camera);
  } else {
    // 物体不在视锥体内,跳过渲染
    console.log("物体在视锥体外部,跳过渲染");
  }
}

3. 空间查询与分区

// 查找与指定 Box3 相交的所有物体
function findIntersectingObjects(box, objects) {
  const result = [];
  
  objects.forEach(object => {
    // 确保几何体有边界框
    if (object.geometry && !object.geometry.boundingBox) {
      object.geometry.computeBoundingBox();
    }
    
    // 获取物体世界坐标系下的边界框
    const objectBox = object.geometry.boundingBox.clone();
    objectBox.applyMatrix4(object.matrixWorld);
    
    // 检查是否相交
    if (box.intersectsBox(objectBox)) {
      result.push(object);
    }
  });
  
  return result;
}

四、性能优化技巧

1. 缓存边界框

对于静态物体,只需计算一次边界框:

// 初始化时计算一次
mesh.geometry.computeBoundingBox();
const staticBox = mesh.geometry.boundingBox.clone();
// 使用克隆的边界框,避免重复计算
function checkCollision() {
  if (staticBox.intersectsBox(anotherBox)) {
    // 处理碰撞
  }
}

2. 使用层级边界框

对于复杂场景,建立层级边界框结构:

// 为每个组计算边界框
const group = new THREE.Group();
group.add(mesh1, mesh2, mesh3);
// 计算子物体边界框的并集
const groupBox = new THREE.Box3();
group.children.forEach(child => {
  if (child.geometry) {
    child.geometry.computeBoundingBox();
    const childBox = child.geometry.boundingBox.clone();
    childBox.applyMatrix4(child.matrixWorld);
    groupBox.union(childBox);
  }
});

五、常见问题与解决方案

1. 边界框计算不准确

  • 原因:几何体未计算边界框或变换矩阵未更新
  • 解决方案
// 确保几何体计算了边界框
mesh.geometry.computeBoundingBox();
// 如果物体有变换,确保矩阵已更新
mesh.updateMatrixWorld();
// 获取世界坐标系下的边界框
const worldBox = mesh.geometry.boundingBox.clone();
worldBox.applyMatrix4(mesh.matrixWorld);

2. 动态物体边界框更新

  • 解决方案
// 每帧更新动态物体的边界框
function update() {
  // 更新物体变换
  mesh.position.x += 0.1;
  mesh.updateMatrixWorld();
  
  // 更新边界框
  mesh.geometry.computeBoundingBox();
  const worldBox = mesh.geometry.boundingBox.clone();
  worldBox.applyMatrix4(mesh.matrixWorld);
  
  // 进行碰撞检测等操作
  checkCollisions(worldBox);
}

六、实践案例:自定义物理引擎碰撞检测

下面是一个使用 Box3 实现简单碰撞检测的完整示例:

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// 场景初始化
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 控制器
const controls = new OrbitControls(camera, renderer.domElement);
camera.position.z = 20;
// 创建两个立方体
const geometry1 = new THREE.BoxGeometry(5, 5, 5);
const material1 = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube1 = new THREE.Mesh(geometry1, material1);
cube1.position.x = -10;
scene.add(cube1);
const geometry2 = new THREE.BoxGeometry(5, 5, 5);
const material2 = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const cube2 = new THREE.Mesh(geometry2, material2);
cube2.position.x = 10;
scene.add(cube2);
// 为两个立方体创建 Box3 对象
const box1 = new THREE.Box3();
const box2 = new THREE.Box3();
// 动画循环
let direction1 = 0.1;
let direction2 = -0.1;
function animate() {
  requestAnimationFrame(animate);
  
  // 更新立方体位置
  cube1.position.x += direction1;
  cube2.position.x += direction2;
  
  // 边界检查
  if (cube1.position.x > 10 || cube1.position.x < -10) {
    direction1 = -direction1;
  }
  
  if (cube2.position.x > 10 || cube2.position.x < -10) {
    direction2 = -direction2;
  }
  
  // 更新矩阵
  cube1.updateMatrixWorld();
  cube2.updateMatrixWorld();
  
  // 更新边界框
  box1.setFromObject(cube1);
  box2.setFromObject(cube2);
  
  // 检测碰撞
  if (box1.intersectsBox(box2)) {
    material1.color.set(0xffff00);
    material2.color.set(0xffff00);
  } else {
    material1.color.set(0x00ff00);
    material2.color.set(0xff0000);
  }
  
  renderer.render(scene, camera);
}
animate();

这个示例展示了如何使用 Box3 实现两个立方体的碰撞检测,并通过颜色变化直观地显示碰撞状态。

通过掌握 Box3 的这些知识,你可以在 Three.js 项目中实现高效的碰撞检测、视锥体剔除和空间查询等功能,大大提升场景性能和交互体验。