iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…