glTF Models
glTF is the de-facto 3D file format for the web — efficient binary, PBR materials, animations, skins, morph targets, all in one .glb. three.js GLTFLoader handles it; combine with DRACO + KTX2 compression and you ship 3D experiences instead of demos.
Load GLB, animate, optimise, dispose
EXAMPLE
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
// 1) Renderer + colour management (boring but essential)
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 1.5, 3);
scene.add(new THREE.AmbientLight(0xffffff, 0.3));
const key = new THREE.DirectionalLight(0xffffff, 1.2);
key.position.set(2, 4, 2); scene.add(key);
// 2) Configure GLTFLoader with compression decoders
const draco = new DRACOLoader();
draco.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
const ktx2 = new KTX2Loader()
.setTranscoderPath('https://unpkg.com/three@latest/examples/jsm/libs/basis/')
.detectSupport(renderer);
const loader = new GLTFLoader();
loader.setDRACOLoader(draco);
loader.setKTX2Loader(ktx2);
loader.setMeshoptDecoder(MeshoptDecoder);
// 3) Load + position the model
let model, mixer;
loader.load('/model.glb', (gltf) => {
model = gltf.scene;
scene.add(model);
// Centre it
const box = new THREE.Box3().setFromObject(model);
const centre = box.getCenter(new THREE.Vector3());
model.position.sub(centre); // recentre at origin
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
model.scale.setScalar(1.5 / maxDim); // fit ~1.5 units tall
// Animations
if (gltf.animations.length) {
mixer = new THREE.AnimationMixer(model);
mixer.clipAction(gltf.animations[0]).play();
}
}, (event) => {
console.log('loading', Math.round((event.loaded / (event.total || 1)) * 100), '%');
}, (err) => {
console.error('GLB load failed', err);
});
// 4) Animation loop
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
const dt = clock.getDelta();
mixer?.update(dt);
renderer.render(scene, camera);
});
// 5) Resize
addEventListener('resize', () => {
renderer.setSize(innerWidth, innerHeight);
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
});
// 6) Disposal — when the user navigates away or you swap models
function disposeModel(m) {
m.traverse((node) => {
if (node.geometry) node.geometry.dispose();
if (Array.isArray(node.material)) node.material.forEach((mt) => mt.dispose());
else if (node.material) node.material.dispose();
});
scene.remove(m);
}
// 7) Optimisation pipeline (one-time, on a workstation)
// gltf-transform — npm i -g @gltf-transform/cli
//
// gltf-transform optimize input.glb output.glb \
// --texture-compress webp --texture-size 1024 --simplify --weld
//
// What this does:
// - WebP textures (smaller than PNG/JPG)
// - Resize textures to 1024 max
// - Mesh simplification (low-poly LOD)
// - Vertex welding (deduplicate)
// Typical result: 5-10x size reduction.
// 8) Use draco/meshopt compression DURING export
// In Blender: File -> Export -> glTF 2.0 -> Compression -> Draco mesh compression: On
// CLI: gltfpack -i input.glb -o packed.glb -cc
// (meshopt compression + texture optimisation)
// 9) KTX2 + Basis textures
// Smaller than WebP on GPU; transcoded to the device's native format.
// Generate via: gltf-transform ktx2 input.glb output.glb
// 10) Debug aids
// const helper = new THREE.SkeletonHelper(model);
// scene.add(helper);
// helper.visible = true;
//
// const axes = new THREE.AxesHelper(1);
// scene.add(axes);
// 11) Pitfalls
// - Loading huge unoptimised GLBs (>10MB) -> long blank screen
// Run them through gltf-transform / gltfpack BEFORE shipping
// - Forgetting outputColorSpace = SRGB -> washed-out colours
// - Two animations + no mixer.update(dt) -> nothing moves
// - Disposing the GLB scene without disposing materials -> GPU leak
// - DRACO/Basis decoder paths wrong -> silent failure to load
Why it matters
Compress every GLB before shipping with gltf-transform or gltfpack — meshopt + KTX2 + 1024px textures typically shrinks a model 5–10x without visible quality loss. Pair with DRACOLoader + KTX2Loader on the client and a 30MB raw model becomes a 3MB download that loads in half the time on mobile.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
new GLTFLoader().load('/scene.glb', g => scene.add(g.scene));
Try it Yourself »
Discussion
Loading…