Standard Perspective Matrix¶
Objective¶
Derive the standard perspective matrix that OpenGL expects.
Description¶

Turn our NDC into Clip Space¶
Matrix form of perspective projection¶

Scale Camera-space x by Camera-space z¶
resulting in

Scale the camera space x by the camera space z¶
Scale Camera-space y by Camera-space z¶
resulting in

Scale the camera space y by the cameraspace z¶
Translate Rectangular Prism’s Center to Center¶
\(x_{midpoint} = 0\) // centered on x
\(y_{midpoint} = 0\) // centered on y
\(z_{midpoint} = {{{farZ}_c + {nearZ}_c} \over 2}\);

Scale by inverse of the dimensions of the Rectangular Prism¶
\(x_{length} = right * 2\);
\(y_{length} = top * 2\);
\(z_{length} = {nearZ}_c - {farZ}_c\);

Pre-multiply the matricies¶
Multiply them all together to get the following. The elements of this premultiplied matrix have no geometric meaning to the author, and that’s ok. The matricies above all of geometric meaning, and we premultiply them together for computational efficiency, as well as being able to do the next step in clip space, which we couldn’t do without having the premultiplied matrix.
Clip Space¶
convert the data from NDC to clip-space.
We have never used clip-space in the class, only NDC, because 4D space is confusing geometrically, nevermind the fact that (NDCx NDCy NDCz) = (Clipx/Clipw, Clipy/Clipy, Clipz/Clipz)
The purpose of going to clip space is that eventually we will be able to remove the camera space’s z coordinate from the matrix. This will allow us to use one perspective projection matrix for all vertices, independent of the z coordinate of each input vector.
I assume, without any evidence to support me, that this was done for efficiency reasons when using OpenGL’s fixed function pipeline. (Side note, the standard perspective projection matrix, which we will get to by demo 25, does not linearly position the \(nearZ_c\) to \(farZ_c\) data into NDC. Everything we’ve done so far in the class does. The standard perspective matrix ends up having less Z-fighting close to \(nearZ_c\), and more problems with Z-fighting near farZ_c)
OpenGL will automatically convert from clip space to NDC such as follows.
So to put our NDC data into clip space, knowing what OpenGL is going to do in the equation above, we need to decide what we want our clip space value, \(w\) to be, and do the inverse of the equation above
Since we want to get the \(z_c\) relative to camera space out of the premultiplied matrix above, we choose the following
because multiplying by this matrix will remove the \(z_c\) out of the upper left quadrant.
But wait. In camera space, the viewing frustum is defined to be in negative \(z\). So that will
Remove Z of Camera Space from Part of the Matrix¶
To get camera z out of the matrix, where it’s currently in two denominators, we can use knowledge of clip space, wherein we put cameraspace’s z into W. because cameraSpace’s z coordinate is negative, we want to scale all dimensions without reflecting over the origin, hence the negative sign in \(-z_c\).
The result of this is in clip space, where for the first time, our w component is not 1, but \(z_c\).
Turning clip space back into NDC
Remove Z of Camera Space from the Rest of the Matrix¶
We successfully moved \(z_c\) out of the upper left quadrant, but in doing so, we moved it down to the lower right. Can we get rid of it there too? Turn out, we can.
Since the vector multiplied by this matrix will provide \(z_c\) as it’s third element, we can put \(-z_c\) into the \(w\) by taking the explicit version of it out of the fourth column, and put \(-1\) into the third column’s \(w\).
To remove \(z_c\) from the matrix, all that to do is remove it from row 3, somehow. We’re about to ride dirty.
If we were to change row three, it would not be the same transformation. But if we ensure the following two properties of our changes, everything will be alright
We need the
\(\begin{bmatrix} 0 \\ 0 \\ \textcolor{red}{1} \\ 0 \\ \end{bmatrix} \cdot \vec{f}_{c}^{clip} (\begin{bmatrix} {x_c} \\ {y_c} \\ \textcolor{red}{nearZ_c} \\ {w_c=1} \\ \end{bmatrix}) = \textcolor{red}{-1.0}\)
\(\begin{bmatrix} 0 \\ 0 \\ \textcolor{red}{1} \\ 0 \\ \end{bmatrix} \cdot \vec{f}_{c}^{clip} (\begin{bmatrix} {x_c} \\ {y_c} \\ \textcolor{red}{farZ_c} \\ {w_c=1} \\ \end{bmatrix}) = \textcolor{red}{1.0}\)
Ordering is preserved after the function is applied, i.e. monotonicity. if \(z_1 < z_2\), then \((\begin{bmatrix} 0 \\ 0 \\ \textcolor{red}{1} \\ 0 \\ \end{bmatrix} \cdot \vec{f}_{c}^{clip}(\begin{bmatrix} 0 \\ 0 \\ \textcolor{red}{z_1} \\ 0 \\ \end{bmatrix} )) < (\begin{bmatrix} 0 \\ 0 \\ \textcolor{red}{1} \\ 0 \\ \end{bmatrix} \cdot \vec{f}_{c}^{clip}(\begin{bmatrix} 0 \\ 0 \\ \textcolor{red}{z_2} \\ 0 \\ \end{bmatrix} ) )\).
If we can make a function, that like the third row of the matrix, has those properties, we can replace the third row and remove camera space’s z, \(z_c\), from the matrix. This is desirable because, if it were to exist, would would not need per vector to create a custom perspective matrix.
Towards that, let’s look at these jibronies.