Alain Galvan ·7/1/2019 @ 8:30 AM · Updated 5 months ago
A summary of different hashing functions, low-discrepancy sequences, turbulence, convolution, and artistic generation techniques.
Tags: blogshadernoisegeneration
Noise generation is at the heart of procedural texture generation tools such as Substance Designer, technical art, computer generated art, procedural mesh generation for terrain, clouds, water, and much more.
It's difficult to find a reference of each of these functions and what is possible with them, so the following is a survey similar to that of [Lagae et al. 2010] of a variety of noise functions and the operations that can be done to drive interesting and complex effects. This is written for scientists and technical artists to serve as a useful reference for the current state of the art.
White noise is an example of completely random noise with an equal number of high and low frequency information over a given domain.
There's a number of different approaches to random number generation.
It's common in ShaderToy to use the fractal error of a sine function to approximate noise.
// 🔺 Sine hash by Patricio Gonzalez Vivo. Public Domain.
// http://patriciogonzalezvivo.com/2015/thebookofshaders/10/
float rand(float n)
{
return frac(sin(n) * 43758.5453);
}
float rand(vec2 uv)
{
return fract(sin(dot(uv.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
vec2 rand2(vec2 p)
{
return fract(sin(vec2(dot(p,vec2(127.1, 311.7)), dot(p,vec2(269.5, 183.3)))) * 43758.5453);
}
These are what's known as Deterministic Hashing Functions, in that the same input would always return the same output. This is perfect for shaders where such deterministic behavior would be desirable to drive effects based on UV coordinates, uniforms, etc.
The Mersenne Twister is a non-deterministic method of generating random values, built off [Matsumoto et al. 1998]. The Mersenee Twister currently powers the C++ standard's official hashing functions.
Van Der Corpus sequences are an example of low discrepancy pseudo-random generators, designed for use in monte-carlo ray tracers to help reduce repeating sampling values during integration.
Halton sequences produce a useful noise pattern that is somewhat low in frequency, making it useful for real time ray tracing.
Ken Perlin first introduces an algorithm to generate a white noise image in [Perlin 1985], where he also defines key terms used to this day such as turbulence.
Perlin Noise [Perlin 2002] is arguably the best known procedural noise function and has been used for years for simulating clouds, smoke, fire, etc.
Perlin noise assigns a psudo-random gradient vector to each point of the integer lattice. The value of the function at a point on the 2D plane is then determined by smooth interpolation between the 4 closest gradient vectors projected onto the vectors between the current point and the lattice point each vector is assigned to. The psudo-randomness of successive hashing of the lattice point coordinates with a permuation table.
Improved perlin noise reduced artifacts in the derivatives and improved overall noise appearance. Moddiied noise [Olano 2005] replaced the permuation table with a hash function based on a psudo-random number generato (PRNG). Better Gradient Noise [Kensler et al. 2008] uses permutation table per dimension and different hashing to better de-correlate the values. it also widened the filter kernel to improve band limitation.
All Perlin noise is periodic determined by the hash function of size of the permuation table.
Better gradient noise uses a 4x4 filter instead of a 2x2.
Simplex is an improvement over Perlin Noise, which features a number of public domain variants such as:
// Voronoi Noise by Inigo Quilez
// https://www.shadertoy.com/view/Xd23Dh
// More info here: http://iquilezles.org/www/articles/voronoise/voronoise.htm
vec3 hash3( vec2 p )
{
vec3 q = vec3( dot(p,vec2(127.1,311.7)),
dot(p,vec2(269.5,183.3)),
dot(p,vec2(419.2,371.9)) );
return fract(sin(q)*43758.5453);
}
float iqnoise( in vec2 x, float u, float v )
{
vec2 p = floor(x);
vec2 f = fract(x);
float k = 1.0+63.0*pow(1.0-v,4.0);
float va = 0.0;
float wt = 0.0;
for( int j=-2; j<=2; j++ )
for( int i=-2; i<=2; i++ )
{
vec2 g = vec2( float(i),float(j) );
vec3 o = hash3( p + g )*vec3(u,u,1.0);
vec2 r = g - f + o.xy;
float d = dot(r,r);
float ww = pow( 1.0-smoothstep(0.0,1.414,sqrt(d)), k );
va += o.z*ww;
wt += ww;
}
return va/wt;
}
There's a number of techniques to tile noise textures:
Turbulence is the layering of noise (sometimes referred to as octaves of noise in literature), and can be used to generate interesting volumetric effects such as clouds, dust, water, etc.
// 3D gradient Noise
// MIT License
// Copyright © 2013 Inigo Quilez
// https://www.shadertoy.com/view/Xsl3Dl
vec3 hash(vec3 p)
{
p = vec3(dot(p, vec3(127.1, 311.7, 74.7)), dot(p, vec3(269.5, 183.3, 246.1)),
dot(p, vec3(113.5, 271.9, 124.6)));
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}
float noise(in vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x),
u.y),
mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x),
u.y),
u.z);
}
const mat3 m = mat3(0.00, 0.80, 0.60, -0.80, 0.36, -0.48, -0.60, -0.48, 0.64);
float turbulance(vec3 pos, float lacunarity, float gain ) {
float f;
vec3 q = 8.0 * pos;
f = gain * noise(q);
q = m * q * 2.01 * lacunarity;
f += (gain * gain) * noise(q);
q = m * q * 2.02 * lacunarity;
f += (gain * gain * gain) * noise(q);
q = m * q * 2.03 * lacunarity;
f += (gain * gain * gain * gain) * noise(q);
q = m * q * 2.01 * lacunarity;
f = 0.5 + 0.5 * f;
return clamp(f, 0.0, 1.0);
}
It's possible to feed noise functions into one another, resulting in an effect similar to a double pendulum and thus could be described as lagrangian mechanics on a n-dimensional domain.
With convolutions it's possible to generate chaotic, complex brownian motion effects, twisting and turning waves, and beautiful patterns.
// Paint Archipelago by @samlo
// https://www.shadertoy.com/view/3lf3z2
float convolution(vec3 p, float time)
{
vec2 offset = vec2(-0.5);
vec2 aPos = vec2(sin(time * 0.005), sin(time * 0.01)) * 6.;
vec2 aScale = vec2(3.0);
float a = turbulance(p * aScale + aPos);
vec2 bPos = vec2(sin(time * 0.01), sin(time * 0.01)) * 1.;
vec2 bScale = vec2(0.6);
float b = turbulance((p + a) * bScale + bPos);
vec2 cPos = vec2(-0.6, -0.5) + vec2(sin(-time * 0.001), sin(time * 0.01)) * 2.;
vec2 cScale = vec2(2.6);
float c = turbulance((p + b) * cScale + cPos);
return c;
}
FastNoise2 is a C++ noise generation library that features a node graph editor for designing 3D noise patterns.
The Rust Random ecosystem of libraries, with cryptographically secure random functions, and implementations of state of the art research in Rust.
Alan Wolfe (@Atrix256)'s series of articles on Blue Noise.
PCG's family of random functions, with implementations in C and comparisons between different techniques such as ChaCha20, Mersenne Twister, etc.
Noice by Kleber Garcia (@kechogarcia) is a CLI tool to generate different types of noise very nicely.
[Lagae et al. 2010] |
[Matsumoto et al. 1998] Mersenne Twister: A 623-Dimensionally Equidistributed Uniform Pseudorandom Number Generator ACM 1998 math.sci.hiroshima-u.ac.jp |
[Perlin 1985] |
[Perlin 2002] |
[Kensler et al. 2008] |
[Kirillov 2019] Nonperiodic Tiling of Noise-based Procedural Textures GPU Zen 2 2019 GPU Zen Website High Performance Graphics Paper |
[Deliot et al. 2019] |