Here are my (modified) notes for my 3D graphics presentation to the ACM in Fall '97.
They're a little rough--sorry about that. I'd be happy to answer any questions you have.
I also have tons of code on my homepage.
- Phil (kooderi@jhu.edu)
------------------
| 3D Programming |
------------------
3D POINTS
struct point3d { float x,y,z; };
May also have color or other attributes
COORDINATE SYSTEMS
Local coordinate system ("object coordinates")
Points on an object. (0,0,0) is the middle of that object
World coordinates
Objects are in a "world". (0,0,0) is the center of the world
(so you can draw objects scattered about)
Screen coords
What you can see on the screen. (0,0) can be upper-left or wherever
We'll use this for 3d axes:
y
| -z
| /
| /
| /
|/
|------------x
This is a right-handed coordinate system (note the -z).
Some use +z going "into" screen and -y going up.
Others use left-handed coordinate systems (+y up, +z into screen).
The coordinate system determines the rotation formulas.
ROTATION OF 3D POINTS (counter-clockwise)
Code for formula (normally computed using matrices):
The x2,x3,x4 are temporary variables for computing x
x2 = x * cos(zt) - y * sin(zt); //zt = rotation angle about z-axis, 0 to 2pi
y2 = x * sin(zt) + y * cos(zt);
z2 = z;
x3 = x2 * cos(xt) - z2 * sin(xt); //rotate about x-axis
y3 = y2;
z3 = x2 * sin(xt) + z2 * cos(xt);
x4 = x3; //rotate about y-axis
y4 = y3 * cos(yt) - z3 * sin(yt);
z4 = y3 * sin(yt) + z3 * cos(yt);
x = x4; //update variables
y = y4;
z = z4;
Once you can rotate points, you can rotate any polyhedron (3d polygon).
For example, to rotate a cube you just rotate its 8 vertices
REPRESENTING OBJECTS
Objects are made up of triangles
struct triangle {
struct point3d p[3];
struct { float r,g,b; } color;
};
struct object {
struct triangle *t; /*array*/
int num_triangles;
};
struct world {
struct object *o; /*array*/
int num_objects;
};
Any polyhedron can be made up of triangles.
Triangles are very fast and easy to display.
Also they only have 3 vertices so it's easy to manipulate them.
Sometimes squares are used as well.
DISPLAYING YOUR WORLD
Most systems have premade graphics routines
(see my web page for implementing filled triangle routines)
Simple approach: draw every triangle in world
Problem: Far back triangles will be drawn over close ones
Solutions: painter's algorithm or z-buffering
Painter's algorithm:
Sort all triangles from farthest away to closest.
Draw them in that order.
That way you'll be sure the close objects are drawn over the far objects.
Z-Buffering:
Have a pseudo-screen that's the same size as your screen.
Instead of storing colors, store z-coordinates (depth) of the pixels.
When you plot a pixel only plot it when the z coordinate is closer than what's
in the buffer. If you end up plotting it, update the z-buffer as well.
That way you'll be sure the closest pixels are drawn and farther away ones
are either never drawn or are drawn over.
Optimization:
Only display triangles that may appear on the screen (called "clipping")
HOW TO DETERMINE COLORS
Here's the basic rule of color:
How much a light source lights up a point on an object depends on the angle
at which it's shining. (for example, a flashlight shining straight against
a wall will be bright, but at an angle will be darker) In fact, the intensity
is proportional to the cosine of that angle.
Say you have a light at point L and an object with normal N at point P.
The intensity of that point (range 0 to 1) is N*(L-P), where * is the dot product.
Note that (L-P) is the "light vector" from object point P to light point L.
Also, N*(L-P) is cosine of the angle between the normal vector and the light vector.
So if an object is blue, its color at point P due to light L is
(Red, Green, Blue) = (0, 0, 255.99 * N*(L-P)) when N*(L-P) is positive, or
= (0, 0, 0) when N*(L-P) is negative (light is behind object)
If that's too much, a cheap and easy way to determine the color is like this:
color_to_draw = object_color * SOME_CONSTANT / (SOME_CONTSTANT + distance_to_point)
Basically that means objects get darker as they get farther away from you.
Play with the constant and see what looks good. (using the farthest distance is good)
Simple, eh? You could also make it depend on the distance from some other point.
(that will make it simulate a light source)
You could also make the intensity depend on both distance and angle.
OVERVIEW OF SHADING MODELS
Lambert shading ("Flat shading") - one color per polygon
Find the average of the vertices. Calculate the color at that point.
or
Find the color at each vertex. Average those colors
Gouraud shading ("Smooth" or "Diffuse") - calculate the color at each vertex.
The rest of the colors are linearly interpolated.
ex: Say you have color A at position P, color B at position Q
Then halfway between is color (A+B)/2.
1/3 of way from P to Q is color (A*2/3 + B*1/3) since it's closer to P.
Phong shading ("Specular" or "Round") - Find the normal vector at each vertex.
(Note this is only useful for 3d objects, not triangles floating around in space)
Then linearly interpolate the normals between the vertices, and compute colors.
This is a very nice shading model and I strongly recommend it.
Well that's about it!
Here are some little code fragments to help you get started.
If you want X-Windows code send me some mail.
PLOTTING PIXELS IN DOS (using TC++/BC++/Watcom/MSC):
void graphics_mode (void)
{
int i;
_asm mov ax,13h /* 320x200 pixels, 256 colors */
_asm int 10h
/* set palette: 0-63 red, 64-127 green, 128-191 blue, 192-256 grey */
/* Watcom and MSC may need to call outp() instead of outportb() */
for (i = 0; i < 64; i++)
{
outportb (0x3c8, i);
outportb (0x3c9, i); outportb (0x3c9, 0); outportb (0x3c9, 0);
outportb (0x3c8, i + 64);
outportb (0x3c9, 0); outportb (0x3c9, i); outportb (0x3c9, 0);
outportb (0x3c8, i + 128);
outportb (0x3c9, 0); outportb (0x3c9, 0); outportb (0x3c9, i);
outportb (0x3c8, i + 192);
outportb (0x3c9, i); outportb (0x3c9, i); outportb (0x3c9, i);
}
}
void text_mode (void)
{
_asm mov ax,3
_asm int 10h
}
void pixel (unsigned x, unsigned y, char color)
{
In real mode: char far *video_memory = (char far *)0xA0000000L;
In pmode: char *video_memory = (char *)0xA0000;
if (x < 320 && y < 200)
video_memory [y * 320 + x] = color;
/* OR FASTER: video_memory [(y<<8) + (y<<6) + x] = color; */
}
example: clears the screen
int x,y;
graphics_mode();
for (y = 0; y < 200; y++)
for (x = 0; x < 320; x++)
pixel (x, y, 0); /* color 0 = black */
getchar();
text_mode();
(EOF)