Rodrigues' Rotation Formula for Arbitrary Axis in GLSL

Rodrigues' rotation formula provides an efficient mathematical method for rotating a three-dimensional vector around an arbitrary axis by a specified angle. This article outlines the mathematical definition of the formula, breaks down its individual vector components, and demonstrates how to implement it cleanly and efficiently in the OpenGL Shading Language (GLSL).

Mathematical Formulation

Given a three-dimensional vector \(\mathbf{v} \in \mathbb{R}^3\), an arbitrary rotation axis represented by a unit vector \(\mathbf{k} \in \mathbb{R}^3\) (where \(\|\mathbf{k}\| = 1\)), and a rotation angle \(\theta\) (in radians), the rotated vector \(\mathbf{v}_{\text{rot}}\) is defined as:

\[\mathbf{v}_{\text{rot}} = \mathbf{v} \cos\theta + (\mathbf{k} \times \mathbf{v}) \sin\theta + \mathbf{k} (\mathbf{k} \cdot \mathbf{v}) (1 - \cos\theta)\]

The formula decomposes the vector \(\mathbf{v}\) into parallel and perpendicular components relative to the axis \(\mathbf{k}\) and rotates the perpendicular component around \(\mathbf{k}\):

  • \(\mathbf{v} \cos\theta\): Scales the original vector by the cosine of the rotation angle.
  • \((\mathbf{k} \times \mathbf{v}) \sin\theta\): Uses the cross product to generate an orthogonal vector perpendicular to both \(\mathbf{k}\) and \(\mathbf{v}\), scaled by \(\sin\theta\), which guides the direction of rotation.
  • \(\mathbf{k} (\mathbf{k} \cdot \mathbf{v}) (1 - \cos\theta)\): Scales the projection of \(\mathbf{v}\) onto \(\mathbf{k}\) by \((1 - \cos\theta)\), ensuring the component of the vector parallel to the axis remains invariant.

GLSL Implementation

GLSL natively supports the basic vector operations required by the formula (dot, cross, and component-wise arithmetic), allowing a direct translation of the mathematics into code.

// Rotates vector 'v' around a normalized 'axis' by 'angle' radians
vec3 rotateAxis(vec3 v, vec3 axis, float angle) {
    float cosTheta = cos(angle);
    float sinTheta = sin(angle);

    return v * cosTheta + 
           cross(axis, v) * sinTheta + 
           axis * dot(axis, v) * (1.0 - cosTheta);
}

Considerations for Shader Optimization

  1. Pre-normalized Axis: The vector representing the axis must be normalized (\(\|\mathbf{k}\| = 1\)). If the axis is not guaranteed to be a unit vector, normalize it prior to running the equation using normalize(axis).
  2. Matrix Construction vs. Direct Evaluation: If rotating a single vector per vertex or fragment, direct evaluation using the vector formula minimizes instruction overhead. If rotating multiple vectors by the same axis and angle, construct a \(3\times3\) rotation matrix once to reduce duplicate trigonometric evaluations:
mat3 rotationMatrix(vec3 axis, float angle) {
    float c = cos(angle);
    float s = sin(angle);
    float t = 1.0 - c;
    
    return mat3(
        t * axis.x * axis.x + c,          t * axis.x * axis.y - s * axis.z,  t * axis.x * axis.z + s * axis.y,
        t * axis.x * axis.y + s * axis.z,  t * axis.y * axis.y + c,          t * axis.y * axis.z - s * axis.x,
        t * axis.x * axis.z - s * axis.y,  t * axis.y * axis.z + s * axis.x,  t * axis.z * axis.z + c
    );
}