Alternative Rotation Representations
3×3 Rotation Matrix
- Memory overhead: A matrix requires 9 floats, or 16 floats if using an affine transformation
- Computational complexity: Rotating a vector through matrix multiplication involves 3 dot products, totaling 9 multiplications and 6 additions
- Poor readability: A matrix is essentially a grid of numbers, making it difficult to interpret visually
- Supports rotation around any axis by any angle without gimbal lock—do not confuse this with Euler angle limitations
Euler Angles
- Only 3 components, using minimal memory, but interpolation breaks down when rotation axes are arbitrary
- Gimbal lock occurs when a 90° rotation causes two of the three principal axes to align perfectly. For instance, rotating 90° around the X-axis causes the Y-axis to align with the Z-axis. Once aligned, independent rotation around the original Y-axis becomes impossible since rotating around Y and Z become equivalent operations
- Axis rotation order must be explicitly defined—different orders produce different results. Three scalar values alone cannot define a rotation; the sequence matters
- Euler angle rotations also construct rotation matrices, but sequentially combining matrices that rotate around the X, Y, and Z axes in a specified order
Unit Quaternions for 3D Rotation
A quaternion has the form q = [qx qy qz qw]. While it appears as a 4D vector, its behavior differs fundamentally.
Throughout this article, 3D vectors are bolded while quaternions are not
Critical constraint: Only unit quaternions (where qx²+qy²+qz²+qw²=1) represent valid 3D rotations
A unit quaternion consists of a 3D vector qᵥ and a scalar qₛ:
- qᵥ: The rotation axis direction (as a unit vector) multiplied by the sine of half the rotation angle
- qₛ: The cosine of half the rotation angle
A unit quaternion can be expressed as:
q = [qᵥ qₛ] = [a·sin(θ/2) cos(θ/2)] = [ax·sin(θ/2) ay·sin(θ/2) az·sin(θ/2) cos(θ/2)]
Where a is the unit vector along the rotation axis and θ is the rotation angle.
In pure mathematics, a quaternion comprises 1 real component plus 3 imaginary components: q = a + bi + cj + dk (a,b,c,d∈ℝ). This convention places the scalar first, followed by the 3 imaginary components as a vector: q = [qₛ qᵥ]
However, in game development and computer graphics, memory layout considerations typically place the scalar component in the last position: q = [qᵥ qₛ]
Quaternion Operations
Since rotations require unit quaternions, basic addition and subtraction operations are rarely used.
Multiplication
Multiplication is the most important quaternion operation. Given two unit quaternions p and q, representing rotations P and Q respectively, the product pq represents the combined rotation (rotation Q followed by rotation P). Multiple quaternion multiplication variants exist, but the operation relevant for 3D rotations is called the Grassmann Product:
pq = [(pₛqᵥ + qₛpᵥ + pᵥ×qᵥ) (pₛqₛ - pᵥ·qᵥ)]
Here, the vector components correspond to the first three elements (x, y, z), and the scalar component is the final element (w).
Conjugate and Inverse
Quaternion norm:
||q|| = √(a² + b² + c² + d²)
Conjugate quaternion: Real part unchanged, imaginary parts negated
q* = [-qᵥ qₛ]
Inverse quaternion: Adjugate divided by the square of the norm. For 3D rotations, quaternions are always unit length, so ||q|| = 1:
q⁻¹ = q* / ||q||² = q* = [-qᵥ qₛ]
This result is highly valuable. When a quaternion has been normalized, computing the inverse requires no division by the norm squared (which is computationally expensive). This also means computing a quaternion inverse is substantially faster than computing a 3×3 matrix inverse—a detail that can be exploited for engine optimization.
Additional algebraic properties, similar to standard vector operations:
- Reciprocity: q⁻¹q = qq⁻¹ = 1
- Conjugate of product: (pq)* = qp
- Inverse of product: (pq)⁻¹ = q⁻¹p⁻¹
- Transpose of product: (pq)ᵀ = qᵀpᵀ
Rotating Vectors with Quaternions
First, convert the vector into quaternion form by setting the scalar component to zero. Given vector v, it becomes:
v = [v 0] = [vx vy vz 0]
To rotate vector v by quaternion q:
v' = qvq⁻¹
Since q⁻¹ = q* for unit quaternions, the conjugate can substitute for the inverse:
v' = qvq*
The resulting quaternion's vector portion (first three components) represents the rotated vector v'.
Multiple rotations concatenate naturally—chain them from left to right.
In game engines, quaternions serve many purposes beyond traditional rotation transforms. The following example illustrates this:
Scenario: A game object exists in world space with a specific orientation. The object's current rotation state is stored as Transform.Rotation = (roll, pitch, yaw) (engines typically maintain orientation in Euler angles but perform internal calculations in quaternion form). We need to compute the three directional vectors Forward, Up, and Right.
-
Using Euler angles with 4×4 matrices:
This approach requires adhering to the engine's specified rotation order. For example, rotating around X first (pitch), then Y (yaw), then Z (roll). Different sequences yield different outcomes.
auto euler = fetchObjectTransform().orientation;
glm::mat4 rotMat(1.0f);
rotMat = glm::rotate(rotMat, glm::radians(euler.z), glm::vec3(0.0f, 0.0f, 1.0f));
rotMat = glm::rotate(rotMat, glm::radians(euler.y), glm::vec3(0.0f, 1.0f, 0.0f));
rotMat = glm::rotate(rotMat, glm::radians(euler.x), glm::vec3(1.0f, 0.0f, 0.0f));
glm::vec3 fwd = glm::vec3(rotMat * glm::vec4(0.0f, 0.0f, 1.0f, 0.0f));
glm::vec3 upward = glm::vec3(rotMat * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
glm::vec3 rgt = glm::vec3(rotMat * glm::vec4(1.0f, 0.0f, 0.0f, 0.0f));
-
Using quaternion rotation:
Construct a quaternion from the object's orientation. This quaternion represents the rotation transform from the object's initial local coordinate space to its current world space orientation. Applying this transform to the local
Forward,Up, andRightvectors yields the world-space directions.
glm::quat orientQuat = glm::quat(fetchObjectTransform().orientation);
glm::vec3 fwd = glm::rotate(orientQuat, glm::vec3(0.0f, 0.0f, 1.0f));
glm::vec3 upward = glm::rotate(orientQuat, glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 rgt = glm::rotate(orientQuat, glm::vec3(1.0f, 0.0f, 0.0f));
Equivalence Between Quaternions and Matrices
Any 3D rotation can be freely converted between 3×3 matrix form R and quaternion form q (using libraries like GLM).
Quaternion to Rotation Matrix
Given quaternion q = [x, y, z, w], the corresponding rotation matrix R is:
R = | 1 - 2y² - 2z² 2xy + 2zw 2xz - 2yw |
| 2xy - 2zw 1 - 2x² - 2z² 2yz + 2xw |
| 2xz + 2yw 2yz - 2xw 1 - 2x² - 2y² |
Rotation Matrix to Quaternion
w = √(1 + tr(R)) / 2
x = (R₂₃ - R₃₂) / (4w)
y = (R₃₁ - R₁₃) / (4w)
z = (R₁₂ - R₂₁) / (4w)
tr(A) = Σᵢ₌₁ⁿ Aᵢᵢ
For practical implementation, leveraging established libraries is recommended. In GLM:
// Convert quaternion to rotation matrix
glm::mat4 rotMatrix = glm::mat4_cast(quaternion);
// Convert rotation matrix to quaternion
glm::quat quaternionFromMat = glm::quat_cast(rotMatrix);
Standard transform matrices in GLM use 4×4 dimensions for affine transformations supporting rotation, translation, and scaling. The fourth row and column for rotation matrices typically contain (0, 0, 0, 1).
Linear Interpolation for Rotations
Linear interpolation (LERP) operates component-wise on quaternions:
q(t) = (1 - t) · q₁ + t · q₂
Expanded to individual components:
q(t) = normalize([
xs(t) = (1-t)·x₁ + t·x₂,
ys(t) = (1-t)·y₁ + t·y₂,
zs(t) = (1-t)·z₁ + t·z₂,
ws(t) = (1-t)·w₁ + t·w₂
]ᵀ)
For higher precision, SLERP (Spherical Linear Interpolation) provides better results at increased computational cost.