JavaScript EditorFree JavaScript Editor     Ajax Editor 



Main Page
Previous Page
Next Page

10.2. Simple Texturing Example

With these built-in functions for accessing textures, it's easy to write a simple shader that does texture mapping. Our goal is to create a texture-mapped sphere by using a realistic texture of the earth's surface. Application code described in this chapter came from ogl2demo, written primarily by Barthold Lichtenbelt. Similar code is available in GLSLdemo, written by Philip Rideout and available for download from the 3Dlabs Web site.

To achieve good results, it helps to start with excellent textures. Color Plate 3 shows an example of a two-dimensional texture map, a cylindrical projection of the earth's surface, including clouds. This image, and other images in this section, were obtained from the NASA Web site and were created by Reto Stöckli of the NASA/Goddard Space Flight Center. These images of Earth are part of a series of consistently processed data sets (land, sea ice, and clouds) from NASA's remote sensing satellite, the Moderate Resolution Imaging Spectroradiometer, or MODIS. Data from this satellite was combined with other related data sets (topography, land cover, and city lights), all at 1 kilometer resolution. The resulting series of images is extremely high resolution (43200 x 21600 pixels). For our purposes, we can get by with a much smaller texture, so we use versions that have been sampled down to 2048 x 1024.

10.2.1. Application Setup

We assume that the image data has been read into our application and that the image width, image height, a pointer to the image data, and a texture name generated by OpenGL can be passed to our texture initialization function:

init2DTexture(GLint texName, GLint texWidth,
              GLint texHeight, GLubyte *texPtr)
{
    glBindTexture(GL_TEXTURE_2D, texName);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texWidth, texHeight, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, texPtr);
}

This initialization function creates a texture object named texName. Calls to glTexParameter set the wrapping behavior and filtering modes. We've chosen to use repeat as our wrapping behavior and to do linear filtering. We specify the data for the texture by calling glTexImage2D (the values passed to this function depend on how the image data has been stored in memory).

When we're ready to use this texture, we can use the following OpenGL calls:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, earthTexName);

This sequence of calls sets the active texture unit to texture unit 0, binds our earth texture to this texture unit, and makes it active. We need to provide the values for two uniform variables. The vertex shader needs to know the light position, and the fragment shader needs to know the texture unit that is to be accessed. We define the light position as a vec3 in the vertex shader named lightPosition and the texture unit as a sampler2D in the fragment shader named EarthTexture. Our application code needs to determine the location of these uniform variables and then provide appropriate values. We assume that our shaders have been compiled, linked using a program object whose handle is programObj, and installed as part of current state. We can make the following calls to initialize the uniform variables:

lightLoc = glGetUniformLocation(programObj, "LightPosition");
glUniform3f(lightLoc, 0.0, 0.0, 4.0);
texLoc   = glGetUniformLocation(programObj, "EarthTexture");
glUniform1i(texLoc, 0);

The light source position is set to a point right in front of the object along the viewing axis. We plan to use texture unit 0 for our earth texture, so that is the value loaded into our sampler variable.

The application can now make appropriate OpenGL calls to draw a sphere, and the earth texture will be applied. A surface normal, a 2D texture coordinate, and a vertex position must be specified for each vertex.

10.2.2. Vertex Shader

The vertex shader for our simple texturing example is similar to the simple brick vertex shader described in Section 6.2. The main difference is that a texture coordinate is passed in as a vertex attribute, and it is passed on as a varying variable with the built-in variable name gl_TexCoord[0] (see Listing 10.1).

Listing 10.1. Vertex shader for simple texturing

varying float LightIntensity;
uniform vec3 LightPosition;

const float specularContribution = 0.1;
const float diffuseContribution  = 1.0 - specularContribution;

void main()
{
    vec3 ecPosition = vec3(gl_ModelViewMatrix * gl_Vertex);
    vec3 tnorm      = normalize(gl_NormalMatrix * gl_Normal);
    vec3 lightVec   = normalize(LightPosition - ecPosition);
    vec3 reflectVec = reflect(-lightVec, tnorm);
    vec3 viewVec    = normalize(-ecPosition);

    float spec      = clamp(dot(reflectVec, viewVec), 0.0, 1.0);
    spec            = pow(spec, 16.0);

    LightIntensity  = diffuseContribution * max(dot(lightVec, tnorm), 0.0)
                       + specularContribution * spec;

    gl_TexCoord[0]  = gl_MultiTexCoord0;
    gl_Position     = ftransform();
}

10.2.3. Fragment Shader

The fragment shader shown in Listing 10.2 applies the earth texture to the incoming geometry. So, for instance, if we define a sphere where the s texture coordinates are related to longitude (e.g., 0° longitude is s = 0, and 360° longitude is s = 1.0) and t texture coordinates are related to latitude (90° south latitude is t = 0.0, and 90° north latitude is t = 1.0), then we can apply the texture map to the sphere's geometry as shown in Color Plate 6.

In the following fragment shader, the incoming s and t texture coordinate values (part of the built-in varying variable gl_TexCoord0) are used to look up a value from the texture currently bound to texture unit 0. The resulting value is multiplied by the light intensity computed by the vertex shader and passed as a varying variable. The color is then clamped, and an alpha value of 1.0 is added to create the final fragment color, which is sent on for further processing, including depth testing. The resulting image as mapped onto a sphere is shown in Color Plate 6.

Listing 10.2. Fragment shader for simple texture mapping example

varying float LightIntensity;
uniform sampler2D EarthTexture;

void main()
{
    vec3 lightColor = vec3(texture2D(EarthTexture, gl_TexCoord[0].st));
    gl_FragColor    = vec4(lightColor * LightIntensity, 1.0);
}


Previous Page
Next Page



R7
JavaScript EditorAjax Editor     JavaScript Editor