A Texture Mapping Technique Using OpenGL

Environment: VC++6, Win32

Overview

Rendering an image on the surface of an object can be accomplished with just a few steps by using OpenGL. My last article on OpenGL demonstrated a simple method for drawing OpenGL output directly to a device independent bitmap (DIB), and for loading OpenGL commands into a call list to make rendering more efficient. This article builds on that framework and demonstrates how to overlay an image on the surface of an object. In this example, the object surfaces are spheres and the textures are images representing some of the planets in our solar system.

The following steps can be used to texture map objects:

  1. Load each texture into a 24-bit DIBSection object. (A regular DIB will do, but making it a DIB section doesn't hurt.)
  2. Tell the OpenGL library how your image is laid out by calling glTexImage2D. You can specify the image as RGB, or BGR, and so forth. Note: the width and height of the image can be different, but the dimensions have to each be powers of 2 plus any border width you specify in the call to glTexImage2D.
  3. As you build your model by specifying vertices, use the glTexCoord2d to specify the location within the texture image corresponding to each model vertex. Note: the image coordinates are scaled from 0 to 1; so, if your image is 100x100 pixels wide and you want to connect a vertex with the center pixel, you call glTexCoord2d(0.50,0.50).

Thats it! You're done.

In case you want to take texture mapping a little farther, there are two other things worth investigating:

The function glTexImage2D provides support for mip-mapping. Mip-mapping is basically when you use multiple images of the same thing at different resolutions. Let's say you want to map an image of the Earth onto a sphere. Using a smaller texture image will improve performance without ruining the model as long as the sphere is small. When the sphere is large (takes up a significant portion of the screen), you might want a higher-resolution image to avoid stretching artifacts: mip-mapping allows you to do that.

Another variation on texture mapping can be accomplished by using the glTexEnvf function. In the sample project, I used the following call: glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);. However, you may replace GL_DECAL with GL_BLEND or GL_MODULATE to get a different texturing effect.

The following code sample shows how to actually connect image coordinates with your model's vertices:

namespace GEO {
  const double PI = 3.14159265359;
  const double TWOPI = 6.28318530718;
  const double DE2RA = 0.01745329252;
  const double RA2DE = 57.2957795129;
  const double FLATTENING = 1.0/298.26;
  const double PIOVER2 = 1.570796326795;
}

if (m_have_texture)
{
  glTexImage2D(GL_TEXTURE_2D,0,3,m_texture_dib.Width(),
                                 m_texture_dib.Height(),
    0,GL_BGR_EXT,GL_UNSIGNED_BYTE,(GLvoid *)
                                 m_texture_dib.GetBits());

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                  GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                  GL_NEAREST);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

  //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
  //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

  glEnable( GL_TEXTURE_2D );
}
else
{
  glDisable( GL_TEXTURE_2D );
}

glBegin( GL_TRIANGLES );

start_lat = -90;
start_lon = 0.0;
R = 1.0;

lat_incr = 180.0 / NumLatitudes;
lon_incr = 360.0 / NumLongitudes;

int row, col;

for (col = 0; col < NumLongitudes; col++){
  phi1 = (start_lon + col * lon_incr) * GEO::DE2RA;
  phi2 = (start_lon + (col + 1) * lon_incr) * GEO::DE2RA;
  for (row = 0; row < NumLatitudes; row++){
    theta1 = (start_lat + row * lat_incr) * GEO::DE2RA;
    theta2 = (start_lat + (row + 1) * lat_incr) * GEO::DE2RA;

    u[0] = R * cos(phi1) * cos(theta1);    //x
    u[1] = R * sin(theta1);                //y
    u[2] = R * sin(phi1) * cos(theta1);    //z

    v[0] = R * cos(phi1) * cos(theta2);    //x
    v[1] = R * sin(theta2);                //y
    v[2] = R * sin(phi1) * cos(theta2);    //z

    w[0] = R * cos(phi2) * cos(theta2);    //x
    w[1] = R * sin(theta2);                //y
    w[2] = R * sin(phi2) * cos(theta2);    //z

    NormalVector(u,v,w,n);
    glNormal3dv(n);
    glTexCoord2d((180.0 - phi1*GEO::RA2DE)/360.0,
                 (theta1 + GEO::PIOVER2)*GEO::RA2DE/180.0);
    glVertex3dv(u);
    glTexCoord2d((180.0 - phi1*GEO::RA2DE)/360.0,
                 (theta2 + GEO::PIOVER2)*GEO::RA2DE/180.0);
    glVertex3dv(v);
    glTexCoord2d((180.0 - phi2*GEO::RA2DE)/360.0,
                 (theta2 + GEO::PIOVER2)*GEO::RA2DE/180.0);
    glVertex3dv(w);

    v[0] = R * cos(phi2) * cos(theta1);    //x
    v[1] = R * sin(theta1);                //y
    v[2] = R * sin(phi2) * cos(theta1);    //z

    NormalVector(u,w,v,n);
    glNormal3dv(n);
    glTexCoord2d((180.0 - phi1*GEO::RA2DE)/360.0,
                 (theta1 + GEO::PIOVER2)*GEO::RA2DE/180.0);
    glVertex3dv(u);
    glTexCoord2d((180.0 - phi2*GEO::RA2DE)/360.0,
                 (theta2 + GEO::PIOVER2)*GEO::RA2DE/180.0);
    glVertex3dv(w);
    glTexCoord2d((180.0 - phi2*GEO::RA2DE)/360.0,
                 (theta1 + GEO::PIOVER2)*GEO::RA2DE/180.0);
    glVertex3dv(v);

    glDisable( GL_TEXTURE_2D );
  }
}

glEnd();

Sample Project

I only have textures for the Earth, Mars, Jupiter, and Saturn, so I just picked a color for the other planets. If time permitted, it would be nice to see a ring around Saturn.

Enjoy.

Downloads

Download demo project - 405 Kb



About the Author

Andy McGovern

Andy McGovern - Software Developer/Engineer. Special interests: astronomy, image processing, orbital mechanics, mathematics. Currently work at the Johns Hopkins University Applied Physics Laboratory on the science operations team for the CRISM instrument on the Mars Reconnaissance Orbiter.