10.3. Multitexturing Example
The resulting image looks pretty good, but with a little more effort we can get it looking even better. For one thing, we know that there are lots of manmade lights on our planet, so when it's nighttime, major towns and cities can be seen as specks of light, even from space. So we use the angle between the light direction and the normal at each surface location to determine whether that location is in "daytime" or "nighttime." For points that are in daytime, we access the texture map that contains daylight colors and do an appropriate lighting calculation. For points in the nighttime region of the planet, we do no lighting and access a texture that shows the earth as illuminated at night. The daytime and nighttime textures are shown in Color Plate 4 and Color Plate 5.
Another somewhat unrealistic aspect to our simple approach is the reflection of sunlight off the surface of oceans and large lakes. Water is a very good reflective surface, and when our viewpoint is nearly the same as the reflection angle for the light source, we should see a specular reflection. But we know that desert, grass, and trees don't have this same kind of reflective property, so how can we get a nice specular highlight on the water but not on the land?
The answer is a technique called a GLOSS MAP. We make a single channel (i.e., grayscale) version of our original texture and assign values of 1.0 for areas that represent water and 0 for everything else. At each fragment, we read this gloss texture and multiply its value by the specular illumination portion of our lighting equation. It's fairly simple to create the gloss map in an image editing program. The easiest way to do this is by editing the red channel of the cloudless daytime image. In this channel, all the water areas appear black or nearly black because they contain very little red information. We use a selection tool to select all the black (water) areas and then fill the selected area (water regions) with white. We invert the selection and fill the land areas with black. The result is a texture that contains a value of 1.0 (white) for areas in which we want a specular highlight, and a value of 0 (black) for areas in which we don't. We use this "gloss" value as a multiplier in our specular reflection calculation, so areas that represent water include a specular reflection term, and areas that represent land don't. Our gloss map is shown in Figure 10.1.
Figure 10.1. Gloss map used to create specular reflections from water surfaces
As you saw in Color Plate 4 and Color Plate 5, our daytime and nighttime textures no longer include cloud cover. So we store our cloud texture as a single channel (i.e., grayscale) texture as shown in Figure 10.2. By doing this, we have more flexibility about how we combine the cloud image with the images of the Earth's surface. For daytime views, we want the clouds to have diffuse reflection but no specular reflection. Furthermore, clouds obscure the surface, so a value of 1.0 for the cloud cover indicates that the earth's surface at that location is completely obscured by clouds. For nighttime views, we don't want any light reflecting from the clouds, but we do want them to obscure the surface below. For convenience, we've stored our single channel cloud image into the red channel of an RGB texture, and we've stored our gloss map as the green channel. The blue channel is unused. (Another choice would be to store the gloss map as the alpha channel for our daytime image and the cloud texture as the alpha channel in our nighttime image.)
Figure 10.2. Texture map showing cloud cover (Blue Marble image by Reto Stöckli, NASA Goddard Space Flight Center)
10.3.1. Application Setup
The setup required for multitexturing is about the same as it was for the simple texturing example, except that we need to set up three textures instead of one. We can call the init2Dtexture function described in Section 10.2.1 three times, once each for the daytime earth texture, the nighttime earth texture, and the cloud/gloss texture. We can activate these textures with the following OpenGL calls:
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, earthDayTexName); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, earthNightTexName); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, earthCloudsTexName);
The necessary uniform variables can be initialized as follows:
lightLoc = glGetUniformLocation(programObj, "LightPosition"); glUniform3f(lightLoc, 0.0, 0.0, 4.0); texLoc = glGetUniformLocation(programObj, "EarthDay"); glUniform1i(texLoc, 0); texLoc = glGetUniformLocation(programObj, "EarthNight"); glUniform1i(texLoc, 1); texLoc = glGetUniformLocation(programObj, "EarthCloudGloss"); glUniform1i(texLoc, 2);
The application can now make appropriate OpenGL calls to draw a sphere. A surface normal, a 2D texture coordinate, and a vertex position must be specified for each vertex.
10.3.2. Vertex Shader
The vertex shader for this multitexturing example is similar to the one described for the simple texturing example in Section 10.2.2, except that the diffuse and specular factors are computed by the vertex shader and passed as separate varying variables to the fragment shader. The computed specular value is multiplied by the constant vector (1.0, 0.941, 0.898) to approximate the color of sunlight (see Listing 10.3).
Listing 10.3. Vertex shader for multitexturing
10.3.3. Fragment Shader
The fragment shader that performs the desired multitexturing is shown in Listing 10.4. The application has loaded the daytime texture in the texture unit specified by EarthDay, the nighttime texture into the texture unit specified by EarthNight, and the cloud/gloss texture into the texture unit specified by EarthCloudGloss. The lighting computation is done in a vertex shader that computes diffuse and specular reflection factors and passes them to the fragment shader independently. The texture coordinates supplied by the application are also passed to the fragment shader and form the basis of our texture lookup operation.
In our fragment shader, the first thing we do is access our cloud/gloss texture because its values will be used in the computations that follow. Next, we look up the value from our daytime texture, multiply it by our diffuse lighting factor, and add to it the specular lighting factor multiplied by the gloss value. If the fragment is unobscured by clouds, our computation gives us the desired effect of diffuse lighting over the whole surface of the Earth with specular highlights from water surfaces. This value is multiplied by 1.0 minus the cloudiness factor. Finally, we add the cloud effect by multiplying our cloudiness factor by the diffuse lighting value and adding this to our previous result.
The nighttime calculation is simpler. Here, we just look up the value from our nighttime texture and multiply that result by 1.0 minus the cloudiness factor. Because this fragment will be in shadow, the diffuse and specular components are not used.
With these values computed, we can determine the value to be used for each fragment. The key is our diffuse lighting factor, which is greater than zero for areas in sunlight, equal to zero for areas in shadow, and near zero for areas near the terminator. The color value ends up being the computed daytime value in the sunlit areas, the computed nighttime value in the areas in shadow, and a mix of the two values to make a gradual transition near the terminator.
An alpha value of 1.0 is added to produce our final fragment color. Several views from the final shader are shown in Color Plate 7. You can see the nice specular highlight off the Gulf of Mexico in the first image. If you look closely at the third (nighttime) image, you can see the clouds obscuring the central part of the east coast of the United States and the northwestern part of Brazil.
It is worth pointing out that this shader should not be considered a general-purpose shader because it has some built-in assumptions about the type of geometry that will be drawn. It will only look "right" when used with a sphere with proper texture coordinates. More can be done to make the shader even more realistic. The color of the atmosphere actually varies, depending on the viewing position and the position of the sun. It is redder when near the shadow boundary, a fact that we often notice near sunrise and sunset. See the references at the end of the chapter for more information about achieving realistic effects such as Rayleigh scattering.
Listing 10.4. "As the world turns" fragment shader