关于 threejs-perf
threejs-perf 是一套面向 Three.js 游戏的性能优化技能,提供静态和移动对象的实例化模式及基准测试数据。适合重复物体或大量实体导致绘制调用和场景更新开销较高的项目。
主要功能
- 01静态重复对象使用 InstancedMesh
- 02批量更新移动实体变换
- 03提供基准实现与优化对照
- 04说明实例化的适用条件
适用场景
- 01优化大量重复场景道具
- 02降低大量移动实体的绘制调用
- 03排查场景图遍历导致的瓶颈
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ playableintelligence/ game-creator --skill threejs-perf安装后在 Claude Code 中输入/threejs-perf确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
instancing-moving.mdinstancing-static.mdtemplates/moving-entities-baseline.tsmoving-entities-instanced.tsstatic-world-baseline.tsstatic-world-instanced.ts详细介绍
Three.js 性能优化
Three.js 游戏的性能模式,在 Three.js r183 上通过测量优化前后的数据验证(通过 Playwright 使用无头 Chromium,在 Apple M1 Pro 上运行软件 WebGL)。
参考文件
instancing-static.md— 使用 InstancedMesh 处理大量静态重复对象(19,600 → 1 次绘制调用)instancing-moving.md— 使用扁平状态缓冲区和批量 InstancedMesh 写入处理移动实体(8,000 个实体)templates/— 每种模式的基线与优化后参考实现
何时使用此技能
- 场景中有 100 个以上共享几何体/材质的重复对象
- 绘制调用超过 500 次,且帧时间不稳定
- 每帧需要更新数千个移动实体的变换
- 性能分析显示场景图遍历是瓶颈
何时不使用
- 对象数量较少(少于 50 个独立网格)——简洁的代码更有优势
- 每个对象都需要独特的材质/着色器,无法进行批处理
- 几何体差异过大,实例化无法带来批处理收益
模式 1:实例化大量静态对象
问题:将森林、碎片、装饰物分别作为 Mesh 会产生不必要的绘制调用。
解决方案:每种共享的几何体+材质组合使用一个 InstancedMesh。
证据:约 19,365 → 2 次绘制调用。渲染 CPU p95:28.5ms → 0.5ms(快约 57 倍)。构建:39.4ms → 3.9ms。参见 instancing-static.md。
// Anti-pattern: one Mesh per prop
for (let i = 0; i < 19600; i++) {
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, 0, z);
scene.add(mesh); // 19,600 draw calls
}
// Correct: one InstancedMesh
const im = new THREE.InstancedMesh(geometry, material, 19600);
const mat = new THREE.Matrix4();
for (let i = 0; i < 19600; i++) {
mat.makeTranslation(x, 0, z);
im.setMatrixAt(i, mat);
}
im.instanceMatrix.needsUpdate = true;
scene.add(im); // 1 draw call
模式 2:移动实体更新循环
问题:将数千个移动角色分别作为 Mesh 会导致场景图频繁变动,并传播变换。
解决方案:使用扁平实体状态缓冲区,并批量写入 InstancedMesh.setMatrixAt()。
证据:8,000 → 1 次绘制调用。渲染 CPU p95:9.9ms → 0.5ms(快约 20 倍)。更新循环 p95:1.4ms → 0.3ms。参见 instancing-moving.md。
// Anti-pattern: per-entity Mesh position writes
meshes.forEach((mesh, i) => {
mesh.position.x = computeX(i, tick);
mesh.position.y = computeY(i, tick);
});
// Correct: batched instance matrix writes
const mat = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
mat.makeTranslation(computeX(i, tick), computeY(i, tick), computeZ(i, tick));
instancedMesh.setMatrixAt(i, mat);
}
instancedMesh.instanceMatrix.needsUpdate = true;
决策树
Is the object repeated 50+ times with same geometry+material?
├── YES → Is it static (no per-frame movement)?
│ ├── YES → Pattern 1: Static InstancedMesh (instancing-static.md)
│ └── NO → Pattern 2: Moving InstancedMesh with batched writes (instancing-moving.md)
└── NO → Standard Mesh is fine. Focus on material/geometry reuse.
测量结果
使用 Playwright 通过无头 Chromium 147 测试,Three.js r183,Apple M1 Pro,预热 30 帧并采样 180 帧,取 3 次运行的中位数。
| 场景 | 指标 | 基线 | 优化后 | 提升 |
|---|---|---|---|---|
| 静态世界(19.6k 个立方体) | 绘制调用 | ~19,365 | 2 | ~9,682 倍 |
| 静态世界(19.6k 个立方体) | 渲染 CPU p95 | 28.5ms | 0.5ms | ~57 倍 |
| 静态世界(19.6k 个立方体) | 构建 | 39.4ms | 3.9ms | ~10 倍 |
| 移动实体(8k 波场) | 绘制调用 | 8,000 | 1 | 8,000 倍 |
| 移动实体(8k 波场) | 渲染 CPU p95 | 9.9ms | 0.5ms | ~20 倍 |
| 移动实体(8k 波场) | 更新循环 p95 | 1.4ms | 0.3ms | ~4.7 倍 |
方法说明
- CPU 端指标是可靠的信号。 绘制调用、渲染 CPU p95、更新循环和构建时间都能可靠地体现 1–2 个数量级的性能提升。
- 无头 Chromium 中的 FPS 和帧时间 p95 不可靠。 Playwright 捆绑的 Chromium 使用 SwiftShader(软件 WebGL),其瓶颈在于对约 90 MB 可见几何体进行片元着色,与绘制调用数量无关。在真实硬件 WebGL 上,FPS 差距会大得多——在真实填充负载下,基线会降至个位数 FPS,而优化后则能稳定达到垂直同步帧率。
- 如果绘制调用减少且渲染 CPU p95 没有退步,基准测试即为通过。
评论
支持 Markdown