Chapter 9. Colors, Materials, and Lights

 

Some people are always grumbling because roses have thorns; I am thankful that thorns have roses.

 
 --Alphonse Karr

One of the basic tenets of creating a good scene is that you have to make it look interesting. There’s no better way to achieve this than to make good use of lighting and textures. This chapter will cover the basics of creating and using color, materials, textures, and lighting in your OpenGL programs.

Color

OpenGL supports two color models. The first is RGBA mode, where you select the value for each of the red, blue, green, and alpha (or opacity) parameters. The second is color-index mode, whereby you fill a palette with the colors that you’ll need in your program. Palette programming is one of the banes of Windows programmers, since until a few years ago you could still expect to find sixteen-color platforms. Today most people are running on SVGA cards and have at least 16-bit color in some mode of the video card. This book will focus on RGBA mode, since most of the interesting things you can do involve lighting and textures, which are difficult or impossible to do using a color palette.

Color and Lighting

There’s an important differentiation you need to understand. When you are using lighting or texture-mapping effects (lighting is turned on), the color of a vertex is the cumulative effect of the material color and the light that’s shining on that vertex. When lighting is turned off, the color of a vertex is the effect of the color setting—and that color is different from the material color. Thus you can toggle the color of a vertex by toggling the lighting state. This section will cover unlighted, non-texture-mapped color. The color you specify is the color you get, unaffected by lighting or texturing (but perhaps affected by the palette if you’ve specified color-index mode).

Color and Shading

Color in RGBA mode is set by specifying the red, green, blue, and alpha intensities. The glColor*() function comes in a variety of formats. Windows programmers will be familiar with the COLORREF struct, which defines an RGB triplet as 3-byte values in the range 0–255. This followed the natural evolution of the PC’s video card from a 16-color device to one of 16 million colors. OpenGL has similarly tailored versions that take everything from single-byte arguments to quad-byte ones, including the normal floating-point versions. Throughout this book you’ll see the floating-point versions simply because it’s easy to remember that the values have to be in the range [0,1].

OpenGL’s state machine applies the currently selected color to the current vertex. Thus you need to set the color of a vertex before specifying the vertex. If you wanted to specify a bright red triangle, you would use the following commands:

glColor3f( 1.0f, 0.0f, 0.0f ); // no alpha value form
glBegin( GL_TRIANGLES );
    glVertex3f( -1.0f, 0.0f, 0.0f );
    glVertex3f( 1.0f, 0.0f, 0.0f );
    glVertex3f( 0.0f, 1.0f, 0.0f );
glEnd();

Note that the glColor*() function can be placed inside a glBegin()/glEnd() pair. Therefore you can specify individual colors for each individual vertex if you desire.

You might be wondering what the color is between two vertices of different colors. For example, what’s the color of the center of the triangle in the following code?

glBegin( GL_TRIANGLES );
    glColor3f( 1.0f, 0.0f, 0.0f );// red
    glVertex3f( -1.0f, 0.0f, 0.0f );
    glColor3f( 0.0f, 1.0f, 0.0f );// green
    ::glVertex3f( 1.0f, 0.0f, 0.0f );
    ::glColor3f( 0.0f, 0.0f, 1.0f );// blue
    ::glVertex3f( 0.0f, 1.0f, 0.0f );
::glEnd();

The answer is it depends on the shading model you’ve specified. If smooth shading (the default) is specified, the color values are interpolated between vertices. In this case the color at the center would be gray (the mixture of pure red, pure blue, and pure green). If flat shading is specified, the one vertex is selected as being representative of all of the vertices; thus the entire primitive is displayed using one single color. Which vertex is used depends on which primitive type you are drawing. Chapter 4 discusses this in greater detail.

If you examine the program sources in the “Chapter 9/Color” subdirectory, you’ll find the unlighted color-shading example, which uses the color cube to show the effects of the various shading models. I’ve left the top off the cube and placed a small black sphere inside the cube so that you can see the effects of smooth shading. Listing 9.1 shows the model for the program.

Example 9.1. The Color Cube (Minus Top)

// Render the color cube, but leave the top off....
// so we can see the sphere inside
BOOL CColorView::RenderScene( void )
{
    ::glColor3f( 0.0f, 0.0f, 0.0f );
    ::auxSolidSphere( .3f );

    // define the colors
    GLfloat color1[3] = { 1.0f, 0.0f, 0.0f }; // red
    GLfloat color2[3] = { 0.0f, 1.0f, 0.0f }; // green
    GLfloat color3[3] = { 0.0f, 0.0f, 1.0f }; // blue
    GLfloat color4[3] = { 1.0f, 1.0f, 1.0f }; // white
    GLfloat color5[3] = { 0.0f, 0.0f, 0.0f }; // black
    GLfloat color6[3] = { 1.0f, 0.0f, 1.0f }; // magenta
    GLfloat color7[3] = { 0.0f, 1.0f, 1.0f }; // cyan
    GLfloat color8[3] = { 1.0f, 1.0f, 0.0f }; // yellow

    // Connect the four sides
    ::glBegin(GL_QUAD_STRIP);
        ::glColor3fv( color6 );
        ::glVertex3f(-1.0f, 1.0f, 1.0f);

        ::glColor3fv( color1 );
        ::glVertex3f(-1.0f, -1.0f, 1.0f);

        ::glColor3fv( color4 );
        ::glVertex3f(1.0f, 1.0f, 1.0f);

        ::glColor3fv( color8 );
        ::glVertex3f(1.0f, -1.0f, 1.0f);

        ::glColor3fv( color7 );
        ::glVertex3f(1.0f, 1.0f, -1.0f);

        ::glColor3fv( color2 );
        ::glVertex3f(1.0f, -1.0f, -1.0f);

        ::glColor3fv( color3 );
        ::glVertex3f(-1.0f, 1.0f, -1.0f);

        ::glColor3fv( color5 );
        ::glVertex3f(-1.0f, -1.0f, -1.0f);

        ::glColor3fv( color6 );
        ::glVertex3f(-1.0f, 1.0f, 1.0f);

        ::glColor3fv( color1 );
        ::glVertex3f(-1.0f, -1.0f, 1.0f);

    ::glEnd();

    // The Bottom
    ::glBegin(GL_QUADS);

        ::glColor3fv( color1 );
        ::glVertex3f(-1.0f, -1.0f, 1.0f);

        ::glColor3fv( color8 );
        ::glVertex3f(1.0f, -1.0f, 1.0f);

        ::glColor3fv( color2 );
        ::glVertex3f(1.0f, -1.0f, -1.0f);

        ::glColor3fv( color5 );
        ::glVertex3f(-1.0f, -1.0f, -1.0f);
    ::glEnd();

    return TRUE;
}

This model draws the color cube—red, green, and blue as one triplet; cyan, magenta, and yellow as another triplet; with a diagonal axis representing the white-black axis. Plate 9.1 shows the effect of smooth shading. You can see how just by specifying the colors of the vertices, smooth shading interpolates to provide nearly every color representable. You should also note how difficult it is to see the edges where the faces of the cube meet. With no lighting effects, it’s nearly impossible to distinguish between the faces of the polygons that make up an object. You can use this effect to your advantage. Plate 9.2 shows the same scene with flat shading. The color of each face is entirely the result of the order in which the vertices were specified. A different order would yield a totally different color for a flat-shaded model.

Lighting effects let you make a model that instantly has a realism to it by the effects of the shading provided by the lighting calculations. However, adding lighting calculations increases the complexity of the calculations that OpenGL must go through to render the scene. If your model requires lighting effects, there’s no way around it. However, you can selectively turn lighting effects on and off for certain parts of your model. If you need to let the user differentiate between the polygons of a model, you might consider subtly changing the color of the polygons that make up the model or applying a texture map. This might let you get away with little or no requirement for calculated lighting in your model. Also remember that infinite lights are much simpler to compute than local lights.

The Color Cube (Minus Top)

Another optimization (or simplification) to your model is deciding when you need smooth shading versus flat shading. You can toggle the shading method used for various parts of your model just as you can toggle the lighting effects. This lets you selectively increase or decrease the complexity of the calculations required to render your model. If you need lighting calculations, you might be able to design your model such that you don’t need smooth shading. If an area of the model looks too faceted, you can always increase the polygon count in that area. Be aware, however, that there’s a point of diminishing returns if you take tessellation too far.

Materials

OpenGL’s materials are descriptions of what things are made of—or at least what they appear to be made of—by describing how they reflect light. If you were shown a picture of a room with a black marble floor, you’d probably be able to distinguish it from a picture of a room with a black carpeted floor. Although both floors may be black, the differences between the way that marble and carpet reflect light make them easily distinguishable. Although you can’t specify marble or carpet as materials, OpenGL makes it fairly easy to describe the reflective properties of each material to achieve the same visual result.

Types of Material Properties

OpenGL has five properties that describe how a surface dissipates light. These properties are typically described by RGBA values that stipulate the color of the dissipated light. For example, a blue sphere under a white light looks blue because the reflected light is blue. If the light were to change to red, the sphere would appear black, because there would be no blue component for the sphere to reflect back. Thus the color of a surface of an object is a complicated summation of the color(s) of any light(s) that are shining, the angle(s) that those lights are in relationship to the surface, and the color and reflective and emissive properties of that surface.

Diffuse and Ambient Properties

The diffuse and ambient reflective material properties are a type of reflective effect that is independent of the viewpoint. Diffuse lighting describes how an object reflects a light that is shining on the object. In other words, it’s how the surface diffuses a direct light source. Ambient lighting describes how a surface reflects the ambient light available. The ambient light is the indirect lighting that’s in a scene: the amount of light that’s coming from all directions so that all surfaces are equally illuminated by it. A surface that has no direct light sources shining on it appears the color of its ambient material—assuming that there’s ambient light available.

Specular and Shininess Properties

The specular and the shininess properties of the surface describe the reflective effects that are affected by the position of the viewpoint. Specular light is reflected light from a surface that produces the reflective highlights in a surface. The shininess is a value that describes how focused the reflective properties are. A ceramic teapot will return less specular light than a plastic one, whereas a metal teapot will have a higher shininess value than both.

Emissive Property

Emissive light is the light that an object gives off by itself. A light source is typically the only object that you might give an emissive value. Lamps, fires, and lightning are all objects that give off their own light. Note that specifying an emissive value is not the same as specifying a light source (something that’s going to illuminate other objects). If you wanted to render a scene with a lamp in it, you render the lamp with an emissive value (so that the lamp appears to be glowing) and place a light source in the lamp (so that the lamp appears to be illuminating the objects around it).

Specifying a Material Property

Specifying a material property is about the same as specifying a color. You provide an RGBA value. If you’re using color-index mode, you should look at the articles from the Microsoft Systems Journal listed in the bibliography section of “OpenGL Sources.” The following discussion deals only with RGBA mode.

Let’s specify a gray material for both the ambient and diffuse properties. The code to specify a dark gray ambient material and a light gray diffuse material might look like this:

GLfloat materialDiffuse[] = { 0.2f, 0.2f, 0.2f, 1.0f };
GLfloat materialAmbient[] = { 0.5f, 0.5f, 0.5f, 1.0f };
::glMaterialfv( GL_FRONT, GL_DIFFUSE, materialDiffuse);
::glMaterialfv( GL_FRONT, GL_AMBIENT, materialAmbient);

Note that we specify an RGBA quartet, since we’re specifying the color that’s perceived under certain lighting conditions. The OpenGL function glMaterial*() is a new one, and its various forms are used to specify the materials properties that make up the resultant color of a surface. The first parameter indicates which face of a polygon the property should be applied to. You can specify the front, back, or both faces. Typically you want only the front face to be illuminated, but under certain conditions you might want the back face illuminated. This also means that you can specify different parameters for the front and back faces. The OpenGL Programming Guide contains a nice example of a teapot illuminated on the outside with one set of parameters and on the inside with a different set of parameters. A clipping plane neatly slices off some of the teapot so that we can see inside.

Selecting an Object’s Material Properties

Choosing the material properties of an object determines how it will look. The steps are as follows:

  1. Decide on the diffuse and ambient colors. The ambient color defines the color that results from ambient light falling on the object and will be the dominant color when no direct illumination is on that part of the surface. The diffuse reflecting color comes into play when the surface is illuminated. For most physically based materials, or real-world materials, these are the same values. OpenGL conveniently has a parameter for glMaterial*() to specify this fact: GL_AMBIENT_AND_DIFFUSE.

  2. If the object has a hidden interior, you’ll probably want only the front faces to be included in the calculations, so you’d use the GL_FRONT parameter.

  3. Decide on the shininess of the object. Pewter, silver, and chrome differ in the perceived shininess of their surfaces. The two controls for this reflective property are the specular and the shininess parameters. It may seem odd that you can specify the color characteristics of the highlights. Generally you’ll want this color to be white, but you might want to color the highlight the same color as the ambient and diffuse values. If you’re modeling some colored crystal, the highlights of a white light shining on the crystal are generally colored the same as the crystal, whereas a chrome surface reflects the color of the light shining on it. The shininess parameter controls the focus of the highlight. A low value spreads the reflectance over the surface (according to the mathematics of the lighting calculations), whereas a high value concentrates the highlight, making it smaller and brighter.

  4. Finally, decide whether the object is giving off light. If it is, assign it the emissive properties you need.

How do you get these values? Trial and error, although after a while you get a collection of values for the materials that you use. One particularly useful thing to do is to write a program to run the gamut of properties and just watch how they vary. This is exactly what the LIGHTING 1 project in the “Chapter 9/Lighting 1” subdirectory does.

The program creates a 4 ˘ 4 ˘ 4 matrix of blue spheres that varies the ambient and diffuse properties along the y-axis, the specular property along the x-axis, and the shininess along the z-axis. The material properties matrices that are used are as follows:

// The Specular values
GLfloat materialSpecular[4][4] = {
    { 0.1f, 0.1f, 0.1f, 1.0f },
    { 0.33f, 0.33f, 0.33f, 1.0f },
    { 0.67f, 0.67f, 0.67f, 1.0f },
    { 0.9f, 0.9f, 0.9f, 1.0f },
    };

// The Ambient and Diffuse values
GLfloat materialAmbDiff[4][4] ={
    { 0.0f, 0.0f, 0.12f, 1.0f },
    { 0.0f, 0.0f, 0.25f, 1.0f },
    { 0.0f, 0.0f, 0.50f, 1.0f },
    { 0.0f, 0.0f, 1.00f, 1.0f },
    };

// The Shininess values
GLfloat materialShininess[4][1] = {
    { 0.0f },
    { 35.0f },
    { 70.0f },
    { 128.0f }
    };

These values were chosen because they give a relatively even increase in each property as the values change. Plate 9.3 illustrates the changes that these properties bring when combined. The colors of the spheres are defined by the ambient and diffuse values, and you can see from both the array and the illustration that the colors start off as a very dark blue and rise to a bright pure blue.

The shininess effect can be seen as the highlight goes from being spread across the entire illuminated hemisphere to a point of light on the surface. If you rotate the matrix about the y-axis, you’ll see that the highlight disappears when the angles of reflection no longer hit the viewpoint. The other interesting thing to note is that lighting is turned off for the text by using glDisable( GL_LIGHTING ) and setting the color to yellow. The text is generated by using the default 3D text created by the COpenGLView class. See chapter 7 for more information on creating and using text in OpenGL.

Creating Your Own Material Properties

Let’s create a material that we can use in the next section. We’re going to use some 3D text made out of something like chrome. The next section is about lighting, and a chrome surface will give nice highlights. The first step is to decide on the color. Chrome is a neutral color; most of its visual effect stems from its highly reflective properties. We choose a medium gray for the ambient color. The diffuse color is a bit brighter, because we want to emphasize the diffuse reflective properties of the surface. The values used in the program are

GLfloat materialAmbient[4] = { 0.25f, 0.25f, 0.25f, 1.0f };
GLfloat materialDiffuse[4] = { 0.4f, 0.4f, 0.4f, 1.0f };

The reflective effects are next, and we select highly reflective and shiny values for these properties:

GLfloat materialSpecular[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat materialShininess[1] = { 128.0f };

Finally, these values are placed in the program, using the following calls:

::glMaterialfv( GL_FRONT, GL_DIFFUSE, materialDiffuse );
::glMaterialfv( GL_FRONT, GL_AMBIENT, materialAmbient );
::glMaterialfv( GL_FRONT, GL_SPECULAR, materialSpecular );
::glMaterialfv( GL_FRONT, GL_SHININESS, materialShininess );

Note that we set them for only the front faces, since the text back faces are hidden. In order to get the visual effect of the material, we need to add some lights to the scene, and that’s what we do in the next section.

Lighting

OpenGL has two types of lighting: global lighting, or the ambient light and its associated parameters, and individual light sources, which have position and direction. The calculations involved in determining how much light is reflected off a surface are quite complex. They are an intricate combination of all of the lights that are turned on; the ambient light; the position of the lights; the position of the objects and the viewpoint; the angles among all the lights, the object, and the viewpoint; and, finally, all of the parameters that are set for each material and each light. The OpenGL Programming Guide has an entire section devoted to explaining the mathematics involved in lighting calculations.

Enabling Lighting

You must do a few steps before you’ll get lighting effects in your program. Since lighting calculations are expensive, you’ll first need to turn these calculations on. This is done, as are many things in OpenGL, by turning on a state variable. In this case the glEnable() function is called with GL_LIGHTING. This will turn on lighting calculations, and the default values for the global ambient light will let you see any objects in your scene.

In addition to the global ambient light, you can turn on at least eight individual lights. The actual number depends on the OpenGL implementation that’s running. You enable each individual light by using a GL_LIGHT* argument to glEnable(), where * is a number from 0 to 7 (or more). Default values for GL_LIGHT0 let its effects be visible; the other lights have default values set so that they are disabled and don’t enter into the lighting calculations. Some hardware implementations are optimized for a single light.

Global Lighting

The glLightModel*() function is used for setting global lighting parameters. You can select the RGBA value for the global ambient light that’s present in your scene. You’ll probably want a small value of light, such as the default, so that all the objects in your scene will be visible even if no individual light is shining on them.

Next, you can set calculations for specular highlights. Highlights are calculated either from an infinite viewpoint (the default) or by taking into account the position of the viewpoint. In local-viewpoint mode, the highlights are calculated by taking into account the angle between the viewpoint and the vertex reflecting the light. This will yield a more realistic scene but at the expense of more complicated lighting calculations. The default is to assume that the angles should be calculated using a viewpoint at infinite distance. Although this greatly simplifies the lighting calculations, some authenticity is lost. The program LIGHTING 2 in the “Chapter 9/Lighting 2” subdirectory uses the local-viewpoint mode to increase the “realness” of the reflections. Plate 9.4 shows a scene from the program.

Finally, by default OpenGL calculates ambient reflections only for front faces. If you wanted the back or inside of an object to be visible, you’d need to turn on the mode for two-sided global lighting calculations, using the GL_LIGHT_MODEL_TWO_SIDE argument. By default this value is turned off.

If you need to turn on either two-sided lighting or the local-viewpoint mode, you should be judicious and turn it on only for the part of the model that requires it. Globally turning on complicated lighting calculations when you don’t need them is a good way to slow down rendering time.

Global Lighting

Individual Light Sources

Once you’ve got the global lighting set, you’ll probably notice that although you can see individual sides of objects, there’s no difference in the light among them. An individual light source will bring into sharp detail the differences between differently illuminated sides. After enabling an individual light source, you’ll probably want to set the ambient and diffuse RGBA values for the light. The ambient part of the light’s setting contribute to the overall global ambient light. By default there is no ambient light component. The command used to set an individual light’s values is the glLight*() function. The parameters passed in are the particular light’s enum value, the enum of the parameter you want to change, and the value of the parameter.

Setting the Illuminating Parameters

The diffuse RGBA value is the color that the light contributes to the reflectance off an object and is what you can consider the “color” of the light. Shining a light with red diffuse RGBA settings on a white sphere would give a red coloring to that part of the sphere that the light illuminates.

The specular component is the color of the highlights that reflect off a shiny surface. Typically you’d set the specular value to be the same as the diffuse value, since the reflected highlight is usually the same color as the light. Plate 9.4 is a scene from the LIGHTING 2 program in the “Chapter 9/Lighting 2” subdirectory. There are three white, mildly reflective spheres. The sphere in the middle has a light with white diffuse and specular light shining on it. The sphere on the left has a green diffuse and white specular light shining on it. The sphere on the right has a white diffuse and a green specular light shining on it. You can easily see the green specular component shining through the white diffuse glow on the sphere on the left. If you animate the scene, you can see the highlight moving around on the sphere while the diffuse component is stationary.

Setting the Position

You can position individual lights by passing in a four-element position into glLight*(). You can select two types of light positions. The first is called a directional light position. The x, y, z values specify a point that, when connected to the origin, defines the line to which all of the light rays are parallel. A directional light is considered to be at an infinite distance from the origin, and hence all of the light coming from the light source is parallel to the line specified between the origin and the point given. Remember that the Modelview matrix transformations work for light positions, as well as for vertex positions!

The second type is called a positional light source. In this case the position vector that is passed in is the position of the light. It’s the origin of the light, and all the light rays originate from this position. By default this light radiates in all directions and is equally bright no matter the distance of a surface from the light.

The method for selecting positional versus directional light sources lies in the w component of the position vector that you pass in. If the w value is 0, the light is taken to be a directional one. If the w value is not 0, it’s used like it normally would be, although usually w is set to 1 in this case.

Setting Attenuation

Attenuation is the reduction in the intensity of the light as the distance increases. OpenGL has three modifiable parameters for calculating the attenuation factor. GL_CONSTANT_ATTENUATION is the constant, and by default its value is 1.0. The other parameters—GL_LINEAR_ATTENUATION and GL_QUADRATIC_ATTENUATION—are used in the calculations to reduce the light as a function of distance. See the OpenGL Programming Guide for more information on the formula used for calculating attenuation. You can set the attenuation only for a positional light. It doesn’t make sense to attempt to do it for a light that’s located at infinity.

Creating a Spotlight

The last setting that affects a light turns an omnidirectional light source into a spotlight. The position of the light is set, the same as for a positional light (it makes sense only for a positional light to be turned into a spotlight). You constrict the range of light dispersion with the GL_SPOT_CUTOFF parameter, which specifies the angle from the centerline of the spotlight’s direction. See Figure 9.1. Thus a value of 15 degrees, for example, defines a cone 30 degrees wide. By default this value is 180 degrees, yielding a full 360 degrees. The acceptable range is [0,90], except for the special value of 180.

Components of a Spotlight

Figure 9.1. Components of a Spotlight

To set the direction of the spotlight, use the GL_SPOT_DIRECTION parameter. This vector is in object coordinates, and it’s affected by whatever matrix transformations are in effect. By default it’s pointing down the negative z-axis.

The final setting for spotlights controls how intense the light is at its center. The GL_SPOT_EXPONENT controls how to concentrate the light’s intensity. By default the light is equally bright across the diameter of the spotlight. The exponent value is the power of the exponent on a cosine of the angle from the center of the spot. The higher the value, the more concentrated the light will be at the center.

If you want to illuminate a large surface, you should remember that the interior of a surface is colored by interpolating the color at the vertices. If you have a light source close to the middle of one large polygon, the center will be dark, because the middle color is interpolated from vertices that are far away from the light source. The solution is to break up the large polygon into smaller polygons so that there are more vertices from which to interpolate.

Components of a Spotlight

Creating a Scene with Multiple Light Sources

Let’s create a scene with some dramatic lighting effects. We’ll use multiple lights to create a scene that shows OpenGL’s lighting model capabilities. Earlier we discussed how to create a set of material properties to get a highly reflective surface. We’ll now use that set of properties in combination with what we’ve learned about lighting to create a spectacularly illuminated scene. The source code for this section can be found in the “Chapter 9/Lighting 3” subdirectory. Plate 9.5 shows a rendering of the scene.

The best scenes are simple, so this scene will consist of nothing more than the text “OpenGL & Windows,” albeit dramatically lighted. I’ve chosen a font that contains a lot of extra serifs and details to better provide reflective surfaces. In the code that accompanies this section, I’ve commented out that line of code and replaced it with a selection of Arial instead. To display the text is simply a matter of generating the font’s display list and then using the GLTextOut( ) member function of the COpenGLView class. Since we’ve already discussed how the materials were set, all that’s required is to generate the display list and to render the text with these two lines of code:

GLuint myFont =
    GenerateDisplayListForFont( "Arial", 0.2f );
GLTextOut( myFont, "OpenGL & Windows" );

The scene has four lights. Two directional white lights generally illuminate the scene and are placed above the text, one in front and one behind. The one in front provides some general illumination of the letters; the one in back provides white highlights. Being directional, unattenuated lights, they provide a general, global illumination of the entire scene.

The next two lights are positional lights, both of which are placed under and behind the text string to provide localized highlights across the text. One light is red and the other blue, and they are placed about a third of the way from each end of the string. The rest of this text is easier to visualize if you examine Plate 9.5. The letters closer to the lights show the effects of that light (although, being unattenuated, distance from the light usually has no meaning). I’ve placed a red and blue sphere at the same position as the light of that color. This makes it easier to see how the lights reflect off the text as it rotates. In particular note the red and blue highlights reflecting from the text. The purple highlights on the “O” are the result of illumination from both the red and blue lights.

The global ambient light has been turned off (set to 0,0,0,1), so illumination is strictly the result of calculations involving the four lights placed in the scene. Note that even though there are letters in the way, the “O” seems fully illuminated by the red light. This demonstrates one simplification of OpenGL’s lighting model that becomes evident when you’re trying to create realistic scenes. This limitation is discussed briefly in the next section.

Ray Tracing

You may have noticed the absence of shadows in the scene. It’s particularly obvious when you rotate the text. OpenGL has no provisions for letting objects obscure one another from lighting. The lighting model that does do this is called ray tracing, and it refers to the method of tracing each ray of light on its way to illuminating each pixel in the scene. If it sounds computationally expensive, it is. However, many people write their own illumination models, including ray tracing ones to use with OpenGL. If you want to find out more about ray tracing, consult one of the general graphics texts listed in the bibliography.

The Module for the Scene

A nonoptimized version of the source code for the module is shown in Listing 9.2. (You wouldn’t place all these light and material commands in the redraw loop.) The important things to note are the positioning of the lights, the selection of the materials, and the fact that the global ambient light has been set to 0.

Example 9.2. The RenderScene( ) Member Function for Plate 9.5

BOOL CLightingView::RenderScene( void )
{
    // select the font
    static GLuint myFont =
        GenerateDisplayListForFont("Arial",0.2f);
    GLfloat materialSpecular[4] = {1.0f, 1.0f, 1.0f, 1.0f};
    GLfloat materialShininess[1] = { 128.0f };
    GLfloat materialAmbient[4] = {0.25f,0.25f,0.25f,1.0f};
    GLfloat materialDiffuse[4] = {0.4f, 0.4f, 0.4f, 1.0f};
    GLfloat local_ambient[] = { 0.0f, 0.0f, 0.0f, 1.0f };

    ::glEnable(GL_DEPTH_TEST);
    ::glDepthFunc(GL_LESS);
::glLightModelfv(GL_LIGHT_MODEL_AMBIENT,local_ambient);

    // The red light
    GLfloat ambient0[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    GLfloat diffuse0[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    GLfloat specular0[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    // note positional setting
    GLfloat position0[] = { 2.0f, -1.5f, -1.5f, 1.0f };

    // the back white light
    GLfloat ambient1[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    GLfloat diffuse1[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    GLfloat specular1[] = { 0.5f, 0.5f, 0.5f, 1.0f };
    // note directional setting
    GLfloat position1[] = { 2.0f, 1.0f, -1.0f, 0.0f };

    // The blue light
    GLfloat ambient2[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    GLfloat diffuse2[] = { 0.0f, 0.0f, 1.0f, 1.0f };
    GLfloat specular2[] = { 0.0f, 0.0f, 1.0f, 1.0f };
    GLfloat position2[] = { -0.5, -0.5, -1.0f, 1.0f };

    // the other white light (in front)
    GLfloat ambient3[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    GLfloat diffuse3[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    GLfloat specular3[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    GLfloat position3[] = { 2.0f, 0.5f, 0.5f, 0.0f };

    // Now set up the individual lights
    ::glEnable(GL_LIGHT0);
    ::glLightfv(GL_LIGHT0, GL_AMBIENT, ambient0);
    ::glLightfv(GL_LIGHT0, GL_POSITION, position0);
    ::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse0);
    ::glLightfv(GL_LIGHT0, GL_SPECULAR, specular0);

    ::glEnable(GL_LIGHT1);
    ::glLightfv(GL_LIGHT1, GL_AMBIENT, ambient1);
    ::glLightfv(GL_LIGHT1, GL_POSITION, position1);
    ::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse1);
    ::glLightfv(GL_LIGHT1, GL_SPECULAR, specular1);

    ::glEnable(GL_LIGHT2);
    ::glLightfv(GL_LIGHT2, GL_AMBIENT, ambient2);
    ::glLightfv(GL_LIGHT2, GL_POSITION, position2);
    ::glLightfv(GL_LIGHT2, GL_DIFFUSE, diffuse2);
    ::glLightfv(GL_LIGHT2, GL_SPECULAR, specular2);

    ::glEnable(GL_LIGHT3);
    ::glLightfv(GL_LIGHT3, GL_AMBIENT, ambient3);
    ::glLightfv(GL_LIGHT3, GL_POSITION, position3);
    ::glLightfv(GL_LIGHT3, GL_DIFFUSE, diffuse3);
    ::glLightfv(GL_LIGHT3, GL_SPECULAR, specular3);

    // Set up the material properties
    ::glMaterialfv( GL_FRONT, GL_SPECULAR,
        materialSpecular );
    ::glMaterialfv( GL_FRONT,GL_SHININESS,
        materialShininess);
    ::glMaterialfv( GL_FRONT, GL_DIFFUSE,
        materialDiffuse);
    ::glMaterialfv( GL_FRONT, GL_AMBIENT,
        materialAmbient);

    // Now that the setup is all done (it has to be
    // done only once) perform the actual rendering

    // turn off lighting to draw regular old wire
    // spheres to show where the lights are
    ::glDisable( GL_LIGHTING );

    ::glPushMatrix();
    ::glColor3fv( diffuse0 );
    ::glTranslatef( position0[0],
        position0[1], position0[2] );
    ::auxWireSphere(0.15f);
    ::glPopMatrix();

    ::glPushMatrix();
    ::glColor3fv( diffuse2 );
    ::glTranslatef( position2[0],
        position2[1], position2[2] );
    ::auxWireSphere(0.15f);
    ::glPopMatrix();

    // Turn lighting back on
    ::glEnable( GL_LIGHTING );

    // Now, draw the text
    ::glPushMatrix();
    ::glTranslatef( -3.0f, 0.0f, 0.0f );
    GLTextOut( myFont, "OpenGL & Windows" );
    ::glPopMatrix();
    return TRUE;
}

As you can see, most of the routine is setup and would be better placed in an initialization routine. The difficult part is understanding how the lights interact with one another and with the materials. As you rotate the scene, note that you can see the red or blue highlights only when you’re looking directly at the text, because the lights have to reflect off a surface such that they can reflect into the viewpoint. Think of each surface as a mirror; if the light or your viewpoint drops below the plane of a mirror, no light will be reflected from that mirror.

Optimizing the Rendering of Dynamically Changing Material Properties

Once you’ve set the materials for your scene, you generally leave them in place. If you have more than one set of materials, encapsulate them in a display list. Occasionally you might want to dynamically change the material properties on the fly. If the changes are fairly simple, such as changing the diffuse color of an object, you can use OpenGL’s optimized call for such an occasion.

Optimizing the Rendering of Dynamically Changing Material Properties

The glColorMaterial() function allows you to change the source of a material property’s values to the RGBA values set by the glColor*() function. Recall that when lighting is enabled, the glColor*() function has no effect. If you enable the glColorMaterial() function with a call to glEnable() with the argument GL_COLOR_MATERIAL, you can selectively change a material property’s source RGBA values to those stored by means of the glColor*() function.

It works like this: You enable the function, then use the glColorMaterial() function to select the face(s) you want to affect and the material property you want to switch over to, using the glColor*() functions values. To change that material’s RGBA values, you make a call to glColor*().

For example, suppose you wanted to draw a shiny red, green, and blue triangle. You could use the glColorMaterial() function to do it, as in the following code segment:

// Set up ambient, shininess, and specular materials values
// using glMaterial*() calls
// turn on glColorMaterial()
::glEnable( GL_COLOR_MATERIAL );
// select the property we want to change
::glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
// glColor()'s values are now hooked to the ambient and
// diffuse color
// Now let's do some rendering
::glBegin( GL_TRANGLES );
    for ( int i = 0 ; i < 10000 ; i++ )
        {
        // position and draw red vertex
        ::glColor3f( 1.0f, 0.0f, 0.0f );; // select red
        ::glVertex3f( 0.0f, 0.0f, 0.0f );
        // position and draw green vertex
        ::glColor3f( 0.0f, 1.0f, 0.0f );; // select green
        ::glVertex3f( 1.0f, 0.0f, 0.0f );
        // position and draw blue vertex
        ::glColor3f( 0.0f, 0.0f, 1.0f );; // select blue
        ::glVertex3f( 0.5f, 1.0f, 0.0f );
        // now do some rotating or translating to the
        // next triangle position...
        .... move to next position
        }
::glEnd(); // all done rendering
// turn off glColorMaterial()
::glDisable( GL_COLOR_MATERIAL );

It’s a good idea to always turn off the glColorMaterial() rerouting when you’re done with it. Nothing is more frustrating than to wonder why you can set specular and highlight for something, but the ambient and diffuse color is always white! This is a powerful command, but it obfuscates the way that colors normally work, so be careful when using it.

Try This

  • Rewrite the LIGHTING 1 example to add a light source. Then vary such things as the specular reflection. If the animation is too slow, try making the spheres cubes.

  • Try using a flat shading model in LIGHTING 2. Compare the visual effects and the rendering speed for both shading models. How about turning the lighting effects off?

  • Play with the LIGHTING 3 (chrome text) example. Try rewriting it so that the red and blue lights are spotlights.

  • Try making the red and blue lights attenuated. (Try setting the quadratic term to 2.0.) How does this affect the color reflecting off the “O”?

  • Make the light in front a spotlight with a narrow beam. However, make the light positioned at and aligned with the viewpoint. (See the OpenGL Programming Guide if you need help on how to do it. Hint: Set the light position before the viewing transformation. Did you remember the default viewpoint location?)

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

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