# 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. Feb 5, 2017 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. Feb 5, 2017 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? Feb 5, 2017 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? Feb 5, 2017 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. Feb 5, 2017 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]$$

In this reference they use row-major order matrices so your assumption that your matrix is column-major is wrong (to start with). Then you will need to check the code you use to multiply this matrix with points. All source code is provided on the reference, so you just need to read and reproduce.

• Yes, I'm sorry. I'm not actually in a Computer Graphics class, just trying to teach myself. May I ask why this matrix (from the reference) uses -f/(f-n) and -f*n/(f-n)? Do you end up with the same results with the matrix @ray gave?
– divx
Feb 5, 2017 at 16:08
• @Kourbou: doesn't the matrix look the same to you? I you learn computer graphics it is important you learn how to read equations (as well as code) Feb 5, 2017 at 17:25