Author

Steve Trettel

Published

January 2026

1 Day 2 Code

Complete, standalone code for each shader referenced in Day 2. Each listing can be copied directly into Shadertoy and run immediately.

1.1 Common Functions

These helper functions are used throughout Day 2:

// Normalize screen coordinates to centered, aspect-corrected space
vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 2.5;
}

// Complex multiplication: (a + bi)(c + di)
vec2 cmul(vec2 z, vec2 w) {
    return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
}

// Cosine palette for smooth coloring
vec3 palette(float t) {
    vec3 a = vec3(0.5, 0.5, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.5);
    vec3 c = vec3(1.0, 1.0, 1.0);
    vec3 d = vec3(0.00, 0.33, 0.67);
    return a + b * cos(6.28318 * (c * t + d));
}

1.2 mandelbrot-zoom

Animated zoom into the Mandelbrot set with smooth coloring.

vec2 cmul(vec2 z, vec2 w) {
    return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
}

vec3 palette(float t) {
    vec3 a = vec3(0.5, 0.5, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.5);
    vec3 c = vec3(1.0, 1.0, 1.0);
    vec3 d = vec3(0.00, 0.33, 0.67);
    return a + b * cos(6.28318 * (c * t + d));
}

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    float zoom = pow(1.5, mod(iTime, 30.0));
    return uv * 2.5 / zoom;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    // Zoom into the seahorse valley
    vec2 center = vec2(-0.745, 0.186);
    vec2 c = center + normalize_coord(fragCoord);
    
    vec3 color = vec3(0.0, 0.0, 0.0);
    
    vec2 z = vec2(0.0, 0.0);
    int i;
    for (i = 0; i < 200; i++) {
        if (length(z) > 2.0) {
            // Smooth coloring
            float log_zn = log(length(z) * length(z)) / 2.0;
            float nu = log(log_zn / log(2.0)) / log(2.0);
            float smooth_iter = float(i) + 1.0 - nu;
            float t = smooth_iter / 200.0;
            color = palette(t * 4.0);
            break;
        }
        z = cmul(z, z) + c;
    }
    
    fragColor = vec4(color, 1.0);
}

1.3 mandelbrot-bw

Black and white Mandelbrot set.

vec2 cmul(vec2 z, vec2 w) {
    return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
}

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 2.5;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 c = normalize_coord(fragCoord);
    c.x = c.x - 0.5;  // shift left to center the interesting part
    
    vec3 color = vec3(0.0, 0.0, 0.0);
    
    vec2 z = vec2(0.0, 0.0);
    
    for (int i = 0; i < 100; i++) {
        if (length(z) > 2.0) {
            color = vec3(1.0, 1.0, 1.0);
            break;
        }
        z = cmul(z, z) + c;
    }
    
    fragColor = vec4(color, 1.0);
}

1.4 mandelbrot-gray

Mandelbrot set with grayscale iteration coloring.

vec2 cmul(vec2 z, vec2 w) {
    return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
}

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 2.5;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 c = normalize_coord(fragCoord);
    c.x = c.x - 0.5;
    
    vec3 color = vec3(0.0, 0.0, 0.0);
    
    vec2 z = vec2(0.0, 0.0);
    int i;
    for (i = 0; i < 100; i++) {
        if (length(z) > 2.0) break;
        z = cmul(z, z) + c;
    }
    
    if (i < 100) {
        float t = float(i) / 100.0;
        float gray = 1.0 - t;
        color = vec3(gray, gray, gray);
    }
    
    fragColor = vec4(color, 1.0);
}

1.5 mandelbrot-color

Mandelbrot set with cosine palette coloring.

vec2 cmul(vec2 z, vec2 w) {
    return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
}

vec3 palette(float t) {
    vec3 a = vec3(0.5, 0.5, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.5);
    vec3 c = vec3(1.0, 1.0, 1.0);
    vec3 d = vec3(0.00, 0.33, 0.67);
    return a + b * cos(6.28318 * (c * t + d));
}

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 2.5;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 c = normalize_coord(fragCoord);
    c.x = c.x - 0.5;
    
    vec3 color = vec3(0.0, 0.0, 0.0);
    
    vec2 z = vec2(0.0, 0.0);
    int i;
    for (i = 0; i < 100; i++) {
        if (length(z) > 2.0) break;
        z = cmul(z, z) + c;
    }
    
    if (i < 100) {
        float t = float(i) / 100.0;
        color = palette(t);
    }
    
    fragColor = vec4(color, 1.0);
}

1.6 julia-static

Julia set with fixed parameter.

vec2 cmul(vec2 z, vec2 w) {
    return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
}

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 2.5;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    // Fixed parameter
    vec2 c = vec2(-0.7, 0.27015);
    
    // z starts at pixel position
    vec2 z = normalize_coord(fragCoord);
    
    vec3 color = vec3(0.0, 0.0, 0.0);
    
    int i;
    for (i = 0; i < 100; i++) {
        if (length(z) > 2.0) break;
        z = cmul(z, z) + c;
    }
    
    if (i < 100) {
        float t = float(i) / 100.0;
        float gray = 1.0 - t;
        color = vec3(gray, gray, gray);
    }
    
    fragColor = vec4(color, 1.0);
}

1.7 julia-explorer

Interactive Julia set explorer: Mandelbrot as parameter space, Julia set overlaid.

vec2 cmul(vec2 z, vec2 w) {
    return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
}

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 2.5;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 p = normalize_coord(fragCoord);
    
    // Get c from mouse position
    vec2 c = normalize_coord(iMouse.xy);
    c.x = c.x - 0.5;
    
    // Default to interesting value if no mouse
    if (iMouse.x < 1.0) {
        c = vec2(-0.7, 0.27015);
    }
    
    // Mandelbrot iteration (for background)
    vec2 mc = p;
    mc.x = mc.x - 0.5;
    vec2 mz = vec2(0.0, 0.0);
    int m_i;
    for (m_i = 0; m_i < 100; m_i++) {
        if (length(mz) > 2.0) break;
        mz = cmul(mz, mz) + mc;
    }
    
    // Julia iteration (for foreground)
    vec2 jz = p;
    int j_i;
    for (j_i = 0; j_i < 100; j_i++) {
        if (length(jz) > 2.0) break;
        jz = cmul(jz, jz) + c;
    }
    
    // Mandelbrot grayscale (faded to serve as background)
    vec3 color = vec3(1.0, 1.0, 1.0);
    if (m_i < 100) {
        float t = float(m_i) / 100.0;
        float gray = 0.6 + 0.4 * (1.0 - t);  // range [0.6, 1.0]
        color = vec3(gray, gray, gray);
    } else {
        color = vec3(0.5, 0.5, 0.5);  // Mandelbrot interior
    }
    
    // Julia grayscale (overlaid)
    if (j_i < 100) {
        float t = float(j_i) / 100.0;
        float gray = 1.0 - t;
        color = vec3(gray, gray, gray);
    } else {
        color = vec3(0.0, 0.0, 0.0);  // Julia interior
    }
    
    // Draw red dot at c position
    vec2 c_pos = c;
    c_pos.x = c_pos.x + 0.5;
    if (length(p - c_pos) < 0.05) {
        color = vec3(1.0, 0.0, 0.0);
    }
    
    fragColor = vec4(color, 1.0);
}

1.8 inversion-toggle

Circle inversion visualization with toggling.

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 4.0;
}

vec2 invert(vec2 p) {
    return p / dot(p, p);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 p = normalize_coord(fragCoord);
    vec2 p_inv = invert(p);
    
    // Toggle between original and inverted every second
    float time = fract(iTime * 0.5);
    vec2 q;
    if (time < 0.5) {
        q = p;
    } else {
        q = p_inv;
    }
    
    vec3 color = vec3(0.1, 0.1, 0.15);
    
    // Draw the unit circle
    float d_unit = abs(length(p) - 1.0);
    if (d_unit < 0.02) color = vec3(0.5, 0.5, 0.5);
    
    // Draw a vertical line at x = 2
    if (abs(q.x - 2.0) < 0.02) color = vec3(1.0, 1.0, 0.0);
    
    // Draw a horizontal line at y = 1.5
    if (abs(q.y - 1.5) < 0.02) color = vec3(1.0, 1.0, 0.0);
    
    // Draw a circle centered at (2, 0) with radius 0.5
    float d_circle = abs(length(q - vec2(2.0, 0.0)) - 0.5);
    if (d_circle < 0.02) color = vec3(1.0, 1.0, 0.0);
    
    fragColor = vec4(color, 1.0);
}

1.9 inversion-grid

Circle inversion of a grid.

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 4.0;
}

vec2 invert(vec2 p) {
    return p / dot(p, p);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 p = normalize_coord(fragCoord);
    vec2 p_inv = invert(p);
    
    // Toggle
    float time = fract(iTime * 0.5);
    vec2 q;
    if (time < 0.5) {
        q = p;
    } else {
        q = p_inv;
    }
    
    vec3 color = vec3(0.1, 0.1, 0.15);
    
    // Draw the unit circle
    float d_unit = abs(length(p) - 1.0);
    if (d_unit < 0.02) color = vec3(0.5, 0.5, 0.5);
    
    // Draw a grid using mod
    vec2 grid = mod(q, 0.5);
    if (grid.x < 0.02 || grid.y < 0.02) color = vec3(1.0, 1.0, 0.0);
    
    fragColor = vec4(color, 1.0);
}

1.10 inversion-moving

Inversion through a moving circle.

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 4.0;
}

struct Circle {
    vec2 center;
    float radius;
};

vec2 invert(vec2 p, Circle c) {
    vec2 d = p - c.center;
    return c.center + c.radius * c.radius * d / dot(d, d);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 p = normalize_coord(fragCoord);
    
    // Animate the inversion circle
    Circle inv_circle;
    inv_circle.center = vec2(sin(iTime) * 0.5, cos(iTime * 0.7) * 0.5);
    inv_circle.radius = 1.0 + 0.3 * sin(iTime * 1.3);
    
    vec2 p_inv = invert(p, inv_circle);
    
    vec3 color = vec3(0.1, 0.1, 0.15);
    
    // Draw the inversion circle
    float d_inv = abs(length(p - inv_circle.center) - inv_circle.radius);
    if (d_inv < 0.02) color = vec3(0.5, 0.5, 0.5);
    
    // Draw a grid in the inverted space
    vec2 grid = mod(p_inv, 0.5);
    if (grid.x < 0.02 || grid.y < 0.02) color = vec3(1.0, 1.0, 0.0);
    
    fragColor = vec4(color, 1.0);
}

1.11 apollonian-setup

The four mutually tangent circles.

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 6.0;
}

struct Circle {
    vec2 center;
    float radius;
};

float distToCircle(vec2 p, Circle c) {
    return abs(length(p - c.center) - c.radius);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 p = normalize_coord(fragCoord);
    
    float r = 1.0;
    float triSide = 2.0 * r;
    float circumradius = triSide / sqrt(3.0);
    
    Circle c1 = Circle(vec2(0.0, circumradius), r);
    Circle c2 = Circle(vec2(-circumradius * sqrt(3.0)/2.0, -circumradius * 0.5), r);
    Circle c3 = Circle(vec2(circumradius * sqrt(3.0)/2.0, -circumradius * 0.5), r);
    Circle outer = Circle(vec2(0.0, 0.0), circumradius + r);
    
    vec3 color = vec3(0.1, 0.1, 0.15);
    
    if (distToCircle(p, c1) < 0.03) color = vec3(1.0, 0.3, 0.3);
    if (distToCircle(p, c2) < 0.03) color = vec3(0.3, 1.0, 0.3);
    if (distToCircle(p, c3) < 0.03) color = vec3(0.3, 0.3, 1.0);
    if (distToCircle(p, outer) < 0.03) color = vec3(1.0, 1.0, 1.0);
    
    fragColor = vec4(color, 1.0);
}

1.12 apollonian-iterated

Full Apollonian gasket with iteration.

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 6.0;
}

struct Circle {
    vec2 center;
    float radius;
};

vec2 invert(vec2 p, Circle c) {
    vec2 d = p - c.center;
    return c.center + c.radius * c.radius * d / dot(d, d);
}

float distToCircle(vec2 p, Circle c) {
    return abs(length(p - c.center) - c.radius);
}

bool isInside(vec2 p, Circle c) {
    return length(p - c.center) < c.radius;
}

vec3 palette(float t) {
    vec3 a = vec3(0.5, 0.5, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.5);
    vec3 c = vec3(1.0, 1.0, 1.0);
    vec3 d = vec3(0.00, 0.33, 0.67);
    return a + b * cos(6.28318 * (c * t + d));
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 p = normalize_coord(fragCoord);
    
    float r = 1.0;
    float triSide = 2.0 * r;
    float circumradius = triSide / sqrt(3.0);
    
    Circle c1 = Circle(vec2(0.0, circumradius), r);
    Circle c2 = Circle(vec2(-circumradius * sqrt(3.0)/2.0, -circumradius * 0.5), r);
    Circle c3 = Circle(vec2(circumradius * sqrt(3.0)/2.0, -circumradius * 0.5), r);
    Circle outer = Circle(vec2(0.0, 0.0), circumradius + r);
    
    int i;
    for (i = 0; i < 50; i++) {
        if (isInside(p, c1)) {
            p = invert(p, c1);
        } else if (isInside(p, c2)) {
            p = invert(p, c2);
        } else if (isInside(p, c3)) {
            p = invert(p, c3);
        } else if (!isInside(p, outer)) {
            p = invert(p, outer);
        } else {
            break;
        }
    }
    
    float t = float(i) / 50.0;
    vec3 color = palette(t);
    
    float dMin = min(min(distToCircle(p, c1), distToCircle(p, c2)), 
                     min(distToCircle(p, c3), distToCircle(p, outer)));
    if (dMin < 0.02) color = vec3(1.0, 1.0, 1.0);
    
    fragColor = vec4(color, 1.0);
}

1.13 apollonian-final

Apollonian gasket with coloring emphasizing the limit set.

vec2 normalize_coord(vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = uv - vec2(0.5, 0.5);
    uv.x *= iResolution.x / iResolution.y;
    return uv * 6.0;
}

struct Circle {
    vec2 center;
    float radius;
};

vec2 invert(vec2 p, Circle c) {
    vec2 d = p - c.center;
    return c.center + c.radius * c.radius * d / dot(d, d);
}

bool isInside(vec2 p, Circle c) {
    return length(p - c.center) < c.radius;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 p = normalize_coord(fragCoord);
    
    float r = 1.0;
    float triSide = 2.0 * r;
    float circumradius = triSide / sqrt(3.0);
    
    Circle c1 = Circle(vec2(0.0, circumradius), r);
    Circle c2 = Circle(vec2(-circumradius * sqrt(3.0)/2.0, -circumradius * 0.5), r);
    Circle c3 = Circle(vec2(circumradius * sqrt(3.0)/2.0, -circumradius * 0.5), r);
    Circle outer = Circle(vec2(0.0, 0.0), circumradius + r);
    
    int i;
    for (i = 0; i < 100; i++) {
        if (isInside(p, c1)) {
            p = invert(p, c1);
        } else if (isInside(p, c2)) {
            p = invert(p, c2);
        } else if (isInside(p, c3)) {
            p = invert(p, c3);
        } else if (!isInside(p, outer)) {
            p = invert(p, outer);
        } else {
            break;
        }
    }
    
    float t = float(i) / 100.0;
    float t2 = pow(t, 2.0);
    vec3 color = 30.0 * vec3(t2, t2, t2);
    
    fragColor = vec4(color, 1.0);
}