June 18, 2013

After posting my last article relating glFrustum to the intrinsic camera matrix, I receieved some emails asking how the (now deprecated) gluPerspective function relates to the intrinsic matrix. We can show a similar result with `gluPerspective`

as we did with `glFrustum`

, namely that it is the product of a `glOrtho`

matrix and a (modified) intrinsic camera matrix, but in this case the intrinsic matrix has different constraints. I'll be re-using notation and concepts from the previous article, so if you aren't familiar with them, I recommend reading it first.

The matrix generated by `gluPerspective`

is

\[
\begin{align}
\left (
\begin{array}{cccc}
\frac{f}{\text{aspect}} & 0 & 0 & 0 \\
0 & f & 0 & 0 \\
0 & 0 & C' & D' \\
0 & 0 & -1 & 0
\end{array}
\right )
\end{align}
\]

where

\[
\begin{align}
f &= \cot(fovy/2) \\
C' &= -\frac{far + near}{far - near} \\
D' &= -\frac{2 \; far \; near}{far - near} \\
\end{align}
\]

Like with `glFrustum`

, `gluPerspective`

permits no axis skew, but it also restricts the viewing volume to be centered around the camera's principal (viewing) axis. This means that the principal point offsets \(x_0\) and \(y_0\) must be zero, *and* the matrix generated by `glOrtho`

must be centered, i.e. `bottom = -top`

and `left = -right`

. The *Persp* matrix corresponding to the intrinsic matrix is:

\[ Persp = \left( \begin{array}{cccc} \alpha & 0 & 0 & 0 \\ 0 & \beta & 0 & 0 \\ 0 & 0 & A & B \\ 0 & 0 & -1 & 0 \end{array} \right) \]

where

\[ \begin{align}
A &= near + far \\
B &= near * far
\end{align} \]

and the *NDC* matrix is

\[ \begin{align}
NDC &= \left( \begin{array}{cccc}
\frac{2}{right - left} & 0 & 0 & t_x \\
0 & \frac{2}{top - bottom} & 0 & t_y \\
0 & 0 & -\frac{2}{far - near} & t_z \\
0 & 0 & 0 & 1
\end{array} \right) \\[1.5em]
&= \left( \begin{array}{cccc}
\frac{2}{width} & 0 & 0 & 0 \\
0 & \frac{2}{height} & 0 & 0 \\
0 & 0 & -\frac{2}{far - near} & t_z \\
0 & 0 & 0 & 1
\end{array} \right)
\end{align}
\]

where

\[ \begin{align}
t_x &= -\frac{right + left}{right - left} \\
t_y &= -\frac{top + bottom}{top - bottom} \\
t_z &= -\frac{far + near}{far - near}
\end{align} \]

It is easy to show that the product \((NDC \times Persp)\) is equivalent to the matrix generated by `gluPerspective(fovy, aspect, near, far)`

with

\[ \begin{align}
\text{fovy} &= 2 \text{arctan}\left (\frac{\text{height}}{2 \beta} \right ) \\
\text{aspect} &= \frac{\beta}{\alpha} \frac{\text{width}}{\text{height}}.
\end{align}
\]

In my experience, the zero-skew assumption is usually reasonable, so `glFrustum`

can provide a decent approximation to the full intrinsic matrix. However there is quite often a non-negligible principal point offset (~ 2% of the image size), even in high-quality cameras. For this reason, `gluPerspective`

might be a good choice for quick-and-dirty demos, but for the most accurate simulation, you should use the full camera matrix like I described previously.

Dissecting the Camera Matrix, Part 3: The Intrinsic Matrix →
← Calibrated Cameras in OpenGL without glFrustum