/threejs-gltf-loading
Load glTF/GLB models in three.js with GLTFLoader and play their skinned animations with AnimationMixer, including DRACO/Meshopt-compressed meshes and KTX2 textures. Use when importing 3D models into three.js — when the user mentions glTF, GLB, GLTFLoader, AnimationMixer,
$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-gltf-loading --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/threejs-gltf-loading
Context preview
The summary Claude sees to decide when to auto-load this skill.
Load glTF/GLB models in three.js with GLTFLoader and play their skinned animations with AnimationMixer, including DRACO/Meshopt-compressed meshes and KTX2 textures. Use when importing 3D models into three.js — when the user mentions glTF, GLB, GLTFLoader, AnimationMixer,
SKILL.md
threejs-gltf-loading.SKILL.mdname: threejs-gltf-loading
description: >
Load glTF/GLB models in three.js with GLTFLoader and play their skinned
animations with AnimationMixer, including DRACO/Meshopt-compressed meshes and
KTX2 textures. Use when importing 3D models into three.js — when the user
mentions glTF, GLB, GLTFLoader, AnimationMixer, animation clips, DRACOLoader, or
"load a 3D model". For scene/camera/renderer setup use threejs-scene-setup; for
materials and lights use threejs-materials-lighting.
three.js glTF Loading
Load `.gltf`/`.glb` models and play their animations in three.js, including compressed geometry (DRACO/Meshopt) and textures (KTX2). Patterns target **r184**; preserve an existing project's pinned release unless migration is requested.
When to use
- Use to import a 3D model, add it to the scene, inspect its node hierarchy, and
play baked/skinned animation clips with an `AnimationMixer`.
- Use when files are `.gltf`/`.glb`, or code imports `GLTFLoader` /
`DRACOLoader` / `KTX2Loader` from `three/addons/loaders/...`.
**When *not* to use:** creating the renderer/camera/loop → `threejs-scene-setup`. Tuning surface look, lights, or shadows on the loaded model → `threejs-materials-lighting`. Authoring/exporting the model itself (Blender) is out of scope; prefer glTF over OBJ/FBX for runtime.
Core workflow
1. **Why glTF.** It's a transmission format: binary vertex data, PBR materials, and animations are ready to render with minimal parsing. Prefer it over OBJ (no scene graph, no animation) and FBX (heavy) for the web. 2. **Load with `GLTFLoader`.** `loader.load(url, onLoad, onProgress, onError)`. The result `gltf` has `gltf.scene` (the `Object3D` root), `gltf.animations` (`AnimationClip[]`), `gltf.cameras`, and `gltf.asset`. 3. **Add `gltf.scene` to your scene** and frame it. Inspect the hierarchy with `traverse` / `getObjectByName` to find the parts you'll control. 4. **Play animations with an `AnimationMixer`.** One mixer per animated root; `mixer.clipAction(clip).play()`; advance with `mixer.update(delta)` every frame. 5. **Decode compressed assets.** Attach a `DRACOLoader` (and/or `KTX2Loader` + Meshopt) so DRACO meshes and KTX2 textures load; point the decoders at their files. 6. **Verify what loaded** — log the scene graph and `gltf.animations`, and confirm the model is visible (right scale, lit) and the clip actually plays.
Patterns
1. Load a model and frame it
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. Play a skinned animation with 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 clock = new THREE.Clock();
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(() => {
const dt = clock.getDelta();
if (mixer) mixer.update(dt); // advance the animation by real seconds
renderer.render(scene, camera);
});3. Cross-fade between two clips
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-compressed geometry
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/three@0.184.0/examples/jsm/libs/draco/');
const loader = new GLTFLoader();
loader.setDRACOLoader(draco);
loader.load('assets/city-draco.glb', (gltf) => scene.add(gltf.scene));5. Find and animate a named part
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(() => {
const dt = clock.getDelta();
for (const w of wheels) w.rotation.x += dt * 4;
renderer.render(scene, camera);
});
});Pitfalls
- **Model loads but is invisible** → it has lit (PBR) materials and the scene has no
light or environment. Add a light or `scene.environment` (see `threejs-materials-lighting`), and check scale — glTF is in metres, so a 0.01-scaled asset is tiny.
- **`load` is async** → `gltf` only exists inside the callback; declare `mixer`/refs
outside and assign them in the callback, or use `await loader.loadAsync(url)`.
- **Animation never moves** → you didn't call `mixer.update(delta)` each frame, or you
passed milliseconds instead of seconds (use `clock.getDelta()`), or you forgot `action.play()`.
- **DRACO/KTX2 model fails** → the decoder/transcoder path is wrong or version-
mismatched. `setDecoderPath`/`setTranscoderPath` must point at files matching your three.js version.
- **Multiple mixers fighting** → use **one** `AnimationMixer` per animated root and
Read more
name: threejs-gltf-loading description: > Load glTF/GLB models in three.js with GLTFLoader and play their skinned animations with AnimationMixer, including DRACO/Meshopt-compressed meshes and KTX2 textures. Use when importing 3D models into three.js — when the user mentions glTF, GLB, GLTFLoader, AnimationMixer, animation clips, DRACOLoader, or "load a 3D model". For scene/camera/renderer setup use threejs-scene-setup; for materials and lights use threejs-materials-lighting.
three.js glTF Loading
Load `.gltf`/`.glb` models and play their animations in three.js, including compressed geometry (DRACO/Meshopt) and textures (KTX2). Patterns target **r184**; preserve an existing project's pinned release unless migration is requested.
When to use
- Use to import a 3D model, add it to the scene, inspect its node hierarchy, and
play baked/skinned animation clips with an `AnimationMixer`.
- Use when files are `.gltf`/`.glb`, or code imports `GLTFLoader` /
`DRACOLoader` / `KTX2Loader` from `three/addons/loaders/...`.
**When *not* to use:** creating the renderer/camera/loop → `threejs-scene-setup`. Tuning surface look, lights, or shadows on the loaded model → `threejs-materials-lighting`. Authoring/exporting the model itself (Blender) is out of scope; prefer glTF over OBJ/FBX for runtime.
Core workflow
1. **Why glTF.** It's a transmission format: binary vertex data, PBR materials, and animations are ready to render with minimal parsing. Prefer it over OBJ (no scene graph, no animation) and FBX (heavy) for the web. 2. **Load with `GLTFLoader`.** `loader.load(url, onLoad, onProgress, onError)`. The result `gltf` has `gltf.scene` (the `Object3D` root), `gltf.animations` (`AnimationClip[]`), `gltf.cameras`, and `gltf.asset`. 3. **Add `gltf.scene` to your scene** and frame it. Inspect the hierarchy with `traverse` / `getObjectByName` to find the parts you'll control. 4. **Play animations with an `AnimationMixer`.** One mixer per animated root; `mixer.clipAction(clip).play()`; advance with `mixer.update(delta)` every frame. 5. **Decode compressed assets.** Attach a `DRACOLoader` (and/or `KTX2Loader` + Meshopt) so DRACO meshes and KTX2 textures load; point the decoders at their files. 6. **Verify what loaded** — log the scene graph and `gltf.animations`, and confirm the model is visible (right scale, lit) and the clip actually plays.
Patterns
1. Load a model and frame it
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. Play a skinned animation with 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 clock = new THREE.Clock();
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(() => {
const dt = clock.getDelta();
if (mixer) mixer.update(dt); // advance the animation by real seconds
renderer.render(scene, camera);
});3. Cross-fade between two clips
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-compressed geometry
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/three@0.184.0/examples/jsm/libs/draco/');
const loader = new GLTFLoader();
loader.setDRACOLoader(draco);
loader.load('assets/city-draco.glb', (gltf) => scene.add(gltf.scene));5. Find and animate a named part
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(() => {
const dt = clock.getDelta();
for (const w of wheels) w.rotation.x += dt * 4;
renderer.render(scene, camera);
});
});Pitfalls
- **Model loads but is invisible** → it has lit (PBR) materials and the scene has no
light or environment. Add a light or `scene.environment` (see `threejs-materials-lighting`), and check scale — glTF is in metres, so a 0.01-scaled asset is tiny.
- **`load` is async** → `gltf` only exists inside the callback; declare `mixer`/refs
outside and assign them in the callback, or use `await loader.loadAsync(url)`.
- **Animation never moves** → you didn't call `mixer.update(delta)` each frame, or you
passed milliseconds instead of seconds (use `clock.getDelta()`), or you forgot `action.play()`.
- **DRACO/KTX2 model fails** → the decoder/transcoder path is wrong or version-
mismatched. `setDecoderPath`/`setTranscoderPath` must point at files matching your three.js version.
- **Multiple mixers fighting** → use **one** `AnimationMixer` per animated root and
<img src="docs/assets/banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.
Repo: gamedev-skills/awesome-gamedev-agent-skills
Other skills on awesome-gamedev-agent-skills.
- /audio-design
Implement game audio practice — bus/mixer architecture and gain in decibels, ducking (sidechain), adaptive/dynamic music via layering and re-sequencing, SFX variation, and beat synchronization. Engine-neutral. Use when the user mentions audio mixing, audio buses,
Open skill - /camera-systems
Build game cameras that feel good — 2D follow with a deadzone, look-ahead, smoothing, and level-bounds clamping; 3D third-person orbit with collision and first-person look; plus multi-target framing and a shake hook. Engine-neutral techniques that pair with the engine's camera
Open skill - /create-game-assets
Plan, generate, source, normalize, and validate cohesive visual game assets. Use for art direction, style bibles, sprites, tilesets, backgrounds, UI art, icons, textures, concept art, or 3D asset briefs.
Open skill - /dialogue-systems
Build branching dialogue and narrative — a node/choice graph with conditions, variables, and localization hooks — and choose between authoring tools Ink and Yarn Spinner or a custom data-driven runner. Engine-neutral. Use when the user mentions dialogue system, branching
Open skill - /game-ai
Design NPC and enemy decision-making with finite state machines, behavior trees, steering behaviors, and A* pathfinding — engine-neutral algorithms that pair with the detected engine's navigation API. Use when building enemy AI, an FSM or behavior tree, steering/flocking, or
Open skill - /game-feel
Add "juice" and game feel that makes actions satisfying — screen shake, hit-stop/freeze frames, tweened/eased motion, squash & stretch, knockback, and layered audio-visual feedback — as engine-neutral techniques that pair with the detected engine's tween, particle, and camera
Open skill

