Author

Steve Trettel

Published

January 2026

1 Day 4 Code

Complete, standalone code for each shader in Day 4. Copy any of these directly into Shadertoy.

1.1 Texture Setup

The texture examples require loading an image into iChannel0:

  1. Click iChannel0 at the bottom of the editor
  2. Select the “Textures” tab
  3. Choose an image (try “Abstract 1” or any photograph)

1.2 Buffer Setup

The buffer examples (painting, Game of Life, waves) require a self-referencing buffer:

  1. Click the + tab next to “Image” and select “Buffer A”
  2. In Buffer A, click iChannel0 at the bottom and select “Buffer A” from the Misc tab
  3. In Image, click iChannel0 and select “Buffer A”

1.3 texture-basic

Display a texture using UV coordinates.

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;
    fragColor = texture(iChannel0, uv);
}

1.4 texture-flip

Flip an image horizontally.

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;
    uv.x = 1.0 - uv.x;
    fragColor = texture(iChannel0, uv);
}

1.5 texture-wavy

Animated wavy distortion.

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;
    
    // Offset x based on y position and time
    uv.x += 0.03 * sin(uv.y * 20.0 + iTime * 2.0);
    
    fragColor = texture(iChannel0, uv);
}

1.6 texture-swirl

Swirl distortion centered on the image.

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;
    vec2 center = vec2(0.5);
    
    vec2 offset = uv - center;
    float dist = length(offset);
    float angle = atan(offset.y, offset.x);
    
    // Rotate more near the center
    float swirl = 2.0 * exp(-dist * 3.0);
    angle += swirl;
    
    uv = center + dist * vec2(cos(angle), sin(angle));
    fragColor = texture(iChannel0, uv);
}

1.7 texture-magnify

Magnifying glass that follows the mouse. Click and drag to move.

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;
    vec2 mouse = iMouse.xy / iResolution.xy;
    
    float dist = length(uv - mouse);
    float radius = 0.15;
    
    if (dist < radius) {
        // Inside lens: sample closer to mouse position (zoom in)
        float zoom = 2.0;
        uv = mouse + (uv - mouse) / zoom;
    }
    
    fragColor = texture(iChannel0, uv);
}

1.8 paint-broken

The broken painting program — no persistence. Demonstrates why we need buffers.

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    float d = length(fragCoord - iMouse.xy);
    
    if (iMouse.z > 0.0 && d < 10.0) {
        fragColor = vec4(1.0);  // White brush
    } else {
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);  // Black background
    }
}

1.9 paint-basic

Persistent painting. Click and drag to draw.

Buffer A:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec4 prev = texelFetch(iChannel0, ivec2(fragCoord), 0);
    
    float d = length(fragCoord - iMouse.xy);
    
    if (iMouse.z > 0.0 && d < 10.0) {
        fragColor = vec4(1.0);
    } else {
        fragColor = prev;
    }
}

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    fragColor = texelFetch(iChannel0, ivec2(fragCoord), 0);
}

1.10 paint-fade

Painting with fading trails.

Buffer A:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec4 prev = texelFetch(iChannel0, ivec2(fragCoord), 0);
    prev *= 0.99;
    
    float d = length(fragCoord - iMouse.xy);
    
    if (iMouse.z > 0.0 && d < 10.0) {
        fragColor = vec4(1.0);
    } else {
        fragColor = prev;
    }
}

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    fragColor = texelFetch(iChannel0, ivec2(fragCoord), 0);
}

1.11 game-of-life

Conway’s Game of Life.

Buffer A:

float hash(vec2 p) {
    p = fract(p * vec2(234.34, 435.345));
    p += dot(p, p + 34.23);
    return fract(p.x * p.y);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (iFrame == 0) {
        float random = hash(fragCoord);
        fragColor = vec4(step(0.5, random));
        return;
    }
    
    ivec2 p = ivec2(fragCoord);
    
    float self = texelFetch(iChannel0, p, 0).r;
    float neighbors = 
        texelFetch(iChannel0, p + ivec2(-1, -1), 0).r +
        texelFetch(iChannel0, p + ivec2( 0, -1), 0).r +
        texelFetch(iChannel0, p + ivec2( 1, -1), 0).r +
        texelFetch(iChannel0, p + ivec2(-1,  0), 0).r +
        texelFetch(iChannel0, p + ivec2( 1,  0), 0).r +
        texelFetch(iChannel0, p + ivec2(-1,  1), 0).r +
        texelFetch(iChannel0, p + ivec2( 0,  1), 0).r +
        texelFetch(iChannel0, p + ivec2( 1,  1), 0).r;
    
    float alive = 0.0;
    if (self == 1.0) {
        if (neighbors == 2.0 || neighbors == 3.0) {
            alive = 1.0;
        }
    } else {
        if (neighbors == 3.0) {
            alive = 1.0;
        }
    }
    
    fragColor = vec4(vec3(alive), 1.0);
}

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    fragColor = texelFetch(iChannel0, ivec2(fragCoord), 0);
}

1.12 wave-equation

Basic wave equation with Gaussian initial condition.

Buffer A:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (iFrame == 0) {
        vec2 center = iResolution.xy * 0.5;
        float d = length(fragCoord - center);
        float u = 3.0 * exp(-d * d / 100.0);
        fragColor = vec4(u, 0.0, 0.0, 1.0);
        return;
    }
    
    ivec2 p = ivec2(fragCoord);
    
    float u = texelFetch(iChannel0, p, 0).r;
    float v = texelFetch(iChannel0, p, 0).g;
    
    float u_n = texelFetch(iChannel0, p + ivec2( 0,  1), 0).r;
    float u_s = texelFetch(iChannel0, p + ivec2( 0, -1), 0).r;
    float u_e = texelFetch(iChannel0, p + ivec2( 1,  0), 0).r;
    float u_w = texelFetch(iChannel0, p + ivec2(-1,  0), 0).r;
    float laplacian = u_n + u_s + u_e + u_w - 4.0 * u;
    
    // Symplectic Euler: update velocity first, then position
    float dt = 0.3;
    float c = 1.0;
    float newV = v + dt * c * c * laplacian;
    float newU = u + dt * newV;
    
    fragColor = vec4(newU, newV, 0.0, 1.0);
}

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    float u = texelFetch(iChannel0, ivec2(fragCoord), 0).r;
    u *= 3.0;
    
    vec3 color;
    if (u > 0.0) {
        color = mix(vec3(0.0), vec3(1.0, 0.5, 0.0), u);
    } else {
        color = mix(vec3(0.0), vec3(0.0, 0.3, 1.0), -u);
    }
    
    fragColor = vec4(color, 1.0);
}

1.13 wave-interactive

Wave equation with mouse interaction. Click to add ripples.

Buffer A:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (iFrame == 0) {
        fragColor = vec4(0.0);
        return;
    }
    
    ivec2 p = ivec2(fragCoord);
    
    float u = texelFetch(iChannel0, p, 0).r;
    float v = texelFetch(iChannel0, p, 0).g;
    
    float u_n = texelFetch(iChannel0, p + ivec2( 0,  1), 0).r;
    float u_s = texelFetch(iChannel0, p + ivec2( 0, -1), 0).r;
    float u_e = texelFetch(iChannel0, p + ivec2( 1,  0), 0).r;
    float u_w = texelFetch(iChannel0, p + ivec2(-1,  0), 0).r;
    float laplacian = u_n + u_s + u_e + u_w - 4.0 * u;
    
    // Symplectic Euler: update velocity first, then position
    float dt = 0.3;
    float c = 1.0;
    float newV = v + dt * c * c * laplacian;
    
    if (iMouse.z > 0.0) {
        float d = length(fragCoord - iMouse.xy);
        float sigma = 10.0;
        newV += 0.01 * exp(-d * d / (2.0 * sigma * sigma));
    }
    
    float newU = u + dt * newV;
    
    fragColor = vec4(newU, newV, 0.0, 1.0);
}

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    float u = texelFetch(iChannel0, ivec2(fragCoord), 0).r;
    u *= 3.0;
    
    vec3 color;
    if (u > 0.0) {
        color = mix(vec3(0.0), vec3(1.0, 0.5, 0.0), u);
    } else {
        color = mix(vec3(0.0), vec3(0.0, 0.3, 1.0), -u);
    }
    
    fragColor = vec4(color, 1.0);
}

1.14 wave-circle

Wave equation in a circular domain. Click to add ripples.

Buffer A:

bool inDomain(vec2 fragCoord, vec2 resolution) {
    vec2 center = resolution * 0.5;
    float radius = min(resolution.x, resolution.y) * 0.4;
    return length(fragCoord - center) < radius;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (iFrame == 0) {
        fragColor = vec4(0.0);
        return;
    }
    
    if (!inDomain(fragCoord, iResolution.xy)) {
        fragColor = vec4(0.0);
        return;
    }
    
    ivec2 p = ivec2(fragCoord);
    
    float u = texelFetch(iChannel0, p, 0).r;
    float v = texelFetch(iChannel0, p, 0).g;
    
    float u_n = texelFetch(iChannel0, p + ivec2( 0,  1), 0).r;
    float u_s = texelFetch(iChannel0, p + ivec2( 0, -1), 0).r;
    float u_e = texelFetch(iChannel0, p + ivec2( 1,  0), 0).r;
    float u_w = texelFetch(iChannel0, p + ivec2(-1,  0), 0).r;
    float laplacian = u_n + u_s + u_e + u_w - 4.0 * u;
    
    // Symplectic Euler
    float dt = 0.3;
    float c = 1.0;
    float newV = v + dt * c * c * laplacian;
    
    if (iMouse.z > 0.0) {
        float d = length(fragCoord - iMouse.xy);
        float sigma = 10.0;
        newV += 0.01 * exp(-d * d / (2.0 * sigma * sigma));
    }
    
    float newU = u + dt * newV;
    
    fragColor = vec4(newU, newV, 0.0, 1.0);
}

Image:

bool inDomain(vec2 fragCoord, vec2 resolution) {
    vec2 center = resolution * 0.5;
    float radius = min(resolution.x, resolution.y) * 0.4;
    return length(fragCoord - center) < radius;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (!inDomain(fragCoord, iResolution.xy)) {
        fragColor = vec4(0.1, 0.1, 0.1, 1.0);
        return;
    }
    
    float u = texelFetch(iChannel0, ivec2(fragCoord), 0).r;
    u *= 3.0;
    
    vec3 color;
    if (u > 0.0) {
        color = mix(vec3(0.0), vec3(1.0, 0.5, 0.0), u);
    } else {
        color = mix(vec3(0.0), vec3(0.0, 0.3, 1.0), -u);
    }
    
    fragColor = vec4(color, 1.0);
}

1.15 mandelbrot-waves

Wave equation inside the Mandelbrot set. Click to add ripples.

Common:

bool inDomain(vec2 fragCoord, vec2 resolution) {
    vec2 center = resolution * 0.5;
    float scale = min(resolution.x, resolution.y) * 0.3;
    vec2 c = (fragCoord - center) / scale;
    c.x -= 0.5;
    
    vec2 z = vec2(0.0);
    for (int i = 0; i < 100; i++) {
        z = vec2(z.x*z.x - z.y*z.y, 2.0*z.x*z.y) + c;
        if (dot(z, z) > 4.0) return false;
    }
    return true;
}

Buffer A:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (iFrame == 0) {
        fragColor = vec4(0.0);
        return;
    }
    
    if (!inDomain(fragCoord, iResolution.xy)) {
        fragColor = vec4(0.0);
        return;
    }
    
    ivec2 p = ivec2(fragCoord);
    
    float u = texelFetch(iChannel0, p, 0).r;
    float v = texelFetch(iChannel0, p, 0).g;
    
    float u_n = texelFetch(iChannel0, p + ivec2( 0,  1), 0).r;
    float u_s = texelFetch(iChannel0, p + ivec2( 0, -1), 0).r;
    float u_e = texelFetch(iChannel0, p + ivec2( 1,  0), 0).r;
    float u_w = texelFetch(iChannel0, p + ivec2(-1,  0), 0).r;
    float laplacian = u_n + u_s + u_e + u_w - 4.0 * u;
    
    // Symplectic Euler
    float dt = 0.3;
    float c = 1.0;
    float newV = v + dt * c * c * laplacian;
    
    if (iMouse.z > 0.0) {
        float d = length(fragCoord - iMouse.xy);
        float sigma = 10.0;
        newV += 0.01 * exp(-d * d / (2.0 * sigma * sigma));
    }
    
    float newU = u + dt * newV;
    
    fragColor = vec4(newU, newV, 0.0, 1.0);
}

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (!inDomain(fragCoord, iResolution.xy)) {
        fragColor = vec4(0.1, 0.1, 0.1, 1.0);
        return;
    }
    
    float u = texelFetch(iChannel0, ivec2(fragCoord), 0).r;
    u *= 3.0;
    
    vec3 color;
    if (u > 0.0) {
        color = mix(vec3(0.0), vec3(1.0, 0.5, 0.0), u);
    } else {
        color = mix(vec3(0.0), vec3(0.0, 0.3, 1.0), -u);
    }
    
    fragColor = vec4(color, 1.0);
}

1.16 mandelbrot-waves-hook

Hook demo: waves already bouncing inside the Mandelbrot set when the page loads.

Common:

bool inDomain(vec2 fragCoord, vec2 resolution) {
    vec2 center = resolution * 0.5;
    float scale = min(resolution.x, resolution.y) * 0.3;
    vec2 c = (fragCoord - center) / scale;
    c.x -= 0.5;
    
    vec2 z = vec2(0.0);
    for (int i = 0; i < 100; i++) {
        z = vec2(z.x*z.x - z.y*z.y, 2.0*z.x*z.y) + c;
        if (dot(z, z) > 4.0) return false;
    }
    return true;
}

Buffer A:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (iFrame == 0) {
        if (!inDomain(fragCoord, iResolution.xy)) {
            fragColor = vec4(0.0);
            return;
        }
        // Off-center Gaussian bump
        vec2 center = iResolution.xy * 0.5 + vec2(50.0, 30.0);
        float d = length(fragCoord - center);
        float u = 3.0 * exp(-d * d / 100.0);
        fragColor = vec4(u, 0.0, 0.0, 1.0);
        return;
    }
    
    if (!inDomain(fragCoord, iResolution.xy)) {
        fragColor = vec4(0.0);
        return;
    }
    
    ivec2 p = ivec2(fragCoord);
    
    float u = texelFetch(iChannel0, p, 0).r;
    float v = texelFetch(iChannel0, p, 0).g;
    
    float u_n = texelFetch(iChannel0, p + ivec2( 0,  1), 0).r;
    float u_s = texelFetch(iChannel0, p + ivec2( 0, -1), 0).r;
    float u_e = texelFetch(iChannel0, p + ivec2( 1,  0), 0).r;
    float u_w = texelFetch(iChannel0, p + ivec2(-1,  0), 0).r;
    float laplacian = u_n + u_s + u_e + u_w - 4.0 * u;
    
    // Symplectic Euler
    float dt = 0.3;
    float c = 1.0;
    float newV = v + dt * c * c * laplacian;
    
    if (iMouse.z > 0.0) {
        float d = length(fragCoord - iMouse.xy);
        float sigma = 10.0;
        newV += 0.01 * exp(-d * d / (2.0 * sigma * sigma));
    }
    
    float newU = u + dt * newV;
    
    fragColor = vec4(newU, newV, 0.0, 1.0);
}

Image:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (!inDomain(fragCoord, iResolution.xy)) {
        fragColor = vec4(0.1, 0.1, 0.1, 1.0);
        return;
    }
    
    float u = texelFetch(iChannel0, ivec2(fragCoord), 0).r;
    u *= 3.0;
    
    vec3 color;
    if (u > 0.0) {
        color = mix(vec3(0.0), vec3(1.0, 0.5, 0.0), u);
    } else {
        color = mix(vec3(0.0), vec3(0.0, 0.3, 1.0), -u);
    }
    
    fragColor = vec4(color, 1.0);
}