Three.js Cheatsheet

Loading Models (glTF)

Use this Three.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

GLTFLoader — Basic Load

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

const loader = new GLTFLoader();

loader.load(
  '/models/scene.glb',
  (gltf) => {
    const model = gltf.scene;          // THREE.Group
    scene.add(model);
  },
  (xhr) => {
    console.log((xhr.loaded / xhr.total * 100).toFixed(1) + '% loaded');
  },
  (error) => {
    console.error('Load error:', error);
  }
);

// Async/await
const gltf = await new Promise((res, rej) =>
  loader.load(url, res, undefined, rej)
);
scene.add(gltf.scene);

GLTF Object Structure

gltf.scene;       // THREE.Group — the root scene node
gltf.scenes;      // Array<THREE.Group> — all scenes in the file
gltf.cameras;     // Array<THREE.Camera>
gltf.animations;  // Array<THREE.AnimationClip>
gltf.asset;       // { version, generator, ... } metadata
gltf.userData;    // any extensions/extras

Draco Compression

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

const draco = new DRACOLoader();
// Path to the draco decoder WASM files (copy from node_modules)
draco.setDecoderPath('/draco/');
draco.setDecoderConfig({ type: 'js' }); // or 'wasm' (default)
draco.preload(); // pre-warm the decoder

const loader = new GLTFLoader();
loader.setDRACOLoader(draco);

loader.load('/models/compressed.glb', (gltf) => {
  scene.add(gltf.scene);
});

Draco decoder files: copy node_modules/three/examples/jsm/libs/draco/ public/draco/

KTX2 Textures in GLTF

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

const ktx2 = new KTX2Loader()
  .setTranscoderPath('/basis/')
  .detectSupport(renderer);

const loader = new GLTFLoader();
loader.setKTX2Loader(ktx2);
loader.load('/models/scene.glb', (gltf) => scene.add(gltf.scene));

Traversing the Loaded Model

loader.load('/models/robot.glb', (gltf) => {
  const model = gltf.scene;

  // Enable shadows on all meshes
  model.traverse((child) => {
    if (child.isMesh) {
      child.castShadow    = true;
      child.receiveShadow = true;
      // child.material.envMapIntensity = 1;
    }
  });

  // Find a specific named node
  const head = model.getObjectByName('Head');
  const body = model.getObjectByName('Body');

  // Center the model
  const box    = new THREE.Box3().setFromObject(model);
  const center = new THREE.Vector3();
  box.getCenter(center);
  model.position.sub(center);

  // Scale to fit
  const size   = new THREE.Vector3();
  box.getSize(size);
  const maxDim = Math.max(size.x, size.y, size.z);
  model.scale.setScalar(2 / maxDim);

  scene.add(model);
});

Animations

loader.load('/models/character.glb', (gltf) => {
  const model  = gltf.scene;
  const clips  = gltf.animations; // Array<AnimationClip>

  const mixer  = new THREE.AnimationMixer(model);

  // Play a specific clip by name
  const walkClip   = THREE.AnimationClip.findByName(clips, 'Walk');
  const walkAction = mixer.clipAction(walkClip);
  walkAction.play();

  // Or play all clips
  clips.forEach((clip) => mixer.clipAction(clip).play());

  // Advance in render loop
  const clock = new THREE.Clock();
  function animate() {
    requestAnimationFrame(animate);
    const delta = clock.getDelta();
    mixer.update(delta);
    renderer.render(scene, camera);
  }
  animate();

  scene.add(model);
});

Other Loaders

LoaderImport PathFormat
GLTFLoaderthree/addons/loaders/GLTFLoader.js.gltf, .glb
DRACOLoaderthree/addons/loaders/DRACOLoader.jsDraco-compressed geometry
OBJLoaderthree/addons/loaders/OBJLoader.js.obj (no material)
MTLLoaderthree/addons/loaders/MTLLoader.js.mtl (for OBJ materials)
FBXLoaderthree/addons/loaders/FBXLoader.js.fbx
ColladaLoaderthree/addons/loaders/ColladaLoader.js.dae
PLYLoaderthree/addons/loaders/PLYLoader.js.ply (point clouds)
SVGLoaderthree/addons/loaders/SVGLoader.js.svg → paths
FontLoaderthree/addons/loaders/FontLoader.jstypeface JSON
EXRLoaderthree/addons/loaders/EXRLoader.js.exr HDR
RGBELoaderthree/addons/loaders/RGBELoader.js.hdr (Radiance)
TGALoaderthree/addons/loaders/TGALoader.js.tga
KTX2Loaderthree/addons/loaders/KTX2Loader.js.ktx2

OBJ + MTL

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

const mtlLoader = new MTLLoader();
mtlLoader.load('/models/scene.mtl', (materials) => {
  materials.preload();
  const objLoader = new OBJLoader();
  objLoader.setMaterials(materials);
  objLoader.load('/models/scene.obj', (obj) => scene.add(obj));
});

Text / TextGeometry

import { FontLoader }    from 'three/addons/loaders/FontLoader.js';
import { TextGeometry }  from 'three/addons/geometries/TextGeometry.js';

const fontLoader = new FontLoader();
fontLoader.load('/fonts/helvetiker_regular.typeface.json', (font) => {
  const geo = new TextGeometry('Hello Three.js', {
    font:           font,
    size:           0.5,
    depth:          0.1,
    curveSegments:  12,
    bevelEnabled:   true,
    bevelThickness: 0.03,
    bevelSize:      0.02,
    bevelSegments:  5,
  });
  geo.center();
  const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ color: 0xffffff }));
  scene.add(mesh);
});

LoadingManager Pattern (Multiple Assets)

const manager = new THREE.LoadingManager();
let progress  = 0;

manager.onStart    = (url, loaded, total) => console.log('Started');
manager.onLoad     = () => console.log('All done — hide loading screen');
manager.onProgress = (url, loaded, total) => {
  progress = loaded / total;
  console.log(`${(progress * 100).toFixed(0)}%`);
};
manager.onError    = (url) => console.error('Failed:', url);

const gltfLoader  = new GLTFLoader(manager);
const texLoader   = new THREE.TextureLoader(manager);

// All loads tracked by the same manager
gltfLoader.load('/model.glb', (g) => scene.add(g.scene));
texLoader.load('/tex/diff.jpg', (t) => (mat.map = t));

Cloning Loaded Models

// Share geometry + material across instances (no re-download)
import { SkeletonUtils } from 'three/addons/utils/SkeletonUtils.js';

// For SKINNED meshes (with bones/animations) — must use SkeletonUtils
const clone1 = SkeletonUtils.clone(gltf.scene);
const clone2 = SkeletonUtils.clone(gltf.scene);

// For static meshes without skeleton — simple clone works
const clone = gltf.scene.clone();
clone.position.set(5, 0, 0);
scene.add(clone);

Gotchas

.glb is binary GLTF (single file, smaller, preferred). .gltf is JSON and references external .bin and texture files.

Textures in GLTF are flipped — always set texture.flipY = false if you load textures manually and use them in a GLTF material context.

Draco decoder must be served as a static file. The WASM version is faster but requires COOP/COEP headers for SharedArrayBuffer.

gltf.scene is the first scene; multi-scene files use gltf.scenes[i].

Shadows must be enabled on every mesh after loading — they default to castShadow = false.

AnimationMixer is per root object. If you clone a model, create a new AnimationMixer for each clone.

SkeletonUtils.clone is required when cloning a model that has SkinnedMesh nodes — regular .clone() will share bone references and break animations.