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.