How to use the glMultiTexCoord2fARB function in OpenGL

Back to OpenGL Tutorial Index

Welcome to third in my terrain tutorials. This has come a little later than expected
simply because of school. In this tutorial I will be adding detail texture, where I
first map a texture, stretched across the whole terrain, then I map another texture
using the glMultiTexCoord2fARB extension to each individual triangle strip. This will
allow for a terrain which can have its colours all drawn within a RAW file and then
mapped onto the terrain, instead of assigning different textures for different heights.
So where you would have the visible blocks between 2 textures, you now have seamless
blending without all the fussy coding.

I suppose you could call this a bit of a step backwards, as I have removed all of the
lighting. I found that the lighting worked will for a static world, but when the
world was rotated through the camera, my lighting was continuously changing.

In this tutorial I have also fixed the terrain, so that it no longer appears as a
rectangle. It takes the shape of the raw file, which in this case, is a square.

--------------------------------------------------------------------------------------
Section 1: Variables

I have added a few variables to this tutorial. Most of them control the camera which I
have integrated from my camera tutorials with a few modifications.

So in detail, here are the new variables.

This variable controls how detailed our terrain is. The lower the number, the more
triangle strips are drawn, and the more detailed the ground will look.
int LOD = 10;

I have added another scaling variable, this will control the height and is seperate
to the variable which will scale the length and width of the terrain.
float scaleheight = 5;

This simply controls the speed of the camera movement. The lower the number, the slower
the speed.
float camerascale = 0.25;

These next few variables, control our camera system.
double xpos = 0;
double ypos = 0;
double zpos = 0;
double xrot = 0;
double yrot = 150;
double zrot = 0;
double lastx;
double lasty;

This when added to the camera ypos, will allow for a neat little bobbing effect.
double bounce;

This will control the scaling of our detail texture. The higher the number, the smaller
the texture will appear, but it will also fill up the object with more of them. So
lowering the number will make the texture larger. >0 will make it smaller, <0 will make
it bigger, eg: 0.9, 0.5 will make it larger.
float terrainDetailLevel = 2;

These will just let us know which texture is assigned to which number.
int tground = 0, tdetail = 1;

Now because this uses an OpenGL extension. This next portion of the code is
EXTREMELY IMPORTANT!! Without these, our effect is impossible. These allow for us
to set more than one texture coordinate per vertex. It can also be used for bump
mapping later on with a few extra calls.
-----------------------
These are the parameters for our multi texturing.
#define GL_TEXTURE0_ARB 0x84C0 the first texture
#define GL_TEXTURE1_ARB 0x84C1 the second texture

#define GL_COMBINE_ARB 0x8570 this gives us the ability to combine our textures (through r,g,b)
#define GL_RGB_SCALE_ARB 0x8573 this lets us scale our texture colours

Here we tell our program that the following functions exist, these are needed for the whole multi texturing experience
typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t);
typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum target);

Now we link the above functions to our calls.
extern PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL;
extern PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;

--------------------------------------------------------------------------------
Section 2: Loading the Heightmap

I have made the following adjustment to our LoadHeightmap function:
for (mapX = 1; mapX < (Width)-1; mapX ++){
for (mapZ = 1; mapZ < (Height)-1; mapZ++){

I found that this is slightly more effective decreasing the risk of errors around
the edge of terrain, where a height would be undefined and would show a wall of
some sort.

-------------------------------------------------------------------------------
Section 3: Displaying the Heightmap

This is where most of our changes will take place as we now need to add the
new texture coordinates and attach our multi texturing.
void DisplayHeightMap (void) {
float Height;

glPushMatrix();

glActiveTextureARB(GL_TEXTURE0_ARB); //set our current texture to texture0
glEnable(GL_TEXTURE_2D); //enable texturing

glBindTexture(GL_TEXTURE_2D, texture[tground]); //bind our ground texture

glMatrixMode(GL_TEXTURE); go to our texture matrix
glLoadIdentity(); //clear our texture matrix

//this rotation will correct our texture so that it matches up with our heightmap
glRotatef(180,1,0,0); //rotate our texture on the x axis 180 degrees
glRotatef(180,0,0,1); //rotate our texture on the z axis 180 degrees
glRotatef(90,0,0,1); //rotate our texture on the y axis 90 degrees

glMatrixMode(GL_MODELVIEW); //switch back to our model matrix

glActiveTextureARB(GL_TEXTURE1_ARB); //activate our next texture, texture1
glEnable(GL_TEXTURE_2D); //enable texturing again

glBindTexture(GL_TEXTURE_2D, texture[tdetail]); //bind our detail texture

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB); //set the texture mode to combine the two
glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2); //scale the color of this texture by 2

glMatrixMode(GL_TEXTURE); //switch to our texture matrix again
glLoadIdentity(); //clear the matrix

glScalef((float)terrainDetailLevel, (float)terrainDetailLevel, 1); //scale the size of the texture

glMatrixMode(GL_MODELVIEW); //switch back to our model matrix

for (mapX = 1; mapX < MapWidth; mapX +=LOD){
for (mapZ = 1; mapZ < MapHeight; mapZ +=LOD){

glBegin(GL_TRIANGLE_STRIP);
Height = HeightMap[mapX][mapZ];
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, (float)mapX/(float)MapWidth, ((float)mapZ)/(float)MapHeight); //attach our ground texture, stretching it across the heightmap
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0); //attach our detail texture, setting it to the current triangle strip
scaling the mapX point by 3, I am able to get my terrain back to a square as it should.
glVertex3f(mapX*scale*3,Height*scaleheight,mapZ*scale); //here i am now changing the x scale by multilying it by 3

Height = HeightMap[mapX][mapZ+LOD];
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX)/(float)MapWidth, ((float)mapZ+LOD)/(float)MapHeight);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
glVertex3f(mapX*scale*3,Height*scaleheight,(mapZ+LOD)*scale);

Height = HeightMap[mapX+LOD][mapZ];
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX+LOD)/(float)MapWidth, (float)mapZ/(float)MapHeight);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
glVertex3f((mapX+LOD)*scale*3,Height*scaleheight,mapZ*scale);

Height = HeightMap[mapX+LOD][mapZ+LOD];
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX+LOD)/(float)MapWidth, ((float)mapZ+LOD)/(float)MapHeight);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
glVertex3f((mapX+LOD)*scale*3,Height*scaleheight,(mapZ+LOD)*scale);
glEnd();

}
}

Turn the second multitexture pass off
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);

Turn the first multitexture pass off
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_2D);

glPopMatrix();
}

----------------------------------------------------------------------------
Section 4: Initialization

void init (void) {
Here we initialize our multitexturing functions
glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress("glActiveTextureARB");
glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC) wglGetProcAddress("glMultiTexCoord2fARB");


Next we load our two textures
texture[tground] = LoadTextureRAW("ground.raw", 512, 512);
texture[tdetail] = LoadTextureRAW("detail.raw", 256, 256);

And finally we load our heightmap
LoadHeightMap ("height.raw", 128, 128);
}

-------------------------------------------------------------------------------

And there we have it. We can now texture our terrain using the glMultiTexCoord2fARB
extension to make our terrain look, in my opinion, much better.

If you have any questions, please email me at swiftless@gmail.com

#include <GL/gl.h>
#include <GL/glut.h>
#include <windows.h>
#include <stdio.h>
#include <math.h>
#include <iostream.h>

int mapX;
int mapY;
int mapZ;

const MapWidth = 128;
const MapHeight = 384;

int LOD = 10;

float scale = 5;
float scaleheight = 5;

float camerascale = 0.25;

double xpos = 0;
double ypos = 0;
double zpos = 0;
double xrot = 0;
double yrot = 150;
double zrot = 0;
double lastx;
double lasty;

double bounce;

float terrainDetailLevel = 1.5;

int tground = 0, tdetail = 1;
GLuint texture[10];

BYTE HeightMap[MapWidth][MapHeight];

//-----------------------
// These are the parameters for our multi texturing.
#define GL_TEXTURE0_ARB 0x84C0 // the first texture
#define GL_TEXTURE1_ARB 0x84C1 // the second texture

#define GL_COMBINE_ARB 0x8570 // this gives us the ability to combine our textures (through r,g,b)
#define GL_RGB_SCALE_ARB 0x8573 // this lets us scale our texture colours

// Here we tell our program that the following functions exist, these are needed for the whole multi texturing experience
typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t);
typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum target);

// Now we link the above functions to our calls.
extern PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL;
extern PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;
//----------------------

GLuint LoadTexture( const char * filename, int width, int height )
{
    GLuint texture;
    unsigned char * data;
    FILE * file;

    //The following code will read in our RAW file
    file = fopen( filename, "rb" );
    if ( file == NULL ) return 0;
    data = (unsigned char *)malloc( width * height * 3 );
    fread( data, width * height * 3, 1, file );
    fclose( file );

    glGenTextures( 1, &texture ); //generate the texture with the loaded data
    glBindTexture( GL_TEXTURE_2D, texture ); //bind the texture to it's array
    glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); //set texture environment parameters

    //here we are setting what textures to use and when. The MIN filter is which quality to show
    //when the texture is near the view, and the MAG filter is which quality to show when the texture
    //is far from the view.

    //The qualities are (in order from worst to best)
    //GL_NEAREST
    //GL_LINEAR
    //GL_LINEAR_MIPMAP_NEAREST
    //GL_LINEAR_MIPMAP_LINEAR

    //And if you go and use extensions, you can use Anisotropic filtering textures which are of an
    //even better quality, but this will do for now.
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR );

    //Here we are setting the parameter to repeat the texture instead of clamping the texture
    //to the edge of our shape.
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );

    //Generate the texture with mipmaps
    gluBuild2DMipmaps( GL_TEXTURE_2D, 3, width, height, GL_RGB, GL_UNSIGNED_BYTE, data );
    free( data ); //free the texture
    return texture; //return whether it was successfull
}

void FreeTexture( GLuint texture )
{
  glDeleteTextures( 1, &texture );
}

void LoadHeightMap (char* Filename, int Width, int Height) {
int smoothen;

FILE * File;

File = fopen( Filename, "rb" );

fread( HeightMap, 1, Width * Height * 3, File );

fclose( File );

for (smoothen = 1; smoothen < 1; smoothen++){
for (mapX = 1; mapX < (Width)-1; mapX ++){
for (mapZ = 1; mapZ < (Height)-1; mapZ++){
mapY = HeightMap[mapX][mapZ];
mapY += HeightMap[mapX-1][mapZ];
mapY += HeightMap[mapX+1][mapZ];
mapY += HeightMap[mapX][mapZ-1];
mapY += HeightMap[mapX][mapZ+1];
mapY += HeightMap[mapX-1][mapZ-1];
mapY += HeightMap[mapX-1][mapZ+1];
mapY += HeightMap[mapX+1][mapZ-1];
mapY += HeightMap[mapX+1][mapZ+1];
mapY = mapY/9;
HeightMap[mapX][mapZ] = mapY ;
}
}
}
}

void DisplayHeightMap (void) {
float Height;

glPushMatrix();

glActiveTextureARB(GL_TEXTURE0_ARB); //set our current texture to texture0
glEnable(GL_TEXTURE_2D); //enable texturing

glBindTexture(GL_TEXTURE_2D, texture[tground]); //bind our ground texture

glMatrixMode(GL_TEXTURE); // go to our texture matrix
glLoadIdentity(); //clear our texture matrix

//this rotation will correct our texture so that it matches up with our heightmap
glRotatef(180,1,0,0); //rotate our texture on the x axis 180 degrees
glRotatef(180,0,0,1); //rotate our texture on the z axis 180 degrees
glRotatef(90,0,0,1); //rotate our texture on the y axis 90 degrees

glMatrixMode(GL_MODELVIEW); //switch back to our model matrix

glActiveTextureARB(GL_TEXTURE1_ARB); //activate our next texture, texture1
glEnable(GL_TEXTURE_2D); //enable texturing again

glBindTexture(GL_TEXTURE_2D, texture[tdetail]); //bind our detail texture

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB); //set the texture mode to combine the two
glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2); //scale the color of this texture by 2

glMatrixMode(GL_TEXTURE); //switch to our texture matrix again
glLoadIdentity(); //clear the matrix

glScalef((float)terrainDetailLevel, (float)terrainDetailLevel, 1); //scale the size of the texture

glMatrixMode(GL_MODELVIEW); //switch back to our model matrix

for (mapX = 1; mapX < MapWidth; mapX +=LOD){
for (mapZ = 1; mapZ < MapHeight; mapZ +=LOD){

    glBegin(GL_TRIANGLE_STRIP);
    Height = HeightMap[mapX][mapZ];
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, (float)mapX/(float)MapWidth, ((float)mapZ)/(float)MapHeight); //attach our ground texture, stretching it across the heightmap
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0); //attach our detail texture, setting it to the current triangle strip
    glVertex3f(mapX*scale*3,Height*scaleheight,mapZ*scale);

    Height = HeightMap[mapX][mapZ+LOD];
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX)/(float)MapWidth, ((float)mapZ+LOD)/(float)MapHeight);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
    glVertex3f(mapX*scale*3,Height*scaleheight,(mapZ+LOD)*scale);

    Height = HeightMap[mapX+LOD][mapZ];
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX+LOD)/(float)MapWidth, (float)mapZ/(float)MapHeight);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
    glVertex3f((mapX+LOD)*scale*3,Height*scaleheight,mapZ*scale);

    Height = HeightMap[mapX+LOD][mapZ+LOD];
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ((float)mapX+LOD)/(float)MapWidth, ((float)mapZ+LOD)/(float)MapHeight);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
    glVertex3f((mapX+LOD)*scale*3,Height*scaleheight,(mapZ+LOD)*scale);
    glEnd();

}
}

// Turn the second multitexture pass off
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);

// Turn the first multitexture pass off
glActiveTextureARB(GL_TEXTURE0_ARB);        
glDisable(GL_TEXTURE_2D);

glPopMatrix();
}

void enable (void) {
    glEnable(GL_DEPTH_TEST); //enable the depth testing
    glDepthFunc(GL_LEQUAL); //set the depth function
    glFrontFace(GL_CCW); //set which face is facing forward
    glCullFace(GL_BACK); //set the face to be culled
    glEnable(GL_CULL_FACE);    //enable culling to speed up the processing time
    glEnable(GL_TEXTURE_2D); //enable texturing
}

void camera (void) {
    int posX = xpos;
    int posZ = zpos;

    ypos = HeightMap[posX][posZ];
    ypos += sin((3.141592654*bounce) * 2) / 2;
    ypos += 10;
    glRotatef(xrot,1.0,0.0,0.0);  //rotate our camera on teh x-axis (left and right)
    glRotatef(yrot,0.0,1.0,0.0);  //rotate our camera on the y-axis (up and down)
    glTranslated(-xpos *scale *3,-ypos*scaleheight,-zpos *scale); //translate the screen to the position of our camera
}

void display (void) {
    glClearColor (0.0,0.0,0.0,1.0);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    enable();
    camera();
    DisplayHeightMap(); //display our heightmap
    glutSwapBuffers();
}

void init (void) {
    // Here we initialize our multitexturing functions
    glActiveTextureARB        = (PFNGLACTIVETEXTUREARBPROC)        wglGetProcAddress("glActiveTextureARB");
    glMultiTexCoord2fARB    = (PFNGLMULTITEXCOORD2FARBPROC)        wglGetProcAddress("glMultiTexCoord2fARB");
    
    texture[tground] = LoadTexture("ground.raw", 512, 512);
    texture[tdetail] = LoadTexture("detail.raw", 256, 256);

    LoadHeightMap ("height.raw", 128, 128);
}

void reshape (int w, int h) {
    glViewport (0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective (60, (GLfloat)w / (GLfloat)h, 0.1, 10000.0);
    glMatrixMode (GL_MODELVIEW);
}

void keyboard (unsigned char key, int x, int y) {
    if (key=='r')
    {
    xrot += 1;
    if (xrot >360) xrot -= 360;
    }

    if (key=='f')
    {
    xrot -= 1;
    if (xrot < -360) xrot += 360;
    }

    if (key=='w')
    {
    float xrotrad, yrotrad;
    yrotrad = (yrot / 180 * 3.141592654f);
    xrotrad = (xrot / 180 * 3.141592654f);
    xpos += float(sin(yrotrad)) * camerascale;
    zpos -= float(cos(yrotrad)) * camerascale * 3;
    ypos -= float(sin(xrotrad)) ;
    bounce += 0.04;
    }

    if (key=='s')
    {
    float xrotrad, yrotrad;
    yrotrad = (yrot / 180 * 3.141592654f);
    xrotrad = (xrot / 180 * 3.141592654f);
    xpos -= float(sin(yrotrad)) * camerascale;
    zpos += float(cos(yrotrad)) * camerascale * 3;
    ypos += float(sin(xrotrad));
    bounce += 0.04;
    }

    if (key=='d')
    {
    float yrotrad;
    yrotrad = (yrot / 180 * 3.141592654f);
    xpos += float(cos(yrotrad)) * camerascale;
    zpos += float(sin(yrotrad)) * camerascale * 3;
    }

    if (key=='a')
    {
    float yrotrad;
    yrotrad = (yrot / 180 * 3.141592654f);
    xpos -= float(cos(yrotrad)) * camerascale;
    zpos -= float(sin(yrotrad)) * camerascale * 3;
    }

    if (key==27)
    {
    exit(0);
    }
}

void mouseMovement(int x, int y) {
    int diffx=x-lastx; //check the difference between the current x and the last x position
    int diffy=y-lasty; //check the difference between the current y and the last y position
    lastx=x; //set lastx to the current x position
    lasty=y; //set lasty to the current y position
    xrot += (float) diffy; //set the xrot to xrot with the addition of the difference in the y position
    if (xrot > 90)
    {
    xrot = 90;
    }
    if (xrot < -90)
    {
    xrot = -90;
    }
    yrot += (float) diffx;    //set the xrot to yrot with the addition of the difference in the x position
}

int main (int argc, char **argv) {
    glutInit (&argc, argv);
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);
    glutInitWindowSize (500, 500);
    glutInitWindowPosition (100, 100);
    glutCreateWindow ("A basic OpenGL Window");

    init();

    glutDisplayFunc (display);
    glutIdleFunc (display);
    glutReshapeFunc (reshape);

    glutPassiveMotionFunc(mouseMovement); //check for mouse movement

    glutKeyboardFunc (keyboard);

    glutMainLoop ();

    return 0;
}

Download Ground Texture (.RAW)

Download Detail Texture (.RAW)

Download Heightmap (.RAW) - same as previously used

Download C++ Source Code for this Tutorial

Download Visual Basic Source Code for this Tutorial (soon maybe)

     

 

Copyright 2008, Donald Urquhart
Proudly supported by http://www.cdadc.com