Dev notes: The (un-)uncanny valley

Recently I’ve been working my way deeper into the area of landscape generation. The big question was: When does a landscape “look nice”?

If you enter “nice landscape” into a search engine of your choice, chances are you’ll end up with something like this.


I’d have to agree: those are some nice places all right!

So when does a landscape look nice? Surprisingly enough, not only does this vague question have a well defined answer, it turns out that there’s some fascinating scientific work to back it up. Sadly, I lost the link to another great article I had found, but there’s a Wikipedia article that does a good job of explaining it.

Wikipedia: Evolutionary aestheticsLandscape and other visual arts preferences

Humans are argued to have strong aesthetical preferences for landscapes which were good habitats in the ancestral environment.

When young human children from different nations are asked to select which landscape they prefer, from a selection of standardized landscape photographs, there is a strong preference for savannas with trees. The East African savanna is the ancestral environment in which much of human evolution is argued to have taken place.

There is also a preference for landscapes with:

water,
with both open and wooded areas,
with trees with branches at a suitable height for climbing and taking foods,
with features encouraging exploration such as a path or river curving out of view,
with seen or implied game animals,
and with some clouds.

These are all features that are often featured in calendar art and in the design of public parks.

There’s one point not explicitly mentioned that I would have added: landmarks, such as high mountains in the distance, that provide navigational aid – both in regards to the relative position of the observer (“Where am I right now?”) and to the scale of distances (“How long will it take me to reach a waypoint?”).

I can easily come up with an explanation why every single point would make a lot of sense from a survivalist perspective, and I’m sure that you can do so, too. So it turns out that the answer is literally in our genes.

Regarding computer generated landscapes, I believe that this list is the key to landscapes that not only look good from a static viewpoint, but will make you want to actively explore them.

A quick recap on procedural landscapes and curves

Surely you’ll already know, but here’s another quick explanation of how to generate procedural landscapes. You start with a flat “plane” and compute a height value for each XY coordinate of the plane, which is your landscape elevation for that coordinate.

This height value is usually the combination of multiple calls to a noise (curve) algorithm, each with a different scale. The amount of combined noise values depends on the required resolution, but can be refined arbitrarily – hence the “fractal” nature of the generated values. A single one of these “layers” is called an “octave”.

Here’s a visualization that uses sine curves as the noise algorithm – in the end, sine is just a regular noise function. Perlin and Simplex curves look very similar (in regards to a peak always followed by a base), except that they aren’t regular.

Those are some nice rolling hills. On a small scale, a landscape generated this way looks just fine.

However, on a planetary level, this doesn’t work at all. And that’s because mountain ranges aren’t hills. They don’t roll. In fact, they are quite pointy, and they usually come in a line or string, well, hence the name range, or so I would guess…


But even worse, the most important part is missing: the valley at the foot of the mountain! What is going on here?

If you look at topography at a global scale, you’ll find it’s defined by tectonic plate movement. Simply put, you have mostly flat plates that bump into each other and fold up into a pointy seam. This is fundamentally different from the “rolling hills” you will find at a local scale, where erosion has worn off the mountain peaks.

Mountain Math

How do you turn rolling curves into mountains and valleys? It’s actually quite easy.

Hover to see: Flipping the top of a curve


if(height < 0) height = -height; // flip the negative parts back into positive height = 1 - height; // mirror it horizontally so the spikes point upwards // alternatively, here's an one-liner version that does the same as above height = 1 - abs(height);

Assuming that your curve goes from -1 to 1 (such as Sine, Perlin or Simplex)

This still doesn't look quite right. The valleys aren't really valleys yet, and the pointy mountaintops are way too pointy and break the terrain shading model. We'll have to round off the mountaintops a bit and give the valleys more definition.

Power Math

The tool we are going to use for this are "raise X to the power of Y" operations, also known as X^Y or exponential function.

The exponential function people are most familiar with is the Square function (X^2): 2^2=4, 3^2=9 and so on. The resulting values grow quickly for any input bigger than 1, for example, 100^2=10000 (or 100*100).

However, for values between 0 and 1 (such as our current curve values), the effects are quite different. It keeps the resulting values much lower, and only as they approach 1.0, they'll finally get bigger. Remember, 0.5^2 is the same as 0.5*0.5 = 0.25.

Hover to see: Better valleys


height = 1 - abs(height);
height = Power( height, 2);

So our Power(x,2) function has the effect of pulling values closer to the floor. Now for the pointy bits.

The very first thing we did was to flip the negative parts of our smooth rolling curve into positive, producing the pointy bits. But at that time, they were still pointing downwards and we had to mirror them so they would point upwards.

So while our pointy bits are still pointing downwards, what would happen if we Power()'d them? Well, the answer is: The parts that are a bit more away from the ground get closer to the ground again. Which magically gives the pointy bits a slightly rounded top.

Hover to see: Rounded-off mountain tops


height = abs(height); //flip it
height = Power( height, 2); //round off the pointy bits
height = 1 - height; //mirror it
height = Power( height, 2); //flatten the valleys

There we go. Math, gentlemen! Can't live with it, can't live without it either.

We're almost there. While this new algorithm is well-suited for shaping tectonically formed landscapes, the "stringy" character of the mountaintop lines looks weird at higher (=smaller) octaves.

The key is to use the FlipPowMirrorPow algorithm for the low octaves, and do the high octaves with the old "rolling curve" method. The result will be a landscape that has well-defined mountain ranges and valleys at the global scale, and a naturally eroded, fractal look at a local scale.

And that is all for today. Stay tuned for the next article about procedural world generation.

by