Skip to content

Instantly share code, notes, and snippets.

@Flafla2
Last active April 25, 2024 15:04
Show Gist options
  • Star 37 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save Flafla2/1a0b9ebef678bbce3215 to your computer and use it in GitHub Desktop.
Save Flafla2/1a0b9ebef678bbce3215 to your computer and use it in GitHub Desktop.
Improved Perlin Noise Implementation in C#
public class Perlin {
public static double OctavePerlin(double x, double y, double z, int octaves, double persistence) {
double total = 0;
double frequency = 1;
double amplitude = 1;
for(int i=0;i<octaves;i++) {
total += perlin(x * frequency, y * frequency, z * frequency) * amplitude;
amplitude *= persistence;
frequency *= 2;
}
return total;
}
private static readonly int[] permutation = { 151,160,137,91,90,15, // Hash lookup table as defined by Ken Perlin. This is a randomly
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, // arranged array of all numbers from 0-255 inclusive.
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
};
private static readonly int[] p; // Doubled permutation to avoid overflow
static Perlin() {
p = new int[512];
for(int x=0;x<512;x++) {
p[x] = permutation[x%256];
}
}
public static double perlin(double x, double y, double z) {
if(repeat > 0) { // If we have any repeat on, change the coordinates to their "local" repetitions
x = x%repeat;
y = y%repeat;
z = z%repeat;
}
int xi = (int)x & 255; // Calculate the "unit cube" that the point asked will be located in
int yi = (int)y & 255; // The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
int zi = (int)z & 255; // plus 1. Next we calculate the location (from 0.0 to 1.0) in that cube.
double xf = x-(int)x; // We also fade the location to smooth the result.
double yf = y-(int)y;
double zf = z-(int)z;
double u = fade(xf);
double v = fade(yf);
double w = fade(zf);
int a = p[xi] +yi; // This here is Perlin's hash function. We take our x value (remember,
int aa = p[a] +zi; // between 0 and 255) and get a random value (from our p[] array above) between
int ab = p[a+1] +zi; // 0 and 255. We then add y to it and plug that into p[], and add z to that.
int b = p[xi+1]+yi; // Then, we get another random value by adding 1 to that and putting it into p[]
int ba = p[b] +zi; // and add z to it. We do the whole thing over again starting with x+1. Later
int bb = p[b+1] +zi; // we plug aa, ab, ba, and bb back into p[] along with their +1's to get another set.
// in the end we have 8 values between 0 and 255 - one for each vertex on the unit cube.
// These are all interpolated together using u, v, and w below.
double x1, x2, y1, y2;
x1 = lerp( grad (p[aa ], xf , yf , zf), // This is where the "magic" happens. We calculate a new set of p[] values and use that to get
grad (p[ba ], xf-1, yf , zf), // our final gradient values. Then, we interpolate between those gradients with the u value to get
u); // 4 x-values. Next, we interpolate between the 4 x-values with v to get 2 y-values. Finally,
x2 = lerp( grad (p[ab ], xf , yf-1, zf), // we interpolate between the y-values to get a z-value.
grad (p[bb ], xf-1, yf-1, zf),
u); // When calculating the p[] values, remember that above, p[a+1] expands to p[xi]+yi+1 -- so you are
y1 = lerp(x1, x2, v); // essentially adding 1 to yi. Likewise, p[ab+1] expands to p[p[xi]+yi+1]+zi+1] -- so you are adding
// to zi. The other 3 parameters are your possible return values (see grad()), which are actually
x1 = lerp( grad (p[aa+1], xf , yf , zf-1), // the vectors from the edges of the unit cube to the point in the unit cube itself.
grad (p[ba+1], xf-1, yf , zf-1),
u);
x2 = lerp( grad (p[ab+1], xf , yf-1, zf-1),
grad (p[bb+1], xf-1, yf-1, zf-1),
u);
y2 = lerp (x1, x2, v);
return (lerp (y1, y2, w)+1)/2; // For convenience we bound it to 0 - 1 (theoretical min/max before is -1 - 1)
}
public static double grad(int hash, double x, double y, double z) {
int h = hash & 15; // Take the hashed value and take the first 4 bits of it (15 == 0b1111)
double u = h < 8 /* 0b1000 */ ? x : y; // If the most signifigant bit (MSB) of the hash is 0 then set u = x. Otherwise y.
double v; // In Ken Perlin's original implementation this was another conditional operator (?:). I
// expanded it for readability.
if(h < 4 /* 0b0100 */) // If the first and second signifigant bits are 0 set v = y
v = y;
else if(h == 12 /* 0b1100 */ || h == 14 /* 0b1110*/)// If the first and second signifigant bits are 1 set v = x
v = x;
else // If the first and second signifigant bits are not equal (0/1, 1/0) set v = z
v = z;
return ((h&1) == 0 ? u : -u)+((h&2) == 0 ? v : -v); // Use the last 2 bits to decide if u and v are positive or negative. Then return their addition.
}
public static double fade(double t) {
// Fade function as defined by Ken Perlin. This eases coordinate values
// so that they will "ease" towards integral values. This ends up smoothing
// the final output.
return t * t * t * (t * (t * 6 - 15) + 10); // 6t^5 - 15t^4 + 10t^3
}
public static double lerp(double a, double b, double x) {
return a + x * (b - a);
}
}
@PAEz
Copy link

PAEz commented Aug 10, 2014

@jaywick
Copy link

jaywick commented Feb 11, 2015

Nice work! But just quickly, where does repeat come from and what is it for exactly?

@xahon
Copy link

xahon commented Mar 16, 2018

What is repeat variable?

@petersvp
Copy link

petersvp commented May 6, 2018

This implementation have negative space issues.

@pslusarczyk
Copy link

Works nice, but it's worth noting that the values seem to form normal distribution (I was expecting homogenous distribution form Perlin noise, but maybe I'm wrong?).
Spectrum for values on 1000x1000 grid:

to 0.1: 30
to 0.2: 4490
to  0.3: 60639
to 0.4: 163112
to 0.5: 271021
to 0.6: 273415
to 0.7: 163795
to 0.8: 57599
to 0.9: 5728
to 1.0: 171

@CptBalu
Copy link

CptBalu commented Jan 18, 2019

Works nice, but it's worth noting that the values seem to form normal distribution (I was expecting homogenous distribution form Perlin noise, but maybe I'm wrong?).
Spectrum for values on 1000x1000 grid:

to 0.1: 30
to 0.2: 4490
to  0.3: 60639
to 0.4: 163112
to 0.5: 271021
to 0.6: 273415
to 0.7: 163795
to 0.8: 57599
to 0.9: 5728
to 1.0: 171

This is expected, as adding up multiple waves might cause more frequent passing of the "centerline", while reaching high or low values needs all the subcomponents being at local extremes, which is of course less likely. Similar to throwing multiple dices, in case of 2 you can throw 7 with more combinations, but 12 only with 6+6

@CptBalu
Copy link

CptBalu commented Jan 18, 2019

This implementation have negative space issues.

Here is my solution for negative numbers issue: Map the block coordinates properly with floor function, then adjust fractional parts for negative numbers. Works perfectly, and doesn't mirror the positive regions but flows smoothly ahead:

        int xi = ((int)(Math.Floor(x) % 256) + 256) % 256;
        int yi = ((int)(Math.Floor(y) % 256) + 256) % 256;
        int zi = ((int)(Math.Floor(z) % 256) + 256) % 256;

        double xf = x - (int)x;                             // We also fade the location to smooth the result.
        if (x < 0)
            xf = 1 + xf;
        double yf = y - (int)y;
        if (y < 0)
            yf = 1 + yf;
        double zf = z - (int)z;
        if (z < 0)
            zf = 1 + zf;

@hfiani
Copy link

hfiani commented May 4, 2020

        if (repeat > 0)
        {                                   // If we have any repeat on, change the coordinates to their "local" repetitions
            x = x % repeat;
            y = y % repeat;
            z = z % repeat;
        }

repeat does not exist

@NerdIt
Copy link

NerdIt commented Nov 21, 2020

This implementation have negative space issues.

Here is my solution for negative numbers issue: Map the block coordinates properly with floor function, then adjust fractional parts for negative numbers. Works perfectly, and doesn't mirror the positive regions but flows smoothly ahead:

        int xi = ((int)(Math.Floor(x) % 256) + 256) % 256;
        int yi = ((int)(Math.Floor(y) % 256) + 256) % 256;
        int zi = ((int)(Math.Floor(z) % 256) + 256) % 256;

        double xf = x - (int)x;                             // We also fade the location to smooth the result.
        if (x < 0)
            xf = 1 + xf;
        double yf = y - (int)y;
        if (y < 0)
            yf = 1 + yf;
        double zf = z - (int)z;
        if (z < 0)
            zf = 1 + zf;

I need this lol, but i have no idea how to implement it

@chillersanim
Copy link

This implementation have negative space issues.

Here is my solution for negative numbers issue: Map the block coordinates properly with floor function, then adjust fractional parts for negative numbers. Works perfectly, and doesn't mirror the positive regions but flows smoothly ahead:

        int xi = ((int)(Math.Floor(x) % 256) + 256) % 256;
        int yi = ((int)(Math.Floor(y) % 256) + 256) % 256;
        int zi = ((int)(Math.Floor(z) % 256) + 256) % 256;

        double xf = x - (int)x;                             // We also fade the location to smooth the result.
        if (x < 0)
            xf = 1 + xf;
        double yf = y - (int)y;
        if (y < 0)
            yf = 1 + yf;
        double zf = z - (int)z;
        if (z < 0)
            zf = 1 + zf;

Using your solution, I got some artefacts at regular intervals in the negative range.
I adapted your solution to this, and it seems to work:

int xi, yi, zi;
double xf, yf, zf;

if (pos.x < 0f)                                             // Added support for negative values
{
    xi = 255 - ((int)(-pos.x) & 255);
    xf = 1 + pos.x - (int) pos.x;
}
else
{
    xi = (int) pos.x & 255;
    xf = pos.x - (int) pos.x; 
}

if (pos.y < 0f)
{
    yi = 255 - ((int)(-pos.y) & 255);
    yf = 1 + pos.y - (int) pos.y;
}
else
{
    yi = (int) pos.y & 255;
    yf = pos.y - (int) pos.y;
}

if (pos.z < 0f)
{
    zi = 255 - ((int)(-pos.z) & 255);
    zf = 1 + pos.z - (int) pos.z;
}
else
{
    zi = (int) pos.z & 255;
    zf = pos.z - (int) pos.z;
}

@NerdIt:
In case it's still relevant...
Just replace the following code part with the code above:

int xi = (int) pos.x & 255;   
int yi = (int) pos.y & 255;   
int zi = (int) pos.z & 255;   
double xf = pos.x - (int) pos.x; 
double yf = pos.y - (int) pos.y;
double zf = pos.z - (int) pos.z;

@inewland53
Copy link

inewland53 commented Sep 16, 2021

For anyone looking for the missing "repeat" variable. Here's what to do:

  1. Add a public variable and default constructor
    public int repeat; public Perlin(int repeat = -1) { this.repeat = repeat; }

  2. Remove the words 'static' from the methods 'perlin' and 'OctavePerlin'

@SpeakerBlack
Copy link

I trying used this code but I always get 0.5 as result from:

OctavePerlin(myX, myY, 0, 8, 1);

Im sure that my implementation is wrong... do you can show me a easy example about use this code?

@rafaeldolfe
Copy link

For anyone having issues with this Perlin noise due to problems with negative numbers or w/e (in my case it was big width/height causing an issue), consider using another perlin noise generator. In Unity3D, it works right off the bat to use Sebastian Lague's perlin noise generator:

https://github.com/SebLague/Procedural-Landmass-Generation/blob/master/Proc%20Gen%20E03/Assets/Scripts/Noise.cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment