Navigation menu |
A portable, open-source, coherent noise-generating library for C++ |
Tutorial 3: Generating and rendering a terrain height mapIn this tutorial, you'll write a program that generates and renders a terrain height map from the output of a noise module. Table of contentsDownloading the noiseutils libraryThe 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:
Once you download the noiseutils library, you'll need to add noiseutils.cpp to your project file. Creating a terrain height mapOpen 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:
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 mapIf 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 fileNow 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:
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 imageIn 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:
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:
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 imageIn 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:
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:
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:
There are many other light parameters you can modify. Try modifying some of these parameters yourself:
Tiling terrain height mapsSo 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:
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:
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:
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:
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. ConclusionIn 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. |
|
© 2003-2005 Jason Bevins |