Noise Functions

noise based terrain pixel art

In a Nutshell

Noise functions of various kinds are used in game development to achieve variety of tasks. It includes making oceans, terrains, forests & vegetations and so on. This post discusses different kinds of noises, their implementation & uses. in nutshell following are the noises I’ll discuss today:

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.

// 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.

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 a technique used in procedural noise generation, such as Perlin or Simplex noise. It involves summing several octaves of noise with varying frequencies and amplitudes.

float hash(vec2 p) {
  return fract(sin(dot(p * 17.17, vec2(14.91, 67.31))) * 4791.9511);
}

float noise(vec2 x) {
  vec2 p = floor(x);
  vec2 f = fract(x);
  f = f * f * (3.0 - 2.0 * f);
  vec2 a = vec2(1.0, 0.0);
  return mix(mix(hash(p + a.yy), hash(p + a.xy), f.x),
         mix(hash(p + a.yx), hash(p + a.xx), f.x), f.y);
}

float fbm(vec2 x) {
  float height = 0.0;
  float amplitude = 0.5;
  float frequency = 3.0;
  for (int i = 0; i < 6; i++){
    height += noise(x * frequency) * amplitude;
    amplitude *= 0.5;
    frequency *= 2.0;
  }
  return height;
}

How textures are created using noise functions?

1. 101 example of sand texture: a general sand texture can be generated using white noise. To add waves to it, like in deserts, we can produce bands using sine wave & then apply perlin noise on bands to distort them randomly.

For a procedurally generated world, it is a good idea to utilize noise functions to create textures, since sampling large amount of textures can slow your performance down for a really large terrain.

More reading

There are some other variations of these noise functions that I decided to explore later. However, I conclude my discussion here. Note that this post is still a draft & I plan to write more here after I get some time.

Table of Contents