mcpField
T
threejs-errors-performance

threejs-errors-performance

github.com/impertio-studio开发23156 次安装30 周前更新
代码仓库

关于 threejs-errors-performance

面向 Three.js 开发者的性能诊断技能,涵盖渲染变慢排查与 GPU 资源管理,并汇总性能分析和优化方法。

主要功能

  1. 01renderer.info 与 FPS 诊断
  2. 02GPU 资源释放指南
  3. 03绘制调用与几何体优化
  4. 04LOD 与纹理优化
  5. 05CPU 与着色器优化

适用场景

  1. 01排查帧率低或内存持续增长
  2. 02减少重复网格的绘制调用
  3. 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 外,安装时一起下载的参考资料和脚本。

.gitkeep
references/
anti-patterns.md
examples.md
methods.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)警告严重
绘制调用< 100100-500> 500
三角形< 1M1M-3M> 3M
纹理(GPU)< 5050-200> 200
着色器程序< 2020-50> 50
帧时间< 16.6ms16.6-33ms> 33ms

内存管理:释放规则

必须释放的内容

Three.js 会分配不会由 JavaScript 自动进行垃圾回收的 GPU 资源。务必手动释放以下资源:

对象类型方法释放的 GPU 资源
BufferGeometrygeometry.dispose()顶点/索引缓冲区
Material(所有类型)material.dispose()着色器程序、uniform
Texture(所有类型)texture.dispose()GPU 纹理内存
WebGLRenderTargetrenderTarget.dispose()帧缓冲区 + 纹理
WebGLRendererrenderer.dispose()整个 WebGL 上下文
PMREMGeneratorpmremGenerator.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 性能分析

  1. Performance 选项卡:录制几秒,查找耗时较长的“GPU”任务和 JS 帧持续时间
  2. Memory 选项卡:在场景更改前后拍摄堆快照,以查找未释放的对象
  3. 记录 renderer.info:定期添加 console.log(renderer.info.memory),以检测内存泄漏
  4. 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。否则实例会渲染在原点。


参考链接

官方来源

评论

支持 Markdown