|
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 wont 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 doesnt have this function (and let's
face it, most dont), 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 isnt 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.
Its tiny!
The only problem is that it will look like its 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;
Its 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.
|