# Procedural Noise Skill ## Use Cases Procedural noise is the most fundamental technique in real-time GPU graphics. It applies to natural phenomena (fire, clouds, water, lava), terrain generation, texture synthesis, volume rendering, motion effects, and more. Core idea: use mathematical functions to generate pseudo-random, spatially continuous signals on the GPU in real time, then produce multi-scale detail through FBM and domain warping. ## Core Principles ### Noise Functions Generate random values at integer lattice points, then smoothly interpolate between them. - **Value Noise**: random scalars at lattice points + bilinear Hermite interpolation. `N(p) = mix(mix(h00,h10,u), mix(h01,h11,u), v)` - **Simplex Noise**: triangular lattice gradient dot products + radial falloff kernel. Skew `K1=(sqrt(3)-1)/2`, unskew `K2=(3-sqrt(3))/6`. Fewer lattice lookups, no axis-aligned artifacts. ### Hash Functions Map integer coordinates to pseudo-random values: - **sin-based** (short but precision-sensitive): `fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453)` - **sin-free** (cross-platform stable): `fract(p * 0.1031)` + dot mixing + fract ### FBM (Fractal Brownian Motion) Multi-octave noise summation: `FBM(p) = sum of amplitude_i * noise(frequency_i * p)` - Lacunarity ~2.0, Gain ~0.5, inter-octave rotation to eliminate artifacts ### Domain Warping Feed noise output back as coordinate offset: `fbm(p + fbm(p))` or cascaded `fbm(p + fbm(p + fbm(p)))` ### FBM Variant Quick Reference | Variant | Formula | Effect | |---------|---------|--------| | Standard | `sum a*noise(p)` | Soft clouds | | Ridged | `sum a*abs(noise(p))` | Sharp ridges/lightning | | Sinusoidal ridged | `sum a*sin(noise(p)*k)` | Periodic ridges/lava | | Erosion | `sum a*noise(p)/(1+dot(d,d))` | Realistic terrain | | Ocean waves | `sum a*sea_octave(p)` | Peaked wave crests | ## Implementation Code ### Hash Functions ```glsl // Sin-free hash (Dave Hoskins) — cross-platform stable float hash12(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); } vec2 hash22(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.xx + p3.yz) * p3.zy); } // Sin hash — shorter code, precision-sensitive on some GPUs float hash(vec2 p) { float h = dot(p, vec2(127.1, 311.7)); return fract(sin(h) * 43758.5453123); } vec2 hash2(vec2 p) { p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3))); return -1.0 + 2.0 * fract(sin(p) * 43758.5453123); } ``` ### Value Noise ```glsl // Hermite smooth bilinear interpolation float noise(in vec2 x) { vec2 p = floor(x); vec2 f = fract(x); f = f * f * (3.0 - 2.0 * f); float a = hash(p + vec2(0.0, 0.0)); float b = hash(p + vec2(1.0, 0.0)); float c = hash(p + vec2(0.0, 1.0)); float d = hash(p + vec2(1.0, 1.0)); return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); } ``` ### Simplex Noise ```glsl // 2D Simplex (skewed triangular grid + h^4 falloff kernel) float noise(in vec2 p) { const float K1 = 0.366025404; // (sqrt(3)-1)/2 const float K2 = 0.211324865; // (3-sqrt(3))/6 vec2 i = floor(p + (p.x + p.y) * K1); vec2 a = p - i + (i.x + i.y) * K2; vec2 o = (a.x > a.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); vec2 b = a - o + K2; vec2 c = a - 1.0 + 2.0 * K2; vec3 h = max(0.5 - vec3(dot(a, a), dot(b, b), dot(c, c)), 0.0); vec3 n = h * h * h * h * vec3( dot(a, hash2(i + 0.0)), dot(b, hash2(i + o)), dot(c, hash2(i + 1.0)) ); return dot(n, vec3(70.0)); } ``` ### Standard FBM ```glsl #define OCTAVES 4 #define GAIN 0.5 mat2 m = mat2(1.6, 1.2, -1.2, 1.6); // rotation+scale, |m|=2.0, ~36.87 deg float fbm(vec2 p) { float f = 0.0, a = 0.5; for (int i = 0; i < OCTAVES; i++) { f += a * noise(p); p = m * p; a *= GAIN; } return f; } ``` Manually unrolled version (slightly varying lacunarity to break self-similarity): ```glsl const mat2 mtx = mat2(0.80, 0.60, -0.60, 0.80); float fbm4(vec2 p) { float f = 0.0; f += 0.5000 * (-1.0 + 2.0 * noise(p)); p = mtx * p * 2.02; f += 0.2500 * (-1.0 + 2.0 * noise(p)); p = mtx * p * 2.03; f += 0.1250 * (-1.0 + 2.0 * noise(p)); p = mtx * p * 2.01; f += 0.0625 * (-1.0 + 2.0 * noise(p)); return f / 0.9375; } ``` ### Ridged FBM ```glsl // abs() produces V-shaped ridges at zero crossings float fbm_ridged(in vec2 p) { float z = 2.0, rz = 0.0; for (float i = 1.0; i < 6.0; i++) { rz += abs((noise(p) - 0.5) * 2.0) / z; z *= 2.0; p *= 2.0; } return rz; } // Sinusoidal ridged variant — lava texture // rz += (sin(noise(p) * 7.0) * 0.5 + 0.5) / z; ``` ### Domain Warping ```glsl // Basic domain warping ("2D Clouds") float q = fbm(uv * 0.5); uv -= q - time; float f = fbm(uv); // Classic three-level cascade vec2 fbm4_2(vec2 p) { return vec2(fbm4(p + vec2(1.0)), fbm4(p + vec2(6.2))); } float func(vec2 q, out vec2 o, out vec2 n) { o = 0.5 + 0.5 * fbm4_2(q); n = fbm6_2(4.0 * o); vec2 p = q + 2.0 * n + 1.0; float f = 0.5 + 0.5 * fbm4(2.0 * p); f = mix(f, f * f * f * 3.5, f * abs(n.x)); return f; } // Dual-axis domain warping float dualfbm(in vec2 p) { vec2 p2 = p * 0.7; vec2 basis = vec2(fbm(p2 - time * 1.6), fbm(p2 + time * 1.7)); basis = (basis - 0.5) * 0.2; p += basis; return fbm(p * makem2(time * 0.2)); } ``` ### Fluid Noise ```glsl // Per-octave gradient displacement simulating fluid transport #define FLOW_SPEED 0.6 #define BASE_SPEED 1.9 #define ADVECTION 0.77 #define GRAD_SCALE 0.5 vec2 gradn(vec2 p) { float ep = 0.09; float gradx = noise(vec2(p.x + ep, p.y)) - noise(vec2(p.x - ep, p.y)); float grady = noise(vec2(p.x, p.y + ep)) - noise(vec2(p.x, p.y - ep)); return vec2(gradx, grady); } float flow(in vec2 p) { float z = 2.0, rz = 0.0; vec2 bp = p; for (float i = 1.0; i < 7.0; i++) { p += time * FLOW_SPEED; bp += time * BASE_SPEED; vec2 gr = gradn(i * p * 0.34 + time * 1.0); gr *= makem2(time * 6.0 - (0.05 * p.x + 0.03 * p.y) * 40.0); p += gr * GRAD_SCALE; rz += (sin(noise(p) * 7.0) * 0.5 + 0.5) / z; p = mix(bp, p, ADVECTION); z *= 1.4; p *= 2.0; bp *= 1.9; } return rz; } ``` ### Derivative FBM ```glsl // Value noise with analytic derivatives vec3 noised(in vec2 x) { vec2 p = floor(x); vec2 f = fract(x); vec2 u = f * f * (3.0 - 2.0 * f); vec2 du = 6.0 * f * (1.0 - f); float a = hash(p + vec2(0, 0)); float b = hash(p + vec2(1, 0)); float c = hash(p + vec2(0, 1)); float d = hash(p + vec2(1, 1)); return vec3( a + (b - a) * u.x + (c - a) * u.y + (a - b - c + d) * u.x * u.y, du * (vec2(b - a, c - a) + (a - b - c + d) * u.yx) ); } // Erosion FBM: higher gradient = lower contribution float terrainFBM(in vec2 x) { const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8); float a = 0.0, b = 1.0; vec2 d = vec2(0.0); for (int i = 0; i < 16; i++) { vec3 n = noised(x); d += n.yz; a += b * n.x / (1.0 + dot(d, d)); // 1/(1+|grad|^2) erosion factor b *= 0.5; x = m2 * x * 2.0; } return a; } ``` ### Quintic Noise with Analytical Derivatives C2-continuous noise using quintic interpolation — eliminates visible grid artifacts in derivatives: ```glsl // Returns vec3(value, dFdx, dFdy) — derivatives are exact, not finite-differenced vec3 noisedQ(vec2 p) { vec2 i = floor(p); vec2 f = fract(p); // Quintic interpolation for C2 continuity vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); vec2 du = 30.0 * f * f * (f * (f - 2.0) + 1.0); float a = hash12(i + vec2(0.0, 0.0)); float b = hash12(i + vec2(1.0, 0.0)); float c = hash12(i + vec2(0.0, 1.0)); float d = hash12(i + vec2(1.0, 1.0)); float k0 = a, k1 = b - a, k2 = c - a, k3 = a - b - c + d; return vec3( k0 + k1 * u.x + k2 * u.y + k3 * u.x * u.y, // value du * vec2(k1 + k3 * u.y, k2 + k3 * u.x) // derivatives ); } ``` ### FBM with Derivatives (Erosion Terrain) Accumulates derivatives across octaves — derivative magnitude dampens amplitude, creating realistic erosion patterns: ```glsl vec3 fbmDerivative(vec2 p, int octaves) { float value = 0.0; vec2 deriv = vec2(0.0); float amplitude = 0.5; float frequency = 1.0; mat2 rot = mat2(0.8, 0.6, -0.6, 0.8); // inter-octave rotation for (int i = 0; i < octaves; i++) { vec3 n = noisedQ(p * frequency); deriv += n.yz; // Key: divide by (1 + dot(deriv, deriv)) for erosion effect value += amplitude * n.x / (1.0 + dot(deriv, deriv)); frequency *= 2.0; amplitude *= 0.5; p = rot * p; // rotate to break axis-aligned artifacts } return vec3(value, deriv); } ``` Key insights: - **Quintic interpolation**: `6t^5 - 15t^4 + 10t^3` gives C2 continuous noise (vs Hermite's C1), eliminating visible grid artifacts in derivatives - **Erosion FBM**: The `1/(1+dot(d,d))` term causes flat areas to accumulate more detail while steep slopes stay smooth — mimicking real erosion - **Inter-octave rotation**: The 2x2 rotation matrix between octaves prevents axis-aligned patterns especially visible in ridged noise ### Voronoise (Voronoi-Noise Hybrid) Unified interpolation between value noise and Voronoi patterns: ```glsl // u=0: Value noise, u=1: Voronoi, v: smoothness (0=sharp cells, 1=smooth) vec3 hash32(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yxz + 33.33); return fract((p3.xxy + p3.yzz) * p3.zyx); } float voronoise(vec2 p, float u, float v) { float k = 1.0 + 63.0 * pow(1.0 - v, 6.0); vec2 i = floor(p); vec2 f = fract(p); vec2 a = vec2(0.0); for (int y = -2; y <= 2; y++) for (int x = -2; x <= 2; x++) { vec2 g = vec2(float(x), float(y)); vec3 o = hash32(i + g) * vec3(u, u, 1.0); vec2 d = g - f + o.xy; float w = pow(1.0 - smoothstep(0.0, 1.414, length(d)), k); a += vec2(o.z * w, w); } return a.x / a.y; } ``` Extremely versatile — smoothly interpolates between cellular Voronoi and continuous noise. ### Preventing Aliasing in Procedural Textures For distant surfaces, high-frequency noise octaves create moiré artifacts. Solutions: 1. **LOD-based octave count**: `int octaves = min(MAX_OCTAVES, int(log2(pixelSize)))` — skip octaves finer than pixel size 2. **Analytical filtering**: For simple patterns (checkers, stripes), use smoothstep with pixel width: `smoothstep(-fw, fw, pattern)` where `fw = fwidth(uv)` 3. **Derivative-based mip**: Use `textureGrad()` with manually computed ray differentials for texture lookups in ray-marched scenes (see texture-mapping-advanced technique) ## Complete Code Template Ready to run in ShaderToy. Switch between standard FBM / ridged FBM / domain warping modes via `#define`: ```glsl // ============================================================ // Procedural Noise Skill — Complete Template // ============================================================ // ========== Mode selection (uncomment to switch) ========== #define MODE_STANDARD_FBM // Standard FBM clouds //#define MODE_RIDGED_FBM // Ridged FBM lightning texture //#define MODE_DOMAIN_WARP // Domain warped organic pattern // ========== Tunable parameters ========== #define OCTAVES 6 #define GAIN 0.5 #define LACUNARITY 2.0 #define NOISE_SCALE 3.0 #define ANIM_SPEED 0.3 #define WARP_STRENGTH 0.4 // ========== Hash function ========== float hash(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * 0.1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); } // ========== Value noise ========== float noise(in vec2 x) { vec2 p = floor(x); vec2 f = fract(x); f = f * f * (3.0 - 2.0 * f); float a = hash(p + vec2(0.0, 0.0)); float b = hash(p + vec2(1.0, 0.0)); float c = hash(p + vec2(0.0, 1.0)); float d = hash(p + vec2(1.0, 1.0)); return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); } // ========== Rotation+scale matrix ========== const mat2 m = mat2(1.6, 1.2, -1.2, 1.6); // ========== Standard FBM ========== float fbm(vec2 p) { float f = 0.0, a = 0.5; for (int i = 0; i < OCTAVES; i++) { f += a * (-1.0 + 2.0 * noise(p)); p = m * p; a *= GAIN; } return f; } // ========== Ridged FBM ========== float fbm_ridged(vec2 p) { float f = 0.0, a = 0.5; for (int i = 0; i < OCTAVES; i++) { f += a * abs(-1.0 + 2.0 * noise(p)); p = m * p; a *= GAIN; } return f; } // ========== Domain warping vec2 FBM ========== vec2 fbm2(vec2 p) { return vec2(fbm(p + vec2(1.7, 9.2)), fbm(p + vec2(8.3, 2.8))); } void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y; uv *= NOISE_SCALE; float time = iTime * ANIM_SPEED; float f = 0.0; vec3 col = vec3(0.0); #ifdef MODE_STANDARD_FBM f = 0.5 + 0.5 * fbm(uv + vec2(0.0, -time)); vec3 sky = mix(vec3(0.4, 0.7, 1.0), vec3(0.2, 0.4, 0.6), fragCoord.y / iResolution.y); vec3 cloud = vec3(1.1, 1.1, 0.9) * f; col = mix(sky, cloud, smoothstep(0.4, 0.7, f)); #endif #ifdef MODE_RIDGED_FBM f = fbm_ridged(uv + vec2(time * 0.5, time * 0.3)); col = vec3(0.2, 0.1, 0.4) / max(f, 0.05); col = pow(col, vec3(0.99)); #endif #ifdef MODE_DOMAIN_WARP vec2 q = fbm2(uv + time * 0.1); vec2 r = fbm2(uv + WARP_STRENGTH * q + vec2(1.7, 9.2)); f = 0.5 + 0.5 * fbm(uv + WARP_STRENGTH * r); f = mix(f, f * f * f * 3.5, f * length(r)); col = vec3(0.2, 0.1, 0.4); col = mix(col, vec3(0.3, 0.05, 0.05), f); col = mix(col, vec3(0.9, 0.9, 0.9), dot(r, r)); col = mix(col, vec3(0.5, 0.2, 0.2), 0.5 * q.y * q.y); col *= f * 2.0; vec2 eps = vec2(1.0 / iResolution.x, 0.0); float fx = 0.5 + 0.5 * fbm(uv + eps.xy + WARP_STRENGTH * fbm2(uv + eps.xy + time * 0.1)); float fy = 0.5 + 0.5 * fbm(uv + eps.yx + WARP_STRENGTH * fbm2(uv + eps.yx + time * 0.1)); vec3 nor = normalize(vec3(fx - f, eps.x, fy - f)); vec3 lig = normalize(vec3(0.9, -0.2, -0.4)); float dif = clamp(0.3 + 0.7 * dot(nor, lig), 0.0, 1.0); col *= vec3(0.85, 0.90, 0.95) * (nor.y * 0.5 + 0.5) + vec3(0.15, 0.10, 0.05) * dif; #endif vec2 p = fragCoord / iResolution.xy; col *= 0.5 + 0.5 * sqrt(16.0 * p.x * p.y * (1.0 - p.x) * (1.0 - p.y)); fragColor = vec4(col, 1.0); } ``` ## Common Variants ### Ridged FBM ```glsl f += a * abs(noise(p)); // V-shaped ridges f += a * (sin(noise(p)*7.0)*0.5+0.5); // Sinusoidal ridges (lava) ``` ### Domain Warped FBM ```glsl vec2 o = 0.5 + 0.5 * vec2(fbm(q + vec2(1.0)), fbm(q + vec2(6.2))); vec2 n = vec2(fbm(4.0 * o + vec2(9.2)), fbm(4.0 * o + vec2(5.7))); float f = 0.5 + 0.5 * fbm(q + 2.0 * n + 1.0); ``` ### Derivative Erosion FBM ```glsl vec2 d = vec2(0.0); for (int i = 0; i < N; i++) { vec3 n = noised(p); d += n.yz; a += b * n.x / (1.0 + dot(d, d)); b *= 0.5; p = m2 * p * 2.0; } ``` ### Fluid Noise ```glsl for (float i = 1.0; i < 7.0; i++) { vec2 gr = gradn(i * p * 0.34 + time); gr *= makem2(time * 6.0 - (0.05*p.x+0.03*p.y)*40.0); p += gr * 0.5; rz += (sin(noise(p)*7.0)*0.5+0.5) / z; p = mix(bp, p, 0.77); } ``` ### Ocean Wave Octave Function ```glsl float sea_octave(vec2 uv, float choppy) { uv += noise(uv); vec2 wv = 1.0 - abs(sin(uv)); vec2 swv = abs(cos(uv)); wv = mix(wv, swv, wv); return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy); } // Bidirectional propagation in FBM: d = sea_octave((uv + SEA_TIME) * freq, choppy); d += sea_octave((uv - SEA_TIME) * freq, choppy); choppy = mix(choppy, 1.0, 0.2); ``` ## Performance & Composition **Performance optimization:** - Reducing octave count is the most direct optimization; use fewer octaves for distant objects: `int oct = 5 - int(log2(1.0 + t * 0.5));` - Multi-level LOD: `terrainL` (3 oct) / `terrainM` (9 oct) / `terrainH` (16 oct) - Texture sampling instead of math hash: `texture(iChannel0, x * 0.01).x` - Manually unroll small loops + slightly vary lacunarity - Adaptive step size: `float dt = max(0.05, 0.02 * t);` - Directional derivative instead of full gradient (1 sample vs 3) - Early termination: `if (sum.a > 0.99) break;` **Common combinations:** - FBM + Raymarching: noise-driven height/density fields, ray marching for intersection (terrain/ocean) - FBM + finite-difference normals + lighting: `nor = normalize(vec3(f(p+ex)-f(p), eps, f(p+ey)-f(p)))` - FBM + color mapping: different power curves mapping to RGB, e.g. flame `vec3(1.5*c, 1.5*c^3, c^6)` or inverse `vec3(k)/rz` - FBM + Fresnel water surface: `fresnel = pow(1.0 - dot(n, -eye), 3.0)` - Multi-layer FBM compositing: shape layer (low freq) + ridged layer (mid freq) + color layer (high freq) - FBM + volumetric lighting: density difference along light direction approximates illumination ## Further Reading For complete step-by-step tutorials, mathematical derivations, and advanced usage, see [reference](../reference/procedural-noise.md)