Using heightmaps in the Terrain component in Unity 4.2

This weekend I spent some time playing around with procedural heightmap generation in Unity. Since the documentation for these APIs is pretty sparse, I wanted to post about all the traps I fell into and maybe save other people some time.

Here’s the code I ended up with. ChunkId.chunkSize is a Vector2 that contains {100, 100}. Perlin is a noise generator from this LibNoise port to Unity. This is a method on a custom component I added to each of my Terrain GameObjects.

public void UpdateHeight( Perlin noise, float height )
{
Terrain terrain = gameObject.GetComponent();
TerrainData terData = terrain.terrainData;
Vector3 pos = gameObject.transform.localPosition;

float[,] rHeight = new float[terData.heightmapWidth, terData.heightmapHeight];

float xStart = (float)id.x * ChunkId.chunkSize.x;
float yStart = (float)id.y * ChunkId.chunkSize.y;
float xStep = ChunkId.chunkSize.x / (float)(terData.heightmapWidth - 1 );
float yStep = ChunkId.chunkSize.y / (float)(terData.heightmapHeight - 1);
for( int x = 0; x< terData.heightmapWidth; x++ )
{
for( int y=0; y< terData.heightmapHeight; y++ )
{
float xCurrent = xStart + (float)x * xStep;
float yCurrent = yStart + (float)y * yStep;
double v = noise.GetValue( xCurrent, yCurrent, 0 );
rHeight[ y, x ] = (float)(v + 1.0) * 0.5f;
}
}

terData.SetHeights( 0, 0, rHeight );
}

When I ran the initial version of this code, I ended up with gaps all over the place wherever two terrain chunks met. These are all the things I had to learn to fix those gaps:

Trap #1: Heightmap data is duplicated on the edges

This should have been obvious. It was also an easy fix. That's why there is a -1 in this code:

float xStep = ChunkId.chunkSize.x / (float)(terData.heightmapWidth - 1 );
float yStep = ChunkId.chunkSize.y / (float)(terData.heightmapHeight - 1);

Trap #2: Height values must be in the range 0 to 1
The values you put into your heightmap aren't actually "height" values even though they're floats. They are the parameter that will let the terrain system scale the height between 0 and the "Terrain Height" parameter in the editor. Because the noise generator produces roughly -1 to 1 values, I had to scale them like this:

rHeight[ y, x ] = (float)(v + 1.0) * 0.5f;

Values above 1 or below 0 are clamped to 1 or 0 respectively so if you pass them in you will end up with some flattened sections of terrain.

Trap #3: Height arrays are Column-Major

You might have noticed something else that's funny about the line of code I just listed:

rHeight[ y, x ] = (float)(v + 1.0) * 0.5f;

For most of the time I was fighting with this I had the x and y reversed because I assumed that the 2D array expected by SetHeights was row-major just like everything else in the universe. This is the biggest documentation hole I ran into and what cost me the most time.

Trap #4: The "top" neighbor is in the positive Z direction

In order to make the LODs and normals on the transitions between terrain chunks work Unity keeps track of which terrains border which other terrains. You tell the engine about these relationships via the SetNeighbors call. For no particularly good reason I was expecting that left was -x, right was +x, top was -z and bottom was +z. Turns out that I had two of those backwards:

  • Left: -x
  • Right: +x
  • Top: +z
  • Bottom: -z

~Joe


2 Responses to “Using heightmaps in the Terrain component in Unity 4.2”

  1. Stefan thought on :

    Hey, this is really helpful and all but I just can’t seem to stitch the terrains together without any seams showing. What is the “id” variable?

  2. Joe replied on :

    The key to avoid seams is the SetNeighbors call.

Leave a Reply