Play Skeletal Animations from GLTF in Three.js

Working with 3D models in Three.js often involves animating characters using skeletal rigs. This article provides a direct, step-by-step guide on how to load a GLTF or GLB file, extract its embedded skeletal animations, and play them seamlessly using the Three.js AnimationMixer system.

1. Load the GLTF Model

First, you need to import and use the GLTFLoader to load your 3D asset. Ensure you have a global variable to store the AnimationMixer so it can be accessed inside your render loop.

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

let mixer; // Global variable for the animation mixer
const loader = new GLTFLoader();

loader.load(
    'path/to/model.gltf',
    (gltf) => {
        const model = gltf.scene;
        scene.add(model);

        // Enable shadows for the skeleton if needed
        model.traverse((object) => {
            if (object.isMesh) object.castShadow = true;
        });

        // Initialize animations
        setupAnimations(gltf);
    },
    undefined,
    (error) => {
        console.error('An error occurred while loading the GLTF model:', error);
    }
);

2. Initialize the AnimationMixer and Play Clips

The gltf object returned by the loader contains an animations array. This array holds THREE.AnimationClip objects. To play these clips, you must bind them to the loaded model using a THREE.AnimationMixer.

function setupAnimations(gltf) {
    const model = gltf.scene;
    const animations = gltf.animations;

    if (animations && animations.length) {
        // Create the mixer for the model
        mixer = new THREE.AnimationMixer(model);

        // Option A: Play the first animation clip
        const firstClip = animations[0];
        const action = mixer.clipAction(firstClip);
        action.play();

        // Option B: Find and play an animation by its name
        // const specificClip = THREE.AnimationClip.findByName(animations, 'Run');
        // const action = mixer.clipAction(specificClip);
        // action.play();
    }
}

3. Update the Mixer in the Animation Loop

For animations to progress over time, the AnimationMixer must be updated on every frame. Use a THREE.Clock to calculate the time delta (the time elapsed since the last frame) and pass it to the mixer’s update method.

const clock = new THREE.Clock();

function animate() {
    requestAnimationFrame(animate);

    // Calculate the time delta
    const delta = clock.getDelta();

    // Update the animation mixer
    if (mixer) {
        mixer.update(delta);
    }

    // Render the scene
    renderer.render(scene, camera);
}

animate();

Controlling the Animation Playback

Once you have the AnimationAction object (returned by mixer.clipAction()), you can control its playback using the following methods: