文章目录
- [Three.js 场景性能优化实战:首屏、帧率与内存的工程化治理](#Three.js 场景性能优化实战:首屏、帧率与内存的工程化治理)
-
- 一、先建立性能基线,别盲调
- 二、首屏优化:让用户先看到"能用的画面"
- 三、帧率优化:先砍无效渲染,再谈高级技巧
-
- 1)只在需要时渲染(按需渲染)
- [2)限制像素比,减少 fill-rate 压力](#2)限制像素比,减少 fill-rate 压力)
- [3)减少 draw call,合并可合并对象](#3)减少 draw call,合并可合并对象)
- 四、内存治理:避免"看不见的泄漏"
-
- [1)资源释放 checklist](#1)资源释放 checklist)
- 2)路由切换必须做"退出清理"
- 五、推荐一套线上可落地的优化顺序
- 六、结语
Three.js 场景性能优化实战:首屏、帧率与内存的工程化治理
在我之前的 Three.js 实战文章里,我们已经把"模型能加载"这个问题跑通了。真正上线后,难点往往不在功能,而在稳定性:首屏慢、偶发卡顿、内存持续上涨。
这篇文章给你一套可以直接落地的优化流程,目标很明确:
- 首屏更快(可见内容更早出现)
- 帧率更稳(交互不抖)
- 内存可控(长时间运行不炸)
一、先建立性能基线,别盲调
优化第一步不是改代码,而是先量化。
建议记录三组指标:
- 首屏时间:从页面加载到场景可交互
- 运行帧率:静止场景 FPS、旋转/拖拽时 FPS
- 内存曲线:进入页面 1 分钟、5 分钟、10 分钟后的内存变化
如果没有这三组数据,后面的每个"优化"都可能只是心理安慰。
二、首屏优化:让用户先看到"能用的画面"
1)拆分加载路径:关键资源优先
把资源分成两类:
- 关键资源:首屏必须显示的模型/纹理
- 非关键资源:远处装饰、次要特效、背景细节
先渲染关键资源,非关键资源走异步补齐。这样即便总加载时长不变,用户体感也会明显更快。
2)控制纹理尺寸与格式
纹理是首屏大户。常见问题是"贴图清晰过度",导致下载和上传 GPU 都慢。
建议:
- UI 可见范围内优先使用压缩纹理(KTX2 / Basis)
- 远景贴图降采样
- 对重复使用贴图做缓存复用
3)加载时给出可感知反馈
不要白屏等完成。至少做三件事:
- 进度条(GLTFLoader onProgress)
- 占位场景(简单光照+地面)
- 失败兜底(超时后提示"低配模式")
三、帧率优化:先砍无效渲染,再谈高级技巧
1)只在需要时渲染(按需渲染)
很多页面即便静止也在持续 requestAnimationFrame,白白烧 CPU。
js
let dirty = true;
function markDirty() { dirty = true; }
controls.addEventListener('change', markDirty);
window.addEventListener('resize', markDirty);
function tick() {
if (dirty) {
renderer.render(scene, camera);
dirty = false;
}
requestAnimationFrame(tick);
}
有动画时再持续渲染,静止时按需刷新,通常能立刻降低风扇噪声和功耗。
2)限制像素比,减少 fill-rate 压力
高 DPR 屏幕默认开太大会直接拖慢帧率:
js
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
对大部分业务场景,1.25 ~ 1.5 的上限已经足够清晰。
3)减少 draw call,合并可合并对象
排查思路:
- 看
renderer.info.render.calls - 同材质静态网格优先合并
- 重复对象优先
InstancedMesh
当 draw call 从 2000 降到几百时,帧率通常会有质变。
四、内存治理:避免"看不见的泄漏"
Three.js 不是 GC 全托管。对象从场景移除后,如果不主动释放 GPU 资源,内存会慢慢涨。
1)资源释放 checklist
移除模型时,至少处理:
- geometry.dispose()
- material.dispose()
- texture.dispose()
- render target / postprocessing pass dispose
可复用对象不要反复创建销毁,维护对象池更稳。
2)路由切换必须做"退出清理"
单页应用里最容易泄漏的点是页面切换。
js
function destroyThreeApp() {
scene.traverse((obj) => {
if (obj.geometry) obj.geometry.dispose();
if (obj.material) {
const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
mats.forEach((m) => m.dispose && m.dispose());
}
});
renderer.dispose();
}
同时别忘了:
- 解绑 controls / window 事件
- 停掉动画帧循环
- 清掉缓存引用(Map/数组)
五、推荐一套线上可落地的优化顺序
- 先加监控:首屏/FPS/内存
- 再做首屏分层:关键资源先出图
- 再控帧率成本:像素比 + 按需渲染 + draw call
- 最后做内存收口:统一销毁流程
这个顺序的好处是:每一步都可验证,且回报稳定,不会陷入"改了一堆却看不出提升"。
六、结语
Three.js 性能优化不是玄学,本质就是工程化:
- 有基线
- 有优先级
- 有回归验证
如果你想,我下一篇可以继续写:
《Three.js 大场景分块加载实战:分区、裁剪与可视集管理》,把"场景越大越卡"的问题继续拆解到可执行方案。