OK, enough teasing! Now that you know how to draw polygons in OpenGL, you can do anything! Lets move on to some more advanced tricks to make really impressive graphical applications.
First off, a while back I promised you perspective…well, here it is. In case you're wondering, perspective means that objects recede, or get smaller with distance. In OpenGL the only thing required to use perspective is to set up the clipping volume for that operation. This can sometimes be tricky, since instead of a cube, a perspective clipping volume is actually sort of a truncated pyramid, called a frustum. The typical OGL function to do this is glFrustum, shown here:
void glFrustum(double left, double right, double bottom, double top, double near, double far);
In this case, near and far are the distances to the front and back clipping planes. This function may look simple at first, but it gets tricky once you start dealing with windows that can be resized, etc. A far better solution is gluPerspective:
void gluPerspective(double fovy, double aspect, double near, double far);
The last two arguments remain the same as above, but what are aspect and fovy? FOVY is the Field Of View, and it specifies the angle which represents how much the viewer can see…somewhat like changing to a wide-angle lens in photography. Aspect is the aspect ratio of width/height in the window. Now I know what you're thinking: "Are you nuts? Fovy? Aspect? I'm supposed to understand this?" But don't worry; you'll see in the example program in this section why gluPerspective sets up a viewing volume so easily. Also note that the function's name begins with glu, not gl. This is an indicator that gluPerspective is not a part of the regular OpenGL library, but rather an extension which remains a part of the GL standard. Just remember that when using any function beginning with glu, you must #include
Well, if you followed that, you now have a nice perspective view to draw into. Now lets make the renderings themselves more interesting by adding motion. If you've ever seen any kind of documentary on cartoons and so forth, you're familiar with the concept of animation. You just take many slightly different static drawings and switch them very fast in front of the viewer to produce a motion effect. This is the easy part; we're already drawing several frames a second into our window. The hard part is moving our pictures so they make sense in relation to each other. In practice this involves a little math. For example, if you have an object at point x, you can move said object over 4 units if you add 4 to all the x coordinates of the vertices that make up the object (whew!). Likewise, if you know how to rotate each individual vertex around a circle (using the cosine and sine to determine the x and y coordinites), you can rotate the entire object in this manner. The problem with these types of operations is they're slow…it's easier to compute all the coordinate positions at once using a matrix. Matrices are kind of like tables that can encapsulate these types of operations. I'm not going to go into detail about it here, since OpenGL contains built in support for handling matrices. The problem is they don't always do what you want; a transformation matrix, when applied, will transform the *entire* scene, not just the object you wanted to move! There are some neat tricks you can use involving the matrix stack to put any combination of transforms into a matrix, but space will not permit me to discuss that here :(. In the meantime we can use matrices to move our objects at times when we want the entire scene to move as a unit.
Here are the functions in OpenGL which deal with matrices:
void glLoadIdentity(void);
You've seen this one before…this function loads the "identity" matrix into the current matrix. This has the effect of resetting everything back to its original position, i.e. an object drawn centered at (0,0,0) will appear in the center of the screen.
void glTranslatef(float x, float y, float z);
This function translates (moves) the entire scene forward, left and up the specified amounts. Use negative values to translate in the other direction.
void glRotatef(float angle, float x, float y, float z);
Again, this function rotates the entire scene. The way this is called might be a little confusing; the amount of rotation is determined by angle (0-359), and the scene is rotated around the vector specified by x, y, and z.
void glScalef(float x, float y, float z);
This is a more rarely-used function; it scales (makes larger or smaller) the scene by the specified factor. For example, scaling by a factor of 2.0 makes the scene appear twice as large, whereas a factor of 1.0 does nothing and a factor of 0.5 will halve the size. You can scale each axis independantly to produce a squishing or stretching effect.
OK, so now you (hopefully) know how to move stuff around using OpenGL, lets do one more thing: depth testing. This is the process of determining what falls in front of what…in other words, is the house in front of the dog or is the dog in front of the house? This may sound trivial at first, but in order to get realistic looking scenes we usually use something called a z-buffer, which tests each pixel to see if it should is unhidden and should therefore be drawn. This would normally be a big to-do, but OpenGL is nice enough to handle all the details for us. Be warned though: unless you have a video accelerator with a lot of video memory, z-buffering will slow down rendering immensely. Z-buffers can make for really cool effects (such as objects passing through other objects), but watch out for the performance hit. That being said, applying a z-buffer to your scene is easy; first, put a call to glEnable in your initialization, like so:
glEnable(GL_DEPTH_TEST);
Then, clear your z-buffer at the end of every frame. You can do this at the same time you clear your drawing window, like so:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Simple, eh? Well now we have depth-cued perspective motion… in other words, your resume is stacked and you're rearing to go! Lets continue on to the final example:
/**********************************************************************/
/********************************************************************/
/* Example 3: A fully interactive 3D world with OpenGL */
/********************************************************************/
#include
#include
#include
#include /* GLU extention library */
void init(void);
void display(void);
void keyboard(unsigned char, int, int);
void resize(int, int);
void drawcube(int, int, int);
int is_depth; /* depth testing flag */
int main (int argc, char **argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(600, 600);
glutInitWindowPosition(40, 40);
glutCreateWindow("The Cube World");
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
/* this time we're going to keep the aspect ratio constant by trapping the window resizes */
glutReshapeFunc(resize);
glutMainLoop();
return 0;
}
void init(void) {
glClearColor(0.0, 0.0, 0.0, 0.0);
glEnable(GL_DEPTH_TEST);
is_depth = 1;
glMatrixMode(GL_MODELVIEW);
}
void display(void) {
if (is_depth) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
else glClear(GL_COLOR_BUFFER_BIT);
/* draw the floor */
glBegin(GL_QUADS);
glColor3f(0.2f, 0.2f, 0.2f);
glVertex3f(-100.0, 0.0, –100.0);
glColor3f(0.4f, 0.4f, 0.4f);
glVertex3f(-100.0, 0.0, 100.0);
glColor3f(0.6f, 0.6f, 0.6f);
glVertex3f(100.0, 0.0, 100.0);
glColor3f(0.8f, 0.8f, 0.8f);
glVertex3f(100.0, 0.0, –100.0);
glEnd();
/* draw 12 cubes with different colors */
drawcube(75, 57, 2);
drawcube(-65, –12, 3);
drawcube(50, –50, 1);
drawcube(-56, 17, 2);
drawcube(67, 12, 3);
drawcube(-87, 32, 1);
drawcube(-26, 75, 2);
drawcube(57, 82, 3);
drawcube(-3, 12, 1);
drawcube(46, 35, 2);
drawcube(37, –2, 3);
glutSwapBuffers();
}
void keyboard(unsigned char key, int x, int y) {
/* This time the controls are:
"a": move left
"d": move right
"w": move forward
"s": move back
"t": toggle depth-testing */
switch (key) {
case 'a':
case 'A':
glTranslatef(5.0, 0.0, 0.0);
break;
case 'd':
case 'D':
glTranslatef(-5.0, 0.0, 0.0);
break;
case 'w':
case 'W':
glTranslatef(0.0, 0.0, 5.0);
break;
case 's':
case 'S':
glTranslatef(0.0, 0.0, –5.0);
break;
case 't':
case 'T':
if (is_depth) {
is_depth = 0;
glDisable(GL_DEPTH_TEST);
} else {
is_depth = 1;
glEnable(GL_DEPTH_TEST);
}
}
display();
}
void resize(int width, int height) {
if (height == 0) height = 1;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
/* note we divide our width by our height to get the aspect ratio */
gluPerspective(45.0, width / height, 1.0, 400.0);
/* set initial position */
glTranslatef(0.0, –5.0, –150.0);
glMatrixMode(GL_MODELVIEW);
}
void drawcube(int x_offset, int z_offset, int color) {
/* this function draws a cube centerd at (x_offset, z_offset) x and z _big are the back and rightmost points, x and z _small are the front and leftmost points */
float x_big = (float)x_offset + 5;
float z_big = (float)z_offset + 5;
float x_small = (float)x_offset – 5;
float z_small = (float)z_offset – 5;
switch(color) {
case 1:
glColor3f(1.0,0.0,0.0);
break;
case 2:
glColor3f(0.0,1.0,0.0);
break;
case 3:
glColor3f(0.0,0.0,1.0);
break;
}
glBegin(GL_QUADS);
glVertex3f(x_small,10.0,z_big); /* front */
glVertex3f(x_small,0.0,z_big);
glVertex3f(x_big,0.0,z_big);
glVertex3f(x_big,10.0,z_big);
glVertex3f(x_big,10.0,z_small); /* back */
glVertex3f(x_big,0.0,z_small);
glVertex3f(x_small,0.0,z_small);
glVertex3f(x_small,10.0,z_small);
glVertex3f(x_big,10.0,z_big); /* right */
glVertex3f(x_big,0.0,z_big);
glVertex3f(x_big,0.0,z_small);
glVertex3f(x_big,10.0,z_small);
glVertex3f(x_small,10.0,z_small); /* left */
glVertex3f(x_small,0.0,z_small);
glVertex3f(x_small,0.0,z_big);
glVertex3f(x_small,10.0,z_big);
glVertex3f(x_small,10.0,z_big); /* top */
glVertex3f(x_big,10.0,z_big);
glVertex3f(x_big,10.0,z_small);
glVertex3f(x_small,10.0,z_small);
glVertex3f(x_small,0.0,z_small); /* bottom */
glVertex3f(x_big,0.0,z_small);
glVertex3f(x_big,0.0,z_big);
glVertex3f(x_small,0.0,z_big);
glEnd();
}
/**********************************************************************/
If you know anything about 3D graphics, you'll probably notice this program is horrendously inefficient. This can easily be changed with OpenGL, but I don't have time to explain how…I leave it as an exercise to the reader (that's what us tutorial people say when we don't feel like doing something :P).
Go ahead and compile this and play around with it…you'll notice there's no collision detection and no way to turn around. This is only the tip of the iceburg as far as OpenGL goes; in fact it's almost embarrassing how much I left out! If you want to find out more about projection, texture mapping, colors, lighting, tesselation, nurbs, fog, stenciling, quadrics, and more, I'd reccomend the following books:
The OpenGL Superbible
The Complete Guide to OpenGL Programming for Windows NT and Windows 95
By Richard S. Wright Jr. and Michael Sweet
Waite Group Press
ISBN 1-57169-073-5
A great book for beginners, which also covers native Win32 graphics with OpenGL… highly reccomended!
The OpenGL Programming Guide, Second Edition
The Official Guide to Learning OpenGL, Version 1.1
By Mason Woo, Jackie Neider and Tom Davis
Addison-Wesley Developers Press
ISBN 0-201-46138-2
Contains *everything* you ever wanted to know about OpenGL 1.1, although it may be a little "thick." Perhaps better used as a reference, but contains invaluable info about the GLUT.
Also check out http://www.opengl.org for the latest OGL info & software.
Lastly, if you ever want to contact me, I'm availible at kased811@ix.netcom.com. I'm afraid I can't offer freelance OpenGL guidance, but if you want to talk about the contemplation of your navel, I'm all ears :P. In the meantime, happy coding and good luck with OpenGL!