Search code examples
perlin-noiseprocedural-generationsimplex-noisenoise-generator

How do the permutation and gradient tables of Perlin and Simplex Noise work in practice?


So I have been doing a bit of research into how Perlin and Simplex noise work and, while I get the core principles of regular Perlin noise I'm a little bit confused about how the permutation and gradient tables work.

From my understanding, they provide better performance than a seeded random number generator as they are tables of pre-computed values that are nicely indexed for quick access.

What I don't entirely get though is how they work practically. I've seen a permutation table implemented as a array of the shuffled values from 0-255 like so:

permutation[] = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};

But I'm unsure what is the practial purpose of this. What I want to know is:

  • How is the permutation table used in relation to the grid points?
  • How is the gradient table generated?
  • How are the values from the permutation table used with the gradient table? Do the permutation values correspond to indices from the gradient table?

Solution

  • I've been taking the libnoise and perlin noise code apart off and on for a while now so that I could understand how it all worked. I hate working with code I don't understand :)

    Walking through http://catlikecoding.com/unity/tutorials/noise/ may help you if you don't use Unity, but you might be able to convert the code accordingly. It helped me alot.

    There are various other sites out there with hints and tips. Google libnoise, procedural, etc should show you some examples you can look through.

    Basically though the gradients used in noise in conjunction with the integer array are the points around 0,0,0 with a few extra to pad it out to a set number. Using a combination of the integer number picked based on the x,y,z coordinate (0 and 1 indicating each side of the point ) for example such that you have:

    // Separate the integer element
    int ix0 = int(point.x);
    int iy0 = int(point.y);
    int iz0 = int(point.z);
    
    // Grab the fractional parts for use later
    float tx0 = point.x - ix0;
    float ty0 = point.y - iy0;
    float tz0 = point.z - iz0;
    float tx1 = tx0 - 1f;
    float ty1 = ty0 - 1f;
    float tz1 = tz0 - 1f;
    
    // Make sure that it is a value compatible with the integer array
    ix0 &= hashMask;
    iy0 &= hashMask;
    iz0 &= hashMask;
    
    // Get the other side of the point
    int ix1 = ix0 + 1;
    int iy1 = iy0 + 1;
    int iz1 = iz0 + 1;
    
    // Grab the integers found at the location in the array
    int h0 = hash[ix0];
    int h1 = hash[ix1];
    int h00 = hash[h0 + iy0];
    int h10 = hash[h1 + iy0];
    int h01 = hash[h0 + iy1];
    int h11 = hash[h1 + iy1];
    
    // Gradient array
    private static Vector3[] gradients3D = {
        new Vector3( 1f, 1f, 0f),
        new Vector3(-1f, 1f, 0f),
        new Vector3( 1f,-1f, 0f),
        new Vector3(-1f,-1f, 0f),
        new Vector3( 1f, 0f, 1f),
        new Vector3(-1f, 0f, 1f),
        new Vector3( 1f, 0f,-1f),
        new Vector3(-1f, 0f,-1f),
        new Vector3( 0f, 1f, 1f),
        new Vector3( 0f,-1f, 1f),
        new Vector3( 0f, 1f,-1f),
        new Vector3( 0f,-1f,-1f),
    
        new Vector3( 1f, 1f, 0f),
        new Vector3(-1f, 1f, 0f),
        new Vector3( 0f,-1f, 1f),
        new Vector3( 0f,-1f,-1f)
    };
    
    private const int gradientsMask3D = 15;
    
    // Grab the gradient value at the requested point
    Vector3 g000 = gradients3D[hash[h00 + iz0] & gradientsMask3D];
    Vector3 g100 = gradients3D[hash[h10 + iz0] & gradientsMask3D];
    Vector3 g010 = gradients3D[hash[h01 + iz0] & gradientsMask3D];
    Vector3 g110 = gradients3D[hash[h11 + iz0] & gradientsMask3D];
    Vector3 g001 = gradients3D[hash[h00 + iz1] & gradientsMask3D];
    Vector3 g101 = gradients3D[hash[h10 + iz1] & gradientsMask3D];
    Vector3 g011 = gradients3D[hash[h01 + iz1] & gradientsMask3D];
    Vector3 g111 = gradients3D[hash[h11 + iz1] & gradientsMask3D];
    
    // Calculate the dot product using the vector and respective fractions
    float v000 = Dot(g000, tx0, ty0, tz0);
    float v100 = Dot(g100, tx1, ty0, tz0);
    float v010 = Dot(g010, tx0, ty1, tz0);
    float v110 = Dot(g110, tx1, ty1, tz0);
    float v001 = Dot(g001, tx0, ty0, tz1);
    float v101 = Dot(g101, tx1, ty0, tz1);
    float v011 = Dot(g011, tx0, ty1, tz1);
    float v111 = Dot(g111, tx1, ty1, tz1);
    
    // Interpolate between 2 dot results using the fractional numbers 
    l0 = Lerp(v000, v100, tx);
    l1 = Lerp(v010, v110, tx);
    l2 = Lerp(l0,l1,ty);
    
    l3 = Lerp(v001, v101, tx);
    l4 = Lerp(v011, v111, tx);
    l5 = Lerp(l3,l4,ty);
    
    l6 = Lerp(l2,l5,tz);
    

    This results in a single number that is a representative of a single unique point in space using the same integer and gradient array. Simply changing the seed and reshuffling the integer array and gradient array will generate a different number allowing you bring uniqueness to an item but using the same code to generate it.

    The reason why the integer array is a repeated set of numbers totalling 512 elements is so that the lookups do not accidentally go over the 0-255 limit which the +1 values added in the code above could cause.

    If you visualise a line ( 1D x0 - x1 ), a square ( 2D x0,y0 - x1,y1 ) and a cube ( 3D x0,y0,z0 - x1,y1,z1 ) you will hopefully see what the code is doing and that for the most part the code will be very similar.

    I tried making my own version of the code but despite several attempts I can now understand why everyone's noise code is so similar. There is really only the one way perlin and similarly simplex noise will work.

    So my goal now is to work this functionality into shader equivalent code to help me, at least, to understand the ins and outs of both perlin noise and shader programming. It's a learning curve but it's fun at the same time.

    Well hopefully, this has answered all your questions. If you want to know whys and wherefores of Ken Perlin's improved Perlin code check out the following:

    http://http.developer.nvidia.com/GPUGems/gpugems_ch05.html - visual of cube