38

I'm trying to implement some source code I found online to generate a height map using Perlin Noise. I've successfully managed to get the height map using the noise3 function, with the third coordinate being a random "seed", to allow for random height maps.

My problem is that the terrain generated is rather dull - I want mountains and I'm getting rolling grassland. I've done some reading up on Perlin Noise (mostly here). Due to the source code I've found obviously not written with readability in mind and my weak grasp on the concept of Perlin Noise in general, I can't figure out what I need to tweak in the code (amplitude and frequency?) to create more drastic terrain.

Some more info on generating height maps using Perlin Noise, Perlin Noise in general, or even some more decipherable code would also be welcome.

EDIT: I understand (kind of) how Perlin Noise works, e.g., with respect to amplitude and frequency, I'm just wondering what variables to change in the code I linked above, which are used for these two aspects.

0xCursor
  • 2,242
  • 4
  • 15
  • 33
Emma
  • 2,012
  • 5
  • 21
  • 30
  • 1
    Plain Perlin noise ("1 layer of noise") is boring. What you're looking for is commonly called "fractal brownian motion noise" (fBm), where you add Perlin noise of different frequencies together. See [here for great definitions of lacunarity, frequency, octave](http://libnoise.sourceforge.net/glossary/index.html) wrt Perlin fractal noise – bobobobo Dec 22 '12 at 14:21
  • For those who come hereafter: The concepts that Eric was trying to implement based on Elias' article are not actually Perlin noise but value noise, layered into fractal (pink) noise. – LarsH Jan 10 '17 at 11:28

4 Answers4

55

Perlin noise is completely controlled by the different variables you set, i.e. amplitude, frequency and persistance. The amount of octaves has a little change, but not much. In code that I have written in the past I have just played around with the order of magnitude of the frequency and persistance until I have gotten what I needed. I can try to find my old source if needed.

PerlinNoise.h

#pragma once

class PerlinNoise
{
public:

  // Constructor
    PerlinNoise();
    PerlinNoise(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed);

  // Get Height
    double GetHeight(double x, double y) const;

  // Get
  double Persistence() const { return persistence; }
  double Frequency()   const { return frequency;   }
  double Amplitude()   const { return amplitude;   }
  int    Octaves()     const { return octaves;     }
  int    RandomSeed()  const { return randomseed;  }

  // Set
  void Set(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed);

  void SetPersistence(double _persistence) { persistence = _persistence; }
  void SetFrequency(  double _frequency)   { frequency = _frequency;     }
  void SetAmplitude(  double _amplitude)   { amplitude = _amplitude;     }
  void SetOctaves(    int    _octaves)     { octaves = _octaves;         }
  void SetRandomSeed( int    _randomseed)  { randomseed = _randomseed;   }

private:

    double Total(double i, double j) const;
    double GetValue(double x, double y) const;
    double Interpolate(double x, double y, double a) const;
    double Noise(int x, int y) const;

    double persistence, frequency, amplitude;
    int octaves, randomseed;
};

PerlinNoise.cpp

#include "PerlinNoise.h"

PerlinNoise::PerlinNoise()
{
  persistence = 0;
  frequency = 0;
  amplitude  = 0;
  octaves = 0;
  randomseed = 0;
}

PerlinNoise::PerlinNoise(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed)
{
  persistence = _persistence;
  frequency = _frequency;
  amplitude  = _amplitude;
  octaves = _octaves;
  randomseed = 2 + _randomseed * _randomseed;
}

void PerlinNoise::Set(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed)
{
  persistence = _persistence;
  frequency = _frequency;
  amplitude  = _amplitude;
  octaves = _octaves;
  randomseed = 2 + _randomseed * _randomseed;
}

double PerlinNoise::GetHeight(double x, double y) const
{
  return amplitude * Total(x, y);
}

double PerlinNoise::Total(double i, double j) const
{
    //properties of one octave (changing each loop)
    double t = 0.0f;
    double _amplitude = 1;
    double freq = frequency;

    for(int k = 0; k < octaves; k++) 
    {
        t += GetValue(j * freq + randomseed, i * freq + randomseed) * _amplitude;
        _amplitude *= persistence;
        freq *= 2;
    }

    return t;
}

double PerlinNoise::GetValue(double x, double y) const
{
    int Xint = (int)x;
    int Yint = (int)y;
    double Xfrac = x - Xint;
    double Yfrac = y - Yint;

  //noise values
  double n01 = Noise(Xint-1, Yint-1);
  double n02 = Noise(Xint+1, Yint-1);
  double n03 = Noise(Xint-1, Yint+1);
  double n04 = Noise(Xint+1, Yint+1);
  double n05 = Noise(Xint-1, Yint);
  double n06 = Noise(Xint+1, Yint);
  double n07 = Noise(Xint, Yint-1);
  double n08 = Noise(Xint, Yint+1);
  double n09 = Noise(Xint, Yint);

  double n12 = Noise(Xint+2, Yint-1);
  double n14 = Noise(Xint+2, Yint+1);
  double n16 = Noise(Xint+2, Yint);

  double n23 = Noise(Xint-1, Yint+2);
  double n24 = Noise(Xint+1, Yint+2);
  double n28 = Noise(Xint, Yint+2);

  double n34 = Noise(Xint+2, Yint+2);

    //find the noise values of the four corners
    double x0y0 = 0.0625*(n01+n02+n03+n04) + 0.125*(n05+n06+n07+n08) + 0.25*(n09);  
    double x1y0 = 0.0625*(n07+n12+n08+n14) + 0.125*(n09+n16+n02+n04) + 0.25*(n06);  
    double x0y1 = 0.0625*(n05+n06+n23+n24) + 0.125*(n03+n04+n09+n28) + 0.25*(n08);  
    double x1y1 = 0.0625*(n09+n16+n28+n34) + 0.125*(n08+n14+n06+n24) + 0.25*(n04);  

    //interpolate between those values according to the x and y fractions
    double v1 = Interpolate(x0y0, x1y0, Xfrac); //interpolate in x direction (y)
    double v2 = Interpolate(x0y1, x1y1, Xfrac); //interpolate in x direction (y+1)
    double fin = Interpolate(v1, v2, Yfrac);  //interpolate in y direction

    return fin;
}

double PerlinNoise::Interpolate(double x, double y, double a) const
{
    double negA = 1.0 - a;
  double negASqr = negA * negA;
    double fac1 = 3.0 * (negASqr) - 2.0 * (negASqr * negA);
  double aSqr = a * a;
    double fac2 = 3.0 * aSqr - 2.0 * (aSqr * a);

    return x * fac1 + y * fac2; //add the weighted factors
}

double PerlinNoise::Noise(int x, int y) const
{
    int n = x + y * 57;
    n = (n << 13) ^ n;
  int t = (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff;
    return 1.0 - double(t) * 0.931322574615478515625e-9;/// 1073741824.0);
}
Nick Banks
  • 4,298
  • 5
  • 39
  • 65
  • 3
    Just for the record, despite the name, the code shown here is an implementation of **value** noise, layered into **fractal** noise. It is **not** Perlin noise, which is a type of gradient noise (https://en.wikipedia.org/wiki/Gradient_noise). – LarsH Jan 10 '17 at 11:31
21

A friend just linked me to this question, and I thought I'd try and clear up a couple things that aren't addressed in the accepted answer.

Elias' interesting and helpful article uses "value noise" not "Perlin noise". Value noise involves curve-fitting of randomized points. Gradient noise (of which Perlin noise is a primary example) creates a lattice of 0-value points and gives each one a random gradient. They are frequently confused with one another!

http://en.wikipedia.org/wiki/Gradient_noise

Secondly, using a 3rd value as a seed is expensive. If you want random terrain, consider translating your origin a random amount instead. 3D calls are significantly more expensive than 2D calls (e.g., prefer getNoise2D(x + XSEED, y + YSEED) over getNoise3D(x, y, ZSEED)). Assuming the z value remains constant, all you are doing is using the z value to select a particular slice of 2D noise.

Thirdly, the straight function call is going to return values that are fairly smooth and rolling overall, not as craggy as real terrain, since it's randomness is limited to a single frequency. To get craggier terrain, a good technique is to sum together multiple calls that progress through the noise space at differing frequencies, usually set a "fractal" values.

Thus, for example, sum together noise(x, y) + (1/2)(noise(x*2, y*2) + (1/4)(noise(x*4, y*4)...

The resulting sum will probably often be outside the range -1 to 1, so you will have to normalize the result before the values are useful. I'd like to suggest setting up a series that is guaranteed to remain within [-1, 1], for example, by progressive weighting depending upon how many 'octaves' you use. (But I don't know if this is truly the most efficient way to do this.)

Example with four octaves: (1/15)(noise(x, y) + (2/15)(noise(2x, 2y) + (4/15)(noise(4x, 4y) + (8/15)(noise(8x, 8y)

Fourthly, when mapping the results, which range from -1 to 1, to the normalization more often used with color values or color maps (0 to 1), Ken Perlin described two algorithms. One was given the name "smooth", where the mapped values are operated on by a simple translation algorithm:

f(x) = (x + 1) / 2

The other was given the name "turbulent", where the mapped values are computed as follows:

f(x) = | x |;

With the former, the resulting values will range over the color range, with sparser population at the extremes. With the latter, the resulting values will "fold" at one end of the color range or color map. This folding will give the terrain angular ridges at the fold point, as opposed to being smoothly rolling. (This assumes that one is keeping the sum of the octaves within the range -1 to 1, and that if a custom color mapping is being used, the color ranges progress smoothly over the course of the map. Neither of these conditions is "required" though, and can be played with for interesting effects.)

I'm working on a SimplexNoise visualizer... [Edit: now up at GitHub: SiVi: A Java-based 2D Gradient Noise Visualizer] ... as a Java project. A first draft of the visualizer can be found ... [Edit: I am deleting a dead link to the old java-gaming.org site. The site has been migrated to jvm.gaming.org. If you go to jvm-gaming, be warned that older references to SiVi tend to have dead links, as well.]

Great article on how SimplexNoise works (and Perlin vs Gradient background): http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf

Stefan Gustavson did a really nice job of this!

Phil Freihofner
  • 7,645
  • 1
  • 20
  • 41
5

Amplitude controls how high/low the terrain is, the frequency how flowing it is, with lower frequency being more flowing.

So if you want a jagged mountainous landscape you need to up both.

wich
  • 16,709
  • 6
  • 47
  • 72
  • He may want ballpark figures to get started -- if so: 2-6 for frequency good for small terrains, octaves 1-16 is fine, amp is just a normalized field. I generate a city and choose buildings based on it for a 32x32 grid, and find these ranges are fine...freq 5+ gives me better results for what I'm doing. – SinisterRainbow Dec 01 '12 at 23:57
2

Here's an example of surface generation I wrote a while ago in JavaScript using 3D Perlin Noise. Since in a surface voxels are either present or not I simply apply a threshold after calculating the Perlin Noise cube. In the example the noise probability is equal for all dimensions. You can get a more realistic landscape when you increase the random values towards the ground and reduce it towards the sky.

http://kirox.de/test/Surface.html

WebGL must be enabled. At the time of writing this I recommend to use Chrome for best performance.

oyophant
  • 1,294
  • 12
  • 23