9.3. Material Properties and Lighting
OpenGL lighting calculations require knowing the viewing direction in the eye coordinate system in order to compute specular reflection terms. By default, the view direction is assumed to be parallel to and in the direction of the z axis. OpenGL also has a mode that requires the viewing direction to be computed from the origin of the eye coordinate system (local viewer). To compute this, we can transform the incoming vertex into eye space by using the current modelview matrix. The x, y, and z coordinates of this point are divided by the homogeneous coordinate w to get a vec3 value that can be used directly in the lighting calculations. The computation of this eye coordinate position (ecPosition3) was illustrated in Section 9.1. To get a unit vector corresponding to the viewing direction, we normalize and negate the eye space position. Shader code to implement these computations is shown in Listing 9.9.
Listing 9.9. Local viewer computation
With the viewing direction calculated, we can initialize the variables that accumulate the ambient, diffuse, and specular lighting contributions from all the light sources in the scene. We can then call the functions defined in the previous section to compute the contributions from each light source. In the code in Listing 9.10, we assume that all lights with an index less than the constant NumEnabled Lights are enabled. Directional lights are distinguished by having a position parameter with a homogeneous (w) coordinate equal to 0 at the time they were provided to OpenGL. (These positions are transformed by the modelview matrix when the light is specified, so the w coordinate remains 0 after transformation if the last column of the modelview matrix is the typical (0 0 0 1)). Point lights are distinguished by having a spotlight cutoff angle equal to 180.
Listing 9.10. Loop to compute contributions from all enabled light sources
One of the changes made to OpenGL in version 1.2 was to add functionality to compute the color at a vertex in two parts: a primary color that contains the combination of the emissive, ambient, and diffuse terms as computed by the usual lighting equations; and a secondary color that contains just the specular term as computed by the usual lighting equations. If this mode is not enabled (the default case), the primary color is computed with the combination of emissive, ambient, diffuse, and specular terms.
Computing the specular contribution separately allows specular highlights to be applied after texturing has occurred. The specular value is added to the computed color after texturing has occurred, to allow the specular highlights to be the color of the light source rather than the color of the surface. Listing 9.11 shows how to compute the surface color (according to OpenGL rules) with everything but the specular contribution:
Listing 9.11. Surface color computation, omitting the specular contribution
The OpenGL Shading Language conveniently provides us a built-in variable (gl_FrontLightModelProduct.sceneColor) that contains the emissive material property for front facing surfaces plus the product of the ambient material property for front-facing surfaces and the global ambient light for the scene (i.e., gl_FrontMaterial.emission + gl_FrontMaterial.ambient * gl_LightModel.ambient). We can add this together with the intensity of reflected ambient light and the intensity of reflected diffuse light. Next, we can do the appropriate computations, depending on whether the separate specular color mode is indicated, as shown in Listing 9.12.
Listing 9.12. Final surface color computation
There is no need to perform clamping on the values assigned to gl_Front-SecondaryColor and gl_FrontColor because these are automatically clamped by definition.