Perspective Projection

Short Version:

Given:

Input values
Symbol Meaning Typical value
\(fov\) Field of view 45 – 90 degrees
\(aspect\) Aspect ratio around 1.8 – use frame buffer Width / Height
\(z_{near}\) distance from camera to near clip plane in Z +1
\(z_{far}\) distance from camera to far clip plane. MUST be greater than zNear. 1000

You’ll need to calculate a few values before populating the matrix:

Intermediate derived values
Symbol Meaning Formula
\(halfHeight\) half of frustum height at \(z_{near}\) \(z_{near} * \tan{\frac{fov}{2}}\)
\(halfWidth\) half of frustum width at \(z_{near}\) \({halfHeight}\times{aspect}\)
\(depth\) depth of view frustum \(z_{far}-z_{near}\)

Then, just populate your matrix:

\(
\large
\begin{bmatrix}
\frac{z_{near}}{halfWidth} & 0 & 0 & 0 \\
\\
0 & \frac{z_{near}}{halfHeight} & 0 & 0 \\
\\
0 & 0 & \frac{-({z_{far}} + {z_{near}})}{depth} & -1 \\
\\
0 & 0 & -2\times\frac{z_{far} \times z_{near}}{depth} & 0 \\
\end{bmatrix}
\)

Explanation

Basically, this takes a vertex in camera space (see note on coordinate spaces), and scales it so that every vertex that is contained within the view frustum fits within a (-1:+1) cube. The rasterizer, a non-programmable hardware stage that executes after the vertex shader but before the fragment shader, divides each vertex by it’s W component.

Avoiding a long and sticky explanation for the moment:

Any point that lies within the view frustum will, after processed by the rasterizer, fit within a 2×2 cube centered at the origin. Any part of a primitive that lies within that space will trigger a call to the fragment shader. Anything outside of that cube will be “clipped” by the rasterizer, and will not render – since it does not exist on screen.

Better explanation required.