Upcoming Events


Quick Stats
5820 people currently visiting GDNet.
2236 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!



Link to us

  search:   

Excerpt from OpenGLŪ SuperBible: Comprehensive Tutorial and Reference, 4th Edition.
Chapter 9: Texture Mapping: Beyond the Basics


Multitexture

Modern OpenGL hardware implementations support the capability to apply two or more textures to geometry simultaneously. If an implementation supports more than one texture unit, you can query with GL_MAX_TEXTURE_UNITS to see how many texture units are available:

GLint iUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &iUnits);

Textures are applied from the base texture unit (GL_TEXTURE0), up to the maximum number of texture units in use (GL_TEXTUREn, where n is the number of texture units in use). Each texture unit has its own texture environment that determines how fragments are combined with the previous texture unit. Figure 9.12 shows three textures being applied to geometry, each with its own texture environment.

In addition to its own texture environment, each texture unit has its own texture matrix and set of texture coordinates. Each texture unit has its own texture bound to it with different filter modes and edge clamping parameters. You can even use different texture coordinate generation modes for each texture.

Figure 9.12
Multitexture order of operations.

By default, the first texture unit is the active texture unit. All texture commands, with the exception of glTexCoord, affect the currently active texture unit. You can change the current texture unit by calling glActiveTexture with the texture unit identifier as the argument. For example, to switch to the second texture unit and enable 2D texturing on that unit, you would call the following:

glActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);

To disable texturing on the second texture unit and switch back to the first (base) texture unit, you would make these calls:

glDisable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);

All calls to texture functions such as glTexParameter, glTexEnv, glTexGen, glTexImage, and glBindTexture are bound only to the current texture unit. When geometry is rendered, texture is applied from all enabled texture units using the texture environment and parameters previously specified.

Multiple Texture Coordinates

Occasionally, you might apply all active textures using the same texture coordinates for each texture, but this is rarely the case. When using multiple textures, you can still specify texture coordinates with glTexCoord; however, these texture coordinates are used only for the first texture unit (GL_TEXTURE0). To specify texture coordinates separately for each texture unit, you need one of the new texture coordinate functions:

GlMultiTexCoord1f(GLenum texUnit, GLfloat s);
glMultiTexCoord2f(GLenum texUnit, GLfloat s, GLfloat t);
glMultiTexCoord3f(GLenum texUnit, GLfloat s, GLfloat t, Glfloat r);

The texUnit parameter is GL_TEXTURE0, GL_TEXTURE1, and so on up to the maximum number of supported texturing units. In these functions, you specify the s, t, and r coordinates of a one-, two-, or three-dimensional texture (including cube maps). You can also use texture coordinate generation on one or more texture units.

A Multitextured Example

Listing 9.2 presents some of the code for the sample program MULTITEXTURE. This program is similar to the CUBEMAP program, and only the important changes are listed here. In this example, we place the CUBEMAP texture on the second texture unit, and on the first texture unit we use a "tarnish" looking texture. When the tarnish texture is multiplied by the cube map texture, we get the same reflective surface as before, but now there are fixed darker spots that appear as blemishes on the mirrored surface. Make note of the fact that each texture unit has its own texture matrix. Therefore, we must take care to apply the inverse of the camera matrix, only to the texture unit containing the reflected cube map. Figure 9.13 shows the output from the MULTITEXTURE program.

Figure 9.13
Output from the MULTITEXTURE sample program.

Listing 9.2 Source Code for the MULTITEXTURE Sample Program

#include "../../shared/gltools.h"  // OpenGL toolkit
#include "../../shared/glframe.h"  // Camera class
#include <math.h>

. . .
. . .

// Storage for two texture objects
GLuint   textureObjects[2];
#define CUBE_MAP  0
#define COLOR_MAP  1

. . .
. . .


//////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering
// context. 
void SetupRC()
  {
  GLbyte *pBytes;
  GLint iWidth, iHeight, iComponents;
  GLenum eFormat;
  int i;
    
  // Cull backs of polygons
  glCullFace(GL_BACK);
  glFrontFace(GL_CCW);
  glEnable(GL_CULL_FACE);
  glEnable(GL_DEPTH_TEST);
    
  glGenTextures(2, textureObjects);
    
  // Set up texture maps  
  
  // Cube Map
  glBindTexture(GL_TEXTURE_CUBE_MAP, textureObjects[CUBE_MAP]);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, 
                         GL_LINEAR_MIPMAP_LINEAR);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
 
  // Load Cube Map images
  for(i = 0; i < 6; i++)
    {    
    // Load this texture map
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE);
    pBytes = gltLoadTGA(szCubeFaces[i], &iWidth, &iHeight, 
                         &iComponents, &eFormat);
    glTexImage2D(cube[i], 0, iComponents, iWidth, iHeight, 
                   0, eFormat, GL_UNSIGNED_BYTE, pBytes);
    free(pBytes);
    }
    
  // Color map
  glBindTexture(GL_TEXTURE_2D, textureObjects[COLOR_MAP]);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
                      GL_LINEAR_MIPMAP_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
  glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
  pBytes = gltLoadTGA("tarnish.tga", &iWidth, &iHeight, 
                         &iComponents, &eFormat);
  glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 
                  0, eFormat, GL_UNSIGNED_BYTE, pBytes);
  free(pBytes);
  
  /////////////////////////////////////////////////////////////////////
  // Set up the texture units

  // First texture unit contains the color map
  glActiveTexture(GL_TEXTURE0);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, textureObjects[COLOR_MAP]);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);  // Decal tarnish
  
  // Second texture unit contains the cube map
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_CUBE_MAP, textureObjects[CUBE_MAP]);
  glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
  glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
  glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
  glEnable(GL_TEXTURE_CUBE_MAP);
  
  // Multiply this texture by the one underneath
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  }


///////////////////////////////////////////////////////////
// Draw the skybox. This is just six quads, with texture
// coordinates set to the corners of the cube map
void DrawSkyBox(void)
  {
  GLfloat fExtent = 15.0f;
  
  glBegin(GL_QUADS);
    //////////////////////////////////////////////
    // Negative X
    // Note, we must now use the multitexture version of glTexCoord
    glMultiTexCoord3f(GL_TEXTURE1, -1.0f, -1.0f, 1.0f);
    glVertex3f(-fExtent, -fExtent, fExtent);
    
    glMultiTexCoord3f(GL_TEXTURE1, -1.0f, -1.0f, -1.0f);
    glVertex3f(-fExtent, -fExtent, -fExtent);
    
    glMultiTexCoord3f(GL_TEXTURE1, -1.0f, 1.0f, -1.0f);
    glVertex3f(-fExtent, fExtent, -fExtent);
    
    glMultiTexCoord3f(GL_TEXTURE1, -1.0f, 1.0f, 1.0f);
    glVertex3f(-fExtent, fExtent, fExtent);

   . . .
   . . .

  glEnd();
  }

    
// Called to draw scene
void RenderScene(void)
  {
  // Clear the window
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
  glPushMatrix(); 
    frameCamera.ApplyCameraTransform(); // Move the camera about

    // Sky Box is manually textured
    glActiveTexture(GL_TEXTURE0);
    glDisable(GL_TEXTURE_2D);
    glActiveTexture(GL_TEXTURE1);

    glEnable(GL_TEXTURE_CUBE_MAP);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glDisable(GL_TEXTURE_GEN_R);   
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    DrawSkyBox();
 

    // Use texgen to apply cube map
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_TEXTURE_GEN_R);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    
    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_TEXTURE_2D);

    glPushMatrix();
      glTranslatef(0.0f, 0.0f, -3.0f);  
 
      glActiveTexture(GL_TEXTURE1);
      glMatrixMode(GL_TEXTURE);
      glPushMatrix();
      
      // Invert camera matrix (rotation only) and apply to 
      // texture coordinates
      M3DMatrix44f m, invert;
      frameCamera.GetCameraOrientation(m);
      m3dInvertMatrix44(invert, m);
      glMultMatrixf(invert);
  
      glColor3f(1.0f, 1.0f, 1.0f);
      gltDrawSphere(0.75f, 41, 41);
 
      glPopMatrix();
      glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

  glPopMatrix();
    
  // Do the buffer Swap
  glutSwapBuffers();
  }




Texture Combiners


Contents
  Secondary Color & Anisotropic Filtering
  Texture Compression
  Texture Coordinate Generation
  Multitexture
  Texture Combiners
  Point Sprites

  Printable version
  Discuss this article