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
r186; 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 importsGLTFLoader/DRACOLoader/KTX2Loaderfromthree/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
- 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.
- Load with
GLTFLoader.loader.load(url, onLoad, onProgress, onError). The resultgltfhasgltf.scene(theObject3Droot),gltf.animations(AnimationClip[]),gltf.cameras, andgltf.asset. - Add
gltf.sceneto your scene and frame it. Inspect the hierarchy withtraverse/getObjectByNameto find the parts you'll control. - Play animations with an
AnimationMixer. One mixer per animated root;mixer.clipAction(clip).play(); advance withmixer.update(delta)every frame. - Decode compressed assets. Attach a
DRACOLoader(and/orKTX2Loader+ Meshopt) so DRACO meshes and KTX2 textures load; point the decoders at their files. - 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
2. Play a skinned animation with AnimationMixer
3. Cross-fade between two clips
4. DRACO-compressed geometry
5. Find and animate a named part
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(seethreejs-materials-lighting), and check scale — glTF is in metres, so a 0.01-scaled asset is tiny. loadis async →gltfonly exists inside the callback; declaremixer/refs outside and assign them in the callback, or useawait loader.loadAsync(url).- Animation never moves → you didn't call
mixer.update(delta)each frame, or you passed milliseconds instead of seconds (usetimer.getDelta()aftertimer.update(time)), or you forgotaction.play(). - DRACO/KTX2 model fails → the decoder/transcoder path is wrong or version-
mismatched.
setDecoderPath/setTranscoderPathmust point at files matching your three.js version. - Multiple mixers fighting → use one
AnimationMixerper animated root and create all actions from it; don't make a new mixer per clip. - Baked-in transforms surprise you → exporters sometimes bake scale/rotation onto child nodes. Dump the hierarchy (names + position/rotation/scale) before relying on a node's local transform; re-export from the source if the rig is unusable.
- Origins are off → re-parent a part under a fresh
Object3Dto give it a clean pivot rather than fighting baked offsets.
References
- For the full decode/transcode setup (DRACO + Meshopt + KTX2 together),
loadAsync- a
LoadingManagerprogress bar, reusing models withSkeletonUtils.clone, and exporter guidance (apply transforms, one clean root), readreferences/loaders-and-animation.md.
- a
Related skills
threejs-scene-setup— the renderer, camera, and loop this model renders into.threejs-materials-lighting— lighting/environment so PBR models look right.fps-shooter— a 3D genre that composes three.js skills.


