Core Techniques and Algorithms in Game Programming2003
OpenGL offers an interface for hardware-accelerated, per-vertex lighting. This lighting must be supported by the hardware (hence the transform and lighting accelerator family). When implemented on hardware, OpenGL lighting provides good results at a relatively low cost. Lighting is optional in OpenGL, and disabled by default. Thus, the first step in any lighting application is to effectively enable it, using the command: glEnable(GL_LIGHTING); Then, light sources and material properties for objects must be defined. Beginning with lights, OpenGL supports several types of luminaries. You can define infinite lights, which are supposed to be very far away and thus send parallel rays to the scene (the Sun would be a good example), or positional sources, which effectively are contained in a point in space. The latter can in turn exist under different incarnations. We can have omnidirectional sources, which emit light in all directions, or spot lights, which emit a cone of light in a certain direction. Additionally, positional light sources can have attenuation built in, so lighting intensity decreases with distance. A scene with OpenGL hardware lighting can be seen in Figure B.5. Figure B.5. Simple hardware lighting under OpenGL.
All these parameters are controlled with two calls only. The first one allows us to declare a new light source and has the syntax: glEnable(GL_LIGHTx); where x is the serial number of the luminaire. Values from 0 to 9 are mandatory, with some implementations supporting even more (at a higher computational cost). You can check the specific number of light sources supported by your implementation by accessing the integer constant GL_MAX_LIGHTS. Lights must be initialized with this call prior to assigning any parameters to them. Once a light has been initialized, the following call is used to define the light's properties: glLight*(GLenum light, GLenum pname, GLenum param) The preceding call has the f, i, fv, iv variants, so we can send a variety of parameter configurations to it. For example, here is how we would initialize a light's position: GLfloat position = {0, 10, 0, 1}; glLightfv( GL_LIGHT0, GL_POSITION, position); Notice that we are passing not a 3D point, but a 4D homogeneous coordinate in the form x,y,z,w. What this means in practical terms is that, for positional light sources, the parameter w will divide x,y,z, so it is a good idea to leave w=1. We can implement an infinite light source by storing w=0, and then using x,y,z to provide the unit vector for the light's direction. Now, let's add some color attributes to the lamp: GLfloat ambient[]= {0.2, 0.2, 0.3}; glLightfv( GL_LIGHT0, GL_AMBIENT, ambient); GLfloat diffuse[]= {1, 1, 1}; glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse); GLfloat specular[]= {1,1,1}; glLightfv( GL_LIGHT0, GL_SPECULAR, specular); This rather simple example declares a positional, omnidirectional light source in OpenGL. I have also provided information on how to make it a directional light. Thus, it is now time to declare a spotlight. The previous code is still valid. All we have to do is send two additional glLight calls to specify the cone of light properties, as shown here: GLfloat spotdir= {0,-1,0}; glLightfv(LIGHT0, GL_SPOT_DIRECTION, spotdir); In the preceding example, we have defined our spotlight as pointing downwards in the vertical direction. The following line states that we want our spotlight to have an aperture of 25°, so the spotlight is completely defined: glLightfv(LIGHT0, GL_SPOT_CUTOFF, 25); We know where the light emanates from, the direction it is targeting, and the aperture of the cone. A final addition to our lighting system is attenuation, which simulates the way light decreases in intensity as we move away from the emitter. OpenGL provides three attenuation options, which can be activated on a per-light basis. Obviously, a light must be positional for attenuation to work and can be both omnidirectional or a spotlight. The attenuation equation used by OpenGL is as follows: lightoutput=lightinput* ((1-constantfactor) + linearfactor*distance + quadraticfactor*distance^2 We can specify three attenuation factors: one constant, which will be applied regardless of the distance from the lamp to the lit point; one linear with distance, which is applied linearly with distance; and one quadratic, which is multiplied by the distance squared. This approximation might seem complex, but in the real world, light attenuates with distance squared, right? However, units in the real world tend to have huge ranges (the Sun is millions of kilometers away, and a lamp can be one meter away). Computer graphics are hard to get right if we directly implement the real-world equation. Sometimes, you will not want attenuation. Most of the time, attenuation will be simulated with a linear decay factor, and other times (in space flight simulators, for example) a real-world quadratic factor will be used. Thus, it is handy to be able to use the three attenuation factors and even to combine them. As usual, the coding is really straightforward. The following line sets the constant attenuation factor: glLighti(GL_LIGHT0, GL_CONSTANT_ATTENUATION, constantfactor) A value of 1 produces no attenuation, and 0 would simply eliminate all scene lighting. The following code lines set the linear and quadratic attenuation factors, respectively: glLighti(GL_LIGHT0, GL_LINEAR_ATTENUATION, linearfactor); glLighti(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, quadraticfactor); It is good practice to first set the lights so lighting looks right, and only when we are happy with the results, start working on the attenuation factors. Setting a light in one step can be a tricky process. Lights are useless without objects to interact with. Shiny objects behave in a certain way, some others are dull in appearance, and even surface color can determine how objects react to scene lighting. Thus, OpenGL supplies a set of calls that allow you to set surface properties in a way similar to setting lights. To begin with, you must set surface colors. This is achieved with the glMaterial* set of calls. Here, for example, is the definition of a material somewhat similar to red plastic: GLfloat ambient[]= {0.2, 0.1, 0.1, 1}; glMaterialfv( GL_FRONT, GL_AMBIENT, ambient); GLfloat diffuse[]= {1, 0, 0, 1}; glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse); GLfloat specular[]= {1, 0.8, 0, 1}; glMaterialfv( GL_FRONT, GL_SPECULAR, specular); glMaterialf( GL_FRONT, GL_SHININESS, 25); This definition works in harmony with OpenGL's rendering pipeline. Set the material right before sending the object to the pipeline, whichever rendering method you use, and the material will be applied to it. But remember that lighting uses information from the glNormal3f (or GL_NORMAL_ARRAY) to perform its computation. Be sure to specify those calls in your geometry declaration. Parameters are straightforward. For the color calls we specify color values as RGBA sets, and shininess is expressed as an exponent that scales the specular component: the larger the value, the more the surface will respond to specular highlights. If you want to learn more about the illumination process, you can find these equations explained in Chapter 17, "Shading." Now you know how to enable OpenGL's lighting functionality. To complete this overview, I'll add a note on lighting performance. Some programmers tend to believe that hardware lighting comes at no cost on lighting-capable hardware. But performance degrades as you increase the lighting complexity. As an example, notice how the number of lights affects performance in Table B.2. Numbers are taken on an Athlon 1.3GHz PC with a GeForce3 card, running a benchmark application that renders spheres to the screen with a variable number of light sources. In case you are wondering, geometry is delivered to the hardware using display lists, which are explained in the "Display Lists" section later in this chapter.
Moreover, cost also depends on the type of light. Directional light sources are faster to compute than spotlights, for example. Again, take a look at the results in Table B.3. Notice the performance hit when compared to Table B.2.
Notice how, under these conditions, directional light sources are approximately twice as fast to compute than spotlights. Thus, the best advice would be to time your code and understand the platform you are coding for to take advantage of its features. Rationalize the use of hardware lighting, so you can enjoy its benefits and avoid its limitations. |