3D Programming Demos and Tutorials

3D to 2D
by Simon Brown

next part

This tutorial
This first tutorial is about the basis behind 3D Graphics - the projection that gets you from 3D coordinates to 2D screen coordinates.

3D space and triangles
To draw a 3D object we have to first be able to define a 3D object in a 3D way. In 2 dimensions we use a simple grid (see Figure 1.1), with 2 axis, which are the horizontal x-axis, and the vertical y-axis. The point where the x and y axis cross is called the origin and has the coordinates. (0, 0). The x-axis goes from left to right. To the left of the origin the x-axis is negative and to the right it is positive. The y-axis goes from bottom to top and is negative below the origin and positive above it. It is possible to say where a point lies on this grid by giving an x and a y value. These are usually written in brackets, separated by a comma, like (3, 4) and (-2, 10) etc.

Figure 1.1 - Cartesian Coordinate system

 

 

 

 

 

If you defined three of these points as shown in Figure 1.2 and connected them with straight lines you would have a flat two-dimensional triangle.

Figure 1.2 - A 2D triangle

 

 

 

 

 

So that's how we represent points and shapes in two dimensions. So what about three dimensions?

Well clearly, we need another dimension, standard procedure is to call it z. Before we discuss where z goes, you should picture the x and y axis as if you had glued Figure 1.1 onto your monitor screen. So the x axis goes from left to right on your screen and the y axis goes from the bottom of your screen to the top. The origin is in the middle of your screen.
The z axis is the line along which you are looking. It goes from your eyes into the screen, passing through the origin. The z axis on your side of the screen has negative values and the other half, which disappears into the inside of your monitor has positive values.

We can now define a point in this three-dimensional space by giving an x, y and a z value, i.e. (3, -2, 5) or (-12, 0, 0) etc. So to work out where such a point is in this 3D space, say (3, -2, 5) for instance, you start at the origin, and move 3 units along the x axis (right), followed by -2 units along the y axis (down) and then 5 units along the z axis (forward).

We could define a 3D triangle by specifying 3 sets of (x,y,z) values and connecting them with imaginary lines. So we could now define a triangle in 3D space. How does this help us?
What we now need to do is find a way of converting this 3D space onto our 2D screen. We won’t worry for now about the various things we might wish to do to our 3D triangle, like rotating, scaling or translating it, that will come later.

3D to 2D projection
Imagine we had defined a cube in 3D space as shown in Figure 1.3.

Figure 1.3 - A cube in 3D Space

 

 

 

 

 

 

 

 

This cube is made up of 12 triangles, each of which has three points (x, y, z). Figure 1.4 shows the same cube from the front perspective, more like looking down the z-axis, like we have been doing.

Figure 1.4 - 3D Cube looking down the z-axis

 

 

 

 

 

 

 

 

Imagine you took this cube and the whole of 3D space and squashed it until it was lying on the plane where z is equal to zero. So we are squashing the cube along the z-axis until it only exists in 2 dimensions. So instead of 3D space going in and out of your monitor screen, it's now been compacted back down to just existing on the flat 2D screen itself. Now that it is in two dimensions we could easily find a way of drawing it onto a 2D computer screen.

Remember at this point that the origin (0,0,0) in 3D space is at the center of our screen, but that (0, 0) in the screen coordinates used by the likes of DirectDraw and VGA is the top left hand corner of the screen. We compensate for this by adding half of the screen width to the x value and half of the screen height to the y value. So if we have a point in 3D space at the Origin (0,0,0) and we were working in 640*480, we would add 640 / 2 = 320 onto the x-value and 480 / 2 = 240 onto the y-value.

But how do you squash 3D space into 2D space, I hear you cry? Well you can use the C++ Standard Library function Squash3DSpaceInto2DSpace( ), or if your compiler doesn’t have this function (and let's face it, most don’t), you can do it the other way and just forget about z.

Eh?

That's right, just forget I ever mentioned it (and after I spent all that time describing it!). Seriously though, if you have a cube (or any number of 3D points) defined in 3D space (x, y, z) and you just completely disregard the z value and plot the x,y values onto the screen, then you have a form of 3D to 2D conversion.

Yuck!
The problem with this method is the view you get takes no account of perspective. Two cubes of the same size, one of which was much further away along the z-axis, would be drawn the same size under this 3D to 2D conversion. If you have ever used a rendering package like 3D Studio MAX or Lightwave 3D, this is how the Top, Front, Left, Right (etc. ) views are generated in the model builder and viewports. The standard views in these package take no account of perspective and are generated by disregarding one axis.

The Real 3D to 2D projection
Of course this isn’t what we want. So how to we do real 3D? We just said that a cube further along the z-axis should appear smaller. Obviously the further away something is the smaller it looks. So the higher z is, the smaller something should be. If you divide 64 by 2 you get 32. If you divide 64 by 4 you get 16. If you divide 64 by 8 you get 8. So the higher the number you divide it by the smaller the result you get, which is the effect we are looking for.

So how do you do 3D computer graphics? You divide by z!

That's all there is to it. So we have a point in 3D space (x, y, z) and we want to covert it to a point on our screen (x, y). All we do is (in pseudo-code)

screen x value = 3d x value / 3d z value

screen y value = 3d y value / 3d z value


That's the basis behind modern 3D computer graphics. You have a point (8, 6, 2) and to represent this on the screen you do-

screen x value = 8 / 2
screen y value = 6 / 2

screen x value = 4
screen y value = 3


and this works. If you define a cube in 3D space, made up of triangles, each of which have three points (x, y, z) and apply this simple equation you will get a 2D representation that takes perspective into account. In other words it will look 3D.

It’s tiny!
The only problem is that it will look like it’s half a mile away. The problem we now have is that if z is even remotely large and we divide x and y by it, then x and y are going to get a lot smaller. The solution is to make them bigger. But to maintain the prospective we have to make all our x,y screen values (screen coordinates) bigger by more or less the same amount. So we multiply by some big number. So-

screen x value = ( 3d x value * some big number )       / 3d z value

screen y value = ( 3d y value * some other big number ) / 3d z value


As to what these numbers should be you will have to experiment. They are usually different from each other. If you are working in 320*200 you might want to try 280 and 240. If you use 640*480 you could try 620 and 600. So if we use some C variable names and assuming we are working in 640*480 we will have-

screen_x = ( global_x * 620 ) / global_z;

screen_y = ( global_y * 600 ) / global_z;



It’s upside down!
The only thing to remember now is that we once again have to take account of the fact that the screen coordinates (0, 0) are at the top left and not in the middle of the screen, so we have to add half of the screen width onto the x value, and half of the screen height onto the y value. Also the very final detail to remember is that our 3D y-axis had positive values going upwards, whereas our screen y values get larger as they go down, so we have to add a negative sign in front of our 3D y value to flip the y axis to the way we want it. So our final equation for 3D to 2D projection is-

screen_x = ( ( global_x * 620   ) / global_z ) + HALF_SCREEN_WIDTH;

screen_y = ( ( - global_y * 600 ) / global_z ) + HALF_SCREEN_HEIGHT;


compare these lines to actual code taken from my first ever 3D cube program-

sc_x [n][i] = (     ( gl_x [n][i] * 620.0   ) / ( gl_z [n][i] ) ) + 320.0;

sc_y [n][i] = ( ( - ( gl_y [n][i] * 600.0 ) ) / ( gl_z [n][i] ) ) + 240.0;


You can see the code is essentially identical.

So that's the 3D to 2D projection, and it forms the basis of most 3D computer graphics. Even Direct3D and OpenGL are performing this same calculation, although it is hidden from the programmer. This only makes up a tiny part of a 3D engine though. Having 3 screen coordinates tells you where to draw your triangle on the screen, but plotting 3 points is no good, you need to be able to fill it in to make a solid triangle. That is the subject of the next tutorial.

All content copyright © Simon Brown 1999-2005.
back to the introduction