// 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 #include #include #include #include #include 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; }