Search code examples
cgraphicsperlin-noise

Perlin Noise Attempt


I am trying my attempt at Perlin Noise (3-dimensional) as outlined in this document: http://lodev.org/cgtutor/randomnoise.html

However, this is what I'm getting. perlin noise attempt It looks like the smoothing isn't working. You can see blocks the size of the 'size' parameter. Can someone point out what I'm doing wrong?

Here's my code:

%ffp

ctl(1):standard,"Size",range=(1,256), pos=(300,20), size=(120,*),val=64,track, action=preview

onFilterStart:
{
allocArray(9,64,64,64,4);   // Array for noise depth
  for(int z = 0; z < 64; z++)
  for(int y = 0; y < 64; y++)
  for(int x = 0; x < 64; x++) {
    fputArray(9,x,y,z,(float)(rand() % 32768) / 32768.0);
  }

return false;
}

forEveryTile:
{
double fractX,fractY,fractZ,xx,yy,zz;
int x1,y1,z1,x2,y2,z2,col;
double value = 0.0, value2 = 0.0, size, isize=(float)ctl(1);
// int X=screen Width, int Y=screen Height

  for(int y = 0; y < Y; y++) {
  for(int x = 0; x < X; x++) {
  //for(int z = 0; z < 64; z++) {
  value2 = 0.0;
  size = isize;

  while (size >=1.0) {
  xx=(float)x/size;
  yy=(float)y/size;
  zz=(float)clock()/size;
  fractX = xx - (int)(xx);
  fractY = yy - (int)(yy);
  fractZ = zz - (int)(zz);
  x1 = ((int)(xx) + 64) % 64;
  y1 = ((int)(yy) + 64) % 64;
  z1 = ((int)(zz) + 64) % 64;
  x2 = (x1 + 64- 1) % 64;
  y2 = (y1 + 64- 1) % 64;
  z2 = (z1 + 64- 1) % 64;

  value=0.0;
  value += fractX     * fractY     * fractZ     * fgetArray(9,z1,y1,x1);
  value += fractX     * (1 - fractY) * fractZ     * fgetArray(9,z1,y2,x1);
  value += (1 - fractX) * fractY     * fractZ     * fgetArray(9,z1,y1,x2);
  value += (1 - fractX) * (1 - fractY) * fractZ     * fgetArray(9,z1,y2,x2);

  value += fractX     * fractY     * (1 - fractZ) * fgetArray(9,z2,y1,x1);
  value += fractX     * (1 - fractY) * (1 - fractZ) * fgetArray(9,z2,y2,x1);
  value += (1 - fractX) * fractY     * (1 - fractZ) * fgetArray(9,z2,y1,x2);
  value += (1 - fractX) * (1 - fractY) * (1 - fractZ) * fgetArray(9,z2,y2,x2);

  value2 += value*size;
  size /= 2.0;
  }

  col=(int)((float)(128.0 * value2 / isize));
  col=max(min(col,255),0);
  psetp(x,y,RGB(col,col,col));

//} //z
} //x
} //y

return true;
}

Solution

  • Your code is kind of hard to read as written.

    For Perlin noise start out with a integer noise function, that behaves like a hash.

    float noise(int x, int y, int z) { return hash(x+y*5+z*7); }
    

    or

    float noise(int x, int y, int z) { return array[x%w+y%h*w+z%d*w*h]; }
    

    Those are just examples. The important part is that noise(x,y,z) = noise(x,y,z). The noise function has to return the same value for the same parameters every time.

    There is a problem though: The noise function only takes integer parameters! But we would like to sample it at float values.

    float noisesample (float x, float y, float z) { ... }
    

    The easiest way to to that is using linear filtering. Any positive float value is between (int)pos and ((int)pos)+1. At sub-position pos-(int)pos. This gets us:

    float Lerp(float a, float b, float f) { return a+(b-a)*f; }
    

    Where f is the sub-position in the [0..1] range and a,b are the values to the left and right. If f is 0, Lerp returns a, if it is 1, it returns b. In between it does linear interpolation.

    So use this for a simple 1D noisesample function:

    float noisesample(float x) { return Lerp(noise((int)x), noise((int)x+1), fract(x) }
    

    with

    float fract(float x) { return x-(int)x; } 
    

    I am using (int)x liberally here, it is the same as floor(x) if x is positive.

    To go from a single parameter noisesample to x,y is easy: Do the Lerp twice for x at y and y+1, and Lerp between those:

    float noisesample(float x, float y) {
        float y0 = Lerp(noise((int)x,(int)y), noise((int)x+1,(int)y), fract(x) }
        float y1 = Lerp(noise((int)x,(int)y+1), noise((int)x+1,(int)y+1), fract(x) }
        return Lerp ( y0, y1, fract(y) );
    }
    

    First interpolate x, twice, then interpolate between the results in y. In total we sample noise() 4 times. I leave it as an exercise how to write noisesample ( float x, float y, float z). It will sample noise() eight times and call Lerp 7 times.

    All that got us is that we can sample noise (somewhat smooth - there are smoother ways!) at float coordinates. And that is what we need to make perlin noise!

    float perlin(float x, float y, float z, int oc=4) {
         // maybe: x = x*2^oc, y, z... 
        float r = 0;
        float s = 1;
        for ( int i=0; i<oc; i++ ) {
            r += noisesample(x,y,z) * s;
            s/=2.0f; // to taste
            x/=2.0f;
            y/=2.0f;
            z/=2.0f; 
        }
        return r; 
    }
    

    The key idea is to understand sampling. It's just a combination of sampling a simple integer noise function.