Working with loaded models

After you've loaded your model, here's what you can do

Working with loaded models

Ok, you've got your model loaded. Now what?

Inside the glTF object

The object returned by the loader isn't a mesh. It's a container holding everything the file contained. If you take a peek inside..

typescript
loader.load('/models/character.glb', (gltf) => {
  gltf.scene;       // THREE.Group, the root of the model's scene graph
  gltf.scenes;      // Array, all scenes in the file (usually just one)
  gltf.animations;  // Array of THREE.AnimationClip, all animation data
  gltf.cameras;     // Array of THREE.Camera, cameras defined in the file
  gltf.asset;       // Object, metadata (generator, version, copyright)
});

In nearly every case, gltf.scene is what you add to your Three.js scene. It's a THREE.Group that contains all the meshes, lights, and hierarchy that the 3D artist set up in their modeling tool. The other properties are there when you need them, but gltf.scene is the entry point.

Adjusting materials

glTF models arrive with materials already assigned, typically MeshStandardMaterial configured with the PBR properties from the source file. Sometimes you need to tweak them, either because the export wasn't perfect or because your scene's lighting setup needs different settings.

typescript
model.traverse((child) => {
  if (child.isMesh) {
    const mat = child.material;

    // Adjust roughness and metalness
    mat.roughness = 0.4;
    mat.metalness = 0.8;

    // Override the environment map intensity
    mat.envMapIntensity = 1.5;

    // Make a material double-sided (shows both faces)
    mat.side = THREE.DoubleSide;

    // Replace the material entirely
    // child.material = new THREE.MeshStandardMaterial({
    //   color: 0xff0000,
    //   roughness: 0.3
    // });
  }
});

A common situation you'll probably encounter is that you'll load a model and it looks too dark or too bright. This usually isn't a material problem. It's a lighting or tone mapping mismatch. Before tweaking materials, make sure your renderer has tone mapping enabled and your scene has appropriate lighting or an environment map set.

Handling scale and position

Models from different sources arrive at different scales. A character modeled in Blender with meters as the unit will be a different size than one modeled in Maya with centimeters. There's no universal fix. You adjust on a per-model basis.

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

  // Scale uniformly
  model.scale.setScalar(0.5);

  // Position the model
  model.position.set(0, 0, 0);

  // Center the model on its bounding box
  const box = new THREE.Box3().setFromObject(model);
  const center = box.getCenter(new THREE.Vector3());
  model.position.sub(center);  // shift so center is at origin

  // Optionally, place it on the ground plane
  const boxAfter = new THREE.Box3().setFromObject(model);
  model.position.y -= boxAfter.min.y;  // bottom sits at y=0

  scene.add(model);
});

The bounding box technique is the most reliable way to center and ground a model regardless of how it was authored. Compute the bounding box, shift the model so its center (or bottom) aligns with where you want it.

Playing animations

glTF files can carry animation clips like walk cycles, idle poses, facial expressions, mechanical movements. Three.js plays these through the AnimationMixer system. The mixer controls playback for a specific object, and each clip becomes an AnimationAction you can play, pause, crossfade, and blend.

typescript
let mixer;

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

  // Create a mixer for this model
  mixer = new THREE.AnimationMixer(model);

  // Play all animations
  gltf.animations.forEach((clip) => {
    mixer.clipAction(clip).play();
  });

  // Or play a specific animation by name
  const idleClip = THREE.AnimationClip.findByName(
    gltf.animations, 'Idle'
  );
  if (idleClip) {
    mixer.clipAction(idleClip).play();
  }
});

// In your animation loop, update the mixer every frame
const clock = new THREE.Clock();

function tick() {
  const delta = clock.getDelta();
  if (mixer) mixer.update(delta);
  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}
tick();

The critical detail here is that you must call mixer.update(delta) every frame in your animation loop. Without it, the animation won't advance. The delta is the time in seconds since the last frame, which THREE.Clock provides.

Dealing with materials after loading

glTF models arrive with materials already assigned, typically MeshStandardMaterial configured with the PBR properties from the source file. Sometimes you need to tweak them, either because the export wasn't perfect or because your scene's lighting setup needs different settings.

typescript
model.traverse((child) => {
  if (child.isMesh) {
    const mat = child.material;

    // Adjust roughness and metalness
    mat.roughness = 0.4;
    mat.metalness = 0.8;

    // Override the environment map intensity
    mat.envMapIntensity = 1.5;

    // Make a material double-sided (shows both faces)
    mat.side = THREE.DoubleSide;

    // Replace the material entirely
    // child.material = new THREE.MeshStandardMaterial({
    //   color: 0xff0000,
    //   roughness: 0.3
    // });
  }
});
←   Loading assetsCommon loading errors   →