在 Three.js 中,遮挡剔除(Occlusion Culling) 和 视锥体剔除(Frustum Culling) 是两种重要的性能优化技术,用于减少渲染的物体数量,提升渲染效率。
视锥体剔除自动移除相机视野之外的物体。Three.js 默认启用此功能。
javascript
// 检查物体是否在视锥体内
const frustum = new THREE.Frustum()
const cameraViewProjectionMatrix = new THREE.Matrix4()
cameraViewProjectionMatrix.multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
)
frustum.setFromProjectionMatrix(cameraViewProjectionMatrix)
const mesh = new THREE.Mesh(geometry, material)
const inFrustum = frustum.intersectsObject(mesh)
javascript
// 禁用单个物体的视锥体剔除
mesh.frustumCulled = false
// 扩展视锥体检测边界(防止物体在边界闪烁)
mesh.geometry.computeBoundingSphere()
mesh.geometry.boundingSphere.radius *= 1.5 // 扩大边界球
javascript
class CustomFrustumCulling {
constructor(camera) {
this.frustum = new THREE.Frustum()
this.camera = camera
}
update() {
this.frustum.setFromProjectionMatrix(
new THREE.Matrix4().multiplyMatrices(
this.camera.projectionMatrix,
this.camera.matrixWorldInverse
)
)
}
isVisible(object) {
const sphere = object.geometry.boundingSphere
if (!sphere) return true
const worldSphere = sphere.clone()
worldSphere.applyMatrix4(object.matrixWorld)
return this.frustum.intersectsSphere(worldSphere)
}
}
Three.js 本身不提供完整的遮挡剔除系统,但可以通过以下方式实现:
javascript
import { WEBGL } from 'three/examples/jsm/WebGL.js'
if (WEBGL.getWebGL2Extensions().occlusion_query) {
// 使用 occlusion query
const occlusionQuery = renderer.getContext().createQuery()
}
javascript
class SimpleOcclusionCulling {
constructor(camera, scene) {
this.camera = camera
this.scene = scene
this.depthMaterial = new THREE.MeshDepthMaterial()
this.renderTarget = new THREE.WebGLRenderTarget(512, 512)
}
checkOcclusion(objects) {
// 渲染深度图
const originalMaterial = objects.map(obj => obj.material)
objects.forEach(obj => obj.material = this.depthMaterial)
renderer.setRenderTarget(this.renderTarget)
renderer.render(this.scene, this.camera)
renderer.setRenderTarget(null)
// 恢复材质
objects.forEach((obj, i) => obj.material = originalMaterial[i])
// 这里可以读取深度缓冲区进行遮挡判断
}
}
javascript
import { Octree } from 'three/examples/jsm/math/Octree.js'
class BVHOcclusionCuller {
constructor() {
this.octree = new Octree()
this.visibleObjects = new Set()
}
buildOctree(objects) {
objects.forEach(obj => {
if (obj.geometry) {
this.octree.add(obj)
}
})
}
update(camera) {
this.visibleObjects.clear()
// 从相机位置发射射线检测可见性
const raycaster = new THREE.Raycaster()
const cameraPos = camera.position
// 采样多个方向
for (let i = 0; i < 100; i++) {
const direction = new THREE.Vector3(
Math.random() * 2 - 1,
Math.random() * 2 - 1,
Math.random() * 2 - 1
).normalize()
raycaster.set(cameraPos, direction)
const intersects = this.octree.raycast(raycaster)
if (intersects.length > 0) {
this.visibleObjects.add(intersects[0].object)
}
}
}
}
javascript
const lod = new THREE.LOD()
// 添加不同细节级别的模型
lod.addLevel(highDetailMesh, 0)
lod.addLevel(mediumDetailMesh, 50)
lod.addLevel(lowDetailMesh, 100)
scene.add(lod)
javascript
let needsRender = true
function checkVisibility() {
needsRender = false
scene.children.forEach(object => {
if (object.visible && camera.frustum.intersectsObject(object)) {
needsRender = true
}
})
}
function animate() {
checkVisibility()
if (needsRender) {
renderer.render(scene, camera)
}
requestAnimationFrame(animate)
}
javascript
class SpatialGrid {
constructor(cellSize) {
this.cellSize = cellSize
this.grid = new Map()
}
addObject(object) {
const key = this.getGridKey(object.position)
if (!this.grid.has(key)) {
this.grid.set(key, [])
}
this.grid.get(key).push(object)
}
getVisibleObjects(cameraFrustum) {
const visible = []
this.grid.forEach((objects, key) => {
// 检查网格单元是否在视锥体内
if (this.isCellVisible(key, cameraFrustum)) {
objects.forEach(obj => {
if (cameraFrustum.intersectsObject(obj)) {
visible.push(obj)
}
})
}
})
return visible
}
}
javascript
// 对大量相同物体使用 InstancedMesh
const instanceCount = 1000
const instancedMesh = new THREE.InstancedMesh(geometry, material, instanceCount)
// 设置每个实例的位置
const matrix = new THREE.Matrix4()
for (let i = 0; i < instanceCount; i++) {
matrix.setPosition(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
)
instancedMesh.setMatrixAt(i, matrix)
}
javascript
// 显示视锥体
const frustumHelper = new THREE.CameraHelper(camera)
scene.add(frustumHelper)
// 显示边界框
const bboxHelper = new THREE.BoxHelper(mesh, 0xffff00)
scene.add(bboxHelper)
// 显示边界球
const sphereHelper = new THREE.SphereHelper(mesh, 0x00ffff)
scene.add(sphereHelper)
权衡开销:复杂的剔除算法可能有 CPU 开销
动态物体:移动物体需要每帧更新剔除状态
透明度处理:透明物体需要特殊处理(按深度排序)
WebGL 限制:某些高级剔除技术需要 WebGL 2.0
首先使用内置视锥体剔除
对静态场景使用烘焙遮挡信息
对大量物体使用空间分区
根据距离使用 LOD
考虑使用 InstancedMesh 减少绘制调用
这些技术可以显著提升 Three.js 应用的性能,特别是在处理复杂场景时。根据具体应用场景选择合适的优化策略。