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 は GPU リソースを確保しますが、JavaScript のガベージコレクターによって自動的に解放されることはありません。次のリソースは必ず手動で破棄してください。

オブジェクトの種類メソッド解放される GPU リソース
BufferGeometrygeometry.dispose()頂点/インデックスバッファー
Material(すべての種類)material.dispose()シェーダープログラム、ユニフォーム
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:1 回の呼び出しで数千個をレンダリング

同じジオメトリとマテリアルの組み合わせを約 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 以降):1 回の呼び出しで複数のジオメトリを描画

BatchedMesh は、異なるジオメトリとマテリアルを 1 回のドローコールで扱えるようにインスタンシングを拡張します。形状が異なる繰り返しオブジェクトに使用してください。

ジオメトリのマージ:静的オブジェクト

個別に移動することのない静的オブジェクトは、1 つのジオメトリにマージしてください。

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 のべき乗のサイズ中ミップマップに必要です。必ず(256、512、1024、2048)を使用してください
圧縮形式(KTX2/Basis)高本番環境では必ず使用してください。GPU の使用量を 4~6 倍削減できます
テクスチャアトラス高複数の小さなテクスチャを 1 つにまとめ、ドローコールを削減します
入れ替え時の 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 の視錐台カリングでは、インスタンスグループ全体が1つの境界球として扱われます。インスタンスが広い範囲に分布している場合は、効果的にカリングできるよう空間ごとにグループを分割してください。


オブジェクトプーリング

フレームごとにオブジェクトを作成して破棄してはいけません。頻繁に生成・破棄されるオブジェクトには、オブジェクトプールを使用してください。

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)は必ずアニメーションループの外で宣言してください。再利用するスクラッチ変数であることを示すため、名前の先頭に _ を付けてください。


シェーダーとマテリアルの最適化

アクション効果
MeshPhysicalMaterial の代わりに MeshStandardMaterial を使用するクリアコート/透過/シーンが必要な場合を除き、シェーダー命令が減ります
固有のマテリアル数を最小限にするシェーダーのコンパイル回数が減ります。同一のメッシュ間では必ずマテリアルを共有してください
モバイルでは material.precision = 'mediump' を設定するモバイル GPU でフラグメントシェーディングが高速になります
必要な場合を除き onBeforeCompile を避ける変更ごとに固有のシェーダーバリアントが作成されます

Chrome DevTools によるプロファイリング

  1. パフォーマンス タブ:数秒間記録し、時間のかかる「GPU」タスクと JS のフレーム所要時間を確認します
  2. メモリ タブ:シーン変更の前後にヒープスナップショットを取得し、解放されていないオブジェクトを探します
  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 を設定してください。 設定しないと、インスタンスは原点に描画されます。


参考リンク

公式ソース

コメント