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)

               Image courtesy 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):
    t = 1/math.tan(math.radians(fov)/2)
    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?

                     Messed up cube


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.

| improve this answer | |
  • $\begingroup$ 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. $\endgroup$ – user18490 Feb 5 '17 at 12:39
  • $\begingroup$ @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. $\endgroup$ – code_dredd Feb 5 '17 at 12:41
  • $\begingroup$ 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? $\endgroup$ – user18490 Feb 5 '17 at 12:44
  • $\begingroup$ @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? $\endgroup$ – code_dredd Feb 5 '17 at 12:52
  • $\begingroup$ 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. $\endgroup$ – user18490 Feb 5 '17 at 12:59

Please read the references that are given to you:

$$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.

| improve this answer | |
  • $\begingroup$ 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? $\endgroup$ – kourbou Feb 5 '17 at 16:08
  • $\begingroup$ @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) $\endgroup$ – user18490 Feb 5 '17 at 17:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.