by Richard S. Wright, Jr.
WHAT YOU'LL LEARN IN THIS CHAPTER:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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 |
---|---|---|---|
8-bit integer |
|
| |
16-bit integer |
|
| |
32-bit integer |
|
| |
32-bit floating point |
| ||
64-bit floating point |
| ||
8-bit unsigned integer |
| ||
16-bit unsigned integer |
|
| |
32-bit unsigned integer |
|
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.
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.
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!
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.
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.
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
.
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(); }
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.
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.
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.)
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.
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
:
The single argument to glutCreateWindow
is the caption for the window's title bar.
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.
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.
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
.
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.
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.
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(); }
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.
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.
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.
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
.
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.
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.
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.
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.
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(); }
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:
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.
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.
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.
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.
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!
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 |
---|---|
The enum argument is out of range. | |
The numeric argument is out of range. | |
The operation is illegal in its current state. | |
The command would cause a stack overflow. | |
The command would cause a stack underflow. | |
Not enough memory is left to execute the command. | |
The specified table is too large. | |
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!
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.
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.
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.
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:
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:
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.
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.
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.
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.
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.
glDisable, glEnable | |
---|---|
Purpose: | |
Include File: |
|
Syntax: | |
void glDisable(GLenum feature);
glEnable
| |
Description: |
|
Parameters: | |
|
|
Table 2.5. Features Enabled/Disabled by glEnable
/glDisable
Feature | Description |
---|---|
Color blending | |
Polygon culling | |
Depth test | |
Dithering | |
OpenGL fog mode | |
OpenGL lighting | |
xth OpenGL light (minimum: 8) | |
Point antialiasing | |
Line antialiasing | |
Line stippling | |
Polygon antialiasing | |
Scissoring enabled | |
Stencil test | |
xD texturing (1, 2, or 3) | |
Cube map texturing | |
Texgen for x (S, T, R, or Q) |
glFinish | |
---|---|
Purpose: | |
Syntax: | |
void glFinish(void); | |
Description: | OpenGL commands are usually queued and executed in batches to optimize performance. The |
Returns: | None. |
See Also: |
|
glFlush | |
---|---|
Purpose: | |
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 |
Returns: | |
See Also: |
|
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 |
Returns: | Fills in the supplied buffer with the OpenGL state information. |
glGetError | |
---|---|
Purpose: | |
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 |
Returns: | One of the OpenGL error codes listed in Table 2.3. |
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 |
Returns: | None. |
See Also: |
|
glOrtho | |
---|---|
Purpose: | |
Syntax: | |
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, 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 |
Parameters: | |
| |
|
|
|
|
|
|
|
|
|
|
Returns: | |
See Also: |
|
glPushAttrib/glPopAttrib | |
---|---|
Purpose: | |
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 |
---|---|
Accumulation buffer settings | |
Color buffer settings | |
Current color and coordinates | |
Depth buffer settings | |
All enabled flags | |
Evaluator settings | |
Fog settings | |
All OpenGL hints | |
Lighting Settings | |
Line settings | |
Display list settings | |
Multisampling | |
Pixel mode | |
Point settings | |
Polygon mode settings | |
Polygon stipple settings | |
Scissor test settings | |
Stencil buffer settings | |
Texture settings | |
Transformation settings | |
Viewport settings | |
glRect | |
---|---|
Purpose: | |
Variations: | |
void glRectd(GLdouble x1, GLdouble y1, GLdouble x2 , GLdouble y2); void glRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); void glRecti(GLint x1, GLint y1, GLint x2, GLint y2); void glRects(GLshort x1, GLshort y1, GLshort x1, GLshort 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: | |
| Specifies the upper-left corner of the rectangle. |
| Specifies the lower-right corner of the rectangle. |
| An array of two values specifying the upper-left corner of the rectangle. Could also be described as v1[2]. |
| An array of two values specifying the lower-right corner of the rectangle. Could also be described as v2[2]. |
Returns: |
glutCreateWindow | |
---|---|
Purpose: | |
Syntax: | |
int glutCreateWindow(char *name);
| |
Description: | This function creates a top-level window in GLUT. This is considered the current window. |
Parameters: | |
|
|
Returns: | |
See Also: |
|
glutDisplayFunc | |
---|---|
Purpose: | |
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 |
Parameters: | |
|
|
Returns: | |
See Also: |
|
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: | |
|
|
Returns: | |
See Also: |
|
Table 2.7. Mask Values for Window Characteristics
Mask Value | Meaning |
---|---|
Specifies a single-buffered window | |
Specifies a double-buffered window | |
Specifies an RGBA-mode window | |
Specifies a 32-bit depth buffer | |
Specifies a luminance only color buffer | |
Specifies a multisampled color buffer | |
Specifies a stencil buffer | |
Specifies a stereo color buffer | |
Specifies an accumulation buffer | |
glutKeyboardFunc | |
---|---|
Purpose: | |
Syntax: | |
void glutKeyboardFunc(void (*func)(unsigned char key, 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 |
Parameters: | |
|
|
Returns: |
glutMainLoop | |
---|---|
Purpose: | |
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: | |
Returns: | None. |
glutMouseFunc | |
---|---|
Purpose: | |
Syntax: | |
void glutMouseFunc(void (*func)(int button, int 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: |
Parameters: | |
|
|
Returns: | |
See Also: |
|
glutPostRedisplay | |
---|---|
Purpose: | |
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: |
|
glutSolidTeapot, glutWireTeapot | |
---|---|
Purpose: | |
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: | |
|
|
Returns: |
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, 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: | |
|
|
Returns: | |
See Also: |
|
Table 2.8. Non-ASCII Key Values Passed to the glutSpecialFunc
Callback
Key Value | Keystroke |
---|---|
F1 key | |
F2 key | |
F3 key | |
F4 key | |
F5 key | |
F6 key | |
F7 key | |
F8 key | |
F9 key | |
F10 key | |
F11 key | |
F12 key | |
Left-arrow key | |
Right-arrow key | |
Up-arrow key | |
Down-arrow key | |
Page Up key | |
Page Down key | |
Home key | |
End key | |
glutSwapBuffers | |
---|---|
Purpose: | |
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: | |
See Also: |
|