关于 threejs-errors-performance
面向 Three.js 开发者的性能诊断技能,涵盖渲染变慢排查与 GPU 资源管理,并汇总性能分析和优化方法。
主要功能
- 01renderer.info 与 FPS 诊断
- 02GPU 资源释放指南
- 03绘制调用与几何体优化
- 04LOD 与纹理优化
- 05CPU 与着色器优化
适用场景
- 01排查帧率低或内存持续增长
- 02减少重复网格的绘制调用
- 03场景或模型移除时管理资源
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ impertio-studio/ three.js-claude-skill-package --skill threejs-errors-performance安装后在 Claude Code 中输入/threejs-errors-performance确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitkeepreferences/anti-patterns.mdexamples.mdmethods.md详细介绍
threejs-errors-performance
性能诊断流程图
Scene is slow / low FPS
│
├─ Check renderer.info.render.calls
│ ├─ > 200 draw calls ──────────────── Go to: Draw Call Optimization
│ └─ < 200 draw calls
│
├─ Check renderer.info.memory
│ ├─ geometries/textures growing ──── Go to: Memory Leak Diagnosis
│ └─ stable counts
│
├─ Check renderer.info.render.triangles
│ ├─ > 2M triangles ────────────────── Go to: Geometry Optimization (LOD, merge)
│ └─ < 2M triangles
│
├─ Check GPU load (DevTools Performance tab)
│ ├─ GPU-bound (long GPU tasks) ───── Go to: Shader / Material Optimization
│ └─ CPU-bound (long JS tasks) ────── Go to: CPU Optimization
│
└─ Check Stats.js memory panel
├─ JS heap growing ─────────────── Go to: JavaScript Object Leaks
└─ Heap stable ──────────────────── Profile specific bottleneck
快速参考
renderer.info:你的首个诊断工具
import * as THREE from 'three';
// ALWAYS check renderer.info when diagnosing performance
console.log(renderer.info.render);
// { calls: number, triangles: number, points: number, lines: number, frame: number }
console.log(renderer.info.memory);
// { geometries: number, textures: number }
console.log(renderer.info.programs);
// Array of compiled shader programs (length = unique material combinations)
规则:如果 renderer.info.memory.geometries 或 renderer.info.memory.textures 随时间持续增长,就说明存在内存泄漏。开发期间务必监控这些值。
Stats.js:FPS 和内存监控
import Stats from 'three/addons/libs/stats.module.js';
const stats = new Stats();
stats.showPanel(0); // 0 = FPS, 1 = MS per frame, 2 = MB heap
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
renderer.render(scene, camera);
stats.end();
requestAnimationFrame(animate);
}
性能预算
| 指标 | 目标(60 FPS) | 警告 | 严重 |
|---|---|---|---|
| 绘制调用 | < 100 | 100-500 | > 500 |
| 三角形 | < 1M | 1M-3M | > 3M |
| 纹理(GPU) | < 50 | 50-200 | > 200 |
| 着色器程序 | < 20 | 20-50 | > 50 |
| 帧时间 | < 16.6ms | 16.6-33ms | > 33ms |
内存管理:释放规则
必须释放的内容
Three.js 会分配不会由 JavaScript 自动进行垃圾回收的 GPU 资源。务必手动释放以下资源:
| 对象类型 | 方法 | 释放的 GPU 资源 |
|---|---|---|
BufferGeometry | geometry.dispose() | 顶点/索引缓冲区 |
Material(所有类型) | material.dispose() | 着色器程序、uniform |
Texture(所有类型) | texture.dispose() | GPU 纹理内存 |
WebGLRenderTarget | renderTarget.dispose() | 帧缓冲区 + 纹理 |
WebGLRenderer | renderer.dispose() | 整个 WebGL 上下文 |
PMREMGenerator | pmremGenerator.dispose() | 预过滤环境贴图 |
| 控件(所有类型) | controls.dispose() | DOM 事件监听器 |
完整释放材质
function disposeMaterial(material) {
const textureProps = [
'map', 'lightMap', 'bumpMap', 'normalMap', 'specularMap',
'envMap', 'alphaMap', 'aoMap', 'displacementMap',
'emissiveMap', 'gradientMap', 'metalnessMap', 'roughnessMap',
'clearcoatMap', 'clearcoatNormalMap', 'clearcoatRoughnessMap',
'transmissionMap', 'thicknessMap', 'sheenColorMap', 'sheenRoughnessMap'
];
for (const prop of textureProps) {
if (material[prop]) material[prop].dispose();
}
material.dispose();
}
完整释放场景
function disposeScene(scene) {
scene.traverse((object) => {
if (object.geometry) {
object.geometry.dispose();
}
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach(disposeMaterial);
} else {
disposeMaterial(object.material);
}
}
});
scene.clear();
}
何时释放,何时复用
以下情况务必释放:
- 将对象永久从场景中移除
- 切换到完全不同的场景
- 卸载已加载的模型(GLTF、FBX、OBJ)
- 组件卸载时(React、Vue、Angular)
以下情况切勿释放:
- 临时隐藏对象(改用
visible = false) - 对象之后还会重新添加到场景中
- 多个网格共享同一几何体/材质(仅在所有使用者都不再使用时释放)
绘制调用优化
InstancedMesh:一次调用渲染数千个对象
渲染超过约 100 个相同几何体 + 材质组合的副本时,务必使用 InstancedMesh。
import * as THREE from 'three';
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const count = 5000;
const mesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.position.set(Math.random() * 200 - 100, 0, Math.random() * 200 - 100);
dummy.rotation.y = Math.random() * Math.PI * 2;
dummy.scale.setScalar(0.5 + Math.random());
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
}
// CRITICAL: without this, instances render at origin
mesh.instanceMatrix.needsUpdate = true;
scene.add(mesh);
| 实例数量 | 建议 |
|---|---|
| < 10 | 使用单独的 Mesh 对象即可 |
| 10-100 | 两种方式均可;根据实际情况进行性能分析 |
| 100-10,000 | 务必使用 InstancedMesh |
| > 10,000 | 使用带空间细分的 InstancedMesh 或 BatchedMesh(r160+) |
BatchedMesh(r160+):一次调用渲染多个几何体
BatchedMesh 将实例化扩展到单个绘制调用中支持不同的几何体和材质。适用于重复出现的异构对象。
几何体合并:静态对象
对于绝不独立移动的静态对象,将它们合并为单个几何体:
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
const geometries = meshArray.map((m) => {
const geo = m.geometry.clone();
geo.applyMatrix4(m.matrixWorld);
return geo;
});
const merged = mergeGeometries(geometries, false);
const mergedMesh = new THREE.Mesh(merged, sharedMaterial);
// Dispose originals after merge
meshArray.forEach((m) => {
m.geometry.dispose();
scene.remove(m);
});
规则:切勿合并需要独立变换、材质或射线检测目标的几何体。合并后将无法与单个对象交互。
LOD(细节层次)
import * as THREE from 'three';
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // visible 0-50 units
lod.addLevel(mediumDetailMesh, 50); // visible 50-200 units
lod.addLevel(lowDetailMesh, 200); // visible 200+ units
scene.add(lod);
// ALWAYS call in animation loop for distance-based switching
lod.update(camera);
规则:对于在较大距离范围内可见的对象,务必至少提供 3 个 LOD 层级。每个层级的三角形数量必须至少减少 50%。
纹理优化
| 技术 | 影响 | 适用情况 |
|---|---|---|
| 调整纹理尺寸 | 高 | 务必使用视觉效果可接受的最小分辨率 |
| 2 的幂次尺寸 | 中 | 使用 mipmap 时必需;务必使用 (256、512、1024、2048) |
| 压缩格式(KTX2/Basis) | 高 | 生产环境务必使用;GPU 占用空间可缩小 4-6 倍 |
| 纹理图集 | 高 | 将多个小纹理合并为一个,以减少绘制调用 |
替换时调用 texture.dispose() | 严重 | 分配新纹理前务必释放旧纹理 |
generateMipmaps: false | 低 | 用于 UI 纹理,或绝不需要远距离过滤的纹理 |
KTX2 压缩纹理
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
const ktx2Loader = new KTX2Loader()
.setTranscoderPath('three/addons/libs/basis/')
.detectSupport(renderer);
ktx2Loader.load('texture.ktx2', (texture) => {
material.map = texture;
material.needsUpdate = true;
});
视锥体剔除
默认启用视锥体剔除(object.frustumCulled = true)。渲染器会跳过位于摄像机视野之外的对象。
切勿全局禁用视锥体剔除。 只有对必须始终渲染、无论摄像机朝向如何的特定对象(天空盒、大型粒子系统、全屏后期处理四边形),才设置 frustumCulled = false。
规则:对于 InstancedMesh,视锥体剔除会将整个实例组视为一个包围球。如果实例分布在较大区域,应将它们拆分为空间分组,以实现有效剔除。
对象池
切勿每帧都创建和销毁对象。对于频繁生成和销毁的对象,请使用对象池:
class MeshPool {
constructor(geometry, material, poolSize) {
this.pool = [];
for (let i = 0; i < poolSize; i++) {
const mesh = new THREE.Mesh(geometry, material);
mesh.visible = false;
this.pool.push(mesh);
}
this.activeIndex = 0;
}
acquire() {
if (this.activeIndex >= this.pool.length) return null;
const mesh = this.pool[this.activeIndex++];
mesh.visible = true;
return mesh;
}
release(mesh) {
mesh.visible = false;
const idx = this.pool.indexOf(mesh);
if (idx !== -1 && idx < this.activeIndex) {
[this.pool[idx], this.pool[this.activeIndex - 1]] =
[this.pool[this.activeIndex - 1], this.pool[idx]];
this.activeIndex--;
}
}
}
CPU 端优化
静态对象矩阵优化
对于初始放置后绝不移动的对象:
object.matrixAutoUpdate = false;
object.updateMatrix(); // compute once
这样可以避免渲染器每帧都为静态对象重新计算局部矩阵。
避免在渲染循环中分配内存
// WRONG: creates new Vector3 every frame — GC pressure
function animate() {
const pos = new THREE.Vector3(1, 2, 3); // NEVER allocate in loop
mesh.position.copy(pos);
}
// CORRECT: reuse pre-allocated objects
const _tempVec = new THREE.Vector3();
function animate() {
_tempVec.set(1, 2, 3);
mesh.position.copy(_tempVec);
}
规则:始终在动画循环之外声明临时数学对象(Vector3、Matrix4、Quaternion、Color、Box3)。使用 _ 作为前缀,表示它们是可重复使用的临时变量。
着色器和材质优化
| 操作 | 影响 |
|---|---|
使用 MeshStandardMaterial 代替 MeshPhysicalMaterial | 着色器指令更少,除非需要 clearcoat/transmission/sheen |
| 尽量减少不同材质的数量 | 减少着色器编译次数;相同网格始终共享材质 |
在移动设备上设置 material.precision = 'mediump' | 提升移动 GPU 上的片段着色速度 |
除非必要,否则避免使用 onBeforeCompile | 每项不同的修改都会创建新的着色器变体 |
Chrome DevTools 性能分析
- Performance 选项卡:录制几秒,查找耗时较长的“GPU”任务和 JS 帧持续时间
- Memory 选项卡:在场景更改前后拍摄堆快照,以查找未释放的对象
- 记录
renderer.info:定期添加console.log(renderer.info.memory),以检测内存泄漏 - WebGL Inspector(浏览器扩展):检查绘制调用、纹理和着色器程序
重要警告
切勿在渲染/动画循环中创建 BufferGeometry、Material 或 Texture 对象。这会导致内存无限增长。
切勿在调用 renderer.dispose() 后调用 renderer.render()。此时 WebGL 上下文已被销毁。
切勿在其他网格仍引用共享几何体/材质/纹理时将其释放。应跟踪引用计数,或仅在所有使用方都已移除后再释放。
始终在替换纹理前释放旧纹理:if (material.map) material.map.dispose(); material.map = newTexture;
始终在移除 OrbitControls 或其他控制实例时调用 controls.dispose()。否则会导致 DOM 事件监听器泄漏。
始终在对 InstancedMesh 调用 setMatrixAt() 后设置 instanceMatrix.needsUpdate = true。否则实例会渲染在原点。
参考链接
- references/methods.md —— 释放和性能分析 API 签名
- references/examples.md —— 性能优化代码示例
- references/anti-patterns.md —— 常见性能错误及修复方法
官方来源
- 释放指南:https://threejs.org/docs/#manual/en/introduction/How-to-dispose-of-objects
- WebGLRenderer.info:https://threejs.org/docs/#api/en/renderers/WebGLRenderer
- InstancedMesh:https://threejs.org/docs/#api/en/objects/InstancedMesh
- LOD:https://threejs.org/docs/#api/en/objects/LOD
- BufferGeometryUtils:https://threejs.org/docs/#examples/en/utils/BufferGeometryUtils