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)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

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 &amp; y &amp; z
\end{pmatrix}
\xrightarrow{\text{4D}}
\begin{bmatrix}
  x &amp; y &amp; z &amp; 1
\end{bmatrix}
\end{align*}



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

\begin{align*}
\begin{bmatrix}x &amp; y &amp;z &amp; 1 \end{bmatrix}
\begin{bmatrix}
  1\over\tan(\alpha/2) &amp; 0 &amp; 0 &amp; 0\\ 
  0 &amp; r\over\tan(\alpha/2) &amp; 0 &amp; 0\\
  0 &amp; 0 &amp; (f+n)\over(f-n) &amp; -1 \\
  0 &amp; 0 &amp; (2nf)\over(f-n) &amp; 0
\end{bmatrix}
=
\begin{bmatrix}
x' &amp; y' &amp; z' &amp; 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' &amp; y' &amp; z' &amp; w
\end{bmatrix}
\xrightarrow{\text{3D}}
\begin{pmatrix}
  x' \over w &amp; y' \over w &amp; 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?

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
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 &amp; 0 &amp;  0 &amp; 0 \\
  0 &amp; q &amp;  0 &amp; 0 \\
  0 &amp; 0 &amp;  b &amp; c \\
  0 &amp; 0 &amp; -1 &amp; 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 &gt; 0 and far &gt; 0, far &gt; near, etc.) if you want to avoid future headaches.