Calculate Vector Rejection in GLSL
This article explains how to compute the orthogonal vector rejection component of one 3D vector from another using OpenGL Shading Language (GLSL). Vector rejection yields the component of a vector that is strictly perpendicular (orthogonal) to a reference vector. Below, you will find the mathematical principle behind the operation along with optimized GLSL implementations for both arbitrary and normalized vectors.
Mathematical Foundation
Given an input vector \(\vec{a}\) and a reference vector \(\vec{b}\), \(\vec{a}\) can be decomposed into two orthogonal components:
\[\vec{a} = \vec{a}_{\parallel} + \vec{a}_{\perp}\]
- \(\vec{a}_{\parallel}\) is the vector projection of \(\vec{a}\) onto \(\vec{b}\).
- \(\vec{a}_{\perp}\) is the vector rejection of \(\vec{a}\) from \(\vec{b}\) (the orthogonal component).
The vector projection is calculated using the dot product:
\[\text{proj}_{\vec{b}}(\vec{a}) = \frac{\vec{a} \cdot \vec{b}}{\vec{b} \cdot \vec{b}} \vec{b}\]
Subtracting the projection from the original vector yields the vector rejection:
\[\text{rej}_{\vec{b}}(\vec{a}) = \vec{a} - \text{proj}_{\vec{b}}(\vec{a}) = \vec{a} - \left( \frac{\vec{a} \cdot \vec{b}}{\vec{b} \cdot \vec{b}} \right) \vec{b}\]
GLSL Implementation for Arbitrary Vectors
When the reference vector b is not guaranteed to be
normalized, use the GLSL dot() function to handle both the
numerator and denominator:
vec3 vectorRejection(vec3 a, vec3 b) {
float bDotB = dot(b, b);
// Prevent division by zero if b is a null vector
if (bDotB == 0.0) {
return a;
}
vec3 projection = (dot(a, b) / bDotB) * b;
return a - projection;
}Optimized Implementation for Normalized Vectors
In shader programming, reference vectors (such as surface normals,
light directions, or camera axes) are frequently normalized beforehand.
When the reference vector b has a length of 1, the term
\(\vec{b} \cdot \vec{b}\) evaluates to
1.0, eliminating the need for division:
vec3 vectorRejectionUnit(vec3 a, vec3 unitB) {
vec3 projection = dot(a, unitB) * unitB;
return a - projection;
}Performance Consideration
In real-time graphics pipelines, avoid dynamic branching by ensuring the reference vector is non-zero before passing it to the function. Pre-normalizing reference vectors outside the shader or in the vertex stage allows you to use the optimized multiplication-only variant, reducing instruction cycles in the fragment shader.