Creating a sandy desert, first try

Last blog post I mentioned a forum that contains a tutorial for Terragen (terrain generating program) to generate a desert scene. I took the math from that tutorial to try it myself. This blogpost will be about the results of my trials. Basically what I’m trying to create is something like this image.

 

 

The equation in the tutorial looks as follows. Don’t worry if you don’t understand the math. Understanding the equation is not necessary to follow along with this blog post.

 

 

There are a number of variables in this equation.

 

p is either zero or one and is used to select the dune profile, rounded or with a sharp peak.

s is zero for X between 0 and Xm and one for X larger than Xm.

Xm is where the peak of the dune will be. A good value for that is 0.72.

X is the input of the function and is expected to be in the range [0..1].

 

Here is what the output of the function looks like. As you can see, this looks a lot like a dune.

 

 

This equation creates exactly 1 dune. If we plug that into a heightfield and output that to an image we get the following picture. This represents a dune when viewed from above. Dark areas indicate low terrain and light areas high terrain. In the future we will translate this representation to 3D, but we’re not there yet.

 

 

Here’s the relevant bit of code. Feel free to skip this part, if you’re not interested in the nerdy goodness. The function called Dune implements the equation. The function called CreateHeightField does exactly what it says on the tin. If you don’t know what a height map is then take a look at the wikipedia page. The height map function takes a function pointer so I can easily plug in other things, such as fractal brownian motion. The function called Dunes is just a wrapper around Dune so I can plug it into the height map.

 

#define PI 3.14159265359

 

double Dune(const double x, const double p, const double xm)
{
    const double s = x > 0 && x < xm? 0.0f : 1.0f;

    const double ps1 = p * s + 1.0;

    const double part1 = ps1 * 0.5;
    const double part2 = 1 - cos((PI / ps1) * ((x - s) / (xm - s)));

    return (part1 * part2) - 1;
}

 

float Dunes(const int x, const int y, const int width, const int depth)
{
    return Dune(x / (float)width, 1, 0.72);
}

 

typedef float(*GetHeight)(const int x, const int y, const int width, const int depth);

 

void CreateHeightField(const int width, const int depth, float* const heightField, GetHeight getHeight)
{
    for (int x = 0; x < width; ++x)
    {
        for (int y = 0; y < depth; ++y)
        {
            const int index = y * width + x;
            const float z = getHeight(x, y, width, depth);
            heightField[index] = z;
        }
    }
}

 

int main()
{
    const int width = 1024;
    const int depth = 1024;
    float* const heightField = (float*)std::malloc(sizeof(float) * width * depth);

 

    CreateHeightField(width, depth, heightField, Dunes);

 

    std::free(heightField);
}

 

There are two issues we need to address. Firstly because a desert with one dune isn’t particularly interesting we need to make more dunes. And secondly this dune looks kinda straight and boring. Not very realistic at all.

 

To fix the first issue we’re going to introduce a couple of new things. We need a way to specify how many dunes we are creating and a way to actually make more dunes. The first we are going to achieve by adding a variable called frequency and multiply that with our input. The second by adding a modulo operator. If we create a picture based on that it looks like this:

 

 

This results in the following code.

 

float Dunes(const int x, const int y, const int width, const int depth)
{
float frequency = 10;
float xa = (int)(frequency * x) % width;

return Dune(xa / (float)width, 1, 0.72);
}

To fix the boring straight look of our dunes we’re going to introduce a bit of randomness. The way we do that is to use visual noise. Think of when you try to take a picture with a camera when it is dark out. The picture will often appear grainy. This grain is noise. Noise is very useful in computer graphics. Perhaps in a future blog post I will explain why noise is useful and what can be done with it (Hint, a lot).  However, not just any noise will do. It needs to have some special properties for it to be useful for us. Simplex noise will do the trick. If we output that to an image it looks like the following.

 

 

 

The code to achieve this effect look like this.

 

float Dunes(const int x, const int y, const int width, const int depth)
{
float frequency = 10;
float shift = 100.0f * SimplexNoise(5.0f * x / (float)width,
5.0f * y / (float)depth);

float xa = (int)(frequency * x + shift) % width;

return Dune(xa / (float)width, 1, 0.72);
}

This already starts to look at lot better. To have even more control over the output we introduce some more parameters. We also want to control how much distance there is between each dune. To do that we introduce a gap variable. To control the height of each dune we create an amplitude control and it would be also quite nice if we could change the wind direction as well. To do that we rotate the dunes. Here’s the output.

 

 

and here’s the code. If we want to have more randomness we can always multiply the variables with some noise. 

 

float Dunes(const int x, const int y, const int width, const int depth)
{
float frequency = 10;
float shift = 100.0f * SimplexNoise::noise(5.0f * x / (float)width,
5.0f * y / (float)depth);
float rotation = -PI / 4.0f;
float amplitude = 0.5f;
int gap = 400;

float xb = (x * cos(rotation) - y * sin(rotation));
int xa = (int)(frequency * xb + shift) % (width + gap);

if (xa < 0) xa = 0;
if (xa > width) xa = width;

return amplitude * Dune(xa / (float)width, 1);
}

Finally, to create an actual desert scene, we are going to combine several layers of dunes with different settings for each layer. I’m still experimenting with the best way to combine each set of dunes. For now I settled on using simplex noise as a weight for each layer and simply adding all the layers together. The final result looks like this.

 

 

There are definitely still problems with this result, such as that there are small wind ripples also on the collapsing side of the dunes, which doesn’t make sense. I’m not entirely happy with how the larger dunes combine. Some ideas that were in the original tutorial I have not expressed in code yet. For now though, this result is good enough.

 

Next, I will convert this into 3D geometry, so we can better see what this desert actually looks like! 

January 19, 2018
Floris Groen
Uncategorized