How to Calculate Quaternion Slerp in GLSL

Spherical linear interpolation (slerp) calculates intermediate orientations between two unit quaternions along the shortest path on a 4D unit hypersphere. In OpenGL Shading Language (GLSL), computing slerp requires evaluating the dot product between the quaternions, ensuring the shortest rotation path, handling the trigonometric weights, and providing a linear fallback when orientations are nearly identical to prevent division by zero.

The Mathematical Principle

Given two unit quaternions \(q_0\) and \(q_1\), the angle \(\Omega\) between them satisfies:

\[\cos(\Omega) = q_0 \cdot q_1\]

The standard slerp formula is defined as:

\[\text{slerp}(q_0, q_1, t) = \frac{\sin((1 - t)\Omega)}{\sin(\Omega)} q_0 + \frac{\sin(t\Omega)}{\sin(\Omega)} q_1\]

Where:

  • \(t \in [0.0, 1.0]\) represents the interpolation factor.
  • \(\Omega\) is the angular distance between the two quaternions.

GLSL Implementation

GLSL represents quaternions as 4D vectors (vec4), commonly structured as vec4(x, y, z, w). Below is an optimized, robust implementation of quaternion slerp for GLSL:

vec4 slerp(vec4 q0, vec4 q1, float t) {
    // Compute the cosine of the angle between the two vectors
    float dotProduct = dot(q0, q1);

    // If the dot product is negative, slerp won't take the shorter path.
    // Invert one quaternion to take the shortest route.
    if (dotProduct < 0.0) {
        q1 = -q1;
        dotProduct = -dotProduct;
    }

    // Clamp to prevent domain errors in acos
    dotProduct = clamp(dotProduct, -1.0, 1.0);

    // If the quaternions are extremely close, use linear interpolation (lerp)
    // to avoid division by zero when sin(omega) approaches zero.
    const float THRESHOLD = 0.9995;
    if (dotProduct > THRESHOLD) {
        return normalize(mix(q0, q1, t));
    }

    // Calculate the angle omega and the sine of omega
    float omega = acos(dotProduct);
    float sinOmega = sin(omega);

    // Compute scale factors
    float factor0 = sin((1.0 - t) * omega) / sinOmega;
    float factor1 = sin(t * omega) / sinOmega;

    return factor0 * q0 + factor1 * q1;
}

Step-by-Step Breakdown

1. The Shortest Path Correction

Quaternions possess double-cover properties: \(q\) and \(-q\) represent the same 3D spatial rotation. A negative dot product indicates that the angular distance between \(q_0\) and \(q_1\) is greater than 90 degrees along the hypersphere. Negating \(q_1\) and flipping the sign of the dot product forces the interpolation to traverse the shortest geodesic arc.

2. Numerical Stability via Lerp Fallback

When \(\cos(\Omega) \to 1\), \(\Omega \to 0\), making \(\sin(\Omega)\) approach zero. Evaluating \(\frac{1}{\sin(\Omega)}\) directly introduces floating-point instability or NaN outputs. When the dot product exceeds a threshold (such as 0.9995), transitioning to normalized linear interpolation using GLSL's built-in mix() and normalize() functions maintains visual smoothness while avoiding numerical precision issues.

3. Trigonometric Interpolation

When the angle is outside the fallback threshold, the exact angle \(\Omega\) is extracted using acos(). The weights \(\frac{\sin((1-t)\Omega)}{\sin(\Omega)}\) and \(\frac{\sin(t\Omega)}{\sin(\Omega)}\) apply a non-linear scaling to the components of \(q_0\) and \(q_1\), ensuring a constant angular velocity along the interpolation path.