Chapter 2. Using OpenGL

by Richard S. Wright, Jr.

What Is OpenGL?

OpenGL is strictly defined as “a software interface to graphics hardware.” In essence, it is a 3D graphics and modeling library that is highly portable and very fast. Using OpenGL, you can create elegant and beautiful 3D graphics with nearly the visual quality of a ray tracer. The greatest advantage to using OpenGL is that it is orders of magnitude faster than a ray tracer. Initially, it used algorithms carefully developed and optimized by Silicon Graphics, Inc. (SGI), an acknowledged world leader in computer graphics and animation. Over time OpenGL has evolved as other vendors have contributed their expertise and intellectual property to develop high-performance implementations of their own.

OpenGL is not a programming language like C or C++. It is more like the C runtime library, which provides some prepackaged functionality. There really is no such thing as an “OpenGL program,” but rather a program the developer wrote that “happens” to use OpenGL as one of its Application Programming Interfaces (APIs). You might use the Windows API to access a file or the Internet, and you might use OpenGL to create real-time 3D graphics.

OpenGL is intended for use with computer hardware that is designed and optimized for the display and manipulation of 3D graphics. Software-only, “generic” implementations of OpenGL are also possible, and the Microsoft implementations fall into this category. With a software-only implementation, rendering may not be performed as quickly, and some advanced special effects may not be available. However, using a software implementation means that your program can potentially run on a wider variety of computer systems that may not have a 3D graphics card installed.

OpenGL is used for a variety of purposes, from CAD engineering and architectural applications to modeling programs used to create computer-generated monsters in blockbuster movies. The introduction of an industry-standard 3D API to mass-market operating systems such as Microsoft Windows and the Macintosh OS X has some exciting repercussions. With hardware acceleration and fast PC microprocessors becoming commonplace, 3D graphics are now typical components of consumer and business applications, not only of games and scientific applications.

Evolution of a Standard

The forerunner of OpenGL was IRIS GL from Silicon Graphics. Originally a 2D graphics library, it evolved into the 3D programming API for that company's high-end IRIS graphics workstations. These computers were more than just general-purpose computers; they had specialized hardware optimized for the display of sophisticated graphics. The hardware provided ultra-fast matrix transformations (a prerequisite for 3D graphics), hardware support for depth buffering, and other features.

Sometimes, however, the evolution of technology is hampered by the need to support legacy systems. IRIS GL had not been designed from the onset to have a vertex-style geometry processing interface, and it became apparent that to move forward SGI needed to make a clean break.

OpenGL is the result of SGI's efforts to evolve and improve IRIS GL's portability. The new graphics API would offer the power of GL but would be an “open” standard, with input from other graphics hardware vendors, and would allow for easier adaptability to other hardware platforms and operating systems. OpenGL would be designed from the ground up for 3D geometry processing.

The OpenGL ARB

An open standard is not really open if only one vendor controls it. SGI's business at the time was high-end computer graphics. Once you're at the top, you find that the opportunities for growth are somewhat limited. SGI realized that it would also be good for the company to do something good for the industry to help grow the market for high-end computer graphics hardware. A truly open standard embraced by a number of vendors would make it easier for programmers to create applications and content that is available for a wider variety of platforms. Software is what sells computers, and if SGI wanted to sell more computers, it needed more software that would run on its computers. Other vendors realized this, too, and the OpenGL Architecture Review Board (ARB) was born.

Although SGI controlled licensing of the OpenGL API, the founding members of the OpenGL ARB were SGI, Digital Equipment Corporation, IBM, Intel, and Microsoft. On July 1, 1992, Version 1.0 of the OpenGL specification was introduced. More recently, the ARB consists of many more members, many from the PC hardware community, and it meets four times a year to maintain and enhance the specification and to make plans to promote the OpenGL standard.

These meetings are open to the public, and nonmember companies can participate in discussions and even vote in straw polls. Permission to attend must be requested in advance, and meetings are kept small to improve productivity. Nonmember companies actually contribute significantly to the specification and do meaningful work on the conformance tests and other subcommittees.

Licensing and Conformance

An implementation of OpenGL is either a software library that creates three-dimensional images in response to the OpenGL function calls or a driver for a hardware device (usually a display card) that does the same. Hardware implementations are many times faster than software implementations and are now common even on inexpensive PCs.

A vendor who wants to create and market an OpenGL implementation must first license OpenGL from SGI. SGI provides the licensee with a sample implementation (entirely in software) and a device driver kit if the licensee is a PC hardware vendor. The vendor then uses this to create its own optimized implementation and can add value with its own extensions. Competition among vendors typically is based on performance, image quality, and driver stability.

In addition, the vendor's implementation must pass the OpenGL conformance tests. These tests are designed to ensure that an implementation is complete (it contains all the necessary function calls) and produces 3D rendered output that is reasonably acceptable for a given set of functions.

Software developers do not need to license OpenGL or pay any fees to make use of OpenGL drivers. OpenGL is natively supported by the operating system, and licensed drivers are provided by the hardware vendors themselves. A free open source software implementation of OpenGL called MESA is included on the CD with this book. For legal reasons, MESA is not an “official” implementation of OpenGL, but the API is identical and we all know it is just OpenGL! Many Linux open source OpenGL hardware drivers are in fact based on the MESA source code.

The API Wars

Standards are good for everyone, except for vendors who think that they should be the only vendors customers can choose from because they know best what customers need. We have a special legal word for vendors who manage to achieve this status: monopoly. Most companies recognize that competition is good for everyone in the long run and will endorse, support, and even contribute to industry standards. An interesting diversion from this ideal occurred during OpenGL's youth on the Windows platform.

Enter DirectX

Few remember anymore what a “Windows Accelerator” is, but there was a time when you could buy a special graphics card that accelerated the 2D graphics commands used by Microsoft Windows. Although Graphics Device Interface (GDI) accelerated graphics cards were great for users of word processors or desktop publishing applications, they fell far short of adequate for PC game programmers. Early Windows-based games consisted primarily of puzzle or card games that did not need speedy animation. PC games that required fast animation, such as action video games, remained DOS-based programs that could control the entire screen and did not have to share system resources with other programs that might be running at the same time.

Microsoft made some attempts to win game programmers over to developing Windows native games, but early attempts to provide faster display access, such as the WinG API, still fell far short of the mark, and game programmers continued writing DOS programs that gave them direct access to graphics hardware. When Microsoft began shipping Windows 95, its first quasi-true 32-bit operating system for the consumer market, the company intended to put an end to DOS once and for all. The original Windows 95 Game Developers Kit included some new APIs for Windows aimed at game programmers. The most important of these was DirectDraw.

To remain competitive, graphics card vendors now needed a GDI driver and a DirectDraw driver, but the driver framework was meant to provide the same low-level (and fast) access to graphics hardware under Windows that developers were used to getting with DOS. This time, Microsoft was more successful, and Windows 95 native game titles that took advantage of the new “game” APIs began shipping. This group of APIs later came to be known as DirectX. DirectX now contains a whole family of APIs meant to empower multimedia developers on the Windows platform. It would be fair to compare DirectX on Windows to QuickTime on the Mac: Both offer a variety of multimedia services and APIs to the programmer.

The original intent for DirectX was direct low-level access to hardware features. This original purpose has been diluted over time as DirectX has come to include higher-level software functionality and additional layers over the low-level devices. What was originally the Windows Game Developers Kit has evolved over time to become the “DirectX Media APIs.” Many of the DirectX APIs are independent of one another, and you can mix and match them at will. For example, you can use a third-party sound library with a Direct3D-rendered game or even use DirectSound with an OpenGL-rendered game.

OpenGL Comes to the PC

Around the same time that one group at Microsoft was focusing on establishing Windows as a viable gaming platform, OpenGL (which was a younger API at the time) began to gain some momentum on the PC as well and was being promoted by yet another group at Microsoft as the API of choice for scientific and engineering applications. Microsoft was even one of the founding members of the OpenGL ARB. Supporting OpenGL on the Windows platform would enable Microsoft to compete with UNIX-based workstations that had traditionally been the host of advanced visualization and simulation applications.

Originally, 3D graphics hardware for the PC was very expensive, so it was not frequently used for computer games. Only industries with deep pockets could afford such hardware, and as a result OpenGL first became popular in the fields of CAD, simulation, and scientific visualization. In these fields, performance was often a premium, so OpenGL was designed and evolved with performance as an important goal of the API. As 3D games became popular on the PC, OpenGL was applied to this domain as well and in some cases with great success.

By the time 3D graphics hardware for the PC became inexpensive enough to attract PC gamers, OpenGL was a mature and well-established 3D rendering API with a strong feature set. This timing coincided with Microsoft's attempts to promote its new Direct3D as a 3D rendering API for games. Ordinarily, a new, difficult-to-use, and relatively feature-weak API such as Direct3D would not have survived in the marketplace.

Many veteran 3D game programmers apply the unofficial term API Wars to this period of time when Microsoft and SGI were battling for the mind share of 3D game developers. Microsoft was a founding member of the OpenGL ARB and wanted OpenGL on Windows so that UNIX workstation software vendors could more easily port their scientific and engineering applications to Windows NT. Portability, however, was a two-edged sword; it also meant that developers who target Windows could later port their applications to other operating systems. PCs were well entrenched in the business workplace. Now it was time to go after the consumer market in a much bigger way.

OpenGL for Games?

When John Carmack, the author of one of the most popular 3D games of all time, rewrote his popular game Quake to use OpenGL over one weekend, his efforts set the gaming world abuzz. John demonstrated easily how 10 or so lines of OpenGL code required two to three pages of Direct3D code to accomplish the same task (rendering a few triangles). Many game programmers began to look carefully at OpenGL as a 3D rendering API suitable for games and not just “scientific” applications. John Carmack's company, ID Software, also had a policy of providing its games on several different platforms and operating systems. OpenGL simply made doing so much easier.

By this point, Microsoft had too much invested in its Direct3D API to back down from its own brainchild. The company was caught between a rock and a hard place. It could not back off promoting Direct3D for games because that would mean giving up the needed influence to keep game developers developing for Windows exclusively. Consumers like to play games, and keeping a hold on the consumer OS market meant keeping a hold on the number-one consumer application category. Likewise, Microsoft could not back off supporting OpenGL for the workstation market because that would mean giving up the needed influence to attract developers and applications away from competing operating systems.

Microsoft began to insist that OpenGL was for precise and exacting rendering needs and Direct3D was for real-time rendering. Official Microsoft literature described OpenGL as being something more like a ray tracer than the real-time rendering API it was designed to be. Why Microsoft wasn't thrown off the ARB for such a misinformation campaign remains under lock and key and a few dozen nondisclosure agreements. SGI took up the task of promoting OpenGL as an alternative to Direct3D, and most developers wanted to be able to choose which technology to use for their games.

Direct3D's Head Start

Before 3D hardware was firmly entrenched, game developers had to use software rendering to create 3D games. It turned out that Microsoft's Direct3D software renderer was many times faster than its own OpenGL renderer. The reason for this difference, according to Microsoft, was that OpenGL is meant for CAD; unfortunately, game programmers don't know much about CAD, but CAD users don't usually like waiting all afternoon for their drawings to rotate either. Microsoft had assumed that OpenGL would be used only with expensive 3D graphics cards in the CAD industry and had not devoted the resources to creating a fast software implementation. Without hardware acceleration, OpenGL was really useless on Windows for anything other than simple static 3D graphics and visualizations. This had nothing to do with the difference between the OpenGL and Direct3D APIs, but only in how they had been implemented by Microsoft.

Silicon Graphics took up the challenge of demonstrating that the design of the OpenGL API was not the flaw, but rather the implementation. At the 1996 SigGraph conference in New Orleans, SGI demonstrated its own OpenGL implementation for Windows. By porting several Direct3D demos to OpenGL, the company easily showed OpenGL running the same animations at equivalent and better speeds.

In reality, however, both software implementations were too slow for really good games. Game developers could write their own optimized 3D code, take liberal shortcuts that would not impact their game, and get much better performance. What really launched both OpenGL and Direct3D as viable gaming APIs was the proliferation of cheap 3D accelerated hardware for the PC. What happened next is history.

Dirty Pool

Some game developers began to develop OpenGL-enabled titles for the 1997 Christmas season. Microsoft encouraged 3D hardware vendors to develop Direct3D drivers, and if they wanted to do OpenGL for Windows 98, they should use a driver kit that Microsoft provided. This kit used the Mini-Client Driver (MCD), which enabled hardware vendors to easily create OpenGL hardware drivers for Windows 98. In response to SGI's OpenGL implementation, an embarrassed Microsoft spent a lot of time tuning its own implementation, and the MCD allowed vendors to tap into this code for everything but the actual drawing commands, which were handled by the graphics card. However, Microsoft was still insisting that OpenGL was not suitable for games development, and the MCD was meant to provide a ready route to the blossoming PC CAD market. Nearly all major PC 3D vendors had MCD-based drivers to demonstrate privately at the 1997 Computer Game Developers Conference. Most were quiet about this fact because it was known that hardware vendors who strongly supported OpenGL for games had difficulty getting needed support for their Direct3D efforts. Because most games were being developed with Direct 3D, this would be market suicide.

In the summer of 1997, Microsoft announced that it would not be licensing the MCD code beyond the development stage and vendors would not be allowed to release their drivers for Windows 98. They could, however, ship MCD-based drivers for Windows NT, the workstation platform where OpenGL belonged. As a result, software vendors who devoted time to OpenGL versions of their games could not ship their titles with OpenGL support for Christmas, hardware vendors were left without shippable OpenGL drivers, and Direct3D conveniently got a year's head start on OpenGL as a hardware API standard for games. Meanwhile, Microsoft restated that OpenGL was for non–real-time NT-based workstation applications and Direct 3D was for Windows 98 consumer games.

Fortunately, this situation did not last too long. Silicon Graphics released its own OpenGL driver kit, based on its speedy software implementation for Windows. SGI's driver kit used a much more complex driver mechanism than the MCD, called the Installable Client Driver (ICD). Microsoft had discouraged consumer hardware vendors from starting with the ICD because the MCD would be so much easier for them to use (this was before Microsoft pulled the rug out from under them). SGI's kit, however, had an easier-to-use interface that made developing drivers similar to using the simpler MCD model, and it even improved driver performance.

As hardware drivers for OpenGL began to show up for Windows 98, game companies once again seriously began looking at using OpenGL for game development. Having won its head start, and now unable to further halt the advancement of OpenGL's use for consumer applications, Microsoft relented and again agreed to support OpenGL for Windows 98. This time, however, SGI had to drop all marketing efforts aimed at evangelizing OpenGL toward game developers. Instead, the two companies would work together on a new joint API called Fahrenheit. Fahrenheit would incorporate the best of Direct3D and OpenGL for a new unified 3D API (available only on Windows and SGI hardware). At the time, SGI was losing the battle with NT for workstation sales, so it relented. Simultaneously, the company released a new SGI-branded NT workstation, with Microsoft's full endorsement. OpenGL had lost its greatest supporter for the consumer PC platform.

The Future of OpenGL

Many in the industry saw the Fahrenheit agreement as the beginning of the end for OpenGL. But a funny thing happened on the way to oblivion, and without SGI, OpenGL began to take on a life of its own. When OpenGL was again widely available on consumer hardware, developers didn't really need SGI or anyone else touting the virtues of OpenGL. OpenGL was easy to use and had been around for years. This meant there was an abundance of documentation (including the first edition of this book), sample programs, SigGraph papers, and so on. OpenGL began to flourish.

As more developers began to use OpenGL, it became clear who was really in charge of the industry: the developers. The more applications that shipped with OpenGL support, the more pressure mounted on hardware vendors to produce better OpenGL hardware and high-quality drivers. Consumers don't really care about API technology. They just want software that works, and they will buy whatever graphics card runs their favorite game the best. Developers care about time to market, portability, and code reuse. (Go ahead. Try to recompile that old Direct3D 4.0 program. I dare you!) Using OpenGL enabled many developers to meet customer demand better, and in the end it's the customers who pay the bills.

As time passed, Fahrenheit fell solely into Microsoft's hands and was eventually discontinued altogether. One can only speculate whether that was Microsoft's intent from the beginning. Direct3D has evolved further to include more and more OpenGL features, functionality, and ease of use. OpenGL's popularity has continued to grow as an alternative to Windows-specific rendering technology and is now widely supported across all major operating systems and hardware devices. Even cell phones with 3D graphics technology support a subset of OpenGL, called OpenGL ES. Today, all new 3D accelerated graphics cards for the PC ship with both OpenGL and Direct3D drivers. This is largely due to the fact that many developers continue to prefer OpenGL for new development. OpenGL today is widely recognized and accepted as an industry standard API for real-time 3D graphics.

Developers have continued to be attracted to OpenGL, and despite any political pressures, hardware vendors must satisfy the developers who make software that runs on their hardware. Ultimately, consumer dollars determine what standard survives, and developers who use OpenGL are turning out better games and applications, on more platforms, and in less time than their competitors. Only a few years ago, game developers were creating games with Microsoft's Direct 3D first because that was the only available API with a hardware driver model under consumer Windows—and then porting to OpenGL occasionally so that the same games ran under Windows NT (which didn't support Direct3D). Today, many game and software companies are creating OpenGL versions first and then porting to other platforms such as the Macintosh. It turns out that competitive advantage is more profitable than political alliances.

This momentum will carry OpenGL into the foreseeable future as the API of choice for a wide range of applications and hardware platforms. All this also makes OpenGL well positioned to take advantage of future 3D graphics innovations. Because of OpenGL's extension mechanism, vendors can expose new hardware features without waiting on either the ARB or Microsoft, and cutting-edge developers can exploit them as soon as updated drivers are available. With the addition of the OpenGL shading language (see Part III of this book), OpenGL has shown its continuing adaptability to meet the challenge of an evolving 3D graphics programming pipeline. Finally, OpenGL is a specification that has shown that it can be applied to a wide variety of programming paradigms. From C/C++ to Java and Visual Basic, even newer languages such as C# are now being used to create PC games using OpenGL. OpenGL is here to stay.

How Does OpenGL Work?

OpenGL is a procedural rather than a descriptive graphics API. Instead of describing the scene and how it should appear, the programmer actually prescribes the steps necessary to achieve a certain appearance or effect. These “steps” involve calls to the many OpenGL commands. These commands are used to draw graphics primitives such as points, lines, and polygons in three dimensions. In addition, OpenGL supports lighting and shading, texture mapping, blending, transparency, animation, and many other special effects and capabilities.

OpenGL does not include any functions for window management, user interaction, or file I/O. Each host environment (such as Microsoft Windows) has its own functions for this purpose and is responsible for implementing some means of handing over to OpenGL the drawing control of a window.

There is no “OpenGL file format” for models or virtual environments. Programmers construct these environments to suit their own high-level needs and then carefully program them using the lower-level OpenGL commands.

Generic Implementations

As mentioned previously, a generic implementation is a software implementation. Hardware implementations are created for a specific hardware device, such as a graphics card or image generator. A generic implementation can technically run just about anywhere as long as the system can display the generated graphics image.

Figure 2.1 shows the typical place that OpenGL and a generic implementation occupy when an application is running. The typical program calls many functions, some of which the programmer creates and some of which are provided by the operating system or the programming language's runtime library. Windows applications wanting to create output onscreen usually call a Windows API called the Graphics Device Interface (GDI). The GDI contains methods that allow you to write text in a window, draw simple 2D lines, and so on.

OpenGL's place in a typical application program.

Figure 2.1. OpenGL's place in a typical application program.

Usually, graphics card vendors supply a hardware driver that GDI interfaces with to create output on your monitor. A software implementation of OpenGL takes graphics requests from an application and constructs (rasterizes) a color image of the 3D graphics. It then supplies this image to the GDI for display on the monitor. On other operating systems, the process is reasonably equivalent, but you replace the GDI with that operating system's native display services.

OpenGL has a couple of common generic implementations. Microsoft has shipped its software implementation with every version of Windows NT since version 3.5 and Windows 95 (Service Release 2 and later). Windows 2000 and XP also contain support for OpenGL.

SGI released a software implementation of OpenGL for Windows that greatly outperformed Microsoft's implementation. This implementation is not officially supported but is still occasionally used by developers. MESA 3D is another “unofficial” OpenGL software implementation that is widely supported in the open source community. Mesa 3D is not an OpenGL license, so it is an “OpenGL work-alike” rather than an official implementation. In any respect other than legal, you can essentially consider it to be an OpenGL implementation nonetheless. The Mesa contributors even make a good attempt to pass the OpenGL conformance tests.

Hardware Implementations

A hardware implementation of OpenGL usually takes the form of a graphics card driver. Figure 2.2 shows its relationship to the application similarly to the way Figure 2.1 did for software implementations. Note that OpenGL API calls are passed to a hardware driver. This driver does not pass its output to the Windows GDI for display; the driver interfaces directly with the graphics display hardware.

Hardware-accelerated OpenGL's place in a typical application program.

Figure 2.2. Hardware-accelerated OpenGL's place in a typical application program.

A hardware implementation is often referred to as an accelerated implementation because hardware-assisted 3D graphics usually far outperform software-only implementations. What isn't shown in Figure 2.2 is that sometimes part of the OpenGL functionality is still implemented in software as part of the driver, and other features and functionality can be passed directly to the hardware. This idea brings us to our next topic: the OpenGL pipeline.

The Pipeline

The word pipeline is used to describe a process that can take two or more distinct stages or steps. Figure 2.3 shows a simplified version of the OpenGL pipeline. As an application makes OpenGL API function calls, the commands are placed in a command buffer. This buffer eventually fills with commands, vertex data, texture data, and so on. When the buffer is flushed, either programmatically or by the driver's design, the commands and data are passed to the next stage in the pipeline.

A simplified version of the OpenGL pipeline.

Figure 2.3. A simplified version of the OpenGL pipeline.

Vertex data is usually transformed and lit initially. In subsequent chapters, you'll find out more about what this means. For now, you can consider “Transform and Lighting” to be a mathematically intensive stage where points used to describe an object's geometry are recalculated for the given object's location and orientation. Lighting calculations are performed as well to indicate how brightly the colors should be at each vertex.

When this stage is complete, the data is fed to the rasterization portion of the pipeline. The rasterizer actually creates the color image from the geometric, color, and texture data. The image is then placed in the frame buffer. The frame buffer is the memory of the graphics display device, which means the image is displayed on your screen.

This diagram provides a simplistic view of the OpenGL pipeline, but it is sufficient for your current understanding of 3D graphics rendering. At a high level, this view is accurate, so we aren't compromising your understanding, but at a low level, many more boxes appear inside each box shown here. There are also some exceptions, such as the arrow in the figure indicating that some commands skip the Transform and Lighting (T&L) stage altogether (such as displaying raw image data on the screen).

Early OpenGL hardware accelerators were nothing more than fast rasterizers. They accelerated only the rasterization portion of the pipeline. The host system's CPU did transform and lighting in a software implementation of that portion of the pipeline. Higher-end (more expensive) accelerators had T&L on the graphics accelerator. This arrangement put more of the OpenGL pipeline in hardware and thus provided for higher performance.

Even most low-end consumer hardware today has the T&L stage in hardware. The net effect of this arrangement is that higher detailed models and more complex graphics are possible at real-time rendering rates on inexpensive consumer hardware. Games and applications developers can capitalize on this effect, yielding far more detailed and visually rich environments.

OpenGL: An API, Not a Language

For the most part, OpenGL is not a programming language; it is an Application Programming Interface (API). Whenever we say that a program is OpenGL-based or an OpenGL application, we mean that it was written in some programming language (such as C or C++) that makes calls to one or more of the OpenGL libraries. We are not saying that the program uses OpenGL exclusively to do drawing. It might combine the best features of two different graphics packages. Or it might use OpenGL for only a few specific tasks and environment-specific graphics (such as the Windows GDI) for others. The only exception to this rule of thumb is, of course, the OpenGL Shading Language, which will be covered later in this book.

As an API, the OpenGL library follows the C calling convention, and in this book, the sample programs are written in C. C++ programs can easily access C functions and APIs in the same manner as C, with only some minor considerations. Most C++ programmers can still program in C, and we don't want to exclude anyone or place any additional burden on the reader (such as having to get used to C++ syntax). Other programming languages—such as Visual Basic—that can call functions in C libraries can also make use of OpenGL, and OpenGL bindings are available for many other programming languages. Using OpenGL from these other languages is, however, outside the scope of this book and can be troublesome. To keep things simple and easily portable, we'll stick with C for our examples.

Libraries and Headers

Although OpenGL is a “standard” programming library, this library has many implementations. Microsoft Windows ships with support for OpenGL as a software renderer. This means that when a program written to use OpenGL makes OpenGL function calls, the Microsoft implementation performs the 3D rendering functions, and you see the results in your application window. The actual Microsoft software implementation is in the opengl32.dll dynamic link library, located in the Windows system directory. On most platforms, the OpenGL library is accompanied by the OpenGL utility library (GLU), which on Windows is in glu32.dll, also located in the system directory. The utility library is a set of utility functions that perform common (but sometimes complex) tasks, such as special matrix calculations, or provide support for common types of curves and surfaces.

The steps for setting up your compiler tools to link to the correct OpenGL libraries vary from tool to tool and from platform to platform. You can find some step-by-step instructions for Windows, Macintosh, and Linux in the platform-specific chapters in Part II of this book.

Prototypes for all OpenGL functions, types, and macros are contained (by convention) in the header file gl.h. Microsoft programming tools ship with this file, and so do most other programming environments for Windows or other platforms (at least those that natively support OpenGL). The utility library functions are prototyped in a different file, glu.h. These files are usually located in a special directory in your include path. For example, the following code shows the typical initial header inclusions for a typical Windows program that uses OpenGL:

#include<windows.h>
#include<gl/gl.h>
#include<gl/glu.h>

For the purposes of this book, we have created our own header file OpenGL.h, which has macros defined for the various platforms and operating systems to include the correct headers and libraries for use with OpenGL. All the sample programs include this source file.

API Specifics

OpenGL was designed by some clever people who had a lot of experience designing graphics programming APIs. They applied some standard rules to the way functions were named and variables were declared. The API is simple and clean and easy for vendors to extend. OpenGL tries to avoid as much policy as possible. Policy refers to assumptions that the designers make about how programmers will use the API. Examples of policies are assuming that you always specify vertex data as floating-point values, assuming that fog is always enabled before any rendering occurs, or assuming that all objects in a scene are affected by the same lighting parameters. To do so would eliminate many of the popular rendering techniques that have developed over time.

Data Types

To make it easier to port OpenGL code from one platform to another, OpenGL defines its own data types. These data types map to normal C data types that you can use instead, if you want. The various compilers and environments, however, have their own rules for the size and memory layout of various C variables. By using the OpenGL defined variable types, you can insulate your code from these types of changes.

Table 2.1 lists the OpenGL data types, their corresponding C data types under the 32-bit Windows environments (Win32), and the appropriate suffix for literals. In this book, we use the suffixes for all literal values. You will see later that these suffixes are also used in many OpenGL function names.

Table 2.1. OpenGL Variable Types and Corresponding C Data Types

OpenGL Data Type

Internal Representation

Defined as C Type

C Literal Suffix

GLbyte

8-bit integer

signed char

b

GLshort

16-bit integer

short

s

GLint, GLsizei

32-bit integer

long

l

GLfloat, GLclampf

32-bit floating point

float

f

GLdouble, GLclampd

64-bit floating point

double

d

GLubyte, GLboolean

8-bit unsigned integer

unsigned char

ub

GLushort

16-bit unsigned integer

unsigned short

us

GLuint, GLenum, GLbitfield

32-bit unsigned integer

unsigned long

ui

All data types start with a GL to denote OpenGL. Most are followed by their corresponding C data types (byte, short, int, float, and so on). Some have a u first to denote an unsigned data type, such as ubyte to denote an unsigned byte. For some uses, a more descriptive name is given, such as size to denote a value of length or depth. For example, GLsizei is an OpenGL variable denoting a size parameter that is represented by an integer. The clamp designation is a hint that the value is expected to be “clamped” to the range 0.0–1.0. The GLboolean variables are used to indicate true and false conditions, GLenum for enumerated variables, and GLbitfield for variables that contain binary bit fields.

Pointers and arrays are not given any special consideration. An array of 10 GLshort variables is simply declared as

GLshort shorts[10];

and an array of 10 pointers to GLdouble variables is declared with

GLdouble *doubles[10];

Some other pointer object types are used for NURBS and quadrics. They require more explanation and are covered in later chapters.

Function-Naming Conventions

Most OpenGL functions follow a naming convention that tells you which library the function is from and often how many and what types of arguments the function takes. All functions have a root that represents the function's corresponding OpenGL command. For example, glColor3f has the root Color. The gl prefix represents the gl library, and the 3f suffix means the function takes three floating-point arguments. All OpenGL functions take the following format:

<Library prefix><Root command><Optional argument count><Optional argument type>

Figure 2.4 illustrates the parts of an OpenGL function. This sample function with the suffix 3f takes three floating-point arguments. Other variations take three integers (glColor3i), three doubles (glColor3d), and so forth. This convention of adding the number and types of arguments (see Table 2.1) to the end of OpenGL functions makes it easy to remember the argument list without having to look it up. Some versions of glColor take four arguments to specify an alpha component (transparency), as well.

A dissected OpenGL function.

Figure 2.4. A dissected OpenGL function.

In the reference sections of this book, these “families” of functions are listed by their library prefix and root. All the variations of glColor (glColor3f, glColor4f, glColor3i, and so on) are listed under a single entry—glColor.

Many C/C++ compilers for Windows assume that any floating-point literal value is of type double unless explicitly told otherwise via the suffix mechanism. When using literals for floating-point arguments, if you don't specify that these arguments are of type float instead of double, the compiler issues a warning while compiling because it detects that you are passing a double to a function defined to accept only floats, resulting in a possible loss of precision. As OpenGL programs grow, these warnings quickly number in the hundreds and make it difficult to find any real syntax errors. You can turn off these warnings using the appropriate compiler options, but we advise against doing so. It's better to write clean, portable code the first time. So, clean up those warning messages by cleaning up the code (in this case, by explicitly using the float type)—not by disabling potentially useful warnings.

Additionally, you might be tempted to use the functions that accept double-precision floating-point arguments rather than go to all the bother of specifying your literals as floats. However, OpenGL uses floats internally, and using anything other than the single-precision floating-point functions adds a performance bottleneck because the values are converted to floats anyway before being processed by OpenGL—not to mention that every double takes up twice as much memory as a float. For a program with a lot of numbers “floating” around, these performance hits can add up pretty fast!

Platform Independence

OpenGL is a powerful and sophisticated API for creating 3D graphics, with more than 300 commands that cover everything from setting material colors and reflective properties to doing rotations and complex coordinate transformations. You might be surprised that OpenGL does not have a single function or command relating to window or screen management. In addition, there are no functions for keyboard input or mouse interaction. Consider, however, that one of the OpenGL designers' primary goals was platform independence. You create and open a window differently under the various platforms. Even if OpenGL did have a command for opening a window, would you use it, or would you use the operating system's own built-in API call?

Another platform issue is the handling of keyboard and mouse input events under the different operating systems and environments. If every environment handled these events the same, we would have only one environment to worry about and no need for an “open” API. This is not the case, however, and it probably won't happen within our brief lifetimes! So OpenGL's platform independence comes at the cost of having no OS and GUI functions.

Using GLUT

In the beginning, there was AUX, the OpenGL auxiliary library. The AUX library was created to facilitate the learning and writing of OpenGL programs without the programmer's being distracted by the minutiae of any particular environment, be it UNIX, Windows, or whatever. You wouldn't write “final” code when using AUX; it was more of a preliminary staging ground for testing your ideas. A lack of basic GUI features limited the library's use for building useful applications.

Only a few years ago, most OpenGL samples circulating the Web (and OpenGL book samples!) were written using the AUX library. The Windows implementation of the AUX library was buggy and prone to cause frequent crashes. The lack of any GUI features whatsoever was another drawback in the modern GUI-oriented world.

AUX has since been replaced by the GLUT library for cross-platform programming examples and demonstrations. GLUT stands for OpenGL utility toolkit (not to be confused with the standard GLU—OpenGL utility library). Mark Kilgard, while at SGI, wrote GLUT as a more capable replacement for the AUX library and included some GUI features to at least make sample programs more usable under X Windows. This replacement includes using pop-up menus, managing other windows, and even providing joystick support. GLUT is not public domain, but it is free and free to redistribute. The latest GLUT distribution available at the time of printing is on the CD that accompanies this book.

For most of this book, we use GLUT as our program framework. This decision serves two purposes. The first is that it makes most of the book accessible to a wider audience than only Windows programmers. With a little effort, experienced Linux or Mac programmers should be able to set up GLUT for their programming environments and follow along most of the examples in this book. Platform-specific details are included in the chapters in Part II of this book.

The second point is that using GLUT eliminates the need to know and understand basic GUI programming on any specific platform. Although we explain the general concepts, we do not claim to write a book about GUI programming, but about OpenGL. Using GLUT for the basic coverage of the API, we make life a bit easier for Windows/Mac/Linux novices as well.

It's unlikely that all the functionality of a commercial application will be embodied entirely in the code used to draw in 3D, so you can't rely entirely on the GLUT library for everything. Nevertheless, the GLUT library excels in its role for learning and demonstration exercises. Even for an experienced Windows programmer, it is still easier to employ the GLUT library to iron out 3D graphics code before integrating it into a complete application.

Setting Up Your Programming Environment

There is no “correct” programming environment for this book. We could not possibly cover all the different ways you can configure your compiler and programming environment. As we've stated before, the purpose of this book is to teach you OpenGL, not how to program in C or how to use Visual C++/C++, Project Builder, and so on. If you are not a whiz at using your particular environment and tools, look at the platform-specific chapters in Part II. There, you will find a quick start guide for each operating system.

In addition to platform libraries, you will need the header file glut.h. It is located in the oolsglut-3.7includeGL directory on the CD-ROM. A good place to put this header is the same directory where your development environment keeps gl.h and glu.h.

Your First Program

To understand the GLUT library better, look at possibly the world's shortest OpenGL program, which was written using the GLUT library. Listing 2.1 presents the SIMPLE program. Its output is shown in Figure 2.5. You'll also learn just a few things about OpenGL along the way!

Example 2.1. Source Code for SIMPLE: A Very Simple OpenGL Program

#include <OpenGL.h>

///////////////////////////////////////////////////////////
// Called to draw scene
void RenderScene(void)
    {
    // Clear the window with current clearing color
    glClear(GL_COLOR_BUFFER_BIT);


    // Flush drawing commands
    glFlush();
    }

///////////////////////////////////////////////////////////
// Setup the rendering state
void SetupRC(void)
    {
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    }

///////////////////////////////////////////////////////////
// Main program entry point
void main(void)
    {
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutCreateWindow("Simple");
    glutDisplayFunc(RenderScene);

    SetupRC();

    glutMainLoop();
    }
Output from the SIMPLE program.

Figure 2.5. Output from the SIMPLE program.

The SIMPLE program doesn't do much. When run from the command line (or development environment), it creates a standard GUI window with the caption Simple and a clear blue background. If you are running Visual C++, when you terminate the program, you see the message Press any key to continue in the console window. You need to press a key to terminate the program. This standard feature of the Microsoft IDE for running a console application ensures that you can see whatever output your program places onscreen (the console window) before the window vanishes. If you run the program from the command line, you don't get this behavior. If you double-click on the program file from Explorer, you see the console window, but it vanishes when the program terminates.

This simple program contains four GLUT library functions (prefixed with glut) and three “real” OpenGL functions (prefixed with gl). Let's examine the program line by line, after which we will introduce some more functions and substantially improve on our first example.

The Header

Listing 2.1 contains only one include file:

#include <OpenGL.h>

This file includes the gl.h and glut.h headers, which bring in the function prototypes used by the program.

The Body

Next, we skip down to the entry point of all C programs:

void main(void)
    {

Console-mode C and C++ programs always start execution with the function main. If you are an experienced Windows nerd, you might wonder where WinMain is in this example. It's not there because we start with a console-mode application, so we don't have to start with window creation and a message loop. With Win32, you can create graphical windows from console applications, just as you can create console windows from GUI applications. These details are buried within the GLUT library. (Remember, the GLUT library is designed to hide just these kinds of platform details.)

Display Mode: Single Buffered

The first line of code, shown here, tells the GLUT library what type of display mode to use when creating the window:

glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

The flags here tell it to use a single-buffered window (GLUT_SINGLE) and to use RGBA color mode (GLUT_RGBA). A single-buffered window means that all drawing commands are performed on the window displayed. An alternative is a double-buffered window, where the drawing commands are actually executed on an off-screen buffer and then quickly swapped into view on the window. This method is often used to produce animation effects and is demonstrated later in this chapter. In fact, we use double-buffered mode for the rest of the book. RGBA color mode means that you specify colors by supplying separate intensities of red, green, and blue components. The alternative is color index mode, which is now largely obsolete, where you specify colors by using an index into a color palette.

Creating the OpenGL Window

The next call to the GLUT library actually creates the window on the screen. The following code creates the window and sets the caption to Simple:

glutCreateWindow("Simple");

The single argument to glutCreateWindow is the caption for the window's title bar.

Displaying Callback

The next line of GLUT-specific code is

glutDisplayFunc(RenderScene);

This line establishes the previously defined function RenderScene as the display callback function. This means that GLUT calls the function pointed to here whenever the window needs to be drawn. This call occurs when the window is first displayed or when the window is resized or uncovered, for example. This is the place where we put our OpenGL rendering function calls.

Set Up the Context and Go!

The next line is neither GLUT- nor OpenGL-specific but is a convention that we follow throughout the book:

SetupRC();

In this function, we do any OpenGL initialization that should be performed before rendering. Many of the OpenGL states need to be set only once and do not need to be reset every time you render a frame (a screen full of graphics).

The last GLUT function call comes at the end of the program:

glutMainLoop();

This function starts the GLUT framework running. After you define callbacks for screen display and other functions (coming up), you turn GLUT loose. glutMainLoop never returns after it is called until the program terminates, and needs to be called only once from an application. This function processes all the operating system–specific messages, keystrokes, and so on until you terminate the program.

OpenGL Graphics Calls

The SetupRC function contains a single OpenGL function call:

glClearColor(0.0f, 0.0f, 1.0f, 1.0f);

This function sets the color used for clearing the window. The prototype for this function is

void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);

GLclampf is defined as a float under most implementations of OpenGL. In OpenGL, a single color is represented as a mixture of red, green, and blue components. The range for each component can vary from 0.0–1.0. This is similar to the Windows specification of colors using the RGB macro to create a COLORREF value. The difference is that in Windows each color component in a COLORREF can range from 0–255, giving a total of 256×256×256—or more than 16 million colors. With OpenGL, the values for each component can be any valid floating-point value between 0 and 1, thus yielding a virtually infinite number of potential colors. Practically speaking, color output is limited on most devices to 24 bits (16 million colors).

Naturally, both Windows and OpenGL take this color value and convert it internally to the nearest possible exact match with the available video hardware.

Table 2.2 lists some common colors and their component values. You can use these values with any of the OpenGL color-related functions.

Table 2.2. Some Common Composite Colors

Composite Color

Red Component

Green Component

Blue Component

Black

0.0

0.0

0.0

Red

1.0

0.0

0.0

Green

0.0

1.0

0.0

Yellow

1.0

1.0

0.0

Blue

0.0

0.0

1.0

Magenta

1.0

0.0

1.0

Cyan

0.0

1.0

1.0

Dark gray

0.25

0.25

0.25

Light gray

0.75

0.75

0.75

Brown

0.60

0.40

0.12

Pumpkin orange

0.98

0.625

0.12

Pastel pink

0.98

0.04

0.7

Barney purple

0.60

0.40

0.70

White

1.0

1.0

1.0

The last argument to glClearColor is the alpha component, which is used for blending and special effects such as translucence. Translucence refers to an object's ability to allow light to pass through it. Suppose you would like to create a piece of red stained glass, and a blue light happens to be shining behind it. The blue light affects the appearance of the red in the glass (blue + red = purple). You can use the alpha component value to generate a red color that is semitransparent so it works like a sheet of glass; an object behind it shows through. There is more to this type of effect than just using the alpha value, and in Chapter 6, “More on Color and Materials,” you'll learn more about this topic; until then, you should leave the alpha value as 1.

Clearing the Color Buffer

All we have done at this point is set OpenGL to use blue for the clearing color. In our RenderScene function, we need an instruction to do the actual clearing:

glClear(GL_COLOR_BUFFER_BIT);

The glClear function clears a particular buffer or combination of buffers. A buffer is a storage area for image information. The red, green, and blue components of a drawing are usually collectively referred to as the color buffer or pixel buffer.

More than one kind of buffer (color, depth, stencil, and accumulation) is available in OpenGL, and they are covered in more detail later in the book. For the next several chapters, all you really need to understand is that the color buffer is the place where the displayed image is stored internally and that clearing the buffer with glClear removes the last drawing from the window.

Flushing That Queue

The final OpenGL function call comes last:

glFlush();

This line causes any unexecuted OpenGL commands to be executed; we have one at this point—glClear.

Internally, OpenGL uses a rendering pipeline that processes commands sequentially. OpenGL commands and statements often are queued up until the OpenGL driver processes several “commands” at once. This setup improves performance because communication with hardware is inherently slow. Making one trip to the hardware with a truckload of data is much faster than making several smaller trips for each command or instruction. We'll discuss this feature of OpenGL's operation further in Chapter 11, “It's All About the Pipeline: Faster Geometry Throughput.” In the short program in Listing 2.1, the glFlush function simply tells OpenGL that it should proceed with the drawing instructions supplied thus far before waiting for any more drawing commands.

SIMPLE might not be the most interesting OpenGL program in existence, but it demonstrates the basics of getting a window up using the GLUT library, and it shows how to specify a color and clear the window. Next, we want to spruce up our program by adding some more GLUT library and OpenGL functions.

Drawing Shapes with OpenGL

The SIMPLE program made an empty window with a blue background. Now, let's do some drawing in the window. In addition, we want to be able to move and resize the window and have our rendering code respond appropriately. In Listing 2.2, you can see the modifications. Figure 2.6 shows the output of this program (GLRect).

Example 2.2. Drawing a Centered Rectangle with OpenGL

#include <OpenGL.h>

///////////////////////////////////////////////////////////
// Called to draw scene
void RenderScene(void)
    {
    // Clear the window with current clearing color
    glClear(GL_COLOR_BUFFER_BIT);

       // Set current drawing color to red
    //           R     G       B
    glColor3f(1.0f, 0.0f, 0.0f);

    // Draw a filled rectangle with current color
    glRectf(-25.0f, 25.0f, 25.0f, -25.0f);

    // Flush drawing commands
    glFlush();
    }


///////////////////////////////////////////////////////////
// Setup the rendering state
void SetupRC(void)
    {
    // Set clear color to blue
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    }


///////////////////////////////////////////////////////////
// Called by GLUT library when the window has chanaged size
void ChangeSize(GLsizei w, GLsizei h)
    {
    GLfloat aspectRatio;

    // Prevent a divide by zero
    if(h == 0)
        h = 1;

    // Set Viewport to window dimensions
    glViewport(0, 0, w, h);

    // Reset coordinate system
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // Establish clipping volume (left, right, bottom, top, near, far)
    aspectRatio = (GLfloat)w / (GLfloat)h;
    if (w <= h)
        glOrtho (-100.0, 100.0, -100 / aspectRatio, 100.0 / aspectRatio,
                 1.0, -1.0);
    else
        glOrtho (-100.0 * aspectRatio, 100.0 * aspectRatio,
                 -100.0, 100.0, 1.0, -1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    }

///////////////////////////////////////////////////////////
// Main program entry point
void main(void)
    {
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutCreateWindow("GLRect");
    glutDisplayFunc(RenderScene);
    glutReshapeFunc(ChangeSize);

    SetupRC();

    glutMainLoop();
    }
Output from the GLRect program.

Figure 2.6. Output from the GLRect program.

Drawing a Rectangle

Previously, all our program did was clear the screen. We've now added the following lines of drawing code:

// Set current drawing color to red
//           R     G       B
glColor3f(1.0f, 0.0f, 0.0f);

// Draw a filled rectangle with current color
glRectf(-25.0f, 25.0f, 25.0f, -25.0f);

These lines set the color used for future drawing operations (lines and filling) with the call to glColor3f. Then glRectf draws a filled rectangle.

The glColor3f function selects a color in the same manner as glClearColor, but no alpha translucency component needs to be specified (the default value for alpha is 1.0 for completely opaque):

void glColor3f(GLfloat red, GLfloat green, GLfloat blue);

The glRectf function takes floating-point arguments, as denoted by the trailing f. The number of arguments is not used in the function name because all glRect variations take four arguments. The four arguments of glRectf, shown here, represent two coordinate pairs—(x1, y1) and (x2, y2):

void glRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2);

The first pair represents the upper-left corner of the rectangle, and the second pair represents the lower-right corner.

How does OpenGL map these coordinates to actual window positions? This is done in the callback function ChangeSize. This function is set as the callback function for whenever the window changes size (when it is stretched, maximized, and so on). This is set by the in the same way that the display callback function is set:

glutReshapeFunc(ChangeSize);

Any time the window size or dimensions change, you need to reset the coordinate system.

Scaling to the Window

In nearly all windowing environments, the user can at any time change the size and dimensions of the window. Even if you are writing a game that always runs in full-screen mode, the window is still considered to change size once—when it is created. When this happens, the window usually responds by redrawing its contents, taking into consideration the window's new dimensions. Sometimes, you might want to simply clip the drawing for smaller windows or display the entire drawing at its original size in a larger window. For our purposes, we usually want to scale the drawing to fit within the window, regardless of the size of the drawing or window. Thus, a very small window would have a complete but very small drawing, and a larger window would have a similar but larger drawing. You see this effect in most drawing programs when you stretch a window as opposed to enlarging the drawing. Stretching a window usually doesn't change the drawing size, but magnifying the image makes it grow.

Setting the Viewport and Clipping Volume

We have already discussed how the viewport and viewing volume affect the coordinate range and scaling of 2D and 3D drawings in a 2D window on the computer screen. Now, we examine the setting of viewport and clipping volume coordinates in OpenGL.

Although our drawing is a 2D flat rectangle, we are actually drawing in a 3D coordinate space. The glRectf function draws the rectangle in the xy plane at z = 0. Your perspective is along the positive z-axis to see the square rectangle at z = 0. (If you're feeling lost here, review this material in Chapter 1, “Introduction to 3D Graphics and OpenGL.”)

Whenever the window size changes, the viewport and clipping volume must be redefined for the new window dimensions. Otherwise, you see an effect like the one shown in Figure 2.7, where the mapping of the coordinate system to screen coordinates stays the same regardless of the window size.

Effects of changing window size, but not the coordinate system.

Figure 2.7. Effects of changing window size, but not the coordinate system.

Because window size changes are detected and handled differently under various environments, the GLUT library provides the function glutReshapeFunc, which registers a callback that the GLUT library will call whenever the window dimensions change. The function you pass to glutReshapeFunc is prototyped like this:

void ChangeSize(GLsizei w, GLsizei h);

We have chosen ChangeSize as a descriptive name for this function, and we will use that name for our future examples.

The ChangeSize function receives the new width and height whenever the window size changes. We can use this information to modify the mapping of our desired coordinate system to real screen coordinates, with the help of two OpenGL functions: glViewport and glOrtho.

Defining the Viewport

To understand how the viewport definition is achieved, let's look more carefully at the ChangeSize function. It first calls glViewport with the new width and height of the window. The glViewport function is defined as

void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);

The x and y parameters specify the lower-left corner of the viewport within the window, and the width and height parameters specify these dimensions in pixels. Usually, x and y are both 0, but you can use viewports to render more than one drawing in different areas of a window. The viewport defines the area within the window in actual screen coordinates that OpenGL can use to draw in (see Figure 2.8). The current clipping volume is then mapped to the new viewport. If you specify a viewport that is smaller than the window coordinates, the rendering is scaled smaller, as you see in Figure 2.8.

Viewport-to-window mapping.

Figure 2.8. Viewport-to-window mapping.

Defining the Clipped Viewing Volume

The last requirement of our ChangeSize function is to redefine the clipping volume so that the aspect ratio remains square. The aspect ratio is the ratio of the number of pixels along a unit of length in the vertical direction to the number of pixels along the same unit of length in the horizontal direction. An aspect ratio of 1.0 defines a square aspect ratio. An aspect ratio of 0.5 specifies that for every two pixels in the horizontal direction for a unit of length, there is one pixel in the vertical direction for the same unit of length.

If you specify a viewport that is not square and it is mapped to a square clipping volume, the image will be distorted. For example, a viewport matching the window size and dimensions but mapped to a square clipping volume would cause images to appear tall and thin in tall and thin windows and wide and short in wide and short windows. In this case, our square would appear square only when the window was sized to be a square.

In our example, an orthographic projection is used for the clipping volume. The OpenGL command to create this projection is glOrtho:

void glOrtho(GLdouble left, GLdouble right, GLdouble bottom,
                      GLdouble top, GLdouble near, GLdouble far );

In 3D Cartesian space, the left and right values specify the minimum and maximum coordinate value displayed along the x-axis; bottom and top are for the y-axis. The near and far parameters are for the z-axis, generally with negative values extending away from the viewer (see Figure 2.9). Many drawing and graphics libraries use window coordinates (pixels) for drawing commands. Using a real floating-point (and seemingly arbitrary) coordinate system for rendering is one of the hardest things for many beginners to get used to. After you work through a few programs, though, it quickly becomes second nature.

Cartesian space.

Figure 2.9. Cartesian space.

Just before the code using glOrtho, notice these two function calls:

// Reset coordinate system
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

The subject of matrices and the OpenGL matrix stacks comes up in Chapter 4, “Geometric Transformations: The Pipeline,” where we discuss this topic in more detail. The projection matrix is the place where you actually define your viewing volume. The single call to glLoadIdentity is needed because glOrtho doesn't really establish the clipping volume, but rather modifies the existing clipping volume. It multiplies the matrix that describes the current clipping volume by the matrix that describes the clipping volume described in its arguments. For now, you just need to know that glLoadIdentity serves to “reset” the coordinate system before any matrix manipulations are performed. Without this “reset,” every time glOrtho is called, each successive call to glOrtho could result in a further corruption of the intended clipping volume, which might not even display the rectangle.

The last two lines of code, shown here, tell OpenGL that all future transformations will affect our models (what we draw):

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

We purposely do not cover model transformation until Chapter 4. You do need to know now, however, how to set up these things with their default values. Otherwise, if you become adventurous and start experimenting, your output might not match what you expect.

Keeping a Square Square

The following code does the actual work of keeping our “square” square:

// Establish clipping volume (left, right, bottom, top, near, far)
aspectRatio = (GLfloat)w / (GLfloat)h;
if (w <= h)
    glOrtho (-100.0, 100.0, -100 / aspectRatio, 100.0 / aspectRatio,
             1.0, -1.0);
else
    glOrtho (-100.0 * aspectRatio, 100.0 * aspectRatio,
             -100.0, 100.0, 1.0, -1.0);

Our clipping volume (visible coordinate space) is modified so that the left side is always at x = –100 and the right side extends to 100 unless the window is wider than it is tall. In that case, the horizontal extent is scaled by the aspect ratio of the window. In the same manner, the bottom is always at y = –100 and extends upward to 100 unless the window is taller than it is wide. In that case, the upper coordinate is also scaled by the aspect ratio. This serves to keep a square coordinate region 200×200 available (with 0,0 in the center) regardless of the shape of the window. Figure 2.10 shows how this works.

Clipping region for three different windows.

Figure 2.10. Clipping region for three different windows.

Animation with OpenGL and GLUT

So far, we've discussed the basics of using the GLUT library for creating a window and using OpenGL commands for the actual drawing. You will often want to move or rotate your images and scenes, creating an animated effect. Let's take the previous example, which draws a square, and make the square bounce off the sides of the window. You could create a loop that continually changes your object's coordinates before calling the RenderScene function. This would cause the square to appear to move around within the window.

The GLUT library enables you to register a callback function that makes it easier to set up a simple animated sequence. This function, glutTimerFunc, takes the name of a function to call and the amount of time to wait before calling the function:

void glutTimerFunc(unsigned int msecs, void (*func)(int value), int value);

This code sets up GLUT to wait msecs milliseconds before calling the function func. You can pass a user-defined value in the value parameter. The function called by the timer has the following prototype:

void TimerFunction(int value);

Unlike the Windows timer, this function is fired only once. To effect a continuous animation, you must reset the timer again in the timer function.

In our GLRect program, we can change the hard-coded values for the location of our rectangle to variables and then constantly modify those variables in the timer function. This causes the rectangle to appear to move across the window. Let's look at an example of this kind of animation. In Listing 2.3, we modify Listing 2.2 to bounce around the square off the inside borders of the window. We need to keep track of the position and size of the rectangle as we go along and account for any changes in window size.

Example 2.3. Animated Bouncing Square

#include <OpenGL.h>

// Initial square position and size
GLfloat x1 = 0.0f;
GLfloat y1 = 0.0f;
GLfloat rsize = 25;

// Step size in x and y directions
// (number of pixels to move each time)
GLfloat xstep = 1.0f;
GLfloat ystep = 1.0f;

// Keep track of windows changing width and height
GLfloat windowWidth;
GLfloat windowHeight;

///////////////////////////////////////////////////////////
// Called to draw scene
void RenderScene(void)
    {
    // Clear the window with current clearing color
    glClear(GL_COLOR_BUFFER_BIT);

       // Set current drawing color to red
    //           R     G       B
    glColor3f(1.0f, 0.0f, 0.0f);

    // Draw a filled rectangle with current color
    glRectf(x1, y1, x1 + rsize, y1 - rsize);

    // Flush drawing commands and swap
    glutSwapBuffers();
    }

///////////////////////////////////////////////////////////
// Called by GLUT library when idle (window not being
// resized or moved)
void TimerFunction(int value)
    {
    // Reverse direction when you reach left or right edge
    if(x1 > windowWidth-rsize || x1 < -windowWidth)
        xstep = -xstep;

    // Reverse direction when you reach top or bottom edge
    if(y1 > windowHeight || y1 < -windowHeight + rsize)
        ystep = -ystep;

    // Actually move the square
    x1 += xstep;
    y1 += ystep;


    // Check bounds. This is in case the window is made
    // smaller while the rectangle is bouncing and the
    // rectangle suddenly finds itself outside the new
    // clipping volume
    if(x1 > (windowWidth-rsize + xstep))
        x1 = windowWidth-rsize-1;
    else if(x1 < -(windowWidth + xstep)
    x1 = - windowWidth -1;

    if(y1 > (windowHeight + ystep))
        y1 = windowHeight-1;
    else if(y1 < -(windowHeight – rsize + ystep))
    y1 = -windowHeight + rsize -1;


     // Redraw the scene with new coordinates
    glutPostRedisplay();
    glutTimerFunc(33,TimerFunction, 1);
    }


///////////////////////////////////////////////////////////
// Setup the rendering state
void SetupRC(void)
    {
    // Set clear color to blue
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    }


///////////////////////////////////////////////////////////
// Called by GLUT library when the window has changed size
void ChangeSize(GLsizei w, GLsizei h)
    {
    GLfloat aspectRatio;

    // Prevent a divide by zero
    if(h == 0)
        h = 1;

    // Set Viewport to window dimensions
    glViewport(0, 0, w, h);

    // Reset coordinate system
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // Establish clipping volume (left, right, bottom, top, near, far)
    aspectRatio = (GLfloat)w / (GLfloat)h;
    if (w <= h)
        {
        windowWidth = 100;
        windowHeight = 100 / aspectRatio;
        glOrtho (-100.0, 100.0, -windowHeight, windowHeight, 1.0, -1.0);
        }
    else
        {
        windowWidth = 100 * aspectRatio;
        windowHeight = 100;
        glOrtho (-windowWidth, windowWidth, -100.0, 100.0, 1.0, -1.0);
        }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    }

///////////////////////////////////////////////////////////
// Main program entry point
void main(void)
    {
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutCreateWindow("Bounce");
    glutDisplayFunc(RenderScene);
    glutReshapeFunc(ChangeSize);
    glutTimerFunc(33, TimerFunction, 1);

    SetupRC();

    glutMainLoop();
    }

Double-Buffering

One of the most important features of any graphics packages is support for double buffering. This feature allows you to execute your drawing code while rendering to an offscreen buffer. Then a swap command places your drawing onscreen instantly.

Double buffering can serve two purposes. The first is that some complex drawings might take a long time to draw, and you might not want each step of the image composition to be visible. Using double buffering, you can compose an image and display it only after it is complete. The user never sees a partial image; only after the entire image is ready is it shown onscreen.

A second use for double buffering is animation. Each frame is drawn in the offscreen buffer and then swapped quickly to the screen when ready. The GLUT library supports double-buffered windows. In Listing 2.3 note the following line:

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

We have changed GLUT_SINGLE to GLUT_DOUBLE. This change causes all the drawing code to render in an offscreen buffer.

Next, we also changed the end of the RenderScene function:

. . .
    // Flush drawing commands and swap
    glutSwapBuffers();
}

No longer are we calling glFlush. This function is no longer needed because when we perform a buffer swap, we are implicitly performing a flush operation.

These changes cause a smoothly animated bouncing rectangle, shown in Figure 2.11. The function glutSwapBuffers still performs the flush, even if you are running in single-buffered mode. Simply change GLUT_DOUBLE back to GLUT_SINGLE in the bounce sample to see the animation without double buffering. As you'll see, the rectangle constantly blinks and stutters, a very unpleasant and poor animation with single buffering.

Follow the bouncing square.

Figure 2.11. Follow the bouncing square.

The GLUT library is a reasonably complete framework for creating sophisticated sample programs and perhaps even full-fledged commercial applications (assuming you do not need to use OS-specific or GUI features). It is not the purpose of this book to explore GLUT in all its glory and splendor, however. Here and in the reference section to come, we restrict ourselves to the small subset of GLUT needed to demonstrate the various features of OpenGL.

The OpenGL State Machine

Drawing 3D graphics is a complicated affair. In the chapters ahead, we will cover many OpenGL functions. For a given piece of geometry, many things can affect how it is drawn. Is a light shining on it? What are the properties of the light? What are the properties of the material? Which, if any, texture should be applied? The list could go on and on.

We call this collection of variables the state of the pipeline. A state machine is an abstract model of a collection of state variables, all of which can have various values, be turned on or off, and so on. It simply is not practical to specify all the state variables whenever we try to draw something in OpenGL. Instead, OpenGL employs a state model, or state machine, to keep track of all the OpenGL state variables. When a state value is set, it remains set until some other function changes it. Many states are simply on or off. Lighting, for example (see Chapter 11), is either turned on or off. Geometry drawn without lighting is drawn without any lighting calculations being applied to the colors set for the geometry. Any geometry drawn after lighting is turned back on is then drawn with the lighting calculations applied.

To turn these types of state variables on and off, you use the following OpenGL function:

void glEnable(GLenum capability);

You turn the variable back off with the corresponding function:

void glDisable(GLenum capability);

For the case of lighting, for instance, you can turn it on by using the following:

glEnable(GL_LIGHTING);

And you turn it back off with this function:

glDisable(GL_LIGHTING);

If you want to test a state variable to see whether it is enabled, OpenGL again has a convenient mechanism:

Glboolean glIsEnabled(GLenum capability);

Not all state variables, however, are simply on or off. Many of the OpenGL functions yet to come set up values that “stick” until changed. You can query what these values are at any time as well. A set of query functions allows you to query the values of booleans, integers, floats, and double variables. These four functions are prototyped thus:

void glGetBooleanv(GLenum pname, GLboolean *params);
void glGetDoublev(GLenum pname, GLdouble *params);
void glGetFloatv(GLenum pname, GLfloat *params);
void glGetIntegerv(GLenum pname, GLint *params);

Each function returns a single value or a whole array of values, storing the results at the address you supply. The various parameters are documented in the reference section (there are a lot of them!). Most may not make much sense to you right away, but as you progress through the book, you will begin to appreciate the power and simplicity of the OpenGL state machine.

Saving and Restoring States

OpenGL also has a convenient mechanism for saving a whole range of state values and restoring them later. The stack is a convenient data structure that allows values to be pushed on the stack (saved) and popped off the stack later to retrieve them. Items are popped off in the opposite order in which they were pushed on the stack. We call this a Last In First Out (LIFO) data structure. It's an easy way to just say, “Hey, please save this” (push it on the stack), and then a little later say, “Give me what I just saved” (pop it off the stack). You'll see that the concept of the stack plays a very important role in matrix manipulation when you get to Chapter 4.

A single OpenGL state value or a whole range of related state values can be pushed on the attribute stack with the following command:

void glPushAttrib(GLbitfield mask);

Values are correspondingly retrieved with this command:

void glPopAttrib(GLbitfield mask);

Note that the argument to these functions is a bitfield. This means that you use a bitwise mask, which allows you to perform a bitwise OR (in C using the | operator) of multiple state values with a single function call. For example, you could save the lighting and texturing state with a single call like this:

glPushAttrib(GL_TEXTURE_BIT | GL_LIGHTING_BIT);

A complete list of all the OpenGL state values that can be saved and restored with these functions is located in the reference sections.

OpenGL Errors

In any project, you want to write robust and well-behaved programs that respond politely to their users and have some amount of flexibility. Graphical programs that use OpenGL are no exception, and if you want your programs to run smoothly, you need to account for errors and unexpected circumstances. OpenGL provides a useful mechanism for you to perform an occasional sanity check in your code. This capability can be important because, from the code's standpoint, it's not really possible to tell whether the output was the Space Station Freedom or the Space Station Melted Crayon!

When Bad Things Happen to Good Code

Internally, OpenGL maintains a set of six error flags. Each flag represents a different type of error. Whenever one of these errors occurs, the corresponding flag is set. To see whether any of these flags are set, call glGetError:

Glenum glGetError(void);

The glGetError function returns one of the values listed in Table 2.3. The GLU library defines three errors of its own, but these errors map exactly to two flags already present. If more than one of these flags is set, glGetError still returns only one distinct value. This value is then cleared when glGetError is called, and glGetError again will return either another error flag or GL_NO_ERROR. Usually, you want to call glGetError in a loop that continues checking for error flags until the return value is GL_NO_ERROR.

You can use another function in the GLU library, gluErrorString, to get a string describing the error flag:

const GLubyte* gluErrorString(GLenum errorCode);

This function takes as its only argument the error flag (returned from glGetError) and returns a static string describing that error. For example, the error flag GL_INVALID_ENUM returns this string:

invalid enumerant

Table 2.3. OpenGL Error Codes

Error Code

Description

GL_INVALID_ENUM

The enum argument is out of range.

GL_INVALID_VALUE

The numeric argument is out of range.

GL_INVALID_OPERATION

The operation is illegal in its current state.

GL_STACK_OVERFLOW

The command would cause a stack overflow.

GL_STACK_UNDERFLOW

The command would cause a stack underflow.

GL_OUT_OF_MEMORY

Not enough memory is left to execute the command.

GL_TABLE_TOO_LARGE

The specified table is too large.

GL_NO_ERROR

No error has occurred.

You can take some peace of mind from the assurance that if an error is caused by an invalid call to OpenGL, the command or function call is ignored. The only exceptions to this are the functions (described in later chapters) that take pointers to memory (that may cause a program to crash if the pointer is invalid) and the out of memory condition. If you receive an out of memory error, all bets are off as to what you might see onscreen!

Identifying the Version

As mentioned previously, sometimes you want to take advantage of a known behavior in a particular implementation. If you know for a fact that you are running on a particular vendor's graphics card, you may rely on some known performance characteristics to enhance your program. You may also want to enforce some minimum version number for particular vendors' drivers. What you need is a way to query OpenGL for the vendor and version number of the rendering engine (the OpenGL driver). Both the GL library and GLU library can return version and vendor-specific information about themselves.

For the GL library, you can call glGetString:

const GLubyte *glGetString(GLenum name);

This function returns a static string describing the requested aspect of the GL library. The valid parameter values are listed under glGetString in the reference section, along with the aspect of the GL library they represent.

The GLU library has a corresponding function, gluGetString:

const GLubyte *gluGetString(GLenum name);

It returns a string describing the requested aspect of the GLU library. The valid parameters are listed under gluGetString in the reference section, along with the aspect of the GLU library they represent.

Getting a Clue with glHint

There is more than one way to skin a cat; so goes the old saying. The same is true with 3D graphics algorithms. Often a trade-off must be made for the sake of performance, or perhaps if visual fidelity is the most important issue, performance is less of a consideration. Often an OpenGL implementation may contain two ways of performing a given task—a fast way that compromises quality slightly and a slower way that improves visual quality. The function glHint allows you to specify certain preferences of quality or speed for different types of operations. The function is defined as follows:

void glHint(GLenum target, GLenum mode);

The target parameter allows you to specify types of behavior you want to modify. These values, listed under glHint in the reference section, include hints for fog and antialiasing accuracy. The mode parameter tells OpenGL what you care most about—faster render time and nicest output, for instance—or that you don't care (the only way to get back to the default behavior). Be warned, however, that all implementations are not required to honor calls into glHint; it's the only function in OpenGL whose behavior is intended to be entirely vendor-specific.

Using Extensions

With OpenGL being a “standard” API, you might think that hardware vendors are able to compete only on the basis of performance and perhaps visual quality. However, the field of 3D graphics is very competitive, and hardware vendors are constantly innovating, not just in the areas of performance and quality, but in graphics methodologies and special effects. OpenGL allows vendor innovation through its extension mechanism. This mechanism works in two ways. First, vendors can add new functions to the OpenGL API that developers can use. Second, new tokens or enumerants can be added that will be recognized by existing OpenGL functions such as glEnable.

Making use of new enumerants or tokens is simply a matter of adding a vendor-supplied header file to your project. Vendors must register their extensions with the OpenGL ARB, thus keeping one vendor from using a value used by someone else. Conveniently, there is a header file glext.h (supplied on the CD-ROM) that includes the most common extensions.

Checking for an Extension

Gone are the days when games would be recompiled for a specific graphics card. You have already seen that you can check for a string identifying the vendor and version of the OpenGL driver. You can also get a string that contains identifiers for all OpenGL extensions supported by the driver. One line of code returns a character array of extension names:

const char *szExtensions = glGetString(GL_EXTENSIONS);

This string contains the space-delimited names of all extensions supported by the driver. You can then search this string for the identifier of the extension you want to use. For example, you might do a quick search for a Windows-specific extension like this:

if (strstr(extensions, "WGL_EXT_swap_control" != NULL))
        {
      wglSwapIntervalEXT =
            (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");

        if(wglSwapIntervalEXT != NULL)
            wglSwapIntervalEXT(1);
        }

If you use this method, you should also make sure that the character following the name of the extension is either a space or a NULL. What if, for example, this extension is superceded by the WGL_EXT_swap_control2 extension? In this case, the C runtime function strstr would still find the first string, but you may not be able to assume that the second extension behaves exactly like the first. A more robust toolkit function is included in the file IsExtSupported.c in the common directory on the CD-ROM:

int gltIsExtSupported(const char *extension);

This function returns 1 if the named extension is supported or 0 if it is not. The common directory contains a whole set of helper and utility functions for use with OpenGL, and many are used throughout this book. All the functions are prototyped in the file gltools.h.

This example also shows how to get a pointer to a new OpenGL function under Windows. The windows function wglGetProcAddress returns a pointer to an OpenGL function (extension) name. Getting a pointer to an extension varies from OS to OS and will be dealt with in more detail in Part II of this book. This Windows-specific extension and the typedef (PFNWGLSWAPINTERVALEXTPROC) for the function type is located in the wglext.h header file, also included on the CD-ROM. We also discuss this particular important extension in Chapter 13, “Wiggle: OpenGL on Windows.”

In the meantime, again the gltools library comes to the rescue with the following function:

void *gltGetExtensionPointer(const char *szExtensionName);

This function provides a platform-independent wrapper that returns a pointer to the named OpenGL extension. The implementation of this function is in the file GetExtensionPointer.c, which you will have to add to your projects to make use of this feature.

Who's Extension Is This?

Using OpenGL extensions, you can provide code paths in your code to improve rendering performance and visual quality or even add special effects that are supported only by a particular vendor's hardware. But who owns an extension? That is, which vendor created and supports a given extension? You can usually tell just by looking at the extension name. Each extension has a three-letter prefix that identifies the source of the extension. Table 2.4 provides a sampling of extension identifiers.

Table 2.4. A Sampling of OpenGL Extension Prefixes

Prefix

Vendor

SGI_

Silicon Graphics

ATI_

ATI Technologies

NV_

NVidia

IBM_

IBM

WGL_

Microsoft

EXT_

Cross-Vendor

ARB_

ARB Approved

It is not uncommon for one vendor to support another vendor's extension. For example, some NVidia extensions are widely popular and supported on ATI hardware. When this happens, the competing vendor must follow the original vendor's specification (details on how the extension is supposed to work). Frequently, everyone agrees that the extension is a good thing to have, and the extension has an EXT_ prefix to show that it is (supposed) to be vendor neutral and widely supported across implementations.

Finally, we also have ARB-approved extensions. The specification for these extensions has been reviewed (and argued about) by the OpenGL ARB. These extensions usually signal the final step before some new technique or function finds its way into the core OpenGL specification. We expound upon the idea of the core OpenGL API versus the OpenGL extensions in greater detail in Part III of this book.

Getting to OpenGL Beyond 1.1 on Windows

Most Windows programmers use the Microsoft development tools, meaning Visual C++ or the newer Visual C++ .NET development environments. The Microsoft software OpenGL implementation for Windows includes only functions and tokens for OpenGL as defined in the OpenGL specification version 1.1. Since that time, we have seen 1.2, 1.3, 1.4, 1.5, and 2.0. This means several OpenGL features are unavailable to users of the OpenGL header files included with Microsoft's development tools. Other OS vendors and tool makers have managed to keep more up to date, however, and the Macintosh OS X and Linux samples should compile with fewer problems.

Nevertheless, OpenGL can at times seem like a moving target, especially where cross-platform compatibility interacts with the later and new enhancements to OpenGL. The header file glext.h, included in the common directory, contains constants and function prototypes for most OpenGL functionality past version 1.1 and is already a part of the standard development tools on some platforms. This header is included specifically for Windows developers, whereas Mac developers, for example, will use the glext.h headers included with the XCode or Project Workbench development environments.

Between the glext.h header file and the gltGetExtensionPointer function in gltools, it will not be difficult for you to build the samples in this book and run them with a wide variety of compilers and development environments.

Summary

We covered a lot of ground in this chapter. We introduced you to OpenGL, told you a little bit about its history, introduced the OpenGL utility toolkit (GLUT), and presented the fundamentals of writing a program that uses OpenGL. Using GLUT, we showed you the easiest possible way to create a window and draw in it using OpenGL commands. You learned to use the GLUT library to create windows that can be resized, as well as create a simple animation. You were also introduced to the process of using OpenGL to do drawing—composing and selecting colors, clearing the screen, drawing a rectangle, and setting the viewport and clipping volume to scale images to match the window size. We discussed the various OpenGL data types and the headers required to build programs that use OpenGL.

With a little coding finally under your belt, you were ready to dive into some other ideas that you need to be familiar with before you move forward. The OpenGL state machine underlies almost everything you do from here on out, and the extension mechanism will make sure you can access all the OpenGL features supported by your hardware driver, regardless of your development tool. You also learned how to check for OpenGL errors along the way to make sure you aren't making any illegal state changes or rendering commands. With this foundation, you can move forward to the chapters ahead.

Reference

glClearColor

Purpose:

Sets the color and alpha values to use for clearing the color buffers.

Include File:

<gl.h>

Syntax:

void glClearColor(GLclampf red, GLclampf green, 
ReferenceGLclampf blue, GLclampf alpha);

Description:

This function sets the fill values to be used when clearing the red, green, blue, and alpha buffers (jointly called the color buffer). The values specified are clamped to the range [0.0f, 1.0f].

Parameters:

red

GLclampfThe red component of the fill value.

green

GLclampfThe green component of the fill value.

blue

GLclampfThe blue component of the fill value.

alpha

GLclampfThe alpha component of the fill value.

Returns:

None.

glDisable, glEnable

Purpose:

Disables or enables an OpenGL state feature.

Include File:

<GL/gl.h>

Syntax:

void glDisable(GLenum feature);
glEnable

Description:

glDisable disables an OpenGL drawing feature, and glEnable enables an OpenGL drawing feature.

Parameters:

feature

GLenumThe feature to disable or enable. A complete list of states is given in the OpenGL specification and grows regularly with new revisions from the ARB and new OpenGL extensions from hardware vendors. For illustration, Table 2.5 lists a small sampling of states that are turned on and off.

Table 2.5. Features Enabled/Disabled by glEnable/glDisable

Feature

Description

GL_BLEND

Color blending

GL_CULL_FACE

Polygon culling

GL_DEPTH_TEST

Depth test

GL_DITHER

Dithering

GL_FOG

OpenGL fog mode

GL_LIGHTING

OpenGL lighting

GL_LIGHTx

xth OpenGL light (minimum: 8)

GL_POINT_SMOOTH

Point antialiasing

GL_LINE_SMOOTH

Line antialiasing

GL_LINE_STIPPLE

Line stippling

GL_POLYGON_SMOOTH

Polygon antialiasing

GL_SCISSOR_TEST

Scissoring enabled

GL_STENCIL_TEST

Stencil test

GL_TEXTURE_xD

xD texturing (1, 2, or 3)

GL_TEXTURE_CUBE_MAP

Cube map texturing

GL_TEXTURE_GEN_x

Texgen for x (S, T, R, or Q)

Returns:

None.

See Also:

glIsEnabled, glPopAttrib, glPushAttrib

glFinish

Purpose:

Forces all previous OpenGL commands to complete.

Syntax:

void glFinish(void);

Description:

OpenGL commands are usually queued and executed in batches to optimize performance. The glFinish command forces all pending OpenGL commands to be executed. Unlike glFlush, this function does not return until all the rendering operations have been completed.

Returns:

None.

See Also:

glFlush();

glFlush

Purpose:

Flushes OpenGL command queues and buffers.

Syntax:

void glFlush(void);

Description:

OpenGL commands are usually queued and executed in batches to optimize performance. This can vary among hardware, drivers, and OpenGL implementations. The glFlush command causes any waiting commands to be executed. This must be accomplished “in finite time.” This is essentially the same as asynchronous execution of the graphics commands because glFlush returns immediately.

Returns:

None.

See Also:

glFinish

glGetXxxxv

Purpose:

Retrieves a numeric state value or array of values.

Variations:

void glGetBooleanv(GLenum value, Glboolean *data);
void glGetIntegerv(GLenum value, int *data);
void glGetFloatv(GLenum value, float *data);
void glGetDoublev(GLenum value, float *data);

Description:

Many OpenGL state variables are completely identified by symbolic constants. The values of these state variables can be retrieved with the glGetXxxxv commands. The complete list of OpenGL state values is more than 28 pages long and can be found in Table 6.6 of the OpenGL specification included on the CD-ROM.

Returns:

Fills in the supplied buffer with the OpenGL state information.

glGetError

Purpose:

Checks for OpenGL errors.

Syntax:

GLenum glGetError(void);

Description:

This function returns one of the OpenGL error codes listed in Table 2.3. Error codes are cleared when checked, and multiple error flags may be currently active. To retrieve all errors, call this function repeatedly until it return GL_NO_ERROR.

Returns:

One of the OpenGL error codes listed in Table 2.3.

glGetString

Purpose:

Retrieves descriptive strings about the OpenGL implementation.

Syntax:

const GLubyte* glGetString(GLenum name);

Description:

This function returns a character array describing some aspect of the current OpenGL implementation. The parameter GL_VENDOR returns the name of the vendor. GL_RENDERER is implementation dependent and may contain a brand name or the name of the vendor. GL_VERSION returns the version number followed by a space and any vendor-specific information. GL_EXTENSIONS returns a list of space-separated extension names supported by the implementation.

Returns:

A constant byte array (character string) containing the requested string.

glHint

Purpose:

Allows optional control of certain rendering behaviors.

Syntax:

void glHint(GLenum target, GLenum hint);

Description:

Certain aspects of GL behavior may be controlled with hints. Valid hints are GL_NICEST, GL_FASTEST, and GL_DONT_CARE. Hints allow the programmers to specify whether they care more about rendering quality (GL_NICEST), performance (GL_FASTEST), or use the default for the implementation (GL_DONT_CARE).

  • GL_PERSPECTIVE_CORRECTION_HINT—. Desired quality of parameter interpolation

  • GL_POINT_SMOOTH_HINT—. Desired sampling quality of points

  • GL_LINE_SMOOTH_HINT—. Desired sampling quality of lines

  • GL_POLYGON_SMOOTH_HINT—. Desired sampling quality of polygons

  • GL_FOG_HINT—. Calculate fog by vertex (GL_FASTEST) or per pixel (GL_NICEST)

  • GL_GENERATE_MIPMAP_HINT—. Quality and performance of automatic mipmap-level generation

  • GL_TEXTURE_COMPRESSION_HINT—. Quality and performance of compressing texture images

Returns:

None.

glIsEnabled

Purpose:

Tests an OpenGL state variable to see whether it is enabled.

Syntax:

void glIsEnabled(GLenum feature);

Description:

Many OpenGL state variables can be turned on and off with glEnable or glDisable. This function allows you to query a given state variable to see whether it is currently enabled. Table 2.5 shows the list of states that can be queried.

Returns:

None.

See Also:

glEnable, glDisable

glOrtho

Purpose:

Sets or modifies the clipping volume extents.

Syntax:

void glOrtho(GLdouble left, GLdouble right, 
GL_TEXTURE_COMPRESSION_HINT—GLdouble bottom, GLdouble top, GLdouble near, 
GL_TEXTURE_COMPRESSION_HINT—GLdouble far);

Description:

This function describes a parallel clipping volume. This projection means that objects far from the viewer do not appear smaller (in contrast to a perspective projection). Think of the clipping volume in terms of 3D Cartesian coordinates, in which case left and right are the minimum and maximum x values, top and bottom are the minimum and maximum y values, and near and far are the minimum and maximum z values.

Parameters:

left

GLdoubleThe leftmost coordinate of the clipping volume.

right

GLdoubleThe rightmost coordinate of the clipping volume.

bottom

GLdoubleThe bottommost coordinate of the clipping volume.

top

GLdoubleThe topmost coordinate of the clipping volume.

near

GLdoubleThe maximum distance from the origin to the viewer.

far

GLdoubleThe maximum distance from the origin away from the viewer.

Returns:

None.

See Also:

glViewport

glPushAttrib/glPopAttrib

Purpose:

Save and restore a set of related OpenGL state values.

Syntax:

void glPushAttrib(GLbitfield mask);
void glPopAttrib(GLbitfield mask);

Description:

OpenGL allows for whole groups of state variables to be saved and retrieved. These functions push these groups onto an attribute stack and allow for them to be popped back off the stack. Table 2.6 shows the complete list of attribute groups.

Returns:

None.

Table 2.6. OpenGL Attribute Groups

Constant

Attributes

GL_ACCUM_BUFFER_BIT

Accumulation buffer settings

GL_COLOR_BUFFER_BIT

Color buffer settings

GL_CURRENT_BIT

Current color and coordinates

GL_DEPTH_BUFFER_BIT

Depth buffer settings

GL_ENABLE_BIT

All enabled flags

GL_EVAL_BIT

Evaluator settings

GL_FOG_BIT

Fog settings

GL_HINT_BIT

All OpenGL hints

GL_LIGHTING_BIT

Lighting Settings

GL_LINE_BIT

Line settings

GL_LIST_BIT

Display list settings

GL_MULTISAMPLE_BIT

Multisampling

GL_PIXEL_MODE_BIT

Pixel mode

GL_POINT_BIT

Point settings

GL_POLYGON_BIT

Polygon mode settings

GL_POLYGON_STIPPLE_BIT

Polygon stipple settings

GL_SCISSOR_BIT

Scissor test settings

GL_STENCIL_BUFFER_BIT

Stencil buffer settings

GL_TEXTURE_BIT

Texture settings

GL_TRANSFORM_BIT

Transformation settings

GL_VIEWPORT_BIT

Viewport settings

GL_ALL_ATTRIB_BITS

All OpenGL states

glRect

Purpose:

Draws a flat rectangle.

Variations:

void glRectd(GLdouble x1, GLdouble y1, GLdouble x2
OpenGL Attribute Groups, GLdouble y2);
void glRectf(GLfloat x1, GLfloat y1, GLfloat x2, 
OpenGL Attribute GroupsGLfloat y2);
void glRecti(GLint x1, GLint y1, GLint x2, GLint y2);
void glRects(GLshort x1, GLshort y1, GLshort x1, 
OpenGL Attribute GroupsGLshort y2);
void glRectdv(const GLdouble *v1, const GLdouble *v2);
void glRectfv(const GLfloat *v1, const GLfloat *v2);
void glRectiv(const GLint *v1, const GLint *v2);
void glRectsv(const GLshort *v1, const GLshort *v2);

Description:

This function provides a simple method of specifying a rectangle as two corner points. The rectangle is drawn in the xy plane at z = 0.

Parameters:

x1, y1

Specifies the upper-left corner of the rectangle.

x2, y2

Specifies the lower-right corner of the rectangle.

*v1

An array of two values specifying the upper-left corner of the rectangle. Could also be described as v1[2].

*v2

An array of two values specifying the lower-right corner of the rectangle. Could also be described as v2[2].

Returns:

None.

glViewport

Purpose:

Sets the portion of a window that can be drawn in by OpenGL.

Syntax:

void glViewport(GLint x, GLint y, GLsizei width, 
OpenGL Attribute GroupsGLsizei height);

Description:

This function sets the region within a window that is used for mapping the clipping volume coordinates to physical window coordinates.

Parameters:

x

GLintThe number of pixels from the left side of the window to start the viewport.

y

GLintThe number of pixels from the bottom of the window to start the viewport.

width

GLsizeiThe width in pixels of the viewport.

height

GLsizeiThe height in pixels of the viewport.

Returns:

None.

See Also:

glOrtho

gluErrorString

Purpose:

Returns a character string explanation for an OpenGL error code.

Syntax:

const GLubyte* gluErrorString(GLenum errCode);

Description:

This function returns an error string, given an error code returned by the glGetError function.

Parameters:

errCode

An OpenGL error code.

Returns:

A constant pointer to an OpenGL error string.

See Also:

glGetError

glutCreateWindow

Purpose:

Creates an OpenGL-enabled window.

Syntax:

int glutCreateWindow(char *name);

Description:

This function creates a top-level window in GLUT. This is considered the current window.

Parameters:

name

char *The caption the window is to bear.

Returns:

An integer uniquely identifying the window created.

See Also:

glutInitDisplayMode

glutDisplayFunc

Purpose:

Sets the display callback function for the current window.

Syntax:

void glutDisplayFunc(void (*func)(void);

Description:

This function tells GLUT which function to call whenever the windows contents must be drawn. This can occur when the window is resized or uncovered or when GLUT is specifically asked to refresh with a call to the glutPostRedisplay function. Note that GLUT does not explicitly call glFlush or glutSwapBuffers for you after this function is called.

Parameters:

func

(*func)(void)The name of the function that does the rendering.

Returns:

None.

See Also:

glFlush, glutSwapBuffers, glutReshapeFunc

glutInitDisplayMode

Purpose:

Initializes the display mode of the GLUT library OpenGL window.

Syntax:

void glutInitDisplayMode(unsigned int mode);

Description:

This is the first function that must be called by a GLUT-based program to set up the OpenGL window. This function sets the characteristics of the window that OpenGL will use for drawing operations.

Parameters:

mode

unsigned intA mask or bitwise combination of masks from Table 2.7. These mask values may be combined with a bitwise OR.

Returns:

None.

See Also:

glutCreateWindow

Table 2.7. Mask Values for Window Characteristics

Mask Value

Meaning

GLUT_SINGLE

Specifies a single-buffered window

GLUT_DOUBLE

Specifies a double-buffered window

GLUT_RGBA or GLUT_RGB

Specifies an RGBA-mode window

GLUT_DEPTH

Specifies a 32-bit depth buffer

GLUT_LUMINANCE

Specifies a luminance only color buffer

GLUT_MULTISAMPLE

Specifies a multisampled color buffer

GLUT_STENCIL

Specifies a stencil buffer

GLUT_STEREO

Specifies a stereo color buffer

GLUT_ACCUM

Specifies an accumulation buffer

GLUT_ALPHA

Specifies a destination alpha buffer

glutKeyboardFunc

Purpose:

Sets the keyboard callback function for the current window.

Syntax:

void glutKeyboardFunc(void (*func)(unsigned char 
Mask Values for Window Characteristicskey, int x, int y);

Description:

This function establishes a callback function called by GLUT whenever one of the ASCII generating keys is pressed. Non-ASCII generating keys such as the Shift key are handled by the glutSpecialFunc callback. In addition to the ASCII value of the keystroke, the current x and y position of the mouse are returned.

Parameters:

func

(*func)(unsigned char key, int x, int y)The name of the function to be called by GLUT when a keystroke occurs.

Returns:

None.

glutMainLoop

Purpose:

Starts the main GLUT processing loop.

Syntax:

void glutMainLoop(void);

Description:

This function begins the main GLUT event-handling loop. The event loop is the place where all keyboard, mouse, timer, redraw, and other window messages are handled. This function does not return until program termination.

Parameters:

None.

Returns:

None.

glutMouseFunc

Purpose:

Sets the mouse callback function for the current window.

Syntax:

void glutMouseFunc(void (*func)(int button, int 
(*func)(unsigned char key, int x, int y):state, int x, int y);

Description:

This function establishes a callback function called by GLUT whenever a mouse event occurs. Three values are valid for the button parameter: GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, and GLUT_RIGHT_BUTTON. The state parameter is either GLUT_UP or GLUT_DOWN.

Parameters:

func

(*func)(int button, int state, int x, int y)The name of the function to be called by GLUT when a mouse event occurs.

Returns:

None.

See Also:

glutSpecialFunc, glutKeyboardFunc

glutReshapeFunc

 

Purpose:

Sets the window reshape callback function for the current window.

Syntax:

void glutReshapeFunc(void (*func)(int width, int 
(*func)(int button, int state, int x, int y):height);

Description:

This function establishes a callback function called by GLUT whenever the window changes size or shape (including at least once when the window is created). The callback function receives the new width and height of the window.

Parameters:

func

(*func)(int x, int y)The name of the function to be called by GLUT when the window size changes.

Returns:

None.

See Also:

glutDisplayFunc

glutPostRedisplay

Purpose:

Tells GLUT to refresh the current window.

Syntax:

void glutPostRedisplay(void);

Description:

This function informs the GLUT library that the current window needs to be refreshed. Multiple calls to this function before the next refresh result in only one repainting of the window.

Parameters:

None.

Returns:

None.

See Also:

glutDisplayFunc

glutSolidTeapot, glutWireTeapot

Purpose:

Draws a solid or wireframe teapot.

Syntax:

void glutSolidTeapot(GLdouble size);
void glutWireTeapot(GLdouble size);

Description:

This function draws a solid or wireframe teapot. This is the famous teapot seen so widely in computer graphics samples. In addition to surface normals for lighting, texture coordinates are also generated.

Parameters:

size

GLdoubleThe approximate radius of the teapot. A sphere of this radius would totally enclose the model.

Returns:

None.

glutSpecialFunc

Purpose:

Sets a special keyboard callback function for the current window for non-ASCII keystrokes.

Syntax:

void glutSpecialFunc(void (*func)(int key, int x, 
GLdouble:int y);

Description:

This function establishes a callback function called by GLUT whenever one of the non-ASCII generating keys is pressed. Non-ASCII generating keys are keystrokes such as the Shift key, which can't be identified by an ASCII value. In addition, the current x and y position of the mouse are returned. Valid values for the key parameter are listed in Table 2.8.

Parameters:

func

(*func)(int key, int x, int y)The name of the function to be called by GLUT when a non-ASCII keystroke occurs.

Returns:

None.

See Also:

glutKeyboardFunc, glutMouseFunc

Table 2.8. Non-ASCII Key Values Passed to the glutSpecialFunc Callback

Key Value

Keystroke

GLUT_KEY_F1

F1 key

GLUT_KEY_F2

F2 key

GLUT_KEY_F3

F3 key

GLUT_KEY_F4

F4 key

GLUT_KEY_F5

F5 key

GLUT_KEY_F6

F6 key

GLUT_KEY_F7

F7 key

GLUT_KEY_F8

F8 key

GLUT_KEY_F9

F9 key

GLUT_KEY_F10

F10 key

GLUT_KEY_F11

F11 key

GLUT_KEY_F12

F12 key

GLUT_KEY_LEFT

Left-arrow key

GLUT_KEY_RIGHT

Right-arrow key

GLUT_KEY_UP

Up-arrow key

GLUT_KEY_DOWN

Down-arrow key

GLUT_KEY_PAGE_UP

Page Up key

GLUT_KEY_PAGE_DOWN

Page Down key

GLUT_KEY_HOME

Home key

GLUT_KEY_END

End key

GLUT_KEY_INSERT

Insert key

glutSwapBuffers

Purpose:

Performs a buffer swap in double-buffered mode.

Syntax:

void glutSwapBuffers(void);

Description:

When the current GLUT window is operating in double-buffered mode, this function performs a flush of the OpenGL pipeline and does a buffer swap (places the hidden rendered image onscreen). If the current window is not in double-buffered mode, a flush of the pipeline is still performed.

Parameters:

None.

Returns:

None.

See Also:

glutDisplayFunc

glutTimerFunc

Purpose:

Registers a callback function to be called by GLUT after the timeout value expires.

Syntax:

void glutTimerFunc(unsigned int msecs, (*func)(int
Non-ASCII Key Values Passed to the glutSpecialFunc Callback value),int value);

Description:

This function registers a callback function that should be called after msecs milliseconds have elapsed. The callback function is passed the user-specified value in the value parameter.

Parameters:

msecs

unsigned intThe number of milliseconds to wait before calling the specified function.

func

void (*func)(int value)The name of the function to be called when the timeout value expires.

value

intUser-specified value passed to the callback function when it is executed.

Returns:

None.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset