Three.js中如何优化场景静态元素的渲染性能
Three.js大型静态地图场景性能优化方案
你遇到的性能瓶颈核心是零散静态对象过多导致的Draw Call爆炸、每帧场景图遍历冗余、渲染状态频繁切换,仅设置matrixAutoUpdate = false只能省掉矩阵重算的极小部分开销,没有触达核心问题,按以下优先级做优化即可:
- 第一优先级:合并静态几何体
所有同材质的Plane、Line不要作为独立Mesh/Line对象挂载到场景,使用BufferGeometryUtils.mergeBufferGeometries()将零散几何体合并为单个/少数几个BufferGeometry:- 遍历所有静态地图元素,将每个元素的世界矩阵应用到自身几何体顶点数据上
- 按材质分组,同材质的几何体合并为一个整体
- 用合并后的几何体生成少量Mesh/Line对象挂载到场景,删除原来的零散对象,同时调用
dispose()释放原有几何体、材质占用的内存
参考实现:
这一步做完通常能把Draw Call从几百上千降到个位数,性能会有量级提升。import { BufferGeometryUtils } from 'three/addons/utils/BufferGeometryUtils.js'; // 按材质分组收集静态几何体 const geomGroup = new Map(); oldStaticMapElements.forEach(el => { const matId = el.material.uuid; if (!geomGroup.has(matId)) geomGroup.set(matId, []); const clonedGeom = el.geometry.clone(); clonedGeom.applyMatrix4(el.matrixWorld); // 烘焙世界变换到顶点 geomGroup.get(matId).push(clonedGeom); el.geometry.dispose(); el.material.dispose(); scene.remove(el); }); // 逐组合并生成新的静态地图对象 geomGroup.forEach((geoms, mat) => { const mergedGeom = BufferGeometryUtils.mergeBufferGeometries(geoms); const staticObj = mat.isLineMaterial ? new THREE.LineSegments(mergedGeom, mat) : new THREE.Mesh(mergedGeom, mat); staticObj.matrixAutoUpdate = false; // 地图如果始终占满视野可关闭视锥剔除,否则按网格分块合并保留视锥剔除能力 staticObj.frustumCulled = false; staticObj.renderOrder = 0; // 固定渲染顺序,避免每帧重排 scene.add(staticObj); }); - 第二优先级:场景节点分层隔离
把合并后的静态地图统一放到独立的staticGroup容器,动态车辆模型放到单独的dynamicVehicleGroup。帧循环逻辑中只遍历动态组更新车辆的位置、旋转、矩阵,完全不触碰静态组节点,减少每帧场景图遍历的开销。 - 第三优先级:专项优化线条渲染
不要使用大量独立的THREE.Line对象渲染地图线条:- 同材质线条全部合并到同一个Line几何体中
- 如果需要自定义线条宽度、端角样式,提前将线条烘焙为三角面网格,和地面Plane几何体合并,省掉线条单独的渲染开销
- 非必要不要给线条开透明,透明渲染会带来额外的排序和混合开销
- 第四优先级:静态资源合并
- 地图使用的多张贴图合并为单张纹理图集,减少材质数量,进一步降低Draw Call
- 超大型地图不要合并为单块几何体,按视距做网格分块+LOD,远距离用低模+烘焙贴图,近距离显示高精度几何体,配合视锥剔除只渲染视野范围内的地块
- 所有静态材质关闭不必要的特性:不需要动态光照就把
material.lights设为false,不需要法线贴图、金属度粗糙度计算就用更轻量的MeshBasicMaterial/MeshLambertMaterial
注意:不要给静态对象绑定任何
onBeforeRender/onAfterRender回调,这类回调会在每帧渲染时触发,额外增加开销。
内容的提问来源于stack exchange,提问作者AeroXu
相关产品推荐
相关产品推荐

