4

I'm trying to implement 2D Perlin noise to create Minecraft-like terrain (Minecraft doesn't actually use 2D Perlin noise) without overhangs or caves and stuff.

The way I'm doing it, is by creating a [50][20][50] array of cubes, where [20] will be the maximum height of the array, and its values will be determined with Perlin noise. I will then fill that array with arrays of cube.

I've been reading from this article and I don't understand, how do I compute the 4 gradient vector and use it in my code? Does every adjacent 2D array such as [2][3] and [2][4] have a different 4 gradient vector?

Also, I've read that the general Perlin noise function also takes a numeric value that will be used as seed, where do I put that in this case?

approxiblue
  • 6,982
  • 16
  • 51
  • 59
Rei
  • 512
  • 1
  • 7
  • 18
  • 1
    Real Perlin noise is fairly hard, mathematically. [Try smoothing a hash function, it's easier.](http://freespace.virgin.net/hugo.elias/models/m_perlin.htm) [Or just download the code.](http://staffwww.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java) ([PDF](http://webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf)) – markspace Feb 27 '15 at 02:31
  • @markspace That's simplex noise. If you want Perlin noise, try [this beautiful lump of code](http://mrl.nyu.edu/~perlin/noise/), written by the man himself. – mk. Feb 27 '15 at 03:00

3 Answers3

5

I'm going to explain Perlin noise using working code, and without relying on other explanations. First you need a way to generate a pseudo-random float at a 2D point. Each point should look random relative to the others, but the trick is that the same coordinates should always produce the same float. We can use any hash function to do that - not just the one that Ken Perlin used in his code. Here's one:

static float noise2(int x, int y) {
    int n = x + y * 57;
    n = (n << 13) ^ n;
    return (float) (1.0-((n*(n*n*15731+789221)+1376312589)&0x7fffffff)/1073741824.0);
}

I use this to generate a "landscape" landscape[i][j] = noise2(i,j); (which I then convert to an image) and it always produces the same thing:

noise noise noise noise ...

But that looks too random - like the hills and valleys are too densely packed. We need a way of "stretching" each random point over, say, 5 points. And for the values between those "key" points, you want a smooth gradient:

static float stretchedNoise2(float x_float, float y_float, float stretch) {
    // stretch
    x_float /= stretch;
    y_float /= stretch;
    // the whole part of the coordinates
    int x = (int) Math.floor(x_float);
    int y = (int) Math.floor(y_float);
    // the decimal part - how far between the two points yours is
    float fractional_X = x_float - x;
    float fractional_Y = y_float - y;
    // we need to grab the 4x4 nearest points to do cubic interpolation
    double[] p = new double[4];
    for (int j = 0; j < 4; j++) {
        double[] p2 = new double[4];
        for (int i = 0; i < 4; i++) {
            p2[i] = noise2(x + i - 1, y + j - 1);
        }
        // interpolate each row
        p[j] = cubicInterp(p2, fractional_X);
    }
    // and interpolate the results each row's interpolation
    return (float) cubicInterp(p, fractional_Y);
}
public static double cubicInterp(double[] p, double x) {
    return cubicInterp(p[0],p[1],p[2],p[3], x);
}
public static double cubicInterp(double v0, double v1, double v2, double v3, double x) {
    double P = (v3 - v2) - (v0 - v1);
    double Q = (v0 - v1) - P;
    double R = v2 - v0;
    double S = v1;
    return P * x * x * x + Q * x * x + R * x + S;
}

If you don't understand the details, that's ok - I don't know how Math.cos() is implemented, but I still know what it does. And this function gives us stretched, smooth noise.

noise -> noise2

The stretchedNoise2 function generates a "landscape" at a certain scale (big or small) - a landscape of random points with smooth slopes between them. Now we can generate a sequence of landscapes on top of each other:

public static double perlin2(float xx, float yy) {
    double noise = 0;
    noise += stretchedNoise2(xx, yy,  5) * 1; // sample 1
    noise += stretchedNoise2(xx, yy, 13) * 2; // twice as influential

    // you can keep repeating different variants of the above lines
    // some interesting variants are included below.

    return noise / (1+2); // make sure you sum the multipliers above
}

To put it more accurately, we get the weighed average of the points from each sample.

( noise2 + 2 * noise3 ) / 3 = enter image description here

When you stack a bunch of smooth noise together, usually about 5 samples of increasing "stretch", you get Perlin noise. (If you understand the last sentence, you understand Perlin noise.)

There are other implementations that are faster because they do the same thing in different ways, but because it is no longer 1983 and because you are getting started with writing a landscape generator, you don't need to know about all the special tricks and terminology they use to understand Perlin noise or do fun things with it. For example:

1) noise 2) noise 3) noise

    // 1
    float smearX = interpolatedNoise2(xx, yy, 99) * 99;
    float smearY = interpolatedNoise2(xx, yy, 99) * 99;
    ret += interpolatedNoise2(xx + smearX, yy + smearY,  13)*1;

    // 2
    float smearX2 = interpolatedNoise2(xx, yy, 9) * 19;
    float smearY2 = interpolatedNoise2(xx, yy, 9) * 19;
    ret += interpolatedNoise2(xx + smearX2, yy + smearY2,  13)*1;

    // 3
    ret += Math.cos( interpolatedNoise2(xx , yy , 5)*4) *1;
mk.
  • 11,360
  • 6
  • 40
  • 54
  • sorry, I don't really understand you :/ the first step that you described, which step in the wikipedia article is it actually? – Rei Feb 27 '15 at 09:26
  • I can give an explanation of Perlin noise, but I can't explain other people's explanations. Many of them are unnecessarily confusing (though [some are good](http://freespace.virgin.net/hugo.elias/models/m_perlin.htm)). If you have trouble understanding something above, let me know. I made some edits and added some pictures that might help. – mk. Feb 28 '15 at 04:31
  • @mk. I know I'm almost 7 years late to the party, but I just want you to know that this answer helped me greatly with a Perlin noise implementation problem I was struggling with for a long time. Thank you so much! – M_Dragon Jan 11 '22 at 22:05
0

About perlin noise

Perlin noise was developed to generate a random continuous surfaces (actually, procedural textures). Its main feature is that the noise is always continuous over space.

From the article:

Perlin noise is function for generating coherent noise over a space. Coherent noise means that for any two points in the space, the value of the noise function changes smoothly as you move from one point to the other -- that is, there are no discontinuities.

Simply, a perlin noise looks like this:

 _         _    __
   \    __/ \__/  \__
    \__/

But this certainly is not a perlin noise, because there are gaps:

 _         _ 
  \_    __/  
    ___/    __/

Calculating the noise (or crushing gradients!)

As @markspace said, perlin noise is mathematically hard. Lets simplify by generating 1D noise.

Imagine the following 1D space:

________________

Firstly, we define a grid (or points in 1D space):

1    2    3    4
________________

Then, we randomly chose a noise value to each grid point (This value is equivalent to the gradient in the 2D noise):

1    2    3    4
________________
-1   0    0.5  1  // random noise value

Now, calculating the noise value for a grid point it is easy, just pick the value:

noise(3) => 0.5

But the noise value for a arbitrary point p needs to be calculated based in the closest grid points p1 and p2 using their value and influence:

// in 1D the influence is just the distance between the points
noise(p)   => noise(p1) * influence(p1) + noise(p2) * influence(p2)
noise(2.5) => noise(2)  * influence(2, 2.5) + noise(3) * influence(3, 2.5)
           => 0 * 0.5 + 0.5 * 0.5 => 0.25

The end! Now we are able to calculate 1D noise, just add one dimension for 2D. :-)

Hope it helps you understand! Now read @mk.'s answer for working code and have happy noises!

Edit:

Follow up question in the comments:

I read in wikipedia article that the gradient vector in 2d perlin should be length of 1 (unit circle) and random direction. since vector has X and Y, how do I do that exactly?

This could be easily lifted and adapted from the original perlin noise code. Find bellow a pseudocode.

gradient.x = random()*2 - 1;
gradient.y = random()*2 - 1;
normalize_2d( gradient );

Where normalize_2d is:

// normalizes a 2d vector
function normalize_2d(v)
   size = square_root( v.x * v.x + v.y * v.y );
   v.x = v.x / size;
   v.y = v.y / size;
Community
  • 1
  • 1
Filipe Borges
  • 2,712
  • 20
  • 32
  • I read in wikipedia article that the gradient vector in 2d perlin should be length of 1 (unit circle) and random direction. since vector has X and Y, how do I do that exactly? – Rei Feb 27 '15 at 08:46
  • **NOTE:** I deleted some comments because they are not related with 2D perling noise (but to 1D perling noise) and are not constructive to your follow up question – Filipe Borges Feb 27 '15 at 17:07
  • I updated the answer to include how generate normalized vector (size 1) with random direction. – Filipe Borges Feb 27 '15 at 17:30
  • @Filipee Borges thank you for the answer. there's some thing I still don't understand, in most perlin noise library, (that I see in processing and unity), the perlin noise function takes a seed number so that the result is different, but the result is always the same for the same seed number (and in the article in my question, the 4 gradient is also always the same, for the same X and Y), where does that seed number is used in these mathematical steps? – Rei Feb 27 '15 at 21:45
  • the seed number is the seed for the pseudo random generator. @see: http://docs.oracle.com/javase/7/docs/api/java/util/Random.html#setSeed%28long%29 and http://www.cplusplus.com/reference/cstdlib/srand/ – Filipe Borges Feb 28 '15 at 01:28
  • Simply put, before calling a random function, you have to initialize the random number generator using a seed (ex: `Random rnd = new Random(); rnd.setSeed(seed);` in Java). For the same seed, the `rnd.next()` calls return the same sequence of pseudo random numbers. Just play with it in Java or C# to understand. – Filipe Borges Feb 28 '15 at 01:34
  • @Rei if you want to inject a seed, you insert it into the lowest-level ("hash") noise function. Or you can just multiply it by 1000 and add it to the x coordinate. – mk. Feb 28 '15 at 04:37
  • so let's say we can use seed when we want random values, (say, by adding it to the base X and Y), but according to ken perlin in this talk http://www.noisemachine.com/talk1/15.html , the surrounding grid points (the 4 gradient vector) should always be the same for a particular grid point, that means when another same X and Y is called, the surrounding grid point is always the same, right? how do we do that? – Rei Feb 28 '15 at 06:56
-1

Compute Perlin noise at coordinates x, y

function perlin(float x, float y) {

    // Determine grid cell coordinates
    int x0 = (x > 0.0 ? (int)x : (int)x - 1);
    int x1 = x0 + 1;
    int y0 = (y > 0.0 ? (int)y : (int)y - 1);
    int y1 = y0 + 1;

    // Determine interpolation weights
    // Could also use higher order polynomial/s-curve here
    float sx = x - (double)x0;
    float sy = y - (double)y0;

    // Interpolate between grid point gradients
    float n0, n1, ix0, ix1, value;
    n0 = dotGridGradient(x0, y0, x, y);
    n1 = dotGridGradient(x1, y0, x, y);
    ix0 = lerp(n0, n1, sx);
    n0 = dotGridGradient(x0, y1, x, y);
    n1 = dotGridGradient(x1, y1, x, y);
    ix1 = lerp(n0, n1, sx);
    value = lerp(ix0, ix1, sy);

    return value;
}
teo van kot
  • 12,350
  • 10
  • 38
  • 70
Justin
  • 1