Preparing to Build an OpenGL Application
Pages: 1, 2
A sample program: What it does
Figure 2. cube.c is a demonstration program that we will use as an example for this article.
As explained earlier, both files might require a small amount of tweaking to build in your environment. To try compiling this yourself, create a temporary directory and download these two files into it. Open a shell, CD to this directory, and run "make." If you get any errors, try some of the suggestions above. If everything compiles OK, run the newly built "./cube" program.
What should appear is a small, 300 pixel square window, with an "exploded" cube spinning slowly on one axis. Most of the cube faces are solid in color, although one demonstrates OpenGL's ability to smoothly blend colors across a polygon.
With the window activated, several keys are active to control the display and the cube's motion. The arrow keys will change the spin rate on two axis. Pressing R will reverse the cube's rotation, and hitting S or the space bar will stop the cube's movement. The Page-Up and Page-Down buttons will move the cube away from or towards the screen, respectively.
Also on the screen are several rendering option status indicators, which can be toggled (or rotated through, in the case of the Mode setting) by pressing the first letter of the indicator label. For example, pressing T will activate the rendering of textures. The current frames-per-second (FPS) rate being delivered is calculated every 50 frames and displayed in the upper left of the screen, along with the current frame count for the next FPS calculation sample set.
Some options only make sense in combinations with others. For example, turning on Filtering does nothing if texturing isn't also on. Nor will turning on Alpha-additive mode make any visible change unless Blending is also active. Lighting can be turned on or off at any time, and it demonstrates why surface normals need to be defined correctly. Lastly, the Mode for the texture can be rotated through the four available modes: GL_DECAL, GL_MODULATE, GL_BLEND and GL_REPLACE.
It is worth spending some time trying the different combinations of rendering options and texture modes and seeing how they all interact. In particular, try all the texture modes with only texturing turned on and then again with texturing and blending. Note also that one face is multicolored, the yellow face varies in its transparency, and the green face has nonuniform texture coordinates. The top and bottom faces have also scaled the texture in different directions to appear quite different.
The texture itself is a simple white square, with smaller, dark blue squares appearing in a regular pattern. The texture also has an alpha channel that is painted with a large opaque circle in the center, with a fine edge set at 50% opaque. Outside the circle the alpha channel is set to be 0% opaque, or 100% transparent. Depending on which texture mode is in use, the alpha channel in the texture can have a dramatic effect on the final image.
How it works
Figure 3. Anyone familiar with C should be able to understand and modify this program.
To aid in readability, functions that are callbacks (called by OpenGL to give control back to us) are prefixed with cb. Functions that are called only by our own functions are prefixed with our. OpenGL, GLU and GLUT functions are prefixed with gl, glu, or glut, respectively.
At the very top of the file, after a notice releasing the code as public domain, are five requests for header files. The first two, "stdio.h" and "time.h," are common system files. The next three, "GL/gl.h," "GL/glu.h," and "GL/glut.h," are the header files needed to use OpenGL, its utility library, and the GLUT library. Next are several global variables used to set and maintain state during execution.
Like most one-file C programs, the most important function, main(), is at the very bottom of the file. In a C program, main() is the first function called, and in our implementation it immediately makes several calls to the GLUT library, first to initialize it, and then to request it to open a window for us.
What we do next is register several "callback" functions with the GLUT library. These are functions we define ourselves, and they are called by the GLUT library when appropriate. cbRenderScene() is registered as being what should be called when Drawing is needed, as well as when things are Idle. This function is where the actual calls to OpenGL requesting drawing services are usually made.
cbResizeScene() is set to be called whenever the window is resized. It is used to calculate the correct aspect ratio of the display and to record the new size for the rendering code. It's a good idea to always include such a function, even if you don't think anyone's ever going to resize the window. Without question, someone, somewhere will figure out a way. Besides, the work done by the function is needed at least once during the init phase anyway, so you'd might as well register the function.
Lastly, the functions cbKeyPressed() and cbSpecialKeyPressed() are set to be called when normal and special keys are pressed. These callbacks are needed because we actually end up passing control to the GLUT function glutMainLoop() in order to start the drawing process, and the registered functions are the only way we can control what is going on during the running of the application.
After the callbacks are registered, main() next calls ourInit(), which immediately calls ourBuildTextures() to create the texture we need. Then ourInit() sets the default background color, some depth buffer settings, and the shading desired (smooth). Next, it sets the correct window view by calling cbResizeScene() (see), and then it sets up and enables our light source. Lastly, we use and activate a shortcut provided by OpenGL: By calling glColorMaterial(), we can have multiple attributes change based on the polygon's color. Without this, both ambient and diffuse settings must be set explicitly for each polygon or even vertex.
ourBuildTextures() is worth reviewing as an example of how textures can be generated at run-time. The routine generates an unpacked, red-green-blue-alpha, or RGBA, texture. Keep in mind of course that the routine could just as easily load bitmapped images from disk instead, and this is in fact how textures are usually created. Also, several forms of compression are available for textures, which can both reduce memory use and can actually increase rendering speeds (less data on the bus).
After everything's finished in the way of initialization, our main() function next prints a bit of helpful language to the console, and then calls glutMainLoop(). This is the entry point to the GLUT dispatch loop, which will call our callback functions as needed. If this function returns, we should simply exit.
At this point, the functions that will usually be called are the two keyboard callbacks and the cbRenderScene() function. The keyboard handlers are quite simple; they simply toggle or rotate global settings variables as appropriate. The rendering function is why we've done all this work so far, and it is covered in more detail in the next article, "OpenGL Rendering and Drawing."
Discuss this article in the O'Reilly Network Forum.
Return to the O'Reilly Network Hub.