GLSL Quaternion Multiplication Using vec4
This article explains how to implement quaternion multiplication in
the OpenGL Shading Language (GLSL) using native four-component vectors
(vec4). It covers the mathematical formulation of the
Hamilton product, breaks down the vector and scalar components, and
provides an optimized GLSL function utilizing built-in vector operations
like dot and cross.
Quaternion Representation in GLSL
In 3D graphics, a quaternion \(q\) is typically represented as a combination of a 3D vector part \(\mathbf{v} = (x, y, z)\) and a scalar real part \(w\):
\[q = (\mathbf{v}, w) = (x, y, z, w)\]
In GLSL, a vec4 natively maps to this representation,
where the .xyz swizzle corresponds to the vector part \(\mathbf{v}\), and the .w
component corresponds to the scalar \(w\).
Mathematical Formulation
The product of two quaternions \(q_1 = (\mathbf{v}_1, w_1)\) and \(q_2 = (\mathbf{v}_2, w_2)\) is defined by the Hamilton product:
\[\mathbf{v}_{\text{result}} = w_1 \mathbf{v}_2 + w_2 \mathbf{v}_1 + (\mathbf{v}_1 \times \mathbf{v}_2)\] \[w_{\text{result}} = w_1 w_2 - (\mathbf{v}_1 \cdot \mathbf{v}_2)\]
This formulation is ideal for GLSL because it maps directly to
built-in vector instructions: scalar multiplication, addition, the cross
product (cross), and the dot product
(dot).
GLSL Implementation
Below is the standard, vector-optimized GLSL function to multiply two
quaternions stored in vec4:
vec4 quatMult(vec4 q1, vec4 q2) {
return vec4(
q1.w * q2.xyz + q2.w * q1.xyz + cross(q1.xyz, q2.xyz),
q1.w * q2.w - dot(q1.xyz, q2.xyz)
);
}How the Code Works
- Vector Component Calculation (
.xyz):q1.w * q2.xyz: Scales the vector part of \(q_2\) by the scalar part of \(q_1\).q2.w * q1.xyz: Scales the vector part of \(q_1\) by the scalar part of \(q_2\).cross(q1.xyz, q2.xyz): Computes the 3D cross product of both vector parts, handling the non-commutative rotational interaction.- Adding these three together yields the directional vector component of the resulting quaternion.
- Scalar Component Calculation (
.w):q1.w * q2.w: Multiplies the two scalar components.dot(q1.xyz, q2.xyz): Computes the Euclidean dot product of the vector parts.- Subtracting the dot product from the scalar product yields the final scalar real component.
Component-Wise Alternative
If you need to avoid the cross function or want to
inspect individual algebra steps, the fully expanded component-wise
formulation can be used:
vec4 quatMultExpanded(vec4 q1, vec4 q2) {
return vec4(
q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,
q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x,
q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w,
q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z
);
}The vector-based version using cross() and
dot() is generally preferred for readability and leverages
dedicated hardware vector units efficiently on modern GPUs.