Three.js - 物体碰撞检测(二十六)

在Three.js的3D开发中,物体碰撞检测是不可或缺的核心功能,无论是游戏开发、虚拟仿真还是交互式应用,精准的碰撞检测都能显著提升用户体验。本篇文章作为“Three.js - 物体碰撞检测”系列的第二十六篇,将系统梳理碰撞检测的实现原理、优化技巧,并结合实战案例,帮助开发者快速掌握这一关键技术。

一、碰撞检测的核心原理

碰撞检测的本质是判断两个物体在空间中是否发生重叠或接触。在Three.js中,碰撞检测主要依赖几何体的边界框(BoundingBox)或精确的几何形状(如三角形网格)进行计算。

1. 边界框检测(AABB或OBB)

边界框检测是最基础的碰撞检测方法,通过计算物体的最小包围盒(Axis-Aligned Bounding Box, AABB)或方向包围盒(Oriented Bounding Box, OBB)来判断是否发生碰撞。Three.js中,可以通过Box3类来创建和检测边界框。

  1. // 创建两个物体的边界框
  2. const box1 = new THREE.Box3().setFromObject(mesh1);
  3. const box2 = new THREE.Box3().setFromObject(mesh2);
  4. // 检测边界框是否相交
  5. const isColliding = box1.intersectsBox(box2);

优点:计算速度快,适合大规模场景的初步筛选。
缺点:精度较低,无法检测复杂形状的精确碰撞。

2. 精确几何检测(Raycasting或三角形相交)

对于需要高精度的场景(如角色与地形的交互),可以使用射线投射(Raycasting)或三角形相交算法。Three.js提供了Raycaster类,支持从一点向特定方向发射射线,检测与物体的交点。

  1. const raycaster = new THREE.Raycaster();
  2. const direction = new THREE.Vector3(0, -1, 0); // 向下发射射线
  3. raycaster.set(mesh1.position, direction.normalize());
  4. const intersects = raycaster.intersectObject(mesh2);
  5. if (intersects.length > 0) {
  6. console.log("碰撞发生!");
  7. }

优点:精度高,可检测复杂形状。
缺点:计算量大,需优化以避免性能瓶颈。

二、碰撞检测的优化技巧

在实际开发中,直接使用精确几何检测可能导致性能下降。以下是几种优化策略:

1. 分层检测(Broad Phase + Narrow Phase)

  • Broad Phase(粗略检测):使用边界框或空间分区(如八叉树、四叉树)快速筛选可能碰撞的物体对。
  • Narrow Phase(精确检测):对粗略检测的结果进行精确几何检测。
  1. // 粗略检测示例(使用边界框)
  2. function broadPhase(objects) {
  3. const potentialPairs = [];
  4. for (let i = 0; i < objects.length; i++) {
  5. for (let j = i + 1; j < objects.length; j++) {
  6. const box1 = new THREE.Box3().setFromObject(objects[i]);
  7. const box2 = new THREE.Box3().setFromObject(objects[j]);
  8. if (box1.intersectsBox(box2)) {
  9. potentialPairs.push([objects[i], objects[j]]);
  10. }
  11. }
  12. }
  13. return potentialPairs;
  14. }
  15. // 精确检测示例
  16. function narrowPhase(pairs) {
  17. pairs.forEach(pair => {
  18. const raycaster = new THREE.Raycaster();
  19. // 假设pair[0]发射射线检测pair[1]
  20. // ...
  21. });
  22. }

2. 空间分区(Spatial Partitioning)

将场景划分为多个区域(如网格、八叉树),仅检测同一区域内的物体,减少不必要的计算。

  1. // 简化版网格分区示例
  2. class Grid {
  3. constructor(size, cellSize) {
  4. this.size = size;
  5. this.cellSize = cellSize;
  6. this.cells = new Map();
  7. }
  8. addToGrid(object) {
  9. const box = new THREE.Box3().setFromObject(object);
  10. // 计算物体所在的网格单元
  11. // ...
  12. // 将物体添加到对应单元
  13. }
  14. getPotentialCollisions(object) {
  15. // 返回与当前物体可能碰撞的邻近单元中的物体
  16. // ...
  17. }
  18. }

3. 缓存与复用

缓存边界框或检测结果,避免重复计算。例如,在物体未移动时,复用上一帧的碰撞结果。

三、实战案例:角色与地形的碰撞检测

以下是一个完整的实战案例,演示如何实现角色与地形的碰撞检测:

1. 场景设置

  1. const scene = new THREE.Scene();
  2. const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  3. const renderer = new THREE.WebGLRenderer();
  4. renderer.setSize(window.innerWidth, window.innerHeight);
  5. document.body.appendChild(renderer.domElement);
  6. // 创建地形(使用PlaneGeometry)
  7. const terrainGeometry = new THREE.PlaneGeometry(20, 20);
  8. const terrainMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, side: THREE.DoubleSide });
  9. const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
  10. terrain.rotation.x = -Math.PI / 2;
  11. scene.add(terrain);
  12. // 创建角色(使用SphereGeometry)
  13. const characterGeometry = new THREE.SphereGeometry(1, 32, 32);
  14. const characterMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  15. const character = new THREE.Mesh(characterGeometry, characterMaterial);
  16. character.position.y = 5; // 初始高度
  17. scene.add(character);

2. 碰撞检测逻辑

  1. function checkCollision() {
  2. const raycaster = new THREE.Raycaster();
  3. const direction = new THREE.Vector3(0, -1, 0); // 向下发射射线
  4. raycaster.set(character.position, direction.normalize());
  5. const intersects = raycaster.intersectObject(terrain);
  6. if (intersects.length > 0 && intersects[0].distance < character.geometry.parameters.radius) {
  7. console.log("碰撞发生!角色落地。");
  8. character.position.y = intersects[0].point.y + character.geometry.parameters.radius;
  9. }
  10. }

3. 动画循环

  1. function animate() {
  2. requestAnimationFrame(animate);
  3. // 模拟角色下落
  4. character.position.y -= 0.05;
  5. checkCollision();
  6. renderer.render(scene, camera);
  7. }
  8. animate();

四、总结与建议

  1. 根据场景选择检测方法:简单场景可用边界框,复杂场景需结合分层检测。
  2. 优化性能:使用空间分区、缓存结果,避免每帧全量检测。
  3. 扩展功能:结合物理引擎(如Cannon.js或Ammo.js)实现更真实的碰撞响应。

通过以上方法,开发者可以高效实现Three.js中的物体碰撞检测,为项目增添交互性与沉浸感。