Infinite Perspective Projection Matrix in GLSL
An infinite perspective projection matrix removes the traditional far clipping plane by taking the mathematical limit as the far distance approaches infinity (\(f \to \infty\)). This technique is widely used in real-time rendering to prevent distant geometry from being clipped, eliminate artifacts in stencil shadow volumes, and optimize depth buffer usage. Below is the step-by-step mathematical derivation of this matrix and its concrete implementation in GLSL.
Standard Perspective Projection Formulation
A symmetric perspective projection matrix mapping the view frustum to OpenGL normalized device coordinates (NDC) in the range \([-1, 1]\) is typically defined as:
\[ M = \begin{bmatrix} \frac{1}{\tan(\text{fov}_y / 2) \cdot \text{aspect}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(\text{fov}_y / 2)} & 0 & 0 \\ 0 & 0 & -\frac{f + n}{f - n} & -\frac{2fn}{f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix} \]
Where:
- \(\text{fov}_y\) is the vertical field of view in radians.
- \(\text{aspect}\) is the viewport aspect ratio (\(\text{width} / \text{height}\)).
- \(n\) is the positive distance to the near clipping plane.
- \(f\) is the positive distance to the far clipping plane.
Deriving the Infinite Limit
To push the far plane to infinity, calculate the limits of the elements in the third row as \(f \to \infty\):
The \(M_{2,2}\) term: \[\lim_{f \to \infty} \left(-\frac{f + n}{f - n}\right) = \lim_{f \to \infty} \left(-\frac{1 + \frac{n}{f}}{1 - \frac{n}{f}}\right) = -1\]
The \(M_{2,3}\) term: \[\lim_{f \to \infty} \left(-\frac{2fn}{f - n}\right) = \lim_{f \to \infty} \left(-\frac{2n}{1 - \frac{n}{f}}\right) = -2n\]
Substituting these limits yields the standard infinite perspective projection matrix:
\[ M_{\infty} = \begin{bmatrix} \frac{1}{\tan(\text{fov}_y / 2) \cdot \text{aspect}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(\text{fov}_y / 2)} & 0 & 0 \\ 0 & 0 & -1 & -2n \\ 0 & 0 & -1 & 0 \end{bmatrix} \]
When transformed by this matrix, an eye-space point with coordinate \(z_e\) produces:
- For \(z_e = -n\) (the near plane): \(z_{\text{ndc}} = -1\).
- For \(z_e \to -\infty\) (infinite distance): \(z_{\text{ndc}} \to 1\).
GLSL Implementation
GLSL defines matrices in column-major order, meaning constructor arguments are passed column by column.
mat4 infinitePerspective(float fovY, float aspect, float nearPlane) {
float f = 1.0 / tan(fovY * 0.5);
return mat4(
vec4(f / aspect, 0.0, 0.0, 0.0), // Column 0
vec4(0.0, f, 0.0, 0.0), // Column 1
vec4(0.0, 0.0, -1.0, -1.0), // Column 2
vec4(0.0, 0.0, -2.0 * nearPlane, 0.0) // Column 3
);
}Reversed-Z Variant (Clip Depth [0, 1])
Modern graphics pipelines often pair infinite projection with a
reversed floating-point depth buffer (mapping near to \(1.0\) and infinity to \(0.0\)) using
gl_ClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE).
Taking the limit for NDC depth \([0, 1]\) where near maps to \(1\) and infinity maps to \(0\):
\[ M_{\infty,\text{rev}} = \begin{bmatrix} \frac{1}{\tan(\text{fov}_y / 2) \cdot \text{aspect}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(\text{fov}_y / 2)} & 0 & 0 \\ 0 & 0 & 0 & n \\ 0 & 0 & -1 & 0 \end{bmatrix} \]
mat4 infinitePerspectiveReversedZ(float fovY, float aspect, float nearPlane) {
float f = 1.0 / tan(fovY * 0.5);
return mat4(
vec4(f / aspect, 0.0, 0.0, 0.0), // Column 0
vec4(0.0, f, 0.0, 0.0), // Column 1
vec4(0.0, 0.0, 0.0, -1.0), // Column 2
vec4(0.0, 0.0, nearPlane, 0.0) // Column 3
);
}