Skip to content
Development
Skill

/threejs-gltf-loading

Loads glTF/GLB models into three.js r187 and plays their animations: GLTFLoader, DRACO, Meshopt and KTX2 decoders, AnimationMixer cross-fades and SkeletonUtils clones. Use when a model is invisible or tiny, decoder files 404, animations never play, or clones share one skeleton.

From plugin
awesome-gamedev-agent-skills
8.6k79 skills

Claude Code

$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-gltf-loading --agent claude-code

SKILL.md

threejs-gltf-loading.SKILL.md
name: threejs-gltf-loading
description: >
  Loads glTF/GLB models into three.js r187 and plays their animations: GLTFLoader, DRACO, Meshopt
  and KTX2 decoders, AnimationMixer cross-fades and SkeletonUtils clones. Use when a model is
  invisible or tiny, decoder files 404, animations never play, or clones share one skeleton. Not
  for scene setup (`threejs-scene-setup`) or lighting (`threejs-materials-lighting`).

three.js glTF loading

Loads `.gltf` / `.glb` models, including compressed geometry (DRACO, Meshopt) and textures (KTX2), and plays their animation clips. Patterns target **r187**; an existing project keeps its pinned release unless the user asks to upgrade. glTF is the runtime format to default to: OBJ has no scene graph or animation, and FBX is heavy to parse in a browser.

When to use

  • Importing a model or character, inspecting its node hierarchy, playing or blending clips,

spawning many copies of an animated enemy, or fixing a model that does not appear.

  • Files are `.gltf` / `.glb`, or code imports `GLTFLoader`, `DRACOLoader` or `KTX2Loader`

from `three/addons/loaders/...`.

  • Not for the renderer, camera and loop (`threejs-scene-setup`), the model's lighting and

materials (`threejs-materials-lighting`), or modelling and export settings in a DCC tool.

Workflow

1. **Inspect the file before writing loader code** when `gltf-transform` is available: `npx @gltf-transform/cli inspect model.glb` lists meshes, extensions (for example `KHR_draco_mesh_compression`, `EXT_meshopt_compression`, `KHR_texture_basisu`), clips and texture sizes. Each listed compression extension needs its decoder attached. 2. **Load with `GLTFLoader`** (`loadAsync` in async code). The result has `gltf.scene`, the root `Object3D`, and `gltf.animations`, an `AnimationClip[]`. 3. **Add `gltf.scene` and frame it.** glTF units are metres; log names and bounds to find the parts code will control. 4. **One `AnimationMixer` per animated root**, actions from `mixer.clipAction(clip)`, and `mixer.update(seconds)` every frame. 5. **Attach decoders** for the extensions found in step 1. Since r185 `DRACOLoader` and `KTX2Loader` find their bundled decoder files without a path. 6. **Verify** with [Verify](#verify).

Patterns

1. Load and frame a model

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const gltf = await new GLTFLoader().loadAsync('assets/robot.glb');
const root = gltf.scene;
scene.add(root);

const box = new THREE.Box3().setFromObject(root);
console.log('size (m):', box.getSize(new THREE.Vector3()).toArray());
console.log('clips:', gltf.animations.map((c) => c.name));

2. Play a clip

const timer = new THREE.Timer();            // THREE.Clock is deprecated since r183
const mixer = new THREE.AnimationMixer(gltf.scene);
const run = THREE.AnimationClip.findByName(gltf.animations, 'Run') ?? gltf.animations[0];
mixer.clipAction(run).play();

renderer.setAnimationLoop(() => {
  timer.update();
  mixer.update(timer.getDelta());           // seconds, not milliseconds
  renderer.render(scene, camera);
});

3. Cross-fade between clips

const actions = Object.fromEntries(gltf.animations.map((c) => [c.name, mixer.clipAction(c)]));
let current = actions.Idle;
current.play();

function transitionTo(name, duration = 0.3) {
  const next = actions[name];
  if (!next || next === current) return;
  next.reset().play();                      // rewind and clear an old fade
  current.crossFadeTo(next, duration, false);
  current = next;
}

4. Compressed geometry and textures

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';

const loader = new GLTFLoader()
  .setDRACOLoader(new DRACOLoader().setDecoderPath(DRACO_GLTF_CONFIG)) // glTF-only WASM build (r185+)
  .setKTX2Loader(new KTX2Loader().detectSupport(renderer))             // needs the renderer
  .setMeshoptDecoder(MeshoptDecoder);
// Share this one loader for every model so decoders and workers are created once.

On releases before r185, or when the host does not serve the bundled files, copy `examples/jsm/libs/draco/` and `libs/basis/` from the installed `three` and pass those folders to `setDecoderPath` / `setTranscoderPath`.

5. Find a named part

const wheels = [];
gltf.scene.traverse((node) => {
  if (node.name.startsWith('Wheel')) wheels.push(node);
});
// In the loop: for (const w of wheels) w.rotation.x += dt * 4;

6. Shrink a model without losing the parts code needs

# Meshopt compression, WebP textures at most 1024 px, keep named nodes and the hierarchy.
npx @gltf-transform/cli optimize in.glb out.glb \
  --compress meshopt --texture-compress webp --texture-size 1024 \
  --flatten false --join-named false
npx @gltf-transform/cli validate out.glb

`optimize` flattens the hierarchy and joins meshes, named ones included, by default. That cuts draw calls, but code that looks up `Wheel_FL` or animates a door node then finds nothing. Keep the two flags above for any model that code reaches into. The output needs `setMeshoptDecoder` (Pattern 4).

Verify

1. **File check**: `npx @gltf-transform/cli validate model.glb` for each new or changed file; fix reported errors before debugging loader code. 2. **Build**: `npm run build` (or `npx tsc --noEmit`) passes. 3. **Runtime probe**: expose `scene` and the mixer on `window` (`Object.assign(window, { scene, mixer })`). Pass this expression to Playwright's `page.evaluate` as a string, so it runs in the page and not in Node (the smoke script in `threejs-scene-setup` works), or ask the user to paste it into the browser console with `await` in front and report the output:

(async () =>
Read more
@gamedev-skills
Ships withawesome-gamedev-agent-skills

<img src="docs/assets/awesome-gamedev-agent-skills-banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.

Stars on GitHub

Momentum

stars in total
8.6k
Star history of gamedev-skills/awesome-gamedev-agent-skills on GitHub, a chart by star-history.com

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills13d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react4d252k
@facebook@facebookInstall Skill

Questions about threejs-gltf-loading.

Which coding agents can use threejs-gltf-loading?
threejs-gltf-loading installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.