Noise Functions

noise based terrain pixel art

In a Nutshell

Noise functions are used to add variation. It is useful for making procedural oceans, terrains, forests & vegetations and so on.

1. White noise

2. Value noise

3. Perlin noise

4. Voronoi (Worley)

5. Fractal Brownian Motion

White Noise

White noise is one of the simplest of the noises, made up of unrelated samples of random values. The mean of all samples is approximately zero. In shaders, it can be approximated using a simple hash function.

film grain shader effect on scene with pirate ship and sea shore, low poly art
Film grain shader uses white noise
// Function to generate a pseudo-random value based on a 2D coordinate
float hash(vec2 position){
    return fract(sin(dot(position, vec2(12.9898, 78.233))) * 43758.5453);
}

The above code does dot product of pixel position with some constant vec2 & then passes the result through sin. Finally, it uses fract function to keep values in range (0, 1).

Value Noise

Value noise is used in situations where we do not want abrupt changes in values, unlike white noise. So it is made by sampling the hash function on vertices of grid, and then interpolating the values between vertices.

In procedural generation, it can be used to generate terrain, textures & to control intensity of various world parameters such as wind & moisture. However, it is relatively inferior to perlin noise due to its blocky nature which makes it look not so realistic (see the images above).

But since it is cheap to sample, it can be used both on CPU side & GPU side to create good looking world with many parameters.

Code for Value Noise:

// Function to generate a pseudo-random value based on a 2D coordinate
float hash(vec2 position){
    return fract(sin(dot(position, vec2(12.9898, 78.233))) * 43758.5453);
}

// value noise
float valueNoise(vec2 p)
{
    // Integer coordinates of the grid cell
    vec2 i = floor(p);

    // Fractional coordinates within the grid cell
    vec2 f = fract(p);

    // Smoothstep function for interpolation
    vec2 u = f * f * (3.0 - 2.0 * f);

    // Hash values for the four corners of the grid cell
    float a = hash(i);
    float b = hash(i + vec2(1.0, 0.0));
    float c = hash(i + vec2(0.0, 1.0));
    float d = hash(i + vec2(1.0, 1.0));

    // Bilinear interpolation
    return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

Perlin Noise

Perlin noise was created because of the blocky & unrealistic looking nature of value noise. In perlin, the values are not simply sampled on vertices & interpolated on places between vertices. Instead of a random value, a random vec2 is sampled at each vertex (using a 2D hash function) & the values between vertices are interpolated based on this 2D gradient vector. This results in a better looking noise.

There are some variations of perlin noise such as improved perlin & simplex noises that we use commonly today.

terrain using perlin noise
Terrain using Perlin noise

Code for Perlin:

// perlin
vec2 hash2d(vec2 position){
    position = vec2( dot(position,vec2(334.1, 781.7)),
              dot(position,vec2(652.5, 153.3)) );
    return -1.0 + 3.0 * fract(sin(position) * 241.5453123);
}


float perlin(vec2 position) {
    vec2 i = floor(position);
    vec2 f = fract(position);

    vec2 u = f * f * (3.0 - 2.0 * f);

    return 0.5 + (0.5 * mix( mix( dot( hash2d(i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ),
                     dot( hash2d(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),
                mix( dot( hash2d(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ),
                     dot( hash2d(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y));
}

Voronoi noise

It is a cellular noise, that can be used to add blobbly effects to things. It can also be used to add tiling & cracks. Also, worley noise is similar thing, which is based on voronoi diagram.

Complete code for voronoi noise:

float voronoi(vec2 point){
    vec2 p = floor( point );
    vec2 f = fract( point );
    float res = 0.0;
    for( int j=-1; j<=1; j++ ) {
        for( int i=-1; i<=1; i++ ) {
            vec2 b = vec2( float(i), float(j) );
            vec2 r = vec2( b ) - f + hash( p + b);
            res += 1./pow(dot(r,r),8.);
        }
    }
    return pow(1./res, 0.0625);
}

Fractional Brownian Motion (fBm)

Fractional Brownian Motion (FBM) is summing several octaves/layers of perlin or simplex noise with varying frequencies and amplitudes.

// 2D Value noise function
float hash(vec2 p) {
    p = fract(p * 0.3183099 + vec2(0.1, 0.1));
    p *= 17.0;
    return fract(p.x * p.y * (p.x + p.y));
}

// 2D Value noise function (smooth)
float noise(vec2 x) {
    vec2 p = floor(x);
    vec2 f = fract(x);

    float n = 
        hash(p)       * (1.0 - f.x) * (1.0 - f.y) +
        hash(p + vec2(1.0, 0.0)) * f.x * (1.0 - f.y) +
        hash(p + vec2(0.0, 1.0)) * (1.0 - f.x) * f.y +
        hash(p + vec2(1.0, 1.0)) * f.x * f.y;

    return n;
}

// Fractional Brownian Motion function (2D)
float fbm(vec2 p) {
	int octaves = 6;
	float lacunarity = 3.0;
	float gain = 0.5;
    float amplitude = 1.0;
    float frequency = 1.5;
    float total = 0.0;

    for (int i = 0; i < octaves; i++) {
        total += noise(p * frequency) * amplitude;
        frequency *= lacunarity;
        amplitude *= gain;
    }

    return total / 2.0;
}