# Simply 3D

## Perspective projection: part 2 – Z is special

Posted by Andy on May 30, 2009

In part one, we found the formula that will project the x and y components of a 3D point onto the view plane, and map the resulting values into the range [-1, 1].  Z is different, and a bit tricky.

The reason it is different is intrinsic in what projection is, by definition.  Projection means dropping a dimension.  In 3D graphics, we’re dropping the z dimension, so that we can render on a display that only has x and y dimensions.  In perspective projection, x and y are projected as a function of z – they’re absorbing some of z‘s information into themselves that preserves some depth information, manifested as far-away things looking smaller.

The reason it is tricky has to do with the way we’re going to use homogeneous coordinates to accomplish the division by z required by the projection formulas we already have.  To explain, I need to skip ahead a little to the matrix multiplication.  Recall from the post on vector/matrix multiplication that while x, y, z, and w from the source vector can each be multiplied by a constant in the matrix, they can only combine with each other additively.  But we need to divide x and y by z – that’s multiplicative, and that’s a problem.

The solution is apparently due to Möbius, who you might remember from such hits as the Möbius strip.  Recall that homogeneous coordinates give us a way to embed a scaling factor into a vector.  If the w coordinate is not one, the real 3D coordinates are computed by dividing the whole vector by w.  Aha!  If we can somehow get the value of z into the w component (and we can), then x and y will effectively be divided by z!  There’s just one problem – with that value in w, z itself will get divided by z too!  We need to correct for this, because we are in fact going to need accurate depth information in the projected coordinates, for things like perspective-correct texture mapping and shadow mapping.

We now have some constraints for how this projection of the z coordinate needs to work.  We know that it must map the range between the near and far planes into the range [0, 1].  And we know that the result must somehow have z itself already factored in, so that subsequent division by z ends up giving the correct answer.  I know that last bit is weird.  Just remember that we’re forced into it by the way homogeneous coordinates facilitate the divide-by-z needed by the x/y projection.

Time to break out the pencil and paper again, and see if we have enough information here to come up with a formula.  Remember that the computation of z will be determined exclusively by the third row of the projection matrix.  And it will consist of adding together bits of x, y, z, and w from the source vector.  Well, right away we can rule out any contribution from x and y, because they have nothing to do with mapping z into a range.  Really, the only input we need to accomplish that is z itself.  So let’s start by seeing if there’s any constant that we can put in the (3,3) position in the matrix that will accomplish the desired mapping when multiplied by z.

We want the result to have z factored into it (to correct for the z-division problem) and we want to reach the result by multiplying z by some constant.  Let’s call that hoped-for constant A:

$clip\_z \times z = zA$

Great.  What else do we know?  We know that when we plug in the z coordinate of the near plane, ultimately Clip_z must equal zero.  And when we plug in the z coordinate of the far plane, Clip_z must equal 1.  Simple substitution into the equation above gives us:

$0 \times zNear = zNear \times A \\ \\ 1 \times zFar = zFar \times A$

And simplifying gives:

$A = 0 \\ \\ A = 1$

Well shucks.  No solution.  This means there’s no single constant A that we can multiply z by to get the result we’re after.  Fortunately, there’s one more option: we might be able to use the last term in that matrix row.  That’s the one that will get multiplied by w and added to the result.  Since all of our input vectors are going to contain normalized homogeneous coordinates, w is always going to be one, which is innocuous.  This will let us pass through one more constant (let’s call it B), to be added to the result.  Which means we can also consider a formula with the form:

$clip\_z \times z = zA + B$

Let’s try plugging in our known values just like before:

$0 \times zNear = zNear \times A + B \\ \\ 1 \times zFar = zFar \times A + B$

This time it turns out we have a solvable system.  Solving for A and B gives:

$A = \dfrac{zFar}{zFar - zNear}$

$B = \dfrac{zNear \times zFar}{zNear - zFar}$

Plugging these back into the original formula and simplifying give us:

$clip\_z = (z \times \dfrac{zFar}{zFar - zNear} + \dfrac{zNear \times zFar}{zNear - zFar}) \div z$

Whew!  We’re done with z.  In the next post, we’ll put it all together into the projection matrix.

1. ### obisaid

how do you get 0 x znear = znear x a + b equal a = zfar / zfar – znear and like wise about b as well

• ### Andysaid

Algebra! You can search for “systems of equations in 2 variables” to learn how to solve two independent equations with two variable.

• ### obisaid

i still dont understand it doesn’t look like i can make it into a matrix form of a and b can you give what they should look like in the initial setup augmented matrix

• ### Andysaid

Hint: You can solve it with substitution. In the first equation, subtract (zNear * A) from both sides. That gives B = -(zNear * A). Then substitute that for B in the second equation.