Lighthouse3d.com

Please send me your comments
Terrain Tutorial

Index

Introduction

A TGA Library
A Simple TGA Library
TGA lib Source

Height Maps
Height Maps from Images

Lighting
Computing Normals
Simulating Lighting
Implementation Details
Screen Shots

Source Code

Artificial Terrain Generation
The Fault Algorithm
Implementation Details
Two Simple Variations
The Circles Algorithm

Mid Point Displacement
The MPD Algorithm

Particle Deposition

Smoothing
Smoothing
Matrix filters
API details

Source (Linux and Win32)

[Previous: The TGA lib source] [Next: Computing Normals]

Terrain Tutorial


Height Maps From Images


Now that we know how to load an image lets see how to create and draw a height map. This section presents a set of functions from the terrain library that deal with height maps. First lets see how we can build a height map from an image.

There are several ways to interpret an image as an height map. If the image is greyscale the process is more or less straightforward, a pixels intensity corresponed to a height.

If the image is RGB then we can convert it to greyscale using the function tgaRGBtoGreyscale from the tgalib (see the tgalib section). Then we can use the Greyscale as before.

The other possible type is RGBA, in which case we can create a height map from the alpha channel, and use the RGB for the terrain colors.

Before presenting the functions lets take a look at the possible status returned:
  • TERRAIN_ERROR_LOADING_IMAGE - this value is returned when there is a problem loading the image.
  • TERRAIN_ERROR_MEMORY_PROBLEM - Oops, no memory to hold the terrain data
  • TERRAIN_ERROR_NOT_SAVED - There was a problem saving the file
  • TERRAIN_ERROR_NOT_INITIALISED - occurs when a function to act upon the terrain is called but there is no terrain!
  • TERRAIN_OK - This what we want, no errors!
  • We need some variables to hold the terrain data:
  • terrainGridWidth - the number of heights available on the x axis.
  • terrainGridLength - the number of heights available on the y axis.
  • terrainHeights - A one-dimensional array containing the heights
  • terrainColors - A one-dimensional array containing the RGB colors for each height
  • terrainNormals - A one-dimensional array containing the normals for each height
  • Note that the terrainColors and terrainNormals arrays contain 3 times the number of components of terrainHeights. This is because we don't store the location in the XZ plane of each height in the array.

    The function to create the height map based on an image has the following signature:


    int terrainLoadFromImage(char *filename, int normals);

    Parameters:
    filename - the name of the image file
    normals - a non-zero value specifies that normals are to be calculated, zero means that we don't want normals


    The value returned by this function indicates if the operation was successful or not. This function computes the heights as the pixels intensity scaled down to the interval [0,1]. The code that follows shows the steps required to create the height map. The image loading is performed by the tgalib presented in the previous sections.


        
    int terrainLoadFromImage(char *filename, int normals) {
    
    	tgaInfo *info;
    	int mode,aux;
    	float pointHeight;
    
    	// if a terrain already exists, destroy it.
    	if (terrainHeights != NULL)
    		terrainDestroy();
    		
    	// load the image, using the tgalib
    	info = tgaLoad(filename);
    	
    	// check to see if the image was properly loaded
    	// remember: only greyscale, RGB or RGBA noncompressed images
    	if (info->status != TGA_OK)
    		return(info->status);
    
    	// if the image is RGB, convert it to greyscale.
    	// mode will store the image's number of components
    	mode = info->pixelDepth / 8;
    	if (mode == 3) {
    		tgaRGBtoGreyscale(info);
    		mode = 1;
    	}
    	
    	// set the width and height of the terrain
    	terrainGridWidth = info->width;
    	terrainGridLength = info->height;
    
    	// alocate memory for the terrain, and check for errors
    	terrainHeights = (float *)malloc(terrainGridWidth * 
    					terrainGridLength * 
    					sizeof(float));
    	if (terrainHeights == NULL)
    		return(TERRAIN_ERROR_MEMORY_PROBLEM);
    
    	// allocate memory for the normals, and check for errors
    	if (normals) {
    		terrainNormals = (float *)malloc(terrainGridWidth * 
    						terrainGridLength * 
    						sizeof(float) * 3);
    		if (terrainNormals == NULL)
    			return(TERRAIN_ERROR_MEMORY_PROBLEM);
    	}
    	else
    			terrainNormals = NULL;
    
    	// if mode = RGBA then allocate memory for colors, and check for errors
    	if (mode == 4) {
    		terrainColors = (float *)malloc(terrainGridWidth * 
    						terrainGridLength * 
    						sizeof(float)*3);
    		if (terrainColors == NULL)
    			return(TERRAIN_ERROR_MEMORY_PROBLEM);
    	}
    	else
    		terrainColors = NULL;
    
    	// fill arrays
    	for (int i = 0 ; i < terrainGridLength; i++)
    		for (int j = 0;j < terrainGridWidth; j++) {
    		
    			// compute the height as a value between 0.0 and 1.0
    			aux = mode*(i*terrainGridWidth + j)
    			pointHeight = info->imageData[aux+(mode-1)] / 256.0;
    			terrainHeights[i*terrainGridWidth + j] = pointHeight;
    			
    			// if mode = RGBA then fill the colors array as well
    			if (mode==4) {
    				terrainColors[3*(i*terrainGridWidth + j)] =
    				 	info->imageData[aux] / 256.0;
    				terrainColors[3*(i*terrainGridWidth + j)+1] =
    				 	info->imageData[aux+1]/256.0;
    				terrainColors[3*(i*terrainGridWidth + j)+2] = 
    					info->imageData[aux+2]/256.0;
    			}
    		}
    	// if we want normals then compute them		
    	if (normals)
    		terrainComputeNormals();
    		
    	// free the image's memory 
    	tgaDestroy(info);
    	
    	return(TERRAIN_OK); 
    }
    
    


    The normals computation will be dealt in future sections.

    Before going into the rendering function lets take a look at two other functions to scale the terrain. The first one scales the heights of the terrain. As mentioned before when creating a height map from an image, the heights are in the interval [0,1]. In the general case we'll probably want to specify the minimum and maximum heights. The terrain lib provides a function to this end with the following syntax:


    int terrainScale(float min,float max);

    Parameters:
    min - the minimum height
    max - the maximum height


    This functions rescales all the heights to fit in the interval [min, max]. There is only one detail which is worth pointing out in this function: if using normals it is necessary to rescale them. This is because the scale applied is not uniform, i.e., it is not equal for all the axes, and therefore the normals previously computed are no longer correct. The function will recompute the normals after scaling the terrain.

    The other scaling function defines the interval between two adjacent grid points. By default the distance is 1.0.


    int terrainDim(float stepWidth, float stepLength);

    Parameters:
    stepWidth - the x distance between two grid points
    stepLength - the z distance between two grid points


    So now that we have a terrain, lets render it! The terrain library creates a display list using triangle strips for the height map. This function takes 3 parameters, which represent the origin of the terrain. If all parameters are zero then the terrain will be created at the local origin. Otherwise we can use these parameters to place the terrain in any position.

    This function looks at the normals array to decide if using materials or just glColor3f. Therefore, normals should not be required when creating the height map if the application is not going to use lighting, otherwise the colors will be lost. Similarly strange results may occur if lighting is enabled in the application but the normals are not computed.

    The syntax of the rendering function is as follows:


    int terrainCreateDL(float xOffset, float yOffset, float zOffset);

    Parameters:
    xOffset - specifies the center of the terrain on the x direction
    yOffset - specifies the y value of the points with zero height


    Next the code of the function is presented.


        
    int terrainCreateDL(float xOffset, float yOffset, float zOffset) {
    
    	GLuint terrainDL;
    	float startW,startL;
    	int i,j,aux;
    
    	// compute the initial point of the terrain on the XZ plane
    	startW = terrainGridWidth / 2.0 - terrainGridWidth;
    	startL = - terrainGridLength / 2.0 + terrainGridLength;
    	
    	// Create the id for the display list
    	terrainDL = glGenLists(1);
    
    	// create the display list
    	glNewList(terrainDL,GL_COMPILE);
    	
    	// test for normals
    	if (terrainNormals != NULL) {
    		glColorMaterial(GL_FRONT, GL_DIFFUSE);
    		glEnable(GL_COLOR_MATERIAL);
    	}
    
    	// generate n-1 strips, where n = terrainGridLength
    	// for each vertex test if colors and normals are enabled
    	for (i = 0 ; i < terrainGridLength-1; i++) {
    		glBegin(GL_TRIANGLE_STRIP);
    		for (j = 0;j < terrainGridWidth; j++) {
    			aux = 3*((i+1)*terrainGridWidth + j);
    			if (terrainColors != NULL) 
    				glColor3f(terrainColors[aux],
    					terrainColors[aux+1],
    					terrainColors[aux+2]);
    			if (terrainNormals != NULL)
    				glNormal3f(terrainNormals[aux],
    					terrainNormals[aux+1],
    					terrainNormals[aux+2]);
    			glVertex3f(
    				startW + j + xOffset,
    				terrainHeights[(i+1)*terrainGridWidth + (j)] + yOffset,
    				startL - (i+1) + zOffset);
    
    			aux = 3*(i*terrainGridWidth + j);
    			if (terrainColors != NULL) 
    				glColor3f(terrainColors[aux],
    					terrainColors[aux+1],
    					terrainColors[aux+2]);
    			if (terrainNormals != NULL)
    				glNormal3f(terrainNormals[aux],
    					terrainNormals[aux+1],
    					terrainNormals[aux+2]);
    			glVertex3f(
    				startW + j + xOffset, 
    				terrainHeights[(i)*terrainGridWidth + j] + yOffset,
    				startL - i + zOffset);					
    		}
    		glEnd();
    	}
    	glEndList();
    
    	// return the list index so that the application can use it
    	return(terrainDL);
    }
    
    


    In an application using the terrain lib we must first create the terrain and the display list.


        
    	//load an image and compute the normals
    	if ((status = terrainLoadFromImage("3dtech.tga",1)) != TERRAIN_OK)
    		// do something, there was an error
    	else
    		//scale the terrain so that the height varies from
    		// -10 to 30
    		terrainScale(-10,30);
    		// create a terrain centered on the origin
    		myDisplayList = terrainCreateDL(0,0,0);
    	
    
    
    


    Then, when rendering just call the display list


        
    	glCallList(myDisplayList);
    
    


    The last function to be presented in here provides a way to get the height for a particular XZ point. The syntax is as follows:


    float terrainGetHeight(int x, int z);

    Parameters:
    x - the x coordinate assuming that the terrain is centered on the XZ plane, and the the spacing between points is 1.
    z - the z coordinate assuming that the terrain is centered on the XZ plane, and the the spacing between points is 1.


    The return value is the height at the requested point.

    A simple example application is provided in here. The application is based on the glut examples provided in the glut tutorial. Use the arrow keys to navigate in the terrain. The mouse, when a button is pressed, will allow you to freely look at the scene and change your orientation. Pressing the arrow keys after the mouse button is pressed will speed up your movement. An image is provided as an example as well as some screen shots taken with the application.

    The image from where the height map is created

    The rendered height map

    A detail of the height map

    [Previous: The TGA lib source] [Next: Computing Normals]