4
\$\begingroup\$

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 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 when doing this. 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:

static 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 to another ugly and obvious).

I don't know if I can give a good true question here other than, how should I actually be going about this? While I've managed a few good looking maps, I feel like I'm going about this in an overly complicated way that still doesn't give me all the customization I'm looking for.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ What I've done in the past is leave a gap between biomes that act as buffer zones. Certain aspects carry over from nearby zones (snow, trees, that sorta thing) and the gaps try to fall/rise from the edges to meet height variations. This creates a weathered look on mountain tops, and a rise up to the bottom of a cliff. Looks very natural. \$\endgroup\$
    – DampeS8N
    May 17, 2013 at 19:41

2 Answers 2

3
\$\begingroup\$

3D Graphic with XNA Game Studio 4.0 has a chapter dedicated to this topic. It includes a complete process for generating the terrain and layering it with a variety of textures.

As luck would have it, that chapter is the sample provided by Packt on the book's page:

In this chapter, we will focus on building a full 3D environment. We will start by creating a class that builds a terrain from a 2D image called a heightmap . We will make a number of improvements to the basic version of this class, allowing multiple textures across its surface through multitexturing and extra detail at close distances to its surface through a "detail texture". We will look at a technique called region growing to add plants and trees to the terrain's surface, and fi nish by combining the terrain with our sky box, water, and billboarding effects to create a mountain scene..."

Sample chapter 7 pdf

Here's a summary of how they do it:

First, an image is used to generate a height map. Judging from the image, it looks like some sort of coherent noise function (Perlin noise, Simplex noise, value noise, etc) was used to generate the input image:

heightmap to terrain mesh

Next, they apply a technique they call multitexturing, which uses the red, green & blue channels of an input texture to draw additional textures onto the height map. In their example they use red for sand, blue for snow and green for rock. The remainder of the image is textured with a default such as grass:

enter image description hereenter image description here

At this point, the terrain looks okay at a distance, but if the camera gets too close the textures will blur. Increase the number of times the texture tiles will result in strobing where the high resolution texture flickers when scaled down & viewed at a distance. They solve these problems by applying a detail texture. The detail texture is essentially noise that is blended (multiplied in the pixel shader) to the terrain near the camera to fake the appearance of a higher resolution texture. Here's the noise texture they used:

enter image description here

Their final step is to add vegitation. Trees are added by randomly selecting locations & evaluating the slope. If the slope is too sttep, the site is rejected. Otherwise the location is used to add a tree billboard texture. Tufts of grass are added using a probablity map. Basically a random locations is selected & a random value is generated. If that random value is greater than the corresponding value in the probablity map, the site is reject. Otherwise a tuft of grass is added at that location.

enter image description hereenter image description here

\$\endgroup\$
2
  • 2
    \$\begingroup\$ This is basically a link only answer & any information is currently behind a subscription wall. If you have access to the material in question, it would be helpful to summarize the steps. \$\endgroup\$
    – Pikalek
    Oct 3, 2018 at 18:40
  • 3
    \$\begingroup\$ I was able to get a hold of the reference material & edited your answer to include a summary. \$\endgroup\$
    – Pikalek
    Oct 5, 2018 at 19:11
3
\$\begingroup\$

While this is an old question, I feel like I have something useful to contribute.

The world generator I'm currently building uses two functions for each biome \$i\$:

  1. The classical heightmap-function \$biome_i(x, y)\$
  2. A weight-function called \$weight_i(x, y)\$.

To get the actual height of a biome the results of those functions are multiplied together: \$elevation_i(x, y) = biome_i(x, y) \times weight_i(x, y)\$

The weight function (or strength, as the asker calls it), can be obtained by whatever means, some suggestions:

  • Another noise function
  • Distance from manually placed "biome control points"
  • Results of some geological simulations like plate tectonics or whatever.

Simply adding all of the biome's height functions obtained this way results in the effects the asker mentioned. At coordinates where multiple biomes have big weights the terrain is extremely elevated.

The trick to solve this is to divide the final elevation by the sum of all heights.

The final heightmap function is $$ elevation(x, y)=\frac{\sum_{i=0}^n (weight_i(x, y)*biome_i(x, y))}{\sum_{i=0}^n weight_i(x, y)}$$.

This ensures a "real" weight sum of exactly 1 (100%) and results in the following properties:

  1. For all coordinates \$x\$,\$y\$, \$height(x, y)\$ ist never higher than the highest \$biome_i(x, y)\$
  2. For all coordinates \$x\$,\$y\$, \$height(x, y)\$ ist never lower than the lowest \$biome_i(x, y)\$
  3. If all functions \$biome_i(x, y)\$ and \$weight_i(x, y)\$ are continuous, so is \$height(x, y)\$

For heightmaps, this means the final elevation will always be between the highest and lowest possible elevation of each of the individual biomes, and there will never be discontinuities.

In the question's example, this may look like this:

static 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)/(fRollingHills_Strength + fMountains_Strength);
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .