Creating a Terrain using a Height Map

Introduction

You are to create a terrain in XNA using a heightmap.

The terrain is created by using a regular grid of points laid on the x-z plane. The x & z coordinates of this grid follow a regular pattern like the vertices of a chess board. The y-coord from each point is taken from a heightmap. The heightmap is an image file, each pixel in the image represents one of the points on our grid. The height of the point (y-coord) is represented by the colour of the pixel. Below is a height map of one of the Hawaiian Islands. The bright areas represent high ground.

Grey-scale height map

 

The height map is loaded as a Texture2D and the pixel data can be easily read.

The following code may be useful. It copies the data from the texture to an array which can then be used to create the points for the terrain.

 

 size = heightmap.Width; // assume a square heightmap 

 height = new float[size, size];// array of height values


 // get the colour data from the heightmap

 Color[] tmpheight = new Color[size*size];
 heightmap.GetData<Colour>(tmpheight); 



 for (i = 0; i < size; i++)
 {
 		for (j = 0; j < size; j++)
      {
      	height[i, j] = 
			(float)(tmpheight[i * size + j].R);  
			// only interested in the 
			// red channel                                                                    
			// convert LIST of datapoints 
			// into an ARRAY of datapoints
                    
      }
}

The next bit of code will create an array of vertex list representing the triangles of the surface. The function PlaneVertex return a VertexPositionNormalTexture object representing a point on the grid. (you will need to write this yourself).

 

 // next create a triangle list for the terrain
            numTri = (size-1) * (size-1) * 2;
            numVert = numTri * 3;
            planeVertices =new VertexPositionNormalTexture[numVert];
            int vert_num=0;
           
            for(i=0;i<size-1;i++){
                for(j=0;j<size-1;j++){

                    planeVertices[vert_num++] = PlaneVertex(i, j, size);
                    planeVertices[vert_num++] = PlaneVertex(i+1, j ,  size);
                    planeVertices[vert_num++] = PlaneVertex(i , j+1,  size);

                    planeVertices[vert_num++] = PlaneVertex(i+1, j+1,  size);
                    planeVertices[vert_num++] = PlaneVertex(i, j+1,  size);
                    planeVertices[vert_num++] = PlaneVertex(i+1, j,  size);


                }
            }

You should be able to render a terrain before you move onto the next phase.

Normals

In order for this terrain to be correctly rendered with lighting, each vertex needs a normal. A normal for a vertex is found by averaging the normals for the neighbouring triangles,. In the following code, the vectors n,s,e & w are vectors to a points neighbouring points (north, south east & west). Vectors sw,nw,se & nw are the normals for the neighbouring triangles. We then just get the average of these to calc the normal for the current point.

 

                    sw=Vector3.Cross(s,w);
                    nw=Vector3.Cross(w,n);
                    ne=Vector3.Cross(n,e);
                    se=Vector3.Cross(e,s);

                    sw.Normalize();
                    nw.Normalize();
                    se.Normalize();
                    ne.Normalize();

                    normals[i,j]=(sw+se+nw+ne)/4.0f;   // average all 4 normals  to get normal for this position
                    normals[i, j].Normalize();

To turn on lighting with a basic effect;

 


effect.EnableDefaultLighting();

Texture

To add realisim we can add a texture to the terrain. We need to load a texture as a Texture2D object (this will usually be a different texture to the height map). In this case I have created a texture which will show the zero height areas as blue (sea), low-lying areas as yellow (beach), and mountain tops as white (snow). This texture need to be correctly aligned in order to work. (The texture was created from the original heightmap + some simple photoshop manipulation)

Terrain texture

This involves giving each point a texture coordinate and setting the texture for the BasicEffect. To apply a texture;

 
			texture = Content.Load<Texture2D>("IslandTexture");
            terrainEffect.Texture = texture;
            terrainEffect.TextureEnabled = true;
            terrainEffect.EnableDefaultLighting();