This is going to be a tutorial on how to put together a simple 3d engine. It assumes you already know a bit of Glide and MSVC++ programming but you don't have to be a diehard professional. You just need to know the basics. I'll assume you already have your Glide SDK running and you can compile some simple Glide-programs. Here is a little program to setup and close your Glide programs. The code in the tutorial was written for Glide but most of it can easily be used for any other system like OpenGL or DirectX.
We'll take a look at a couple of problems:
I just want to make 1 more statement. Many of the things I write may not be very elegant code and I know that most things can be done far more efficient but I'm a starter, just like most of you. This is the way I managed to do certain this. If you have any comment on how to improve my program's...please let me know.
You can reach me at tkrul@casema.net
Making an Object structure
The Object structure is a structure in which we will keep
information concerning a specific object, like shape, color,
position, etc. First we should know which information we need to
save in our structure. We'll need at least the Vertex en Faces
information and maybe we could use some sort of object id number.
We'll also need to save our 2d 'screen' coordinates. Let's split
up our problem and fist take a look at how to save a single
vertex. Take a good look at Structs1.txt to see that I mean.
Structs1.txt
Next we do this for every datatype we need. These can be
Faces, Normals, 2d and 3d coordinates, etc. When completed I
could look something like this:
Structs2.txt
Now we can simply define a Object structure. I use pointers in
the object structure because we don't know the number of vertices
and/or faces in advance. By using pointer we can just load a file
in memory and then make the pointer to point to the information
in memory. The whole file should look something like this:
Structs3.txt
Loading an Object in to your structure using 3ds Asc files
Now starts the fun part, loading you own 3d
files. Let's have a look at this so called ASC fileformat. This
is a sample of such a file:
AscFile.txt
Acs files can be generated using 3dstudio (if anybody knows any
other piece of software which exports this format, please let me
know !) but free ascfiles can be found all over the web.
As you can see the file starts with the Ambient light color. For now we can just skip this. After that follows objectname, number of vertices and number of faces. This is the first information we need to know. We will use fgets and fscans to read a line at a time. We can excess the file using the following code:
int
LoadAscFile(char *filename, D3Object_ptr Object) |
|
|
|
} |
Now the only thing that remains is reading our vertices and faces. But before we can do that we must make a local pointer and reserve memory for this info.
D3Vertex_ptr D3VertexList =
NULL; // first make a pointer
D3VertexList = (D3Vertex_ptr) calloc(Object->NumVertices,
sizeof(D3Vertex) ) // Than reserve enough memory to store the
vertices;
The whole loadroutine could look something like this:
int LoadAscFile(char
*filename, D3Object_ptr Object, float scale)
{
|
}
Note:
- This routine does a few other things besides just loading an
object in memory. It can take a scale factor as argument. This
scalefactor is used to scale the object when it is loaded. This
is simply done by lust multiplying all vertices with the scale
factor.
- fscanf could be used is many cases which would have resulted in
cleaner code, but for some reason it just didn't work ! Any ideas
?
- I use a command called PrecomputeVertexNormals(...). This may
seem useless at this time as there is no such function but it is
used if gouraud shading is enabled. I'll get to this function
when talking about gouraud shading.
- The function ReadTil(string) reads on till the string is found.
Most asc files contain pagenumbering, so if we always just go to
the next line we will get in trouble. ReadTil( ) is defined like
this:
#define ReadTil(string) while
(strcmp(line, string)) fscanf(fp, "%s",line);
Rotating an Object
Now I'll be explaining just the minimal you'll be needing to know about 3d rotation. If your serious about coding 3d you should get yourself a book. A good startingpoint would be "The Black art of 3d Game programming". It is written for dos and uses old technique's but it's illustrates the problem's well.
![]() |
If we want to rotate an object in three dimensions we
first have to look how we're going rotate an object in
two dimensions, as this is almost the same problem but if
we want it in 3d we must do it over tree angles instead
of one. Take for example the figure shown here and we want to rotate point A to point B. The rotationangle is now C. Normally we would do something like this: Xnew = COS( C ) * Xold Ynew = SIN( C ) * Yold But this only works if C is the angle calculated from the horizontal line. If we want to work around this problem we will need to change our calculation in to: Xnew = COS( C ) * Xold - SIN( C ) * Yold Ynew = SIN( C ) * Xold + COS( C ) * Yold |
By now we have rotated one point over one angle in one direction. The next thing to do is just repeating this principle in all direction X, Y and Z. A MSVC++ code version could look like this:
void
RotateObject(D3Object_ptr Object, int angleX, int angleY,
int angleZ)
|
As I said this is the absolute minimal you need to know if you want to create a 3d program for your self. Please refer to some good books if you want to know more.
Gouraud shading our Object
Let's see,...We now have our Object loaded, rotated and we could easily render it to screen. Just divide every X and Y by its Z (the further the point is the bigger Z, so the point is divided by a bigger value resulting in points that are closer to the origin (which should be at the center of the screen) if they are further away from the camera. But if you didn't use any color in your polygons it is going to look really awful. Let lighten this up a little bit.
Let me start with flat shading. If we flat shade an object we calculate the normal of a face, calculate the angle between the lightsource and the face normal and use this angle as an indication of the amount of light that hits this face. See the following pictures to illustrate this.
![]() |
We can calculate a normal using the
following formula. Let us assume we know the vectors AB (P.x ,P.y, P.z) and AC (Q.x, Q.y, Q.z). The normalvector now has the following components. N.x = ( ( Q.y * P.z ) - ( Q.z * P.y ) ) N.y = ( ( Q.z * P.x ) - ( Q.x * P.z ) ) N.z = ( ( Q.x * Py ) - ( Q.y * P.x ) ) To
calculate P and Q use the this: Let us assume B and C are
to coordinates with are not in the origin and we would
want to know the vector BC. To get the components of BC
just subtract all the B components from the C components.
This would make We now can calculate the angle C
between the ligthsource and the normal of the face. |
|
![]() |
![]() |
Now use the Angle to scale all color components R,G
and B of the face. All this should result into an object looking something like shown left. All the calculation stay the same if we go on to gouraud shading. The only difference in we don't use the facenormals but we calculate vertexnormals. Just figure out which faces share a vertex. Then calculate the normals of these faces, add them and divide them by the same number of faces. |
![]() |
![]() |
Calculate the angle C between the Light and the
Vertexnormal as we did with flat shading and again use it
to scale the colorcomponents of the vertex. The picture
left is an example of how it could look... not such a
good one but that is because it is handdraw.
Simple..huh ? Well, I skipped some details. One such is we must do the calculation of the vertexnormals in advance and save them in memory to decrease the number of calculation we have to do in real-time. |
Here is the sourcecode for recalculating the vertexnormals, calculating the angle between the lightsource and the vertexnormal and scaling the colorcomponents.
And loading and displaying TGA files
This part of the tutorial has nothing to do
with 3d programming, but if you are creating a game or any other
application you will sometimes have to use static ( non 3
dimensional ) screens. These are almost always just simple
pictures that fill the whole screen. I will show you how you can
load a TGA picture directly on screen and how you can load an TGA
in to memory and then copy it to the screen using grLfbWriteRegion().
All TGA routines I describe only work with UNCOMPRESSED TGA !
Loading a uncompressed TGA is very simple. Skip the first 18 bytes, then read all the pixelsvalues as RGB (one byte each). That means if we read a screen of 640 x 480 we need to read 640*480*3 bytes = 921600 bytes
Look here to see an examplesource
Well, that's is for now... Hope I've helped some of you out there. The full source can be downloaded here . Let me hear of your progressions and mail me if you have any questions.
GiMMiC software 1998
Tommy Krul, The Netherlands
So,... I'm back again. Got massive reaction on my first
tutorial. Many thanks go to all of you who took the effort on
mailen me with questions and ideas...
With this second tutorial I'll try to correct a few thing I did
wrong the first time. Let me start of with apologizing for some
of the errors of the sourcecode which came with the first
tutorial. Thanks to all of you who attented me on some of those
errors. But although I really do appriciate your comment (and
keep sending them !) the code is not intended to be very good...
Most of the code that I published were only the first (working)
version of the procedures. I did this to make sure all of you out
there do not just copy and paste my code but you start coding
yourself. As you will soon discover it feels much more rewarding
if you coded the biggest part yourself. To fully understand code
that someone else has writting is almost impossible !
I'll also talk about somethings that in the first tutorial I
assumed you all already knew like setting up the compiler. I just
want to make this tutorial as complete as possible. For all of
you already compiling your own glide programs, just skip that
part.
Finally I must admit that the tutorials don't really contain a
really good storyline. The subjects don't really follow a
cronological order. I'll be doing a revision of all tutorial when
I have written some more. OK, now let's go back to our subject.
In this tutorial I will discuss these problems
Setting up the compiler
I must admit that almost all sites that have something to do with programming 3dfx have a small tutorial on who to set the compiler up, but I got quite some question on this subject ( hope everything is working now Maxman ) so I'll give my version of it aswell. It is a copy of a mail I wrote for someone. Just skip this part if you already can compile your programs. I assume you are using Microsoft Visual C++, but if you are using an other compiler the procedure should be almost the same.
![]() |
Be sure to have your include and library directories
correctly configured. You only have to do this the first
time. To do this do the following:
|
Now start a new Workspace. To compile a glide program you always have to start a project. As I'm not a really good windows programmer I use Console Applications. Just open a new Workspace and start a console application. After MSVC++ has made some files and directories, go to the PROJECT menu and click SETTINGS. Now do the following
![]() |
|
MSVC is now configured correctly and you should be able to compile your own glide programs.
So far for Compiler-stuf. Give me a minute to
start a cd ( Buckshot Lefonque "Music Evolution"
)...much better. I hate silence !
Now let us make the giant leap to loading textures. See my first
tutorial for subjects concerning problems that fit between
compiler setup and loading texuremaps.
Texture maps and how to load them
For all of you who don't know what texture mapping is ( but I think most of you already know ) I'll explain it in short. Texture mapping is a techniek used to greatly increase the detail of objects without the need for complex geometric transformations. These textures can be realtime or prerendered images. There are about a dozen techniques to acomplish this but wI will limit myself to the most commenly used methode, as we are dealing with glide and glide does most of the hard work for us.
Back in the old days we had to do texture mapping ourselfs.
The basic idea is take a image, transform it in some way and
render it onto a triangle. Sounds easy ? Well, with glide it
is...but getting it to run smoothly in poor old DOS was a
nigthmare.
![]() |
How to use textures in glide Well, let me first explain how glide uses textures. Using glide textures have to be square or rectangle arrays of data. Every value in that texture is called a texel and has an address (s,t) each in the range of an 16 bit integer [ -32768...32767 ]. I'll assume you are using Window coordinates, as Clip coordinates seem to give some problems on older Voodoo cards. If we are using Window Coordinates all textures have there longest side texture coordinates running from 0 to 255. The other side of the texture has a length of 255/n where is n = [1,2,4,8]. This makes textures have an aspect ratio 1:1, 1:2, 1:4, 1:8. See the Glide 3.0 programming Guide page 86/87/88 for more details. The vertexstructure now needs additional information. This information should tell glide the (s,t) coordinates of the specific vertex. Just add GR_PARAM_ST0 to the vertexLayout. See example 1. The (s,t) (in my code called (u,v) ! ) must be multiplyed by (1/ Z ) before passing them to glide ! Let's take a look at what more glide needs to know and how it should be setup to use textures. We will just have to do two things.
(re)configuring the colorcombine unit Now comes the hard part. Before we can point glide to the texture map, naturally we should first have a texture map to point to ! Now we could do this in the main loop but that would result in really bad code, so let us write a subroutine which can load one or more texture in to memory. |
![]() |
...and how to load them
First we make an Texture map structure to hold texture map information (like dimensions, number of mipmaps, etc. ). The datatype for textureinformation is predifined in glide and called GrTexInfo. All we need now is just a variable to hold the address in the TMU (Teture Map Unit on the Voodoo card ! ) and a pointer to the texture map data. With this we can construct a very simple datatype to hold a texture map. It should look something like this:
typedef struct TextureMap_type
{
|
} TextureMap;
Now load the texture map information from the *.3df file using
gu3dfGetInfo(FileName,&TexInfo) where
TexInfo is a datatype Gu3dfInfo (also predefined
in glide. The amount of memory required for the mipmaps can now
be read for TexInfo.mem_required. So just malloc
that amount of memory at TexInfo.data. When you
reserved that memory you can load the texture into TexInfo using
gu3dfLoad(FileName,&TexInfo). We would have been
ready now if we didn't wrote this loadroutine as a subroutine...
If you do write it as a subroutine we have to give our global
structs the same values as our local structs. Also, if we want to
be able to load mutiple texture we have to keep track of the
first free address in the TMU. The lowest possible address can be
obtained using grTexMinAddress(GR_TMU0). But
this always returns the same value as it always just returns the
lowest possible offset of the TMU. So lets initialise a global
variable named NextTextureOffset and at the
beginning og our program give it the value of grTexMinAddress(GR_TMU0).
If we load a texture we now simple load that sample at this
offset and when we're done we add the size of the just loaded
texture to the offset... Simple ?
Take a good look at this source to get a
better feeling of what I mean.
And Point glide to the texture map address
Pffff, let me take a break,....Are you still there
?...Finally I've got some good news. If you understood everything
I wrote so far,...than the rest of this tutorial will seem very
simple...Use the grTexSource command to point to
your texture. Its first agrument is the value of the Texture map
unit where the texture map is located. These can be GR_TMU0,
GR_TMU1 or GR_TMU2. Then follow
the startaddress (name_of_the_structure->VideoMemoryAddressTMU0),
which mipmaplevels can be found at this address (..._EVEN,
.._ODD, ..._BOTH ) and format and dimensions of the texture. This
last one we loaded into a structure, when we loaded the texture
from disk. So simply point glide to your texture using:
grTexSource(GR_TMU0,tex1->VideoMemoryAddressTMU0,
GR_MIPMAPLEVELMASK_BOTH, &tex1->TexInfo);
For some more sourcecode, download the full source at the
end of this turorial.
Ps. Many thanks go to Andreas Ingo for writing the Texturemap
example source, which can be found at the download section. The
biggest part of this code was based upon his work. Thanks !
Well if you servived this you can take on the world ! So let us get busy with some fun stuf
How do they create those really cool transparent effects
Well if you understood everything till here than these effects are really simple ! Just setup glide to use texturemaps and enable AlphaBlending. Thats all ! grAlphaBlendFunction(GR_BLEND_ONE,GR_BLEND_ONE,GR_BLEND_ONE,GR_BLEND_ZERO)... The color of the "underlaying" triangle determens the intensety and color in which the texture is seen. A totally white triangle will fully add the two image (back and foreground ) together ( 50%, 50% ). If the triangle is gray (50% of total white ) than the result would be 75 % background 25 % foreground.
Now that we know that lets kick some ass with a few lighting effect
Difficultylevel 1
Let us start we the very, very, very cool but over hyped
lensflare effect. The basic idea is that when a bright ligth
passes a cameralens a reflection of the diafragma can be seen.
But you really don't want to render this effect real time as the
fysics that come with this effect are Fu**C*InG hard ! So we'll
do (just like in all game and demo's) a fake lensflare. We'll
take a look at these prerenderd ( PhotoPaint ) images of
lensflares and try to figure out how they move and react.
![]() |
![]() |
![]() |
As you can see, the starburst is at the spot where the bright light is. The centers of the holos (or "the other relections you can see" ) are always on the vector pointing from the center of the light to the center of the screen. Now this isn't to hard to create... But before we begin we must create the pictures that will form the flare. This can be done in a standard paint or raytrace program. Save them as tga files and convert them using the TEXUS program. If you aren't such a good artest, just download the full source. I have included 4 flares in 3df format.
Now this time I won't be showing all sourcecode in bits and pieces, instead I'll discribe a good path to take in programming this effect. If you are really going to have big problems programming this, take a look at the full source. There you can find two routines called Render and RenderLensFlare which produce the flare.
Difficultylevel 2-3-4
Well this piece is going a bit more difficult than the
lensflare. I'm going to discuss implanting a SIMPLE particle
system.
Q:
What is a particle system ?
A:
It is a techniek used for simulating complex visual effects
like explosions, fire, water, smoke, etc..
Q:
Go on !
A:
It works by simulation indivitual particles. A particle can
be dead or alive. The movement of every particle follows a set of
rules. These rules are simple rules, like 'a part accelorates
towards the ground'. Every particle is given a starting position,
starting direction, speed, energie, acceloration, ect. Then let
when follow the rules and you have your particle system.
Q:
Mmmm,...sounds great but I don't understand a thing you're
saying !
A:
Let me show you.
This is what I did for creating a really cool explosion.
The Explosion
Take lets say 100 particles and let them all share the same
startingpoint. Now give them a a random direction ( x, y, z ) and
speed. Be sure to not take values that are to big ! We are
dealing with high framerates ! This is all just setupwork.
Now when it comes to animating the particles. If the particle is
alive, just add DirectionX*Speed to PositionX, DirectionY to
....and DirectionZ to ... Multiply the Speed and Color by let us
say 0.93 to scale them down. Now if the Speed of the particle
comes below a certain trashhold, kill it, else render it ( use
the same render routine as you wrote for the flensflare ! ).
Simply do this for all particle. To easy for you ? Try this: When
an explosion is at its brightest the light should blind the
viewer...Try fading the screen to white and back when a big
explosion occurs.
The Fire
Fire can be done in exactly the same way as an
explosion. But when it comes to killing the particle, don't kill
it but give it new values just as you did when setting up the
particle system. Fires also don't need as many particles as an
explosion to look convinsing.
Smoke
Help yourself with this one. It can be done in almost
the same way as fire. Just use an other texture ( a more blurry
one ) and make the particles move slower. Maybe you can take
sideeffects like winddirection into account.
Note:
Well, of course there a bounch of you who for whom this is all far to simple...Certain Effects can follow each other. Try make a fire that slowly dies and starts smoking...Just let you fantasy go free !
Well, that's about it for now. Let me know that you would like my to write about on my next tutorial...Feel free to mail me with your questions and ideas. Happy New Year.
Download full sourcecode here.
GiMMiC Software 1999
Tommy Krul, The Netherlands
Tools for Programming and more on www.voodooland.org