Procedural Graphics

Procedural shaders compute images from math alone — the heart of Shadertoy, demoscene, and The Book of Shaders.

19.1 Coordinate setup

vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;   // centered, aspect-correct, y in [-0.5, 0.5]

19.2 Shaping functions

float y = smoothstep(0.2, 0.8, uv.x);            // smooth ramp
float pulse = step(0.4, uv.x) - step(0.6, uv.x); // box
float wave = sin(uv.x * 20.0 + iTime) * 0.5 + 0.5;
float plotLine(vec2 st, float f) {                // draw a function graph
    return smoothstep(f - 0.01, f, st.y) - smoothstep(f, f + 0.01, st.y);
}
float impulse(float k, float x) { float h = k * x; return h * exp(1.0 - h); }   // (Inigo Quilez)
float parabola(float x, float k) { return pow(4.0 * x * (1.0 - x), k); }

19.3 2-D signed distance functions (SDFs)

A signed distance function returns the distance to a shape's surface (negative inside).

float sdCircle(vec2 p, float r) { return length(p) - r; }

float sdBox(vec2 p, vec2 b) {
    vec2 d = abs(p) - b;
    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}

float sdSegment(vec2 p, vec2 a, vec2 b) {
    vec2 pa = p - a, ba = b - a;
    float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
    return length(pa - ba * h);
}

float sdRoundedBox(vec2 p, vec2 b, float r) { return sdBox(p, b - r) - r; }

// Boolean operations
float opUnion(float a, float b)        { return min(a, b); }
float opIntersect(float a, float b)    { return max(a, b); }
float opSubtract(float a, float b)     { return max(a, -b); }
float opSmoothUnion(float a, float b, float k) {
    float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
    return mix(b, a, h) - k * h * (1.0 - h);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
    float d = opSmoothUnion(sdCircle(p - vec2(0.15 * sin(iTime), 0.0), 0.15),
                            sdBox(p + vec2(0.15, 0.0), vec2(0.1)), 0.1);
    float aa = fwidth(d);
    vec3 col = mix(vec3(1.0, 0.6, 0.2), vec3(0.1), smoothstep(-aa, aa, d));   // fill, anti-aliased
    col *= 0.8 + 0.2 * 
…

19.4 Domain transformations

p = fract(p * 5.0) - 0.5;                      // repeat space (tiling)
vec2 id = floor(p * 5.0);                      // cell id for per-cell variation
p = abs(p);                                    // mirror symmetry
p *= mat2(cos(a), -sin(a), sin(a), cos(a));    // rotate
float angle = atan(p.y, p.x); float radius = length(p);   // polar coordinates
float sector = mod(angle, 6.2831 / 6.0);       // kaleidoscope

19.5 Random & noise

Other noises: simplex noise, Worley/cellular (Voronoi) noise, curl noise (flow fields), blue noise (dithering, from textures).

// Hash functions (pseudo-random from coordinates)
float hash21(vec2 p) {
    p = fract(p * vec2(123.34, 456.21));
    p += dot(p, p + 45.32);
    return fract(p.x * p.y);
}

// Value noise
float valueNoise(vec2 p) {
    vec2 i = floor(p), f = fract(p);
    float a = hash21(i), b = hash21(i + vec2(1, 0)), c = hash21(i + vec2(0, 1)), d = hash21(i + vec2(1, 1));
    vec2 u = f * f * (3.0 - 2.0 * f);           // smooth interpolation
    return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

// Gradient (Perlin-style) noise
vec2 grad(vec2 p) { float a = hash21(p) * 6.2831853; return vec2(cos(a), sin(a)); }
float gradientNoise(vec2 p) {
    vec2 i = floor(p), f = fract(p);
    vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);   // quintic fade
    return mix(mix(dot(grad(i), f), dot(grad(i + vec2(1, 0)), f - vec2(1, 0)), u.x),
               mix(dot(grad(i + vec2(0, 1)), f - vec2(0, 1)), dot(grad(i + vec2(1, 1)), f - vec2(1, 1)), u.x), u.y);
}

// Fractal Brownian Motion (fBm): sum of noise octaves
float fbm(vec2 p) {
    float v = 0.0, amp = 0.5;
    mat2 rot = mat2(0.8, 0.6, -0.6, 0.8);
    for (int i = 0; i < 6; ++i) {
        v += amp * gradientNoise(p);
        p = rot * p * 2.0;  
…
// Voronoi (cellular) noise: distance to the nearest feature point
float voronoi(vec2 p) {
    vec2 i = floor(p), f = fract(p);
    float minD = 1.0;
    for (int y = -1; y <= 1; ++y)
        for (int x = -1; x <= 1; ++x) {
            vec2 cell = vec2(x, y);
            vec2 pt = vec2(hash21(i + cell), hash21(i + cell + 17.0));
            pt = 0.5 + 0.5 * sin(iTime + 6.2831 * pt);       // animated points
            minD = min(minD, length(cell + pt - f));
        }
    return minD;
}

19.6 Procedural patterns

Checkerboards (mod(floor(uv.x) + floor(uv.y), 2.0)), stripes, bricks (offset every other row), polka dots, hexagonal grids, truchet tiles, gradients and palettes:

// Inigo Quilez cosine palette: a + b * cos(2π (c t + d))
vec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d) { return a + b * cos(6.28318 * (c * t + d)); }
vec3 col = palette(t, vec3(0.5), vec3(0.5), vec3(1.0), vec3(0.0, 0.33, 0.67));