Procedural Terrain and Biomes

Started by
4 comments, last by Mythix 10 years, 11 months ago

Hello all,

I'm working on a terrain generator. Through multiple noise functions, I'm able to create many kinds of terrain I like, but I'm having a bit of difficulty joining them together in a seamless fashion.

My first thought was to use a single low level noise call (calling it the mask) and then I could simply say things like:

if (returnVal < .1) GenerateBiome1();

if (returnVal < .2) GenerateBiome2();

if (returnVal < .3) GenerateBiome3();

and so on...

The issue here is that the height of the terrain doesn't blend together very well. If biome 1 was more mountainous and biome 2 was flat land, it's a drastic switch from one to the other and it looks atrocious.

My next thought was to attempt blending by giving a 'strength' to each biome based on what my mask noise returns. In C# code, it looks something like this at the moment:


tatic public float GetHeight(float X, float Z)
{
    float fRollingHills_Location = .25f, fRollingHills_Impact = .5f, fRollingHills = 0;
    float fMountains_Location = .75f, fMountains_Impact = .5f, fMountains = 0;

    float fMask = Game1.GetNoise2(0, X, Z, .01f);
    float fRollingHills_Strength = (fRollingHills_Impact - Math.Abs(fMask - fRollingHills_Location)) / fRollingHills_Impact;
    float fMountains_Strength = (fMountains_Impact - Math.Abs(fMask - fMountains_Location)) / fMountains_Impact;

    if (fRollingHills_Strength > 0) fRollingHills = fRollingHills_Strength * (Game1.GetNoise2(0, X, Z, .02f) * 10f + 10f);
    if (fMountains_Strength > 0) fMountains = fMountains_Strength * (Game1.GetNoise2(0, X, Z, .05f) * 25f + 10f);

    return fRollingHills + fMountains;
}
 

The issue I have here is that even for only two biomes, it's pretty important for me to manually choose the "location" and "impact" values to ensure I'm getting some combination of strengths to total 100% by the time it returns a value (if not, I get things like giant cliffs making the borders from one biome another ugly and obvious). I'm also not entirely sure I understand it just yet, but the way in which I multiply the noise for a height looks like it needs to change to handle multiple biomes blending smoothly.
Does anyone have any experience in this and can either help correct me, explain it better, explain a whole new method to do this that might be easier, or just point me to some good resources on the subject? While I've managed a few good looking maps, if I try to go up to 10 or more biomes, without having more automation in my code which I think might be just beyond me with this method, I don't think I'll be able to succeed.
Thanks,
Mythix
Advertisement

I tend to believe that it is a mistake to try to deal with biomes as discrete units like this. It leads to weird situations just like you describe, weird situations where you might have desert+cactus right next door to snow or, as you describe, high mountains right next to lowlands with a sharp cliff transition in between.

My personal preference is to approach it from a simulation and modeling point of view. I would first start with the terrain, laying out mountains and hills and lowlands using various combinations of fractals, then use this terrain-type map to inform a simulation process for distributing moisture. Given even a small set factors such as elevation, terrain slope, terrain roughness, latitude and moisture you can infer a lot about what biome a particular area should belong to. Based on this set of data, I can use the type mask to lay down terrain instances and set tiles, then populate the map with appropriate doodads.

JTippetts talked about his island generation process in his journal some time back, and I modeled (stole) my method from his, although in my most recent project the terrain was continuous and infinite rather than on an island. But the idea was pretty much the same, and I even used his noise library to do it.

Hey Mythix, I had a similar problem and we discussed some aspects of it in this post: http://www.gamedev.net/topic/639342-infinite-terrain-generation-in-chunks-need-hints-on-handling-chunk-edges/

Without looking back at the code right now, I think what I basically did was generate a height value for each possible biome at a given point, and bilinearly interpolated between the relevant values in each case. So if a mountain biome was next to the flat land biome, each would contribute 50% towards the values returned on the border. (Extended to 2 dimensions, obviously.)

A cheaper way would perhaps be to return a list of contributing biomes at each point and their relevant weights, and only calculate those values. If the number of biomes you have significantly exceeds 4, then I imagine this method might be quicker, providing that you can optimise the creation of those little lists. (Ref arguments, perhaps.)

(There's also a lot of chatter about me not entirely understanding the mathematics behind the noise function, but you can skip most of that.)

Sorry FLeBlanc, that's a bit too advanced for me. If I get something working in a much larger scale, I'll probably come back to this some day, but I'm betting that'll be a while.

I went ahead with the weighted approach Kylotan, which is exactly as my code showed above, but I did it in a much more elegant manner. I now have a single function to create biomes and another to draw upon them as needed.

I'm having a few speed issues at the moment (more like over optimizing issues on my end, lol), but if I get past them I might throw a screenshot out later in case anyone is curious what I'm tinkering with.

Thanks for the help!

Care to share details of your approach? The reason for my bilinear interpolation approach is because it guarantees smooth transitions and values that always add up to a 100% contribution, although I am considering scaling the function to make the transitions 'sharper'. (eg. replacing the linear values with a sigmoid curve).

I'm horrible with explaining things, but I'll try.

I'm using value noise with about 4 octaves as my mask. I then set a biome up by specifying values for it's own noise call with what I'm calling a location value and an impact value. Per biome, I take (fImpact - abs(fMask - fLocation)) / fImpact. That gives me a scaling percentage of the biome's "strength" around the fLocation value.

If my location was .2 and my impact was .15, with noise scaling from 0 to 1, it'd look like this:

Mask - Biome Strength

0.10 - 0.33

0.15 - 0.67

0.20 - 1.00

0.25 - 0.67

0.30 - 0.33

I then call the noise function for said biome, multiply it by the biome strength, and add that to my total height multiplier. Once I've ran through each biome (only calculating the noise call for that biome if the strength is >0), I take that sum and multiply it by the max height of my terrain to get the height of that point.

I'm also storing per point on the heightmap what biomes played a part in that point in descending order so that I can use those values for details of the terrain (trees, sand, snow, or whatever else).

It certainly wouldn't be difficult to scale up the strengths to ensure 100% contribution based on how many biomes played a part at that point, but I'm pretty happy with the results I'm getting with a pretty even spread at the moment (with only a little overlap).

This topic is closed to new replies.

Advertisement