In a Nutshell
Noise functions are math-like functions used to generate random numbers in computers. They are often used to add variation in computer-generated content, such as terrains, oceans, forests, and other visual effects in video games (see procedural generation).
There are several types of noise functions, such as:
1. White noise: a TV static noise, looks like sand grains.
2. Value noise: It is like zoomed-in white noise smoothened
3. Perlin noise: It looks like value noise, but is much cleaner than it and is used very often in procedural generation.
4. Voronoi (Worley) noise: It looks like animal cells. It can be used for generating different procedural textures, and is also very often used in procedural generation.
5. Fractal Brownian Motion: It is a technique in which we combine several noise function layers to create more complex-looking output.
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. I used Perlin noise in my 3D grass shader to simulate wind variation.
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;
}