# My perspective projection is messed up?

So I've been messing with perspective projection matrices recently. I used numpy and GTK/Cairo to make a very small Python renderer. I'm very confused with the results I'm getting though.

I took this Homogeneous Coordinates technique from an online lecture. If I understood correctly, the objective is to transform every point inside a "Viewing Pyramid" that's frustum shaped so they fit in a cube. (Image from of songho.ca)

You need a Field of View angle ($\alpha$), the Near and Far plane distances ($n$ and $f$ respectively), and the aspect ratio ($r$). Firstly you turn every 3D Point into a Homogeneous Point by adding a 1 like so:

\begin{align*} \begin{pmatrix} x & y & z \end{pmatrix} \xrightarrow{\text{4D}} \begin{bmatrix} x & y & z & 1 \end{bmatrix} \end{align*}

Then you multiply your point matrix by a perspective projection matrix:

\begin{align*} \begin{bmatrix}x & y &z & 1 \end{bmatrix} \begin{bmatrix} 1\over\tan(\alpha/2) & 0 & 0 & 0\\ 0 & r\over\tan(\alpha/2) & 0 & 0\\ 0 & 0 & (f+n)\over(f-n) & -1 \\ 0 & 0 & (2nf)\over(f-n) & 0 \end{bmatrix} = \begin{bmatrix} x' & y' & z' & w \end{bmatrix} \end{align*}

And to go back to a 3D point in space you divide by the fourth dimension:

\begin{align*} \begin{bmatrix} x' & y' & z' & w \end{bmatrix} \xrightarrow{\text{3D}} \begin{pmatrix} x' \over w & y' \over w & z' \over w \end{pmatrix} \end{align*}

This is exactly what I've done with numpy:

def projection_matrix(fov, aspect, near, far):
a = (far + near)/(far - near)
b = (2*near*far)/(far-near)
r = aspect

return numpy.matrix([[t,   0,   0,   0],
[0, r*t,   0,   0],
[0,   0,   a,  -1],
[0,   0,   b,   0]])


But for some reason the renderer is totally messed up. This is supposed to be a spinning cube... What am I missing here?

The math for the projection matrix is (with fov as $\alpha$):

$q \leftarrow \frac{1}{tan(\frac{\alpha}{2})}$

$a \leftarrow \frac{q}{aspect}$

$b \leftarrow \frac{(far + near)}{(near - far)}$

$c \leftarrow \frac{(2 * far * near)}{(near - far)}$

Notice that there're some things you're doing that are differently, such as the order of your subtractions between near and far, how you organize the matrix values, and your multiplication between your r * t.

Using the variables above, the column-major matrix below would be the resulting perspective projection matrix:

\begin{bmatrix} a & 0 & 0 & 0 \\ 0 & q & 0 & 0 \\ 0 & 0 & b & c \\ 0 & 0 & -1 & 0 \end{bmatrix}

From the above, we get:

def perspective_projection_matrix(fov, aspect, near, far):
q = 1 / tan(radians(fov * 0.5))
a = q / aspect
b = (far + near) / (near - far)
c = (2*near*far) / (near - far)

# construct column-major matrix here...


NOTE: I left the last part out because I'm not familiar enough with numpy to know whether it expects row-major or column-major order.

Also, you should validate all your arguments (e.g. both near > 0 and far > 0, far > near, etc.) if you want to avoid future headaches.

• That's one of the problems you have. If you try to debug code without knowing what the library you use do, then you will not be able to fix much. Use libraries you either understand or write your own code for this. – user18490 Feb 5 '17 at 12:39
• @user18490 I wrote my own code... I just had to translate my code from Java into Python here and I don't use numpy specifically. – code_dredd Feb 5 '17 at 12:41
• Please check the reference I provided you. Do you understand the difference between column and row-major order matrices? Also how can you tell the matrix you give above is column-major? – user18490 Feb 5 '17 at 12:44
• @user18490 I'm going over the reference (it's 5am here, though), which is trying to explain how a view-matrix works, but that should be kept in the other question... don't mix stuff up cross-questions. Also, I understand the difference between row and column-major matrices... not sure what you're trying to get at? – code_dredd Feb 5 '17 at 12:52
• I don't know what you mean by mixing stuff. You have code that doesn't work. You say this matrix you provide (in form of code) is column-major. I tell you 'no' it's not. The reference I pointed you to, explains how a perspective matrix is actually built, which is what you are trying to do. The fact that it's 5am your place is not relevant to the question: tare the time to digest the theory (before posting anything else). Then it will help you fix your code. – user18490 Feb 5 '17 at 12:59

$$S = \dfrac{1}{\tan(\dfrac{fov}{2}*\dfrac{\pi}{180})}$$
$$\left[\begin{array}{cccc} S && 0 && 0 && 0 \\ 0 && S && 0 && 0 \\ 0 && 0 && -\dfrac{f}{(f-n)} && -1\\ 0 && 0 && -\dfrac{f*n}{(f-n)} && 0\\ \end{array}\right]$$