|

|
3D tutorials
Here I will present a basic 3D system (rotating, lit cube) and add other extensions in further tutorials. I'll explain everything that needs to be done to get the final product, and there are explanatons in the source code too. It's not optimized, so that you can see what's really going on.
Display
The first requirement for 3D graphics is to be able to display something. I used VBE 2 for SVGA resolution (that means you need UNIVBE 5.1 or later, and compile the code with svga.c, ie: gcc 3d.c svga.c -o 3d.exe ...). My code's somewhat faster than Allegro, so I used it, because no complicated functions are required for these purposes. If high resolution is too slow for you, then use mode 13h, but it's not worth it for now, since we're only displaying a single cube. All you need is a 486 with a local bus video card.
Basics
You need to understand a few concepts that come up in 3D frequently. Three dimensional space has length, width and depth. These can be described with a coordinate system containing (x, y, z) values corresponding to each dimension. These values mean the displacement of a point along each 'axis'. An axis is an imaginary line running horizontally left or right, vertically, or horizontally in and out of the screen. These are all perpendicular to each other and can be described using 'vectors'. A vector is described by (x, y, z) coordinates and is thought of as a line running from (0, 0, 0) through the specified coordinate. This way the x axis is represented by (1, 0, 0), ie. a line from (0, 0, 0) (also called the origin) going 1 unit to the right. The same is true for the other axes. A 'vertex' is a point in space also described by (x, y, z) coordinates which symbolize the amount of displacement along each of the axes. Vertices are used to describe an object and are points where two or more lines on the object's surface meet.
Projection
So let's begin with simple points (single vertex) first. The whole foundation of 3D graphics is to be able to plot a 3D point (which describes a 3d object) on a 2D screen. This is done by using the depth or z value to affect the x,y coordinates, ie: by dividing each by z and plotting the result. This has the effect of creating less displacement as the depth increases, called perspective. Objects farther away appear smaller.
The view point is usually at the origin, looking straight into the screen along the z axis. Even if your application requires the camera to move around and rotate, you actually move and rotate the whole world around it (all the objects at the same time). This is to simplify the perspective projection function. It looks something like this:
screen.x = screen_center.x + distance*(vertex.x / vertex.z)
screen.y = screen_center.y - distance*(vertex.y / vertex.z)
where:
screen_center is the coordinate of the middle point on the screen, ie: (x_res/2, y_res/2). If you didn't use this, you would never see negative coordinates - not good.
distance is a constant that specifies how far the imaginary view point (the phisical viewer) is behind the view plane (screen), or the 'field of view' which it translates to - smaller numbers give a wider FOV (you can see more through a keyhole if you're closer to it). I find 256 or 512 to give good results, and the multiplication gets nicely optimized.
Rotating the world is one of the reasons why you separate your object's coordinates from what you see on the screen. You should usually create your objects relative to a fixed center (0, 0, 0), so a box would be defined by two of its corners at (-1, -1, -1) and (1, 1, 1). As you can see, the object's center is at (0, 0, 0). These are called object coordinates, and are going to stay the same. You use them to get the world coordinates after you transform them to the appropriate location. For animations you transform them by different amounts to give the sense of movement. This is another reason for not changing the object coordinates: after too many transformations the coordinates would get screwed up by rounding errors, so that even if you rotate them a full 360 degrees, you won't end up where you started. Ok, so the world coordinates are the ones that you are going to finally display on the screen after perspective projection.
Transformations
But how do you move 3D points around (transform them)? Simple displacement or translation is done by adding or subrtacting from the point's coordinates. If you want to move a point to the right you add 1 to its x coordinate, for example. You can also scale an object by multiplying each of its vertices by a given value. Rotation is a bit more complicated, because you have to use sine and cosine functions on each coordinate. The rotation occurs around the origin and is done separately around each axis by the given angle. This means that you have to work with object coordinates, as for scaling. The order of rotations around each axis also matters. Here's the algorythm:
x, y, z are the coordinates of the point to be rotated,
ax, ay, az are the angles the object is to be rotated by around each axis,
rx, ry, rz are the rotated coordinates
ry = y*cos(ax) - z*sin(ax) // rotation around the x axis
rz = z*cos(ax) + y*sin(ax)
rz = rz*cos(ay) - x*sin(ay) // around y
rx = x*cos(ay) + rz*sin(ay)
^ note: this rz is the one you started with, not the one
you got from the previous line; same goes for the next axis
rx = rx*cos(az) - ry*sin(az) // around z
ry = ry*cos(az) + rx*sin(az)
I think a very simple optimization won't hurt here. Sin and cos are calculated very slowly by the fpu, and if you don't have one, it's even worse. What you can do is set up an array of values that contain the sines and cosines of their indexes. It's done very simply with a for loop:
double sin[360]
for(a=0; a<360; a++)
sin[a]=sin(a);
Now all you do is use sin[angle] instead of sin(angle). Of course, you'd need a different name for the array to avoid conflicts. Another thing: the above statements won't work in actual code, because the sin() function takes angles in radians, not normal degrees (there are 2pi radians in a full circle). This is another advantage of the lookup table, you can work in degrees. Well, not exactly... In the code I used an array of 256 values, simply because they can be described by an 8 bit index which wraps around at every full circle, ie: 365? is really 5?. The only difference is that you will work with different angles, for example: 90? is 64, 180? is 128, etc. If you wanted to use an array of 360 values, then you'd have to go: sin[angle%360] which is much slower, so that you don't go over the limit of the array. All these transformations should be done on object coordinates, and the result stored in world coords. There is also a way to do all of the transformations, even the projection, at once using matrices, but it's not necessary for the current application.
Now that you have points all over space, and you're able to move them around, they can be used to form coherent objects. As I said before, an object should be defined as centered at the origin, for reasons I've already talked about. Objects are usually defined by points at their corners or on their surface. These points are called 'vertices' (vertex in singular). Neighbouring vertices are collected and connected using lines for wireframe display or used as vertices of 'polygons' for solids.
Next >>
Safety Tutorials
Spyware Removal Links
Mozilla Tips
|
 |