libnoise logo

A portable, open-source, coherent noise-generating library for C++


Tutorial 3: Generating and rendering a terrain height map

In this tutorial, you'll write a program that generates and renders a terrain height map from the output of a noise module.

Downloading the noiseutils library

The libnoise library does not have the capability to create images or terrain height maps from coherent noise; you must write the code to do this yourself. This is probably not your idea of fun, so for your convenience, you'll download the noiseutils library (32 KB). This library contains the following helpful classes:

  • A noise map class — this class implements a two-dimensional array that stores floating-point values. It's designed to store coherent-noise values generated by a noise module.
  • Several noise-map builder classes — each of these classes fills a noise map with coherent-noise values generated by a noise module. While filling a noise map, it iterates the coordinates of the input value along the surface of a specific mathematical object. Each of these classes implements a different mathematical object, such as a plane, a cylinder, or a sphere.
  • An image class — this class implements a two-dimensional array that stores color values.
  • Several image-renderer classes — these classes render images given the contents of a noise map. Each of these classes renders an image in a different way.
  • Several file writer classes — each of these classes writes the contents of a noise map or an image to a file.

Once you download the noiseutils library, you'll need to add noiseutils.cpp to your project file.

Creating a terrain height map

Open the source file you created in the previous tutorial. The code in that file should look like this:

#include <iostream>
#include <noise/noise.h>

using namespace noise;

int main (int argc, char** argv)
{
  module::Perlin myModule;
  double value = myModule.GetValue (14.50, 20.25, 75.75);
  std::cout << value << std::endl;
  return 0;
}

Since you're creating and rendering a terrain height map in this tutorial, you won't need the code that writes to stdout. Remove the following highlighted code:

#include <iostream>
#include <noise/noise.h>

using namespace noise;

int main (int argc, char** argv)
{
  module::Perlin myModule;
  double value = myModule.GetValue (14.50, 20.25, 75.75);
  std::cout << value << std::endl;
  return 0;
}

Your code should now look like the following:

#include <noise/noise.h>

using namespace noise;

int main (int argc, char** argv)
{
  module::Perlin myModule;

  return 0;
}

Now, you're ready to begin. First, you'll include the noiseutils header file. All classes and functions in that header file are within the noise::utils namespace. Add the following highlighted code:

#include <noise/noise.h>
#include "noiseutils.h"

Next, you'll create an empty two-dimensional noise map that will eventually contain your terrain height map. Add the following highlighted code:

  module::Perlin myModule;

  utils::NoiseMap heightMap;

  return 0;

Now, you'll create a planar noise-map builder, which fills a noise map with coherent-noise values generated from an (x, z) plane.

To create a planar noise-map builder, you'll need to create an instance of the NoiseMapBuilderPlane class. To specify the source of the coherent-noise values, you'll pass the Perlin-noise module to the SetSourceModule() method. To specifiy the destination of the coherent-noise values, you'll pass the empty noise map to the SetDestNoiseMap() method. Add the following highlighted code:

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;
  heightMapBuilder.SetSourceModule (myModule);
  heightMapBuilder.SetDestNoiseMap (heightMap);

Next, you'll specify the size of the noise map to generate. For this tutorial, you'll specify a size of 256 x 256 points. Add the following highlighted code:

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;
  heightMapBuilder.SetSourceModule (myModule);
  heightMapBuilder.SetDestNoiseMap (heightMap);
  heightMapBuilder.SetDestSize (256, 256);

This code does not immediately change the size of the noise map. The builder changes the size of the noise map just prior to filling it with coherent-noise values.

You'll also need to specify the (x, z) coordinates of the bounding rectangle. The builder generates the coherent-noise values from the input values contained within this bounding rectangle. These input values are evenly spaced within the rectangle. Because you've set the size of the noise map to 256 x 256, the builder generates 65,536 (256 x 256) coherent-noise values.

In this tutorial, you'll set the bounding rectangle such that its bottom-left coordinates are (2, 1) and its upper-right coordinates are (6, 5). Refer to the following diagram:

Location of the terrain height map

To set this bounding rectangle, add the following highlighted code:

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;
  heightMapBuilder.SetSourceModule (myModule);
  heightMapBuilder.SetDestNoiseMap (heightMap);
  heightMapBuilder.SetDestSize (256, 256);
  heightMapBuilder.SetBounds (2.0, 6.0, 1.0, 5.0);

Note that the coordinates are passed to the SetBounds() method as lower-x, upper-x, lower-z, and upper-z.

Now, you're ready to build the noise map. Add the following highlighted code:

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;
  heightMapBuilder.SetSourceModule (myModule);
  heightMapBuilder.SetDestNoiseMap (heightMap);
  heightMapBuilder.SetDestSize (256, 256);
  heightMapBuilder.SetBounds (2.0, 6.0, 1.0, 5.0);
  heightMapBuilder.Build ();

This program creates a terrain height map that is stored in a noise map. Because the output of the Perlin-noise module usually lies within the range -1 and +1, the values stored in this height map will also be within the same range.

Rendering the terrain height map

If you now run the program, nothing will happen. This is because you've created the terrain height map, but have not yet rendered it to an image. To do this, you'll instantiate a RendererImage class. You'll also need to create an empty image object that will receive the rendered image.

Before the renderer can render the image, you'll need to pass the noise map containing your terrain height map to the SetSourceNoiseMap() method. You'll also need to pass the empty image object to the SetDestImage() method. Add the following highlighted code:

  heightMapBuilder.SetBounds (2.0, 6.0, 1.0, 5.0);
  heightMapBuilder.Build ();

  utils::RendererImage renderer;
  utils::Image image;
  renderer.SetSourceNoiseMap (heightMap);
  renderer.SetDestImage (image);

The renderer is now properly set up. To render the image, add the following highlighted code:

  utils::RendererImage renderer;
  utils::Image image;
  renderer.SetSourceNoiseMap (heightMap);
  renderer.SetDestImage (image);
  renderer.Render ();

Writing the image to an output file

Now that you've written the code to render the image, you can now write this image to an output file in Windows Bitmap (*.bmp) format. To do this, add the following highlighted code:

  renderer.SetDestImage (image);
  renderer.Render ();

  utils::WriterBMP writer;
  writer.SetSourceImage (image);
  writer.SetDestFilename ("tutorial.bmp");
  writer.WriteDestFile ();

Everything is now ready. Compile and run the program. Within a second or two, your program should create a file, tutorial.bmp, in the current directory.

Open up the tutorial.bmp file in your favorite image editor to see a grayscale rendering of the terrain height map:

First terrain height map rendering

In this image, lower elevations are darker than higher elevations.

You've now successfully used the libnoise and noiseutils libraries to create and render a terrain height map to an image file.

Coloring the image

In this section, you'll add some realistic coloring to the image. The renderer allows you to specify a color gradient. For this tutorial, you'll use the following gradient to color the image:

Color gradient for the rendered image

As you can see, all elevations below 0 are under water. Near sea level is a narrow bar of sand, followed by grass. The grass gives way to dirt, then eventually to rock. At the highest elevations, snow appears.

You can create a gradient in the renderer by passing several Color objects to it. The Color constructor requires a red, green, blue, and alpha (transparency) component; these values range between 0 and 255. Since Windows bitmap files do not support transparency, you'll set the alpha component in all Color objects to full opacity (255).

To add this gradient, add the following highlighted code:

  renderer.SetDestImage (image);
  renderer.ClearGradient ();
  renderer.AddGradientPoint (-1.0000, utils::Color (  0,   0, 128, 255)); // deeps
  renderer.AddGradientPoint (-0.2500, utils::Color (  0,   0, 255, 255)); // shallow
  renderer.AddGradientPoint ( 0.0000, utils::Color (  0, 128, 255, 255)); // shore
  renderer.AddGradientPoint ( 0.0625, utils::Color (240, 240,  64, 255)); // sand
  renderer.AddGradientPoint ( 0.1250, utils::Color ( 32, 160,   0, 255)); // grass
  renderer.AddGradientPoint ( 0.3750, utils::Color (224, 224,   0, 255)); // dirt
  renderer.AddGradientPoint ( 0.7500, utils::Color (128, 128, 128, 255)); // rock
  renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
  renderer.Render ();

Now compile and run the program again. Once it exits, open the tutorial.bmp file. You'll now see the terrain height map rendered in glorious color:

First full-color terrain height map rendering

If you examine the rendered image, you can see some nice coastlines around the islands. You'll also see that the interior mountains are covered in snow. There are even some smaller islands off the coast of the larger islands.

Adding an artificial light source to the image

In this section, you'll add an artificial light source to the rendered image. Add the following highlighted code:

  renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
  renderer.EnableLight ();
  renderer.Render ();

Now compile and run the program again. Once it exits, open the tutorial.bmp file. You'll notice that the rendered image is lit by a light source:

First terrain height map rendering with light source

The artificial light source brought out some of the relief in the terrain height map but the light source does not provide a lot of contrast. To triple the contrast, add the following highlighted code:

  renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
  renderer.EnableLight ();
  renderer.SetLightContrast (3.0); // Triple the contrast
  renderer.Render ();

Once again, compile and run the program. Once it exits, open the tutorial.bmp file. You'll notice that the image has better contrast, bringing out the relief in the terrain height map even more:

Terrain height map rendering with a high-contrast light source

This image looks quite nice although it is a little dark. This is because the calculations for determining brightness at a point only produces the maximum brightness value if the terrain at that point is flat and the artificial light source is directly above it. Unfortunately, the resulting image would look nearly identical to the non-lit version.

You can, however, increase the light's brightness. To double the brightness of the light source, add the following highlighted code:

  renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
  renderer.EnableLight ();
  renderer.SetLightContrast (3.0); // Triple the contrast
  renderer.SetLightBrightness (2.0); // Double the brightness
  renderer.Render ();

Compile and run the program once more. Once it finishes, open the tutorial.bmp file. You'll now see a much brighter image:

Terrain height map rendering with a high-intensity light source

There are many other light parameters you can modify. Try modifying some of these parameters yourself:

  • To modify the direction of the light source, pass an angle, in degrees, to the SetLightAzimuth() method. 0 degrees is due east, 90 degrees is due north, 180 degrees is due west, and 270 degrees is due south. The default angle is 45 degrees (northeast).
  • To modify the elevation of the light source, pass an angle, in degrees, to the SetLightElevation() method. An angle of 0 degrees moves the light source to the horizon and an angle of 90 degrees moves the light source directly overhead. The default angle is 45 degrees.
  • To set the color of the light source, pass a Color object to the SetLightColor() method.

Tiling terrain height maps

So far, you have only rendered a small area of the terrain. The actual terrain is nearly infinite, because coherent noise extends nearly infinitely in all directions.

In this tutorial, you've rendered the terrain height map with coherent-noise values retrieved from a bounding rectangle with bottom-left coordinates of (2, 1) and upper-right coordinates of (6, 5), as seen in the following diagram:

Location of the terrain height map

In this section, you'll change the coordinates of this bounding rectangle to render a terrain height map that is directly to the right of the original height map. These two height-map 'tiles' can then be seamlessly joined together.

You'll move the coordinates of the bounding rectangle by four units towards the right because the bounding rectangle is four units wide. The new coordinates of this bounding rectangle become (6, 1) and (10, 5), as seen in the following diagram:

New location for the terrain height map

In the above image, the bounding rectangle of the original terrain height map is shown in light gray.

Modify the following highlighted code:

  utils::NoiseMap heightMap;
  utils::NoiseMapBuilderPlane heightMapBuilder;
  heightMapBuilder.SetSourceModule (myModule);
  heightMapBuilder.SetDestNoiseMap (heightMap);
  heightMapBuilder.SetDestSize (256, 256);
  heightMapBuilder.SetBounds (6.0, 10.0, 1.0, 5.0);
  heightMapBuilder.Build ();

Rename the original image file so that you don't overwrite it. Now compile and run the program again, then open the tutorial.bmp file. You'll now see a new section of the terrain:

New terrain height map rendering

Open the two image files in your favorite image editor. Move the new image to the right of the original image. You'll note that they seamlessly join together:

Two terrain height map renderings seamlessly tiled together

This is one of the many advantages of using coherent noise instead of traditional subdivision methods to generate a terrain height map: you can quickly generate a small section of the height map, then generate nearby sections only if needed. You don't need to store the entire height map in memory.

Conclusion

In this tutorial, you have created a program that generated and rendered a terrain height map to an image file. You have colored the image with a realistic color gradient and applied an artificial light source to it. You've also rendered two height maps and seamlessly joined them together, creating a larger image.

Before going to the next tutorial, you may want to play around with your program. You can modify the image size, change the color gradients, and change the color of the light source to produce new, varied images. You can even explore the terrain by moving the bounding rectangle to different coordinates.