# Generating Terrain Textures from Heightmaps

## Prerequisites

### Generating Terrain Textures from Heightmaps

#### Introduction

• There are several algorithms available for generating heightmaps:
• Midpoint displacement
• Diamond Square
• Perlin Noise
• The below heightmap is generated using the diamond-square algorithm.
• It has also been dilated to flatten it out slightly.
• We can convert this heightmap to a set of vertices and render it as a terrain:
• However, rendering it as a solid green color isn't very interesting, in this article we will generate a more detailed texture for it.

#### Finding the Derivative

• Ideally we want to color the flat areas green to represent grass, while the slopes should be colored brown, to represent dirt/rock.
• The difference between a flat area and a sloped area is the gradient, to find the gradient for an image we need to calculate its derrivative.
• To do this we can use the Sobel operator:
``````private static Mat getImageDerivative(Mat heightmap)
{
Mat horiz = new Mat();
CvInvoke.Sobel(heightmap, horiz, DepthType.Cv16S, 1, 0);

Mat horizABS = new Mat();
CvInvoke.ConvertScaleAbs(horiz, horizABS, 1, 0);

Mat vert = new Mat();
CvInvoke.Sobel(heightmap, vert, DepthType.Cv16S, 0, 1);

Mat vertABS = new Mat();
CvInvoke.ConvertScaleAbs(vert, vertABS, 1, 0);

Mat derivative = new Mat();
CvInvoke.AddWeighted(horizABS, 0.5, vertABS, 0.5, 0, derivative);

//the derivative will have values in range minVal to maxVal
//we need convert this to the range 0-255

double minVal = 0;
double maxVal = 0;
Point minLoc = new Point();
Point maxLoc = new Point();
CvInvoke.MinMaxLoc(derivative, ref minVal, ref maxVal, ref minLoc, ref maxLoc);

CvInvoke.ConvertScaleAbs(derivative, derivative, 255 / Math.Max(-minVal, maxVal), 0);

return derivative;
}``````
• This produces an image of the gradient:
• In the above image, the dark pixels represent flat areas while the light pixels represent slopes.
• To get a sense of how this works, let's render it as a texture on our terrain:
• If you look closely, you can see the slopes are bright while the flat areas are dark.

#### Processing the Derivative

• Having an image that gets brighter as the gradient increases is fine for testing, but it's not really how it works in the real world.
• Instead, let's split the image into black and white, black for flat areas and white for slopes:
``````private static Mat convertToBinary(Mat derivative)
{
Mat blackAndWhite = new Mat(derivative.Width, derivative.Height, DepthType.Cv8U, 1);
MCvScalar lower = new MCvScalar(0);
MCvScalar upper = new MCvScalar(64);

//pixels in the range 0-64 will be set to black
//pixels in the range 65-255 will be set to white
CvInvoke.InRange(derivative, new ScalarArray(lower), new ScalarArray(upper), blackAndWhite);
return blackAndWhite;
}``````
• Instead of white and black, let's use more natural colors:
``````private static Mat colorizeImage(Mat blackAndWhite)
{
MCvScalar brownBGR = new MCvScalar(54, 75, 128);
MCvScalar greenBGR = new MCvScalar(68, 128, 68);

Mat terrain = new Mat(blackAndWhite.Width, blackAndWhite.Height, DepthType.Cv8U, 3);
terrain.SetTo(brownBGR, blackAndWhite);

CvInvoke.BitwiseNot(blackAndWhite, blackAndWhite);
terrain.SetTo(greenBGR, blackAndWhite);

return terrain;
}``````

• In real landscapes, lower regions are darker while higher regions are brighter.
• Luckily our heightmap already contains this information! We can add this to our terrain texture:
``````private static Mat addHeightmapToTerrain(Mat terrain, Mat heightmap)
{
Mat hsv = new Mat();
CvInvoke.CvtColor(terrain, hsv, ColorConversion.Bgr2Hsv);

Mat[] channels = hsv.Split();

//we merge the heightmap with the vibrance channel of the image
CvInvoke.AddWeighted(channels[2], 0.5, heightmap, 0.5, 0, channels[2]);

CvInvoke.Merge(new VectorOfMat(channels), hsv);
CvInvoke.CvtColor(hsv, terrain, ColorConversion.Hsv2Bgr);

return terrain;
}``````

• Our terrain is still only using two solid colors, let's add some texture to them:
``````private static Mat addNoiseToTerrain(Mat terrain)
{
Mat hsv = new Mat();
CvInvoke.CvtColor(terrain, hsv, ColorConversion.Bgr2Hsv);

Mat[] channels = hsv.Split();

Random random = new Random();
byte[] buffer = new byte[hsv.Width * hsv.Height];
random.NextBytes(buffer);

GCHandle pinnedArray = GCHandle.Alloc(buffer, GCHandleType.Pinned);

Mat randomMat = new Mat(hsv.Width, hsv.Height, DepthType.Cv8U, 1, pointer, hsv.Width);
CvInvoke.AddWeighted(channels[2], 0.8, randomMat, 0.2, 0, channels[2]);

pinnedArray.Free();

CvInvoke.Merge(new VectorOfMat(channels), hsv);
CvInvoke.CvtColor(hsv, terrain, ColorConversion.Hsv2Bgr);

return terrain;
}``````

• The transition from green to brown should show a difference in gradient, but we can make it even clearer by adding a thin black line between the two.
• We can achieve this by finding the second derivative of the image:
##### Code
``````Mat secondDerivative = getImageDerivative(blackAndWhite);
MCvScalar black = new MCvScalar(0, 0, 0);
terrain.SetTo(black, secondDerivative);``````

#### Implementation Details

• The image processing code is written in c# + EmguCV.
• The terrain is scaled up to be 10x larger than the heightmap.
• The 3D images were rendered in OpenTK.