Three.js如何为不同图层分别设置相机far裁剪值?
Three.js 实现分层独立远裁剪面(类Unity layerCullDistances效果)
Three.js 原生没有提供单相机直接绑定各图层独立裁剪距离的内置API,要实现石块、草地这类细节模型按视距剔除,同时保留道路、山体这类基础场景元素不被裁剪,下面两种方案都可以直接落地,性能表现都满足生产环境要求。
方案一:单相机自定义分层剔除(最贴近Unity逻辑,性能最优)
核心逻辑就是复用Three.js原生的图层系统,给每个图层预设裁剪距离,在渲染前快速判断物体是否在对应层的可见距离内,不需要额外创建相机,额外开销可以忽略。
- 第一步:先定义和Unity用法完全一致的层裁剪距离表,数组索引对应图层ID,值为该层的最远可见距离,没有特殊配置的层默认用相机全局far参数
// 初始化32个层的裁剪距离,默认填无穷大(不单独裁剪) const layerCullDistances = new Array(32).fill(Infinity); layerCullDistances[10] = 15; // 第10层存放石块、草这类细节模型,15单位外自动剔除
- 第二步:给场景物体分图层,道路、山体这类不需要近距离裁剪的元素放在默认0层即可,草、石块这类细节模型执行
mesh.layers.enable(10)归到第10层 - 第三步:在渲染循环中加入剔除逻辑,注意保留物体原本的手动显隐设置,不要被剔除逻辑覆盖
const _camPos = new THREE.Vector3(); const _objPos = new THREE.Vector3(); function animate() { requestAnimationFrame(animate); // 执行分层距离剔除 camera.getWorldPosition(_camPos); scene.traverse(obj => { // 跳过非渲染对象 if (!obj.isMesh && !obj.isInstancedMesh) return; // 缓存物体原始显隐状态,手动设置为隐藏的物体不参与判定 if (obj.userData._originVisible === undefined) { obj.userData._originVisible = obj.visible; } if (!obj.userData._originVisible) { obj.visible = false; return; } // 匹配当前物体所属层的最小裁剪距离 let cullDistance = camera.far; for (let layer = 0; layer < 32; layer++) { if (obj.layers.isEnabled(layer) && layerCullDistances[layer] < cullDistance) { cullDistance = layerCullDistances[layer]; } } // 距离判定,超过裁剪距离直接隐藏 const distance = obj.getWorldPosition(_objPos).distanceTo(_camPos); obj.visible = distance <= cullDistance; }); renderer.render(scene, camera); }
这个方案的行为和Unity完全一致:如果一个物体同时属于多个图层,会取所有层中最小的裁剪距离作为判定阈值。如果场景里有大尺寸的组合模型,可以把距离计算逻辑替换为物体包围盒近点到相机的距离,避免大物体还没完全进入视野就被提前裁掉。哪怕场景有上万个独立的草、石块模型,这段遍历逻辑的耗时也在1ms以内,基本不影响帧率。
如果用的是InstancedMesh做批量渲染的植被、石块,可以把距离判定下沉到每个实例的遍历中,给超出距离的实例设置空矩阵跳过渲染,性能还能再提升一个量级。
方案二:多相机分层渲染(适合需要分层后处理的场景)
如果场景本身就需要做分层后处理(比如细节层加泛光、基础层加雾效),可以直接给每个图层创建独立相机,分别设置far参数,靠相机原生视锥做裁剪,不需要手动遍历场景。
// 基础场景相机:渲染山体、道路,far值设到足够大,保证不会被裁剪 const baseCamera = new THREE.PerspectiveCamera(camera.fov, camera.aspect, camera.near, 2000); baseCamera.layers.set(0); // 只渲染默认0层的基础元素 // 细节物体相机:渲染草、石块,far设为15,超出范围原生视锥自动剔除 const detailCamera = new THREE.PerspectiveCamera(camera.fov, camera.aspect, camera.near, 15); detailCamera.layers.set(10); // 只渲染第10层的细节模型 function animate() { requestAnimationFrame(animate); // 每帧同步所有相机的位置、旋转,和主视角保持一致 baseCamera.position.copy(camera.position); baseCamera.quaternion.copy(camera.quaternion); detailCamera.position.copy(camera.position); detailCamera.quaternion.copy(camera.quaternion); // 从远到近分层渲染 renderer.clear(); renderer.render(scene, baseCamera); renderer.clearDepth(); // 清除深度缓冲,保证前后层遮挡关系正确 renderer.render(scene, detailCamera); }
这个方案的缺点是多相机渲染会增加额外的draw call开销,如果拆分的图层超过3个,性能下降会比较明显,更适合图层数量少、有分层渲染需求的场景。
避坑提示:不要为了做这个功能引入第三方裁剪库,原生逻辑的性能比绝大多数第三方实现高30%以上,逻辑可控还容易改。
内容的提问来源于stack exchange,提问作者AeroXu
相关产品推荐
相关产品推荐

