How glTF Linear Animation Interpolation Works

This article provides a technical overview of how glTF animation samplers calculate intermediate values between keyframes using the LINEAR interpolation mode. It explains the mechanics of determining the interpolation factor between keyframe timestamps and details the exact mathematical approaches required by the glTF 2.0 specification, differentiating between linear vector interpolation (Lerp) for translation, scale, and morph weights, and spherical linear interpolation (Slerp) for rotations.

Animation Sampler Basics

In glTF, an animation sampler defines how animation data is sampled and interpolated over time. It relies on two main components:

  • input: An accessor pointing to floating-point timestamps representing keyframe times (\(t_0, t_1, \dots, t_n\)) in strictly increasing order.
  • output: An accessor pointing to keyframe values (translations, rotations, scales, or morph target weights) corresponding directly to the input timestamps.

When the animation sampler's interpolation property is set to "LINEAR", the engine must mathematically bridge the gap between keyframe values across any given current time \(T\).

Calculating the Interpolation Factor

Given a current playback time \(T\) that falls between two adjacent keyframes \(t_k\) and \(t_{k+1}\) such that \(t_k \le T < t_{k+1}\), the first step is computing the normalized interpolation factor \(t\):

\[t = \frac{T - t_k}{t_{k+1} - t_k}\]

The resulting scalar \(t\) ranges from 0.0 (at timestamp \(t_k\)) to 1.0 (at timestamp \(t_{k+1}\)).

Translation and Scale: Linear Interpolation (Lerp)

When the animation target is a node's translation or scale, the output values are 3D vectors (VEC3). The sampler computes intermediate values using standard component-wise linear interpolation (Lerp):

\[\vec{V}(t) = (1 - t)\vec{V}_k + t\vec{V}_{k+1}\]

Each component (\(x, y, z\)) is interpolated independently. The result directly represents the node's local position or scale factor at time \(T\).

Rotation: Spherical Linear Interpolation (Slerp)

When the animation target is a node's rotation, the output values are unit quaternions (VEC4) ordered as \((x, y, z, w)\).

Standard linear interpolation cannot be used directly on quaternions because it fails to preserve a constant angular velocity and degrades the unit length of the quaternion, which leads to visual distortion. Instead, the glTF specification mandates Spherical Linear Interpolation (Slerp).

Slerp interpolates along the shortest arc on the 4D unit hypersphere:

  1. Shortest Path Correction: Compute the dot product between the two quaternions: \[\cos(\Omega) = q_k \cdot q_{k+1} = x_k x_{k+1} + y_k y_{k+1} + z_k z_{k+1} + w_k w_{k+1}\] If \(\cos(\Omega) < 0\), invert one of the quaternions (e.g., \(q_{k+1} = -q_{k+1}\)) and negate \(\cos(\Omega)\) so the rotation takes the shortest path.
  2. Interpolation Formula: \[q(t) = \frac{\sin((1 - t)\Omega)}{\sin(\Omega)} q_k + \frac{\sin(t\Omega)}{\sin(\Omega)} q_{k+1}\] Where \(\Omega = \arccos(\cos(\Omega))\).
  3. Small Angle Fallback: If \(\cos(\Omega)\) is close to 1.0 (meaning the angle between keyframes is extremely small), engines fall back to a normalized Lerp (Nlerp) to avoid division by zero: \[q(t) = \text{normalize}((1 - t)q_k + t q_{k+1})\]

The final quaternion must always be normalized to ensure it remains a valid unit rotation.

Morph Target Weights: Component-wise Interpolation

When targeting weights for morph targets, the output elements represent scalar arrays. If a mesh has \(m\) morph targets, each keyframe provides \(m\) weight values.

The sampler applies standard linear interpolation component-wise across each individual morph target weight:

\[w_i(t) = (1 - t)w_{i, k} + t w_{i, k+1}\]

for each target index \(i \in [0, m - 1]\).