1294 MODELS INDEXED Account
GUIDE · THREE.JS

How to load GLB models in three.js

UPDATED
7 MIN READ · BY 3D ASSET

To load a GLB model in three.js, import GLTFLoader from three/addons, call loadAsync() with the file URL and add gltf.scene to your scene. If the file is Draco-compressed, attach a DRACOLoader with a decoder path first. Add an environment map so PBR materials are not rendered black.

What a GLB file contains

GLB is the binary form of glTF 2.0, the Khronos standard for real-time 3D. One .glb file holds the node hierarchy, meshes, PBR materials, textures, cameras and animation clips. three.js reads it with GLTFLoader, which ships in the three package under three/addons/loaders/.

glTF has a fixed convention you can rely on: one unit is one metre and +Y is up. A well-made asset therefore needs no scaling or rotation when it reaches three.js.

1. The minimal loader

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

const loader = new GLTFLoader();
const gltf = await loader.loadAsync('/models/firehouse.glb');
scene.add(gltf.scene);

gltf.scene is an ordinary THREE.Group: move, rotate and scale it like any other object. gltf.animations holds the animation clips and gltf.parser gives access to the raw glTF JSON if you need it.

2. Draco-compressed files

Draco (KHR_draco_mesh_compression) shrinks geometry several times over, which matters on the web. A Draco file that is loaded without a decoder fails with an error about an unsupported required extension. Attach a DRACOLoader once and reuse the loader for every file:

import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';

const draco = new DRACOLoader();
draco.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.7/');
const loader = new GLTFLoader().setDRACOLoader(draco);

To avoid a third-party request, copy node_modules/three/examples/jsm/libs/draco/ into your public folder and point setDecoderPath at it. Files compressed with Meshopt instead need loader.setMeshoptDecoder(MeshoptDecoder); KTX2 textures need a KTX2Loader. WebP textures (EXT_texture_webp) work without any extra setup.

3. Make PBR materials look right

glTF materials are physically based: metalness and roughness react to light and, above all, to reflections. With only a directional light, metal and glossy surfaces look flat or black. Give the scene an environment map:

import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';

const pmrem = new THREE.PMREMGenerator(renderer);
scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;
renderer.toneMapping = THREE.ACESFilmicToneMapping;

Since three.js r152 the renderer outputs sRGB and glTF colour textures are tagged sRGB by the loader, so you should not set colour spaces by hand. For shadows, enable them on the renderer and on the meshes:

renderer.shadowMap.enabled = true;
gltf.scene.traverse((o) => { if (o.isMesh) { o.castShadow = true; o.receiveShadow = true; } });

4. Play animations

const mixer = new THREE.AnimationMixer(gltf.scene);
const clip = THREE.AnimationClip.findByName(gltf.animations, 'Walk');
if (clip) mixer.clipAction(clip).play();
else console.warn('No such clip. Available:', gltf.animations.map((c) => c.name));

const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
  mixer.update(clock.getDelta());
  renderer.render(scene, camera);
});

5. Place the same model many times

Load a file once and clone it. gltf.scene.clone(true) copies the hierarchy but shares geometry and materials, so copies are cheap. Skinned characters must be cloned with SkeletonUtils.clone() from three/addons/utils/SkeletonUtils.js, otherwise every copy follows the first skeleton. For hundreds of identical props, an InstancedMesh per mesh draws them all in one call.

Troubleshooting

SymptomUsual causeFix
Model is black or very darkSeveral possible causes: no lights, no environment for metallic PBR materials, very low exposure, or textures that failed to loadCheck in this order: add a light; set scene.environment (step 3); check renderer.toneMappingExposure; look for texture 404s in the network tab; inspect material.metalness and material.map on one mesh
“extension not supported: KHR_draco_mesh_compression”Draco file, no decoderAttach a DRACOLoader (step 2)
Model is invisibleWrong scale or camera inside itMeasure it: new THREE.Box3().setFromObject(gltf.scene).getSize(new THREE.Vector3())
404 or CORS errorPath is relative to the page, not the scriptServe the file from your public folder and use an absolute path
Washed-out coloursColour spaces set by handRemove manual colorSpace overrides on glTF textures

Try it with real files

Every 3D Asset pack ships its models in glb/ exactly as above: glTF 2.0, Draco geometry, WebP textures, 1 unit = 1 metre, pivot on the ground. The pack README repeats the loader snippet. You can test the whole path with the free samples before buying anything.

Frequently asked questions

What is the difference between .glb and .gltf?

Both are glTF 2.0. A .gltf file is JSON that usually references separate .bin and image files, though they can also be embedded in it as data URIs. A .glb usually packs everything into one binary file, though it may still reference external files. GLTFLoader reads both the same way.

Do I need Draco?

Only if the file was compressed with it. Draco makes downloads smaller at the cost of a short decode step, which is usually a good trade on the web. An uncompressed file loads without a decoder.

Which three.js version do I need?

Any recent release. The code on this page uses the three/addons import path and the colour-management defaults of r152 and later.

Sources

More guides