O'Reilly Network    
 Published on O'Reilly Network (http://www.oreillynet.com/)
 See this if you're having trouble printing code examples

Preparing to Build an OpenGL Application

by Chris Halsall

Figure 1

Figure 1. The OpenGL API was built on SGI's earlier work with IrisGL.

When it comes to writing games, we spend hours artificially creating images that take our brain and eyes only a few milliseconds to process. We rely on advanced hardware components such as powerful video cards and their software counterparts to create these magical illusions.

If you've been wondering how to build OpenGL applications on the Linux platform, then take a close look at this article and its companion piece, "OpenGL Rendering and Drawing." I'll walk you through the basic steps and put you on the path to creating your own OpenGL games and applications.

The OpenGL API

In order to make 3D application developers' lives easier, SGI developed the OpenGL API, which was built on their earlier work with IrisGL. OpenGL presents a uniform user/client interface to the wide range of commercial hardware that the 3D application might encounter and will provide emulation via software for those requested features not supported in the hardware.

Related Articles:

Creating Real Time 3D Graphics With OpenGL

OpenGL Rendering and Drawing

Utah GLX

With the assistance of a few freely available support libraries, creating a simple application can require as little as a few hours of time and a couple of hundred lines of code. Language bindings exist for most popular languages, including C, C++, Java, Perl, Delphi and even Visual Basic. While a great deal of development information is available online for OpenGL, anyone serious about developing is advised to purchase, at minimum, the OpenGL Programmers Guide, known as the "red book." The newly released third edition includes documentation on the latest OpenGL 1.2 extensions.

Developing OpenGL applications with Linux

Most modern desktops -- Unix, Windows, and Mac, for example -- have an OpenGL API available, or at least a reasonable clone (such as MesaGL for Linux). These APIs usually only include dynamic link libraries for use by the client applications and most likely don't include the static link libraries and/or header files needed for compiling OpenGL clients from source.

If you want to develop OpenGL applications in the Linux environment, install Mesa GL 3.2, Mesa-devel, Mesa-glut, and Mesa-glut-devel. Having these will ensure that you have all of the tools necessary for compiling OpenGL clients. The Mesa-demos package can help you make sure that everything's installed properly.

OpenGL Hardware for Linux

OpenGL hardware for Linux is a large topic which could fill several articles by itself. The short version is: It's fairly easy to get full-screen acceleration now, while accelerated 3D-in-a-window generally requires using XFree86 version 4.0. For more details, including installation instructions, visit the www.xfree86.org site and the Direct Rendering Infrastructure (DRI) site on SourceForge at dri.sourceforge.net).

Compiling OpenGL programs with Mesa can require a bit of tweaking, depending on how the "include" and "library" files are named. Some installations name the MesaGL libraries "libMesaGL.x" instead of "libGL.x" and may similarly name the ".h" files. This isn't a problem as such, and in fact allows the MesaGL libraries and headers to co-exist with the SGI SI. But you should be aware of this if you encounter header-include errors during the compile pass or link errors during the link pass while trying to build a downloaded OpenGL application from source code with only MesaGL installed.

Simply changing the requested header files and/or the Makefile to reflect how your installation is configured is the easiest way to fix any compile time errors. But sometimes similar errors will occur for dynamically linked applications built on another system. In these cases, or to be able to build a package from source without any modifications, copying or symbolically linking the header and library files can often solve the problem as well. In this case Mesa is being used as a literal "drop-in" replacement, and this demonstrates how good a clone MesaGL really is.

A sample program: What it does

Figure 2

Figure 2. cube.c is a demonstration program that we will use as an example for this article.

Available for download is cube.c, a short, heavily documented demonstration C program written for this article and released into the public domain. A sample Makefile is available as well.

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

Figure 3. Anyone familiar with C should be able to understand and modify this program.

The code that makes up this OpenGL application is quite simple and is intended as something to be used to experiment and hack around with. Anyone familiar with C should be able to understand and modify this program without any trouble. Even those without much C experience should be able to see what's going on.

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."

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

Discuss this article in the O'Reilly Network Forum.

Return to the O'Reilly Network Hub.

Copyright © 2009 O'Reilly Media, Inc.