性能优化
3D 应用的性能瓶颈通常在于 Draw Call 过多(CPU 与 GPU 通信开销过大)和 显存溢出。以下是大厂面试必考的核心优化方案:
性能监控与调试手段
不能盲目优化,需要依赖数据说话:
- Stats.js:最基础的性能面板,实时监控 FPS(帧率,须保持 60)和 MS(每帧渲染毫秒数,须保证在 16ms 以内),以及 MB(内存占用)。
- renderer.info:Three.js 自带的渲染器信息对象。通过打印
renderer.info.render.calls可以直接看到当前帧的 Draw Call 数量;通过renderer.info.memory可以查看当前驻留在显存中的几何体和纹理数量,是排查内存泄漏的利器。 - Spector.js:强大的浏览器 WebGL 调试扩展。它可以截取 WebGL 的一帧,逐条列出底层的原生 Draw Call 指令、Shader 编译耗时以及绑定的 Buffer 状态,用于极深度的渲染管线调优。
1. 减少 Draw Call 的核心方案
- 几何体合并 (Geometry Merge):将多个材质相同的静态小物体通过
BufferGeometryUtils.mergeBufferGeometries合并为一个大的 Geometry,只需 1 次 Draw Call。缺点是合并后无法单独控制单个物体的位移。 - 实例化渲染 (InstancedMesh):如果场景中有成千上万个几何体和材质相同,但位置/缩放/颜色不同的物体(如森林中的树、草地、雨滴),必须使用
InstancedMesh。它通过一次 Draw Call 提交一个网格,并在 GPU 侧利用实例属性矩阵完成海量渲染,性能极高。 - 材质/纹理复用:相同的材质(Material)和纹理(Texture)尽量在内存中只实例化一次,然后分配给不同的 Mesh,避免 GPU 频繁切换着色器状态。
- 利用 Shader 替代实体网格(数学绘图):对于大量简单的几何图案(如雷达箭头、光环、同心圆、波纹特效),不要让建模师建出真实的几何体(这会增加大量的顶点和 Draw Call)。最佳实践是只画一个简单的 Plane 平面或全屏 Quad,然后在 Fragment Shader(片元着色器)中利用数学公式(如基于 UV 坐标计算距离场 SDF)直接在 GPU 像素层面“画”出这些图形。这种方案是 0 多边形消耗,且天然支持无限放大不失真。
2. 渲染层面优化
- LOD (Level of Detail):根据相机距离动态切换模型精度。离得近展示高精度模型(几万面),离得远展示低精度模型(几百面)甚至贴图(Billboard),大幅降低 GPU 顶点计算量。
- 视椎体剔除 (Frustum Culling):Three.js 默认开启(
mesh.frustumCulled = true)。不在相机视野范围内的物体不会送给 GPU 渲染。但在包含海量对象的场景下,CPU 逐个计算包围盒是否在视椎体内也是负担,可以配合空间索引(如八叉树 Octree、BVH)做粗粒度剔除。 - 离屏渲染 (OffscreenCanvas):配合 Web Worker,将渲染逻辑和 Three.js 引擎完全剥离到 Worker 线程中执行,避免阻塞主线程的 UI 交互。
3. 模型与资源优化
- 纹理压缩:使用
KTX2/Basis格式的 GPU 压缩纹理。普通 JPG/PNG 在解析后会解压成庞大的位图占用显存,而压缩纹理可以直接以压缩状态保留在显存中供 GPU 读取。 - 模型压缩:使用 Draco 算法压缩 GLTF/GLB 模型,可将体积缩减 70% 以上(利用了 WebAssembly 进行前端极速解码)。
4. 内存与对象管理
Three.js 不会自动清理 GPU 显存!当你从场景中 scene.remove(mesh) 时,它的几何体和材质依然驻留在显存中。
- 及时 dispose():必须显式调用
geometry.dispose()、material.dispose()和texture.dispose()才能彻底释放 WebGL 资源,否则会导致 OOM (Out Of Memory) 崩溃。 - 对象池 (Object Pool):在需要频繁创建和销毁物体的场景(如射击游戏的子弹、下雨特效的雨滴)中,频繁调用
new THREE.Mesh()和dispose()会引发严重的 JS 垃圾回收(GC)停顿(Jank),导致画面卡顿。最佳实践是初始化时预先创建固定数量的对象放入“池”中,需要时取出显示,不需要时隐藏(visible = false),循环复用。
5. 高清文本渲染优化
- SDF (Signed Distance Field) 文字:在 3D 场景中渲染海量文字标签时,传统做法是用 Canvas 2D 绘制文字并转成贴图(Texture)。这种做法不仅极度消耗显存,而且放大后边缘会严重模糊(马赛克)。SDF 算法通过记录像素到文字边缘的距离,配合片元着色器(Shader)进行插值,能够以极低的纹理分辨率渲染出无限放大不失真的锐利文字。在 Three.js 中,大厂通常推荐使用
troika-three-text库来实现工业级的 SDF 文字渲染。