2
$\begingroup$

This has been haunting me for several days now. I want to find the component that makes up of this 4x4 perspective projection matrix, with $l$(left), $r$(right), $b$(bottom), $t$(top), $n$(near), $f$(far) defining the frustum.

$\left( \begin{array}{cccc} \frac{2n}{r-l}& 0& \frac{r+l}{r-l}& 0\\ 0& \frac{2n}{t-b}& \frac{t+b}{t-b}& 0\\ 0& 0& -\frac{f+n}{f-n}&-\frac{2fn}{f-n}\\ 0& 0& -1& 0 \end{array} \right)$

Here's my approach so far. The first matrix component would just copy $z$ over to $w$ coordinate:

$\left( \begin{array}{cccc} 1& 0& 0& 0\\ 0& 1& 0& 0\\ 0& 0& 1& 0\\ 0& 0& -1& 0 \end{array} \right)$

The second matrix translates the eye ($at = \left(0, 0, 0\right)$) to ($\frac{l+r}{2}$, $\frac{t+b}{2}$, $\frac{n+f}{2}$, the center of the frustum because canonical coordinate is just a cube of size $2$ with its centroid at the origin $\left(0, 0, 0\right)$

$\left( \begin{array}{cccc} 1& 0& 0& \frac{l+r}{2}\\ 0& 1& 0& \frac{b+t}{2}\\ 0& 0& 1& \frac{n+f}{2}\\ 0& 0& 0& 1 \end{array} \right)$

The last matrix just scales the thing so that the frustum fits into the cube of size $2$.

$\left( \begin{array}{cccc} \frac{2}{r-l}& 0& 0& 0\\ 0& \frac{2}{t-b}& 0& 0\\ 0& 0& \frac{2}{f-n}& 0\\ 0& 0& 0& 1 \end{array} \right)$

As you can see the multiplication of these 3 matrices isn't equal to the perspective projection matrix. What am I missing?

$\endgroup$
1
$\begingroup$

The second matrix translates the eye [...]

You don't do that in a projection matrix. You do that with your view matrix:

  1. Model (/Object) Matrix transforms an object into World Space
  2. View Matrix transforms all objects from world space to Eye (/Camera) Space (no projection so far!)
  3. Projection Matrix transforms from Eye Space to Clip Space

Therefore you don't do any matrix multiplications to get to a projection matrix. Those multiplications happen in the shader, where you do $Projection\cdot View \cdot Model$$^1$ Matrix (for example in the vertex shader to specify your position output).

Also, remember that a perspective projection changes angles (i.e. parallel lines won't be parallel anymore). As far as I can see that is missing in your derivation.

Edit

As to how you actually derive it, I'll largely use the explanation byEtay Meiri. It has some additional information and illustration, so you may want to check it, if something seems unclear.

First, your camera is (as mentioned earlier) positioned in the origin. Now assume, you will only project onto a plane of distance $d$ and the plane you project onto is bounded by the aspect ration $ar$ (that is $ar = \frac{window\text{ }width}{window\text{ }height}$ in positive and negative $x$ direction and by $1$ in positive and negative $y$ direction.

You can now determine $d$ by your vertical field of view $\alpha$ (since looking from the side, your camera, the center at the top of your projection plane and the center of the bottom of your projection plane build a triangle and your vertical field of view is the angle at your camera):

$\frac{1}{d} = \tan(\frac{\alpha}{2})\\ \implies d = \frac{1}{\tan(\frac{\alpha}{2})}$

Now you go about calculating projected points. Assume you have an arbitrary point $v = (v_x, v_y, v_z)$, and you want to calculate the point on your plane $p = (p_x, p_y, d)$. Looking at it from the side again, the triangle with your camera, your projection plane top center point and your projected point is similar to the triangle with your camera, your projection plane top center prolonged to have the same z coordinate, as your point $v$ and the point $v$, i.e. they have the same angles$^2$. Therefore, you can now calculate the projected $p_x$ and $p_y$ coordinate:

$ \frac{p_y}{d} = \frac{v_y}{v_z} \implies p_y = \frac{v_y\cdot d}{v_z} = \frac{v_y}{v_z \cdot \tan(\frac{\alpha}{2})}\\ \frac{p_x}{d} = \frac{v_x}{v_x} \implies p_x = \frac{v_x\cdot d}{v_z} = \frac{v_x}{v_x \cdot \tan(\frac{\alpha}{2})} $

To additionally take into account, that the projection plane ranges from $-ar$ to $ar$ in $x$ direction, you would add $ar$ to the denominator in order to make the projected $x$ range be $\left[-1, 1\right]$:

$ p_x = \frac{v_x}{ar \cdot v_x \cdot \tan(\frac{\alpha}{2})} $

If you think about applying this to a matrix now, you need to take into account the division by $z$, which is different for any point with differing $z$ value. Therefore, the division by z is deferred until after the projection matrix is applied (and is done without any code of you, i.e. for OpenGL you'd specify the gl_Position in Clip Space ($\implies$ after projection) and the division is done for you).

The depth test (Z-Test) still needs the $z$ value though, so it needs to be "safed" from the $z$ divide. Therefore you copy your $z$ value to the $w$ component (which is what you got right in your assumption) and end up with the following matrix:

$\left( \begin{array}{cccc} \frac{1}{ar\cdot\tan(\frac{\alpha}{2})}&0&0\\ 0&\frac{1}{\tan(\frac{\alpha}{2})}&0&0\\ 0&0&0&0\\ 0&0&1&0 \end{array} \right)$

The next step is to not project onto a plane, but into a $z$ range of $\left[-1, 1\right]$ (this range is for correct clipping, as is the same range when handling the $x$ and the $y$ coordinate). To be more specific, you don't want all points to end up in that range, but all points between your near and your far plane, so you map $[n, f]$ to $[-1, 1]$. The resulting range is $2$, so you first scale your near-to-far range to $2$. Then you move it to be $[-1, 1]$:

$ f(z) = A\cdot z + B $

And now taking into account that we want to safe the $z$ value from $z$ divide, we get to

$f(z) = A + \frac{B}{z}$

Mapping this scaling to $[-1, 1]$ is a little bit of leg work: you know that any point with $z = n$ (on your near plane) will be projected to $p_z = -1$ and any point with $z = f$ (on your far plane) will be projected to $p_z = 1$. This leads to the following equation system:

$ A + \frac{B}{n} = -1\\ A + \frac{B}{f} = 1 $

Solving this for $A$ and $B$ leads to$^3$:

$ A = \frac{-n-f}{n-f}\\ B = \frac{2fn}{n-f} $

Your third row of the projection matrix must produced the (undivided) $p_z$ (projected) z value. Therefore, you can now choose the individual elements of said row $(\begin{array}{cccc}a&b&c&d\end{array})$ such that the correct $z$ value is produced when multiplying with your point. The correct $p_z$ value is (as established earlier): $p_z = A\cdot z + B$

Therefore this must be the right hand side of your multiplication. Assume your point to project is $(x, y, z, w)$, then you must achieve the following:

$ a \cdot x + b \cdot y + c \cdot z + d \cdot w = A \cdot z + B $

Obviously, your point's $x$ and $y$ coordinate should not influence the projected $z$ coordiante, so you can set $a$ and $b$ to $0$.

$ c \cdot z + d \cdot w = A \cdot z + B $

Since you know that $w$ for any point is $1$, you're left with

$ c \cdot z + d = A \cdot z + B $

And thus, you have $c = A$ and $d = B$.

Now your matrix is complete for projection

$\left( \begin{array}{cccc} \frac{1}{ar\cdot\tan(\frac{\alpha}{2})}&0&0\\ 0&\frac{1}{\tan(\frac{\alpha}{2})}&0&0\\ 0&0&\frac{-n-f}{n-f}&\frac{2fn}{n-f}\\ 0&0&1&0 \end{array} \right)$

Obviously, the tutorial works a little differently in projecting with the vertical field of view, so we can take a look at how you can additionally get to that (with the help of Eric Lengyel:

Instead of projecting onto a plane at $z = d$, we will project onto the near plane, and thus get for a point $v = (v_x, v_y, v_z)$ the projected point $p = (p_x, p_y, n)$:

$ p_x = \frac{v_x \cdot n}{v_z} $

Additionally, you want to map any point with $l\leq x \leq r$ to $\left[-1, 1\right]$, as before. Thus you get

$f(x) = (x-l) \frac{2}{r-l}-1$

Combine those two and you achieve

$p_x = \frac{2n}{r-l}\left(-\frac{v_x}{v_y}\right)- \frac{r+l}{r-l}$

Now put this (and the term for $y$) into the matrix and you get to the first row, first column being $\frac{2n}{r-l}$, and the first row, third column being $\frac{r+l}{r-l}$. Since you may assume $r$ and $l$ to be different, here you can see why those two matrices differ. $r$ and $l$ are guaranteed to be the same in the tutorial's matrix and therefore you get $r+l = ar-ar = 0$ in the denominator, making the third row first column (and second column accordingly) $0$.


$^1$Assuming you have Column-Major matrices like in OpenGL$

$^2$For the $x$ calculation, you will need to take a different point of your projection plane of course, but the idea is the same

$^3$The differences of the sign come from how you orient your camera: in your assumption it is along the negative $z$ axis, whereas in the tutorial it is along the positive $z$ axis

$\endgroup$
  • $\begingroup$ I'm not sure how can I derive this projection matrix. Can you explain it more clearly? $\endgroup$ – Manh Nguyen Huu Feb 13 '18 at 22:17
  • $\begingroup$ I have added the complete derivation of a simpler version, plus the explanation on the differences to the matrix you wanted to achieve. hope that helps. $\endgroup$ – Tare Feb 14 '18 at 10:24
  • $\begingroup$ Thank you very much, I understand more thoroughly now. 1 question though, so the third row, mapping [-1, 1], aren't there any matrices multiplication that can get to that row? What I mean is a combination of scaling, translating, shearing, etc $\endgroup$ – Manh Nguyen Huu Feb 14 '18 at 15:32
  • $\begingroup$ Although I guess it's possible, there is no matrix multiplication that I know of that does this. I also don't see the reason why you would want to add another multiplication, that achieves nothing else than the single matrix but adds an overhead of calculations? $\endgroup$ – Tare Feb 15 '18 at 8:04
  • $\begingroup$ I did a more in depth derivation of the perspective projection matrix here: dovo329.github.io/DeriveOpenGLPerspectiveProjectionMatrix $\endgroup$ – Doug Voss Sep 1 '18 at 14:56

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.