Interpolating Orientation with Quaternions
A camera swings to face a new target; a character's head turns to follow a bird; a spaceship banks
into a curve. Each is an orientation keyframe problem: you have a start rotation at
one frame and an end rotation at another, and the tool must invent every rotation in between. For
positions the answer is easy — slide along a straight line. For orientations the
naive answer is a trap. Rotations do not live on a flat line; they live on a curved space
(SO(3)), and the only way to glide across it at a steady rate is to walk a
great-circle arc on the sphere of
unit quaternions.
That walk is called SLERP, and getting it right — including one notorious sign flip —
is what makes rotation animation look smooth instead of drunk.
This page is the animation-specific companion to
spherical linear interpolation:
we take the maths you already met and put it to work on
orientation tracks —
blending, splining, and the cheap approximations you will actually meet in a game engine.
Why the obvious methods fail
There are two "obvious" ways to interpolate a rotation, and both are wrong. The first is to store the
rotation as three Euler angles
(yaw, pitch, roll) and lerp each number independently. The second is to store it as a
3\times 3 rotation matrix and lerp the nine entries. Watch them break:
- Component-lerp of Euler angles does not trace a shortest path and does not move
at constant angular speed — the three angles race each other, so the object wobbles and can even
reverse. Worse, near a
gimbal-lock
pose two axes fold together and the interpolation flings the object through a wild detour.
- Lerp of rotation matrices averages the entries entry-by-entry, and the result
leaves the rotation group: the midpoint matrix is no longer orthonormal — it shears and
scales, so your object is momentarily squashed. You would have to re-orthonormalize every frame,
and even then the path is not constant-speed.
The cure is to interpolate on the natural home of rotations. A unit quaternion is a point on the unit
sphere in four dimensions, and every point on that sphere is a valid rotation — there is no
"off the manifold" to fall through. Interpolation just becomes: walk from one point on the
sphere to another along the shortest arc.
Unit quaternions double-cover the rotations
A rotation of angle \alpha about a unit axis
\mathbf{\hat n} is the unit quaternion
q = \left(\cos\tfrac{\alpha}{2},\; \sin\tfrac{\alpha}{2}\,\mathbf{\hat n}\right).
Notice the half-angle. Because of it, turning all the way around
(\alpha \to \alpha + 2\pi) sends
q \to -q, yet it is the same physical orientation. So
q and -q — antipodal points on the 4-sphere —
name one and the same rotation. This is the famous double cover.
- The unit quaternions form the sphere S^3 in four dimensions.
- Each rotation in SO(3) corresponds to exactly two
unit quaternions, q and -q.
- The map q \mapsto R(q) is two-to-one and smooth: it wraps
S^3 twice around the rotation group.
This is not a curiosity — it is the single fact that most rotation-interpolation bugs come from. When
you blend q_0 toward q_1, the engine has a
choice of two arcs (toward q_1 or toward -q_1),
and only one of them is the short way. Pick wrong and a gentle
90^\circ turn will swing the long way round through
270^\circ.
SLERP: the constant-speed arc
Let \theta be the angle between the two unit quaternions on the sphere,
given by \cos\theta = q_0 \cdot q_1 (the four-component dot product).
Spherical linear interpolation walks the great-circle arc between them:
- \operatorname{slerp}(q_0, q_1, t) = \frac{\sin\big((1-t)\theta\big)}{\sin\theta}\,q_0 \;+\; \frac{\sin(t\,\theta)}{\sin\theta}\,q_1, \qquad t \in [0,1].
- It stays on the unit sphere for every t (no renormalization needed).
- It sweeps the arc at constant angular velocity: the angle turned from
q_0 is exactly t\,\theta.
Constant angular velocity is the whole point. It means equal steps in t
produce equal turns — the rotation reads as a single, even sweep, exactly what "linear" between two
orientation keys should feel like. The two weights are just the sine-rule coefficients that split the
arc; as \theta \to 0 they collapse to (1-t) and
t, so SLERP degrades gracefully into an ordinary lerp for nearby
orientations (where \sin\theta would otherwise divide by near-zero — real
code falls back to NLERP there).
Before you SLERP, check the sign of the dot product. If
q_0 \cdot q_1 < 0, the two quaternions are on opposite hemispheres of the
4-sphere, so the great-circle arc between them as written goes the long way — more than
180^\circ around. Because q_1 and
-q_1 are the same rotation (double cover!), the fix is free: negate
one of them, q_1 \leftarrow -q_1, and interpolate to the antipode instead.
Now \cos\theta \ge 0, \theta \le 90^\circ, and you
take the short arc.
Skip this one line and a crisp 90^\circ head-turn can whip around the
270^\circ back way — the classic "my character's head spun like an owl" bug.
Every production SLERP starts with:
function slerp(q0: Quat, q1: Quat, t: number): Quat {
let d = dot(q0, q1);
if (d < 0) { q1 = negate(q1); d = -d; } // shortest path via the double cover
if (d > 0.9995) return normalize(lerp(q0, q1, t)); // nearly parallel: nlerp fallback
const theta = Math.acos(d);
const s = Math.sin(theta);
const w0 = Math.sin((1 - t) * theta) / s;
const w1 = Math.sin(t * theta) / s;
return add(scale(q0, w0), scale(q1, w1));
}
Worked example: SLERP two quaternions 90° apart
Take q_0 = (1,0,0,0) (no rotation) and a quaternion representing a
90^\circ rotation about the x-axis,
q_1 = (\cos 45^\circ,\, \sin 45^\circ, 0, 0) = (0.7071, 0.7071, 0, 0). Their
dot product is \cos\theta = 0.7071, so the sphere angle is
\theta = 45^\circ (half the 90^\circ physical
turn — that half-angle again). It is positive, so no sign flip is needed.
Evaluate at t = 0.5. The weights are equal:
w_0 = w_1 = \frac{\sin(0.5 \cdot 45^\circ)}{\sin 45^\circ} = \frac{\sin 22.5^\circ}{\sin 45^\circ} = \frac{0.3827}{0.7071} = 0.5412.
So q(0.5) = 0.5412\,(q_0 + q_1) = 0.5412\,(1.7071,\,0.7071,\,0,\,0) = (0.9239,\,0.3827,\,0,\,0).
Its scalar part is \cos\tfrac{\alpha}{2} = 0.9239 = \cos 22.5^\circ, so the
rotation angle is \alpha = 45^\circ — exactly halfway
between 0^\circ and 90^\circ. That is
constant angular speed, confirmed.
Now compare NLERP at t = 0.25. True SLERP gives
0.25 \times 90^\circ = 22.5^\circ. NLERP forms the straight-line blend
(1-t)q_0 + t\,q_1 = (0.9268,\,0.1768,\,0,\,0) and normalizes it to
(0.9823,\,0.1874,\,0,\,0). Its scalar part
0.9823 = \cos 10.78^\circ gives a rotation of
21.6^\circ — nearly a whole degree behind where it should be. NLERP
lags early in the arc and rushes through the middle: the endpoints and the exact midpoint are perfect,
but the speed in between is not constant.
SLERP vs NLERP, seen as a speed graph
The cleanest way to see the difference is to plot the angle turned so far
against the parameter t, for the same two orientations
90^\circ apart. SLERP is a dead-straight line from
0^\circ to 90^\circ — equal t
steps, equal turns, constant speed. NLERP (normalized lerp) is a gentle S-curve: it
starts slower than SLERP, speeds up through the middle, then slows again. Both agree at the
two ends and at the exact midpoint; everywhere else NLERP is off. Drag t and
read the gap between the two curves.
For a small arc the S-curve hugs the straight line and NLERP is a fine, cheap substitute — no
\arccos, no sine, no division by a tiny
\sin\theta. That is why real engines SLERP for big blends and NLERP for the
thousands of tiny per-frame ones.
NLERP: the cheap cousin
Normalized linear interpolation is exactly what it says: lerp the four components on
the straight chord, then normalize back onto the sphere.
\operatorname{nlerp}(q_0, q_1, t) = \frac{(1-t)\,q_0 + t\,q_1}{\lVert (1-t)\,q_0 + t\,q_1\rVert}.
- NLERP traces the same great-circle arc as SLERP (both project the chord onto the
sphere), so the path is identical — no wrong detours.
- But it moves along that arc at non-constant angular velocity: slow near the
ends, fast in the middle.
- It is much cheaper (no trig) and, like SLERP, needs the same
q_0 \cdot q_1 < 0 sign flip to take the short way.
Because the path is right and only the timing along it is slightly bowed, NLERP is the
default for real-time skeletal animation, where you blend many bone rotations per frame and the tiny
arc between adjacent poses makes the speed error invisible.
SQUAD: splining through many orientation keys
SLERP joins two keys. An animation track has many orientation keys, and if you just
SLERP from each key to the next you get straight arcs with a sharp kink at every
key — the angular velocity jumps, and the motion visibly stutters at each keyframe. The fix is the
rotational analogue of a
Catmull–Rom spline:
SQUAD (Spherical and QUADrangle interpolation).
- For each interior key q_i compute an intermediate control
quaternion
s_i = q_i \exp\!\left(-\tfrac{1}{4}\big(\log(q_i^{-1} q_{i+1}) + \log(q_i^{-1} q_{i-1})\big)\right),
which nudges the tangent so neighbouring segments meet smoothly.
- Then interpolate a segment with a double SLERP:
\operatorname{squad}(q_i, q_{i+1}, s_i, s_{i+1}, t) = \operatorname{slerp}\big(\operatorname{slerp}(q_i, q_{i+1}, t),\; \operatorname{slerp}(s_i, s_{i+1}, t),\; 2t(1-t)\big).
- The result passes through every key and has a continuous angular velocity across
them — smooth, no kinks.
SQUAD is to orientation what a Catmull–Rom (or Bézier) spline is to position: a
C^1 curve pinned to the keys, built entirely out of SLERPs of SLERPs. The
control quaternions s_i are the quaternion tangent handles.
Interpolating a whole orientation track
Putting it together, evaluating an object's rotation at an arbitrary time
t along a keyframed track is a short recipe:
- Find the two keys that bracket t and the local parameter
u \in [0,1] between them (after applying the key's easing / F-curve, so
spacing still works — orientation obeys the same
timing and spacing
rules as position).
- Ensure the neighbouring quaternions are in the same hemisphere — walk the track
once and flip any key with q_i \cdot q_{i+1} < 0, so no segment takes
the long way.
- Blend: SLERP (or NLERP) for a straight arc, or SQUAD with precomputed control quaternions for a
smooth spline through all the keys.
The same track can feed a camera, a bone, or a prop; the only per-object work is the two lines of
blending. That is why quaternions won: correct rotation interpolation reduces to a dot product, a sign
check, and a couple of SLERPs.
For a single static rotation, a matrix is fine. Quaternions earn their keep the moment you need to
interpolate, compose repeatedly, or store many keys. They are four numbers
instead of nine or sixteen; composing them is one quaternion product instead of a matrix multiply;
they never drift into a non-rotation the way accumulated matrix products do (you just renormalize four
numbers); and — the killer feature here — they have SLERP, a clean, closed-form, constant-speed blend
with no gimbal lock. A modern engine keeps orientations as quaternions everywhere and only expands to a
matrix at the last moment, to actually transform vertices.