Loading glTF Models and Animations in Three.js

Updated June 2026
Games need 3D models for characters, environments, props, and visual effects. The glTF format (GL Transmission Format) is the standard for delivering 3D assets to the web, and Three.js provides a full loading pipeline for glTF files that handles geometry, materials, textures, and skeletal animations. This guide covers the complete workflow from loading your first model to optimizing a production asset pipeline.

Before Three.js games used glTF, developers struggled with format fragmentation. OBJ files lacked animation support. FBX loading was unreliable in the browser. Collada (DAE) files were verbose and slow to parse. The Khronos Group designed glTF specifically for efficient real-time delivery, and it has become so dominant that Blender, Maya, 3ds Max, Substance Painter, and every major 3D tool now exports to it natively. If you are building a Three.js game, glTF is the format you should use for nearly every asset.

Step 1: Understand the glTF Format

glTF comes in two variants. The text-based .gltf format stores the scene description in a JSON file, with geometry data in separate .bin files and textures as external image files. This is useful during development because you can inspect and edit the JSON. The binary .glb variant packs everything into a single file, which is better for production because it requires only one HTTP request and avoids CORS issues with external resources.

A glTF file can contain multiple meshes, each with their own geometry and materials. It stores a complete material description using the PBR metallic-roughness workflow, including base color, metallic factor, roughness factor, normal maps, occlusion maps, and emissive maps. Three.js maps these directly to MeshStandardMaterial properties, so models exported from Blender or Substance Painter look correct without manual material configuration.

Skeletal animations, morph target (blend shape) animations, and scene hierarchy are all encoded in the glTF file. A character model might include an armature with dozens of bones and multiple animation clips (idle, walk, run, jump, attack), all in a single file. Three.js parses all of this automatically and makes it available through the loaded result object.

Step 2: Set Up the GLTFLoader

The GLTFLoader is part of Three.js's addons (previously called examples). Import it with import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'. Create an instance and call its load method with the URL of your model file, a success callback, an optional progress callback, and an error callback.

The success callback receives a result object with several properties. result.scene is a Three.js Group containing all the meshes in the model. result.animations is an array of AnimationClip objects. result.cameras and result.scenes contain any cameras or additional scenes defined in the file. For most game use cases, you add result.scene directly to your Three.js scene with scene.add(result.scene).

For loading multiple models, create a loading manager with new THREE.LoadingManager() and pass it to the GLTFLoader constructor. The manager provides callbacks for tracking overall loading progress, which you can use to display a loading screen or progress bar. All loaders that share a manager report to it collectively, so you get a unified loading state across all your game assets.

Step 3: Add Draco Compression

Draco is a geometry compression library from Google that reduces mesh data size by 80-95%. A character model with a 5MB geometry payload might compress to 300-500KB. Three.js integrates Draco through the DRACOLoader, which runs in a Web Worker so that decompression does not block the main thread.

Import DRACOLoader with import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'. Create an instance, set the decoder path to the directory containing the Draco WASM files (available from the Three.js examples/jsm/libs/draco/ directory, or from a CDN), and pass the DRACOLoader to your GLTFLoader with gltfLoader.setDRACOLoader(dracoLoader).

Exporting Draco-compressed glTF from Blender is a checkbox in the export dialog. In the glTF export settings, enable "Compression" under the geometry section. For automated pipelines, the gltf-transform CLI tool applies Draco compression to existing glTF files: gltf-transform draco input.glb output.glb. This is useful for batch processing assets or integrating compression into a build pipeline.

Step 4: Use KTX2 Compressed Textures

Textures are often the largest part of a 3D model. A character with 2048x2048 base color, normal, and metallic-roughness maps uses about 48MB of GPU memory in uncompressed RGBA format. KTX2 with Basis Universal compression reduces both download size and GPU memory usage by transcoding textures into the native compressed format for each GPU (BC7 on desktop, ASTC on modern mobile, ETC1 on older mobile).

Set up the KTX2Loader similarly to DRACOLoader. Import it, set the transcoder path to the directory containing the Basis transcoder files, and pass it to the GLTFLoader with gltfLoader.setKTX2Loader(ktx2Loader). The transcoder runs in a Web Worker and automatically selects the best compressed format for the user's GPU.

Convert textures to KTX2 format using the gltf-transform CLI: gltf-transform ktx2 input.glb output.glb. This converts all embedded textures to KTX2 with Basis Universal compression. The resulting file loads faster over the network and uses 4-6 times less GPU memory, which is critical for mobile devices where video memory is limited.

Step 5: Play Skeletal Animations

Skeletal animations use a hierarchy of bones to deform a mesh. When you load a glTF file with animations, the result object contains an animations array of AnimationClip objects. To play them, create an AnimationMixer attached to the model's scene group, then create actions from the clips.

Create the mixer with const mixer = new THREE.AnimationMixer(model). Create an action from a clip with const action = mixer.clipAction(clip). Call action.play() to start the animation. In your game loop, call mixer.update(delta) each frame to advance the animation by the elapsed time.

Games rarely play a single animation. Characters transition between idle, walking, running, and action states. Use action.crossFadeTo(nextAction, duration) to smoothly blend between animations over a specified time. Set up animation state by keeping references to each action and controlling their weights and time scales. The mixer handles blending multiple active actions, so you can layer a shooting animation on top of a walking animation by controlling which bones each action affects.

For animation events (like playing a footstep sound when a foot hits the ground), listen for specific time points in the animation loop. Check the action's time property each frame and trigger events when it crosses your desired timestamps. Alternatively, embed custom properties in the glTF file using Blender's custom property system and read them from the loaded result.

Step 6: Optimize Models for Games

Raw models from 3D artists are often too detailed for real-time rendering in the browser. A single character at 100,000 polygons is manageable, but a scene with 50 such characters plus an environment will struggle on mobile devices. Target 5,000-20,000 polygons per game character, depending on how many will be visible simultaneously.

Use the gltf-transform CLI to automate optimization. The simplify command reduces polygon count while preserving visual quality. The dedup command eliminates duplicate accessor data. The prune command removes unused nodes, materials, and textures. Chain these with Draco and KTX2 compression for a complete optimization pipeline: gltf-transform simplify input.glb temp.glb --ratio 0.5 && gltf-transform draco temp.glb temp2.glb && gltf-transform ktx2 temp2.glb output.glb.

For environments and props that appear many times in a scene, use Three.js's InstancedMesh to render all copies in a single draw call. Load the model once, create an InstancedMesh with the geometry and material, then set the transform matrix for each instance. This turns hundreds of draw calls into one, which is often the single biggest performance win in a scene with repeated objects like trees, rocks, or buildings.

Key Takeaway

Use GLB for production, apply Draco geometry compression and KTX2 texture compression through gltf-transform, and manage animations with AnimationMixer. This pipeline delivers models that load fast, look accurate, and perform well on the range of hardware your browser game players actually use.