Now that you've read the preceding companion piece to this article, "Preparing to Build a OpenGL Application," you're ready to tackle rendering and drawing. Let's jump right in to the details of rendering.

Rendering in OpenGL

The function registered as the Display (and usually Idle) callback
is where control is passed whenever it's time to draw the next frame
of the display. For our program this is the cbRenderScene()
function, and it's also where
our motion calculations are done.

Figure 1. With different drawing mode settings, a wide range of visual effects can be created.

In more advanced applications,
physics calculations may be executing in another process or be
based on the actual amount of time it took to render the last frame.
This is necessary for objects' motions to be independent of frame-rate.

If you take a look at the function, you'll see it first simply
enables or sets different modes depending on several global variables.
This is where lighting, textures, and blending are enabled and
disabled as appropriate. Take a close look at the settings requests
for the filtering; it only affects textures.

Next we're asked to work with the GL_MODELVIEW matrix, which lets us
make transformations that work on the model, or objects, of
our scene. Into this matrix we load the Identity matrix, which is simply
a way of resetting things to the origin, so there's no rotation, translation or
scaling. Lastly, we translate the model out Z_Off distance from the
screen so it can be seen and rotate the model as appropriate.

Let the drawing begin!

Now we're ready to start the actual drawing. (Yippee!) The first
thing we do is clear both the color (frame) buffer and the
depth buffer. It should be pretty obvious why we do this, but it
can be both amusing and enlightening to try not clearing either
or both buffers. Some applications can even guarantee that they'll be
painting the whole screen themselves, so you can save a few cycles by
not clearing the color buffer; the depth buffer must still be cleared,
however.

Figure 2. By turning double-buffering off, you can watch OpenGL draw each frame. Note that OpenGL draws from the bottom up, and each Quad is made up of two triangles.

To start the faces of our cube, we first call glBegin(),
passing GL_QUADS to tell OpenGL that any vertex data received from
this point until the next glEnd() should be interpreted as
four-vertex polygons. Next we call glNormal3f(), passing in
the normal vector for our first polygon. The normal
vector is simply the direction facing right angles to the surface,
and is needed for OpenGL
to be able to calculate lighting correctly. For our application,
all the vertices of each polygon share the same normal, but for curved
surfaces approximated with many polygons, adjusting the normal on a
per-vertex basis can allow the object to appear smooth even though it
isn't.

After setting the normal, we next call glColor4f() with the RGBA settings
for a red face with a 75% level of opaque. Lastly, to complete our
first face, we make four calls to glTexCoord2f() interleaved with
four calls to glVertex3f() to define the position of the texture
on the polygon and the position of the polygon in our 3D world: the floor.

We next do the same type of thing for the top-most grey face, but note
that the texture coordinates are different. The third face rendered,
painted green, also has unusual texture coordinates, but this time they're
non-regular. Compare the code to the visual results, and tweak to see
what kinds of effects can be achieved. Both the grey and green faces are
set to be 50% opaque.

The fourth face, blue, is the first face with the full texture being
requested across its face, and it is adjusted to be only 25% opaque. The
second-to-last face to be drawn demonstrates how OpenGL can smoothly
blend colors across the face of a polygon, ranging from 90% red, green
or blue in three corners to black in the fourth.

The last face, painted yellow, shows that the same kind of blending
applies for the alpha-channel of a polygon. The yellow face ranges
from full transparent at one corner to fully opaque at another.
After we're finished drawing the yellow face, we call
glEnd() to tell OpenGL we're finished drawing Quads (at least, for now).

Drawing text

After drawing the cube, we next want to render some text. However, we
want it to stay on the screen at the same location, and not spin around
with the cube (although that might be neat). To do this, we first
reload the Identity matrix, so we're back to the origin again. Then
we call glMatrixMode() with GL_PROJECTION, which tells OpenGL we
want to work with our projection matrix.

Now, we've already got a nice projection matrix loaded, which
we need to render our rotated cube. However, we also need to reset
this with an orthogonal projection matrix to be able to render in the
true coordinate system of the display. For just this type of
situation, OpenGL lets you save matrices by using the glPushMatrix()
call. After doing this, we can change the projection matrix as needed,
and we can glPopMatrix() the saved matrix back later.

Next, we disable texturing and lighting in case they were activated,
since lit or textured text isn't what we want. The color for the
text is then set, including a 75% level of transparency, just for fun.
Then our code simply renders a text string using sprintf(),
which is then painted on the screen with a call to glRasterPos2i()
and then another to ourPrintString(). The latter is a simple
support function which loops through a string, calling the GLUT library
to render a bitmap for each character as needed. Note that in the
OpenGL system, (0,0) is the bottom-left of the screen.

The next section of code renders the frames-per-second value and
the current frame count in a similar fashion as above. One extra bit
of code first renders a small, mostly opaque dark rectangle for the
FPS value to be rendered onto. This technique is often used to ensure
that some important bit of text can always be seen, regardless of what
might be rendered behind it.

After all the text rendering is complete, we call glPopMatrix() to
recover our saved projection matrix. Then, since we've completed all
the drawing we're going to, we call glutSwapBuffers() to
display our just-drawn image to the user. It's at this point that we
do our motion calculations, and then we make a call to ourDoFPS()
in order to collect the FPS stats. Then we exit the function, returning
control back to OpenGL.

Do that about 50 times a second or so, and you've got a pretty good idea
what's involved in an OpenGL application loop. Obviously, for an application to
be useful (or at least amusing), a great deal more should be going on
in the scene. But what we've covered here is the core basis for most
OpenGL applications.

Notes, and bug!

Figure 3. A ha! Transparent objects must be drawn depth sorted, or errors may occur.

Readers should be aware that for optimization reasons, objects are
now usually defined all at once by way of something called Vertex
Arrays. This
is used instead of what we're doing in this demonstration program, which
is passing vertex attribute data one function-call at a time.

Even though they're now a rather universal extension to OpenGL, vertex arrays are beyond the scope of this
article. The concepts covered here are still applicable, and discrete
function-call methods are still common during prototyping.

I must also point out an intentional "bug" that's living in our sample program,
which sharp-eyed readers may have already noticed. It has to do with
transparent objects and how they must be rendered in descending
distance from the camera (back to front) in order for them to appear correctly.

This isn't a problem when the objects aren't transparent, as the z-buffer
is used to eliminate hidden surfaces (by OpenGL simply refusing to draw
anything farther from the camera than what's already drawn).
But in the case of transparency,
each object contributes to the final image. Thus, the order in which the
objects are rendered matter. To see this, turn on blending (which
turns off z-buffering), and then rotate the cube so the red, green and
blue faces are closest to you. These are drawn first, and so it will be
clear something's not quite right when the faces "behind" these are next
drawn on top of them.

Figure 4. Or you can cheat, and use an exclusively additive blend function.

There are several solutions to this problem, all of which are compromises.
(Welcome to computer science). You can:

Do nothing, and hope your users don't notice. This is an
option if your objects are very transparent and don't overlap often
or only very briefly.

Depth-sort all your transparent polygons, breaking apart any that
intersect, and render them in order
after all opaque objects have been drawn. This is the "correct" method,
and for a spinning cube is trivial. But for many arbitrarily moving
objects, this can quickly become a lot of work for both programmer and
processor.

Use a hack. Rather than defining the glBlendFunc() parameters as
being GL_SRC_ALPHA and GL_ONE_MINUS_SRC_ALPHA, the parameters are instead
set to GL_SRC_ALPHA and GL_ONE. What this does is, instead of first
removing
what's already rendered behind a polygon in the frame buffer based
on the polygon's alpha
setting before painting, it simply adds the polygon's color values to the
frame buffer immediately. This means it doesn't matter what order the
transparent polygons are rendered in, but it does result in a strange brightening.
In some cases your users might not notice the effects of this trick, and
in others it might even be favorable. For example, volumetric fire
can be simulated in a reasonable fashion in this mode by using a
series of textures
containing a digitized fire animation and rendering two or more flat polygons
layered and/or at right-angles to each other.

Decide not to use transparent objects. Without transparency, the
z-buffer can always be used to handle hidden-surface removal without
need for depth-sorting.

Final thoughts

OpenGL is the lowest-level, cross-platform, common hardware accelerated
3D API available.
The demo program we've provided
here should compile and run with only minor tweaking under any Unix
with OpenGL or a clone installed. It should also work without much effort in a
Windows- or Mac-based development environment, although we haven't tried that.

We've done our best to explore some of the more interesting and useful aspects
of OpenGL with these articles and demo program, but in truth we've only
scratched the surface of what can be done with the library. Interested
readers are encouraged to visit the
OpenGL.org site to learn more.
The "OpenGL Programming Guide," published by Addison-Wesley, and
the "OpenGL SuperBible," published by the Waite Group, are both
excellent reference books as well.

XFree86 4.0 will soon be included in major Linux distributions, and
when that happens it will deliver off-the-CD access to Linux users' 3D
accelerated hardware. With such hardware becoming commonplace in
even the least expensive machines, I look forward to an explosion in
3D applications under Linux in the near future. Could your software
project use a 3D interface?

Chris Halsall
is the Managing Director of Ideas 4 Lease (Barbados). Chris is a specialist... at automating
information gathering and presentation systems.