Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perlin noise for terrain generation

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?

like image 229
Rei Avatar asked Sep 16 '25 11:09

Rei


1 Answers

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;
like image 78
mk. Avatar answered Sep 18 '25 01:09

mk.