threejs-gltf-loading について
three.js に glTF・GLB モデルを組み込む開発者向けのスキルです。ノード階層の確認、アニメーション再生、圧縮ジオメトリやテクスチャのデコードを扱います。
主な機能
- 01glTF・GLB モデルの読み込み
- 02モデルのノード階層の確認
- 03アニメーションクリップの再生とブレンド
- 04DRACO・KTX2 アセットのデコード
- 05変更した glTF ファイルの検証
活用シーン
- 01three.js シーンへの 3D モデルの読み込み
- 02スキン付きキャラクターのアニメーション再生
- 03名前付きモデルパーツの検索と制御
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ gamedev-skills/ awesome-gamedev-agent-skills --skill threejs-gltf-loadingインストール後、Claude Code で/threejs-gltf-loadingを入力して読み込まれたか確認します。
同梱ファイルreferences/loaders-and-animation.md
詳細
three.js glTF の読み込み
.gltf/.glb モデルを読み込み、three.js でアニメーションを再生します。圧縮ジオメトリ(DRACO/Meshopt)とテクスチャ(KTX2)にも対応しています。パターンは r186 を対象としています。移行を求められていない限り、既存プロジェクトで固定されているリリースを維持してください。
使用する場面
- 3D モデルをインポートしてシーンに追加し、ノード階層を調べ、
AnimationMixerでベイク済みアニメーションクリップやスキニングアニメーションを再生する場合に使用します。 - ファイルが
.gltf/.glbの場合や、コードがthree/addons/loaders/...からGLTFLoader/DRACOLoader/KTX2Loaderをインポートしている場合に使用します。
使用しない場面: レンダラー/カメラ/ループの作成 → threejs-scene-setup。読み込んだモデルの表面の見た目、ライト、影の調整 → threejs-materials-lighting。モデル自体の作成/エクスポート(Blender)は対象外です。実行時には OBJ/FBX より glTF を優先してください。
基本的なワークフロー
- glTF を選ぶ理由。 glTF は伝送用フォーマットです。バイナリの頂点データ、PBR マテリアル、アニメーションが、最小限の解析で描画できる状態になっています。Web では、シーングラフやアニメーションのない OBJ や、重い FBX より glTF を優先してください。
GLTFLoaderで読み込む。loader.load(url, onLoad, onProgress, onError)を呼び出します。結果のgltfには、gltf.scene(Object3Dのルート)、gltf.animations(AnimationClip[])、gltf.cameras、gltf.assetが含まれます。gltf.sceneをシーンに追加し、フレームに収めます。traverse/getObjectByNameで階層を調べ、操作するパーツを見つけます。AnimationMixerでアニメーションを再生する。 アニメーション対象のルートごとにミキサーを1つ作成します。mixer.clipAction(clip).play()を呼び出し、毎フレームmixer.update(delta)で更新します。- 圧縮アセットをデコードする。
DRACOLoader(および/またはKTX2Loaderと Meshopt)をアタッチして、DRACO メッシュと KTX2 テクスチャを読み込めるようにします。デコーダーのファイルを指定してください。 - プロジェクトにツールがすでにある場合は、変更した glTF ファイルを検証します。 変更した
.gltfまたは.glbごとにgltf-transform validate path/to/model.glbを実行し、報告されたエラーを修正してから読み込みます。 - 読み込まれた内容を確認する。 シーングラフと
gltf.animationsをログに出力し、モデルが表示されること(適切なスケールで、ライティングされていること)と、クリップが実際に再生されることを確認します。
パターン
1. モデルを読み込んでフレームに収める
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load(
'assets/robot.glb',
(gltf) => {
const root = gltf.scene;
scene.add(root);
// Inspect: gltf.animations is an array of AnimationClip.
console.log('clips:', gltf.animations.map((c) => c.name));
},
(event) => console.log(`${(event.loaded / event.total) * 100}% loaded`),
(error) => console.error('glTF load failed:', error)
);
2. AnimationMixer でスキニングアニメーションを再生する
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
let mixer; // declare outside so the loop can see it
const timer = new THREE.Timer(); // THREE.Clock is deprecated since r183
new GLTFLoader().load('assets/character.glb', (gltf) => {
scene.add(gltf.scene);
mixer = new THREE.AnimationMixer(gltf.scene); // one mixer per animated root
const clip = THREE.AnimationClip.findByName(gltf.animations, 'Run')
?? gltf.animations[0];
mixer.clipAction(clip).play();
});
renderer.setAnimationLoop((time) => {
timer.update(time);
const dt = timer.getDelta();
if (mixer) mixer.update(dt); // advance the animation by real seconds
renderer.render(scene, camera);
});
3. 2つのクリップをクロスフェードする
const actions = {};
mixer = new THREE.AnimationMixer(gltf.scene);
for (const clip of gltf.animations) {
actions[clip.name] = mixer.clipAction(clip);
}
actions['Idle'].play();
function transitionTo(name, duration = 0.3) {
const next = actions[name];
next.reset().play();
for (const [n, action] of Object.entries(actions)) {
if (n !== name) action.crossFadeTo(next, duration, false);
}
}
4. DRACO 圧縮ジオメトリ
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
const draco = new DRACOLoader();
// Point at the decoder files you ship (or a pinned CDN copy of the same version).
draco.setDecoderPath('https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/libs/draco/');
const loader = new GLTFLoader();
loader.setDRACOLoader(draco);
loader.load('assets/city-draco.glb', (gltf) => scene.add(gltf.scene));
5. 名前付きパーツを探してアニメーションさせる
new GLTFLoader().load('assets/car.glb', (gltf) => {
scene.add(gltf.scene);
const wheels = [];
gltf.scene.traverse((node) => {
if (node.name.startsWith('Wheel')) wheels.push(node);
});
renderer.setAnimationLoop((time) => {
timer.update(time);
const dt = timer.getDelta();
for (const w of wheels) w.rotation.x += dt * 4;
renderer.render(scene, camera);
});
});
注意点
- モデルは読み込まれるが表示されない → ライティングが必要な(PBR)マテリアルを使用している一方で、シーンにライトも環境もありません。ライトまたは
scene.environmentを追加し(threejs-materials-lightingを参照)、スケールも確認してください。glTF の単位はメートルなので、スケールが 0.01 のアセットは非常に小さくなります。 loadは非同期です →gltfが使えるのはコールバック内だけです。mixer/参照を外側で宣言し、コールバック内で代入するか、await loader.loadAsync(url)を使用してください。- アニメーションが動かない → 毎フレーム
mixer.update(delta)を呼び出していないか、ミリ秒を秒として渡している可能性があります(timer.update(time)の後にtimer.getDelta()を使用してください)。または、action.play()を忘れています。 - DRACO/KTX2 モデルの読み込みに失敗する → デコーダー/トランスコーダーのパスが間違っているか、バージョンが一致していません。
setDecoderPath/setTranscoderPathは、three.js のバージョンに対応したファイルを指す必要があります。 - 複数のミキサーが競合する → アニメーション対象のルートごとに
AnimationMixerを1つだけ使用し、すべてのアクションをそこから作成してください。クリップごとに新しいミキサーを作成しないでください。 - ベイク済みトランスフォームに注意する → エクスポーターによっては、スケール/回転が子ノードにベイクされることがあります。ノードのローカルトランスフォームを前提にする前に、階層(名前+位置/回転/スケール)を出力してください。リグが使えない場合は、ソースから再エクスポートしてください。
- 原点がずれている → ベイクされたオフセットを調整するのではなく、パーツを新しい
Object3Dの下に再配置して、適切なピボットを設定してください。
参考資料
- DRACO、Meshopt、KTX2 を組み合わせた完全なデコード/トランスコードのセットアップ、
LoadingManagerのプログレスバーを使ったloadAsync、SkeletonUtils.cloneによるモデルの再利用、エクスポーターに関するガイダンス(トランスフォームを適用する、クリーンなルートを 1 つにする)については、references/loaders-and-animation.mdを参照してください。
関連スキル
threejs-scene-setup— このモデルを描画するレンダラー、カメラ、ループです。threejs-materials-lighting— PBR モデルを適切に見せるためのライティング/環境です。fps-shooter— three.js のスキルを組み合わせた 3D ジャンルです。
コメント
Markdown 対応