Three.js 视锥体裁剪(Frustum Culling)

654 阅读4分钟

一、什么是视锥体裁剪?

视锥体裁剪是一种在 3D 渲染中提高性能的技术。它的核心思想很简单:只渲染摄像机视野内的物体,而忽略那些不在视野内的物体。这样可以显著减少需要处理的几何体数量,从而提高渲染效率。

在 Three.js 中,视锥体是一个棱台形状的区域,由摄像机的位置、方向和视野范围决定。这个区域包含了所有可以被摄像机看到的物体。

二、视锥体裁剪的工作原理

Three.js 中的视锥体裁剪基于视锥体的六个面:

  • 近平面(Near):离摄像机最近的平面
  • 远平面(Far):离摄像机最远的平面
  • 上平面(Top)
  • 下平面(Bottom)
  • 左平面(Left)
  • 右平面(Right)

视锥体裁剪的基本流程:

  1. 计算视锥体的六个面
  1. 对于场景中的每个物体:
    • 计算物体的边界球(Bounding Sphere)
    • 判断边界球是否在视锥体内部
    • 如果完全在外部,则不渲染该物体

三、Three.js 中视锥体裁剪的实现

在 Three.js 中,视锥体裁剪是自动处理的,但我们也可以手动控制和优化这个过程。

1. 基本场景设置

首先,让我们创建一个基本的 Three.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 geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 设置摄像机位置
camera.position.z = 5;
// 渲染循环
function animate() {
    requestAnimationFrame(animate);
    
    // 更新立方体旋转
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
    
    renderer.render(scene, camera);
}
animate();

2. 禁用自动视锥体裁剪

如果你想手动控制视锥体裁剪,可以禁用物体的自动裁剪:

// 禁用立方体的自动视锥体裁剪
cube.frustumCulled = false;

3. 手动执行视锥体裁剪

下面是一个手动执行视锥体裁剪的示例:

// 创建视锥体对象
const frustum = new THREE.Frustum();
// 创建投影矩阵
const cameraViewProjectionMatrix = new THREE.Matrix4();
// 在渲染循环中手动执行视锥体裁剪
function animate() {
    requestAnimationFrame(animate);
    
    // 更新立方体旋转
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
    
    // 更新视锥体
    camera.updateMatrixWorld(); // 更新摄像机世界矩阵
    cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
    frustum.setFromProjectionMatrix(cameraViewProjectionMatrix);
    
    // 检查立方体是否在视锥体内
    if (frustum.intersectsObject(cube)) {
        // 如果在视锥体内,则渲染
        renderer.render(scene, camera);
    } else {
        // 如果不在视锥体内,可以选择不渲染或执行其他操作
        console.log('立方体不在视锥体内');
    }
}
animate();

4. 处理大量物体的视锥体裁剪

当场景中有大量物体时,手动管理视锥体裁剪可以带来显著的性能提升。下面是一个处理多个物体的示例:

// 创建多个立方体
const cubes = [];
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
for (let i = 0; i < 100; i++) {
    const cube = new THREE.Mesh(geometry, material);
    cube.position.x = Math.random() * 20 - 10;
    cube.position.y = Math.random() * 20 - 10;
    cube.position.z = Math.random() * 20 - 10;
    scene.add(cube);
    cubes.push(cube);
    
    // 禁用自动视锥体裁剪,我们将手动处理
    cube.frustumCulled = false;
}
// 在渲染循环中手动执行视锥体裁剪
function animate() {
    requestAnimationFrame(animate);
    
    // 更新视锥体
    camera.updateMatrixWorld();
    cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
    frustum.setFromProjectionMatrix(cameraViewProjectionMatrix);
    
    // 只渲染在视锥体内的立方体
    for (let i = 0; i < cubes.length; i++) {
        const cube = cubes[i];
        
        // 更新立方体旋转
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
        
        // 检查立方体是否在视锥体内
        if (frustum.intersectsObject(cube)) {
            cube.visible = true;
        } else {
            cube.visible = false;
        }
    }
    
    renderer.render(scene, camera);
}
animate();

四、视锥体裁剪的性能考量

视锥体裁剪在处理大量物体时尤为重要。例如,在一个开放世界游戏中,可能有成千上万的物体,但在任何给定时刻,只有一小部分是可见的。通过视锥体裁剪,可以:

  1. 减少需要处理的几何体数量
  1. 减少需要执行的着色器计算
  1. 降低内存带宽需求

在 Three.js 中,默认情况下所有物体都启用了视锥体裁剪。如果你有特殊需求(如自定义渲染逻辑),可以禁用自动裁剪并实现自己的裁剪算法。

五、总结

视锥体裁剪是 3D 渲染中一项重要的性能优化技术,特别是在处理复杂场景时。Three.js 为我们提供了内置的视锥体裁剪功能,同时也允许我们自定义裁剪逻辑。

通过本教程,你学习了:

  • 视锥体裁剪的基本概念
  • 视锥体的六个面
  • Three.js 中如何启用和禁用自动视锥体裁剪
  • 如何手动实现视锥体裁剪
  • 视锥体裁剪对性能的影响

希望这些知识能帮助你优化 Three.js 应用的性能!