Cool Mandlebrot Shader (Euler Julia Resonance)

Cool Mendlebrot Shader: Many interesting visuals can be achieved by tweaking parameters. Generated by Sonnet 4.6. 

Shader code
shader_type canvas_item;

// =============================================================================
// EULER-JULIA RESONANCE SHADER  v2.1  (Godot 4 / canvas_item)
// Drop onto a ColorRect → Full Rect. Set aspect_ratio = viewport W / H.
// =============================================================================
//
// FIXES vs v1:
//   - PI and TAU removed (built-in in Godot 4 GLSL, can't redeclare)
//   - pan split into pan_x / pan_y (vec2 uniform can't carry hint_range)
//   - 11 themes via color_theme : hint_range(0, 10)
//   - Low-iteration mode (5-15) is visually rich via domain + orbit traps
//
// QUICK START DEFAULTS:
//   color_theme       0        (0-10, see list below)
//   time_scale        0.10     loop period ~62.8 s
//   zoom              1.90
//   c_radius          0.7885   Julia parameter circle radius
//   euler_twist       0.80
//   max_iter          8       try 5-15 for a painterly look
//   pan_x / pan_y     0.0
//   glow_strength     1.40
//   vignette_strength 0.80
//   shimmer_strength  0.40
//   aspect_ratio      1.7778   <- set to viewport_width / viewport_height
// =============================================================================

// --- Color theme -------------------------------------------------------------
uniform int color_theme : hint_range(0, 10) = 0;
// 0  Void Pulse      - deep indigo / electric violet / magenta
// 1  Abyssal Cyan    - near-black navy / electric cyan / ice
// 2  Ember Forge     - charcoal / deep red / molten orange
// 3  Toxic Circuit   - black / acid green / electric lime
// 4  Solar Eclipse   - black / deep amber / white corona
// 5  Neon Noir       - near-black / hot pink / electric blue
// 6  Obsidian Fire   - pure black / smoldering orange / ashy edge
// 7  Cosmic Dust     - deep space blue / gold / rose gold
// 8  Blood Moon      - near-black / deep crimson / rust / bone
// 9  Deep Sea        - void teal / bioluminescent aqua / pale coral
// 10 Inkwell         - pure black and white / fine ink gradients

// --- Animation ---------------------------------------------------------------
uniform float time_scale      : hint_range(0.01, 0.50) = 0.10;
uniform float color_speed     : hint_range(0.00, 2.00) = 0.35;

// --- Fractal -----------------------------------------------------------------
uniform float zoom            : hint_range(0.50, 6.00) = 1.90;
uniform float c_radius        : hint_range(0.10, 1.50) = 0.7885;
// 0.40=soft blobs  0.7885=classic  0.90=dendrites  1.10=Cantor dust
uniform float euler_twist     : hint_range(0.00, 5.00) = 0.80;
// 0.0=standard Julia  0.5-1.5=galaxy arms  3+=heavy spirals
uniform int   max_iter        : hint_range(5, 300) = 8;
// 5-15=painterly domain  80-120=classic fractal  200+=deep detail

// --- Pan (split from vec2 to allow hint_range) -------------------------------
uniform float pan_x           : hint_range(-2.0, 2.0) = 0.0;
uniform float pan_y           : hint_range(-2.0, 2.0) = 0.0;

// --- Visuals -----------------------------------------------------------------
uniform float glow_strength      : hint_range(0.0, 3.0) = 1.40;
uniform float vignette_strength  : hint_range(0.0, 2.0) = 0.80;
uniform float shimmer_strength   : hint_range(0.0, 1.0) = 0.40;
uniform float aspect_ratio       : hint_range(0.50, 3.00) = 1.7778;
// 16:9=1.7778  4:3=1.3333  21:9=2.3333  1:1=1.0

// =============================================================================
// COMPLEX HELPERS
// =============================================================================

vec2 csq(vec2 z) {
    return vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
}

// Euler rotation: multiply z by e^(i*theta)
vec2 euler_rot(vec2 z, float theta) {
    float s = sin(theta);
    float c = cos(theta);
    return vec2(z.x * c - z.y * s, z.x * s + z.y * c);
}

// =============================================================================
// COSINE PALETTE  (Inigo Quilez technique)
// pal(t) = a + b*cos(TAU*(c*t+d))
// Period = 1.0, so color cycling never has discontinuities.
// =============================================================================
vec3 pal(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
    return a + b * cos(TAU * (c * t + d));
}

// =============================================================================
// 11 THEME PALETTES
// Three palettes per theme: pA (escape), pB (trap), pC (stripe/domain)
// All dark themes keep 'a' values near black. Theme 10 outputs greyscale.
// =============================================================================
void get_palettes(int theme, float ta, float tb, float tc,
                  out vec3 pA, out vec3 pB, out vec3 pC) {

    if (theme == 0) {
        // Void Pulse - indigo core, violet flash, magenta edge
        pA = pal(ta, vec3(0.02, 0.00, 0.05), vec3(0.45, 0.10, 0.60), vec3(1.00, 0.70, 0.40), vec3(0.00, 0.15, 0.20));
        pB = pal(tb, vec3(0.00, 0.00, 0.08), vec3(0.20, 0.05, 0.55), vec3(2.00, 1.00, 0.50), vec3(0.50, 0.20, 0.25));
        pC = pal(tc, vec3(0.04, 0.00, 0.06), vec3(0.55, 0.00, 0.45), vec3(1.00, 1.20, 0.50), vec3(0.80, 0.90, 0.30));

    } else if (theme == 1) {
        // Abyssal Cyan - near-black navy, electric cyan, icy white
        pA = pal(ta, vec3(0.00, 0.02, 0.06), vec3(0.05, 0.40, 0.55), vec3(1.00, 0.60, 0.30), vec3(0.00, 0.10, 0.20));
        pB = pal(tb, vec3(0.00, 0.03, 0.05), vec3(0.00, 0.55, 0.50), vec3(1.50, 1.00, 0.50), vec3(0.30, 0.10, 0.50));
        pC = pal(tc, vec3(0.00, 0.04, 0.06), vec3(0.05, 0.50, 0.60), vec3(1.00, 0.80, 0.50), vec3(0.60, 0.20, 0.10));

    } else if (theme == 2) {
        // Ember Forge - charcoal, deep red, molten orange
        pA = pal(ta, vec3(0.04, 0.01, 0.00), vec3(0.50, 0.15, 0.02), vec3(0.80, 0.60, 1.20), vec3(0.00, 0.25, 0.50));
        pB = pal(tb, vec3(0.05, 0.01, 0.00), vec3(0.55, 0.20, 0.00), vec3(1.00, 0.50, 0.00), vec3(0.10, 0.30, 0.40));
        pC = pal(tc, vec3(0.06, 0.02, 0.00), vec3(0.60, 0.35, 0.05), vec3(1.00, 0.80, 0.50), vec3(0.00, 0.10, 0.20));

    } else if (theme == 3) {
        // Toxic Circuit - black, acid green, electric lime
        pA = pal(ta, vec3(0.00, 0.04, 0.00), vec3(0.05, 0.50, 0.05), vec3(1.20, 1.00, 2.00), vec3(0.00, 0.00, 0.50));
        pB = pal(tb, vec3(0.00, 0.05, 0.00), vec3(0.00, 0.55, 0.10), vec3(2.00, 1.00, 0.00), vec3(0.00, 0.25, 0.25));
        pC = pal(tc, vec3(0.01, 0.04, 0.01), vec3(0.10, 0.50, 0.10), vec3(1.00, 1.50, 1.00), vec3(0.50, 0.00, 0.50));

    } else if (theme == 4) {
        // Solar Eclipse - black, deep amber, white corona
        pA = pal(ta, vec3(0.03, 0.02, 0.00), vec3(0.50, 0.35, 0.00), vec3(0.80, 0.50, 1.00), vec3(0.00, 0.10, 0.40));
        pB = pal(tb, vec3(0.04, 0.03, 0.00), vec3(0.55, 0.45, 0.05), vec3(1.00, 0.70, 0.00), vec3(0.20, 0.00, 0.50));
        pC = pal(tc, vec3(0.05, 0.04, 0.01), vec3(0.60, 0.50, 0.30), vec3(1.00, 0.80, 0.40), vec3(0.00, 0.20, 0.30));

    } else if (theme == 5) {
        // Neon Noir - near-black, hot pink, electric blue
        pA = pal(ta, vec3(0.02, 0.00, 0.04), vec3(0.55, 0.00, 0.40), vec3(1.00, 0.50, 0.40), vec3(0.00, 0.50, 0.20));
        pB = pal(tb, vec3(0.00, 0.00, 0.05), vec3(0.10, 0.10, 0.55), vec3(1.00, 2.00, 1.00), vec3(0.50, 0.20, 0.25));
        pC = pal(tc, vec3(0.02, 0.00, 0.04), vec3(0.50, 0.00, 0.50), vec3(0.50, 1.00, 0.50), vec3(0.25, 0.50, 0.00));

    } else if (theme == 6) {
        // Obsidian Fire - pure black, smoldering orange, ashy white edge
        pA = pal(ta, vec3(0.02, 0.01, 0.00), vec3(0.55, 0.25, 0.00), vec3(0.70, 0.40, 1.50), vec3(0.00, 0.30, 0.60));
        pB = pal(tb, vec3(0.03, 0.01, 0.00), vec3(0.60, 0.30, 0.02), vec3(1.00, 0.60, 0.20), vec3(0.10, 0.20, 0.40));
        pC = pal(tc, vec3(0.04, 0.02, 0.01), vec3(0.55, 0.40, 0.25), vec3(1.20, 0.90, 0.50), vec3(0.00, 0.10, 0.20));

    } else if (theme == 7) {
        // Cosmic Dust - deep space blue, gold, rose gold shimmer
        pA = pal(ta, vec3(0.01, 0.00, 0.03), vec3(0.35, 0.25, 0.10), vec3(0.80, 0.60, 1.00), vec3(0.00, 0.15, 0.30));
        pB = pal(tb, vec3(0.02, 0.01, 0.02), vec3(0.40, 0.30, 0.15), vec3(1.00, 0.70, 0.50), vec3(0.30, 0.10, 0.20));
        pC = pal(tc, vec3(0.02, 0.01, 0.02), vec3(0.45, 0.20, 0.25), vec3(1.00, 1.00, 0.50), vec3(0.60, 0.30, 0.10));

    } else if (theme == 8) {
        // Blood Moon - near-black, deep crimson, rust, pale bone
        pA = pal(ta, vec3(0.04, 0.00, 0.00), vec3(0.50, 0.02, 0.02), vec3(0.80, 1.00, 1.20), vec3(0.00, 0.25, 0.50));
        pB = pal(tb, vec3(0.05, 0.01, 0.00), vec3(0.55, 0.05, 0.00), vec3(1.00, 0.50, 0.30), vec3(0.10, 0.40, 0.60));
        pC = pal(tc, vec3(0.05, 0.02, 0.00), vec3(0.55, 0.20, 0.05), vec3(1.00, 0.70, 0.50), vec3(0.00, 0.10, 0.20));

    } else if (theme == 9) {
        // Deep Sea - void teal, bioluminescent aqua, pale coral
        pA = pal(ta, vec3(0.00, 0.03, 0.04), vec3(0.00, 0.40, 0.45), vec3(1.20, 1.00, 0.50), vec3(0.00, 0.20, 0.30));
        pB = pal(tb, vec3(0.00, 0.04, 0.04), vec3(0.05, 0.45, 0.40), vec3(2.00, 1.00, 0.30), vec3(0.50, 0.10, 0.30));
        pC = pal(tc, vec3(0.02, 0.03, 0.03), vec3(0.20, 0.40, 0.35), vec3(1.00, 1.20, 0.70), vec3(0.70, 0.40, 0.10));

    } else {
        // theme 10: Inkwell - pure black and white
        // All three palettes output identical greyscale values (r=g=b).
        // pA: escape bands - soft grey waves from near-black to white
        // pB: orbit trap  - finer detail layer, slightly offset phase
        // pC: stripe/edge - sharpest contrast, used for crisp boundary lines
        pA = pal(ta, vec3(0.03), vec3(0.48), vec3(1.00), vec3(0.00));
        pB = pal(tb, vec3(0.02), vec3(0.45), vec3(1.00), vec3(0.25));
        pC = pal(tc, vec3(0.01), vec3(0.50), vec3(2.00), vec3(0.10));
    }
}

// =============================================================================
// FRAGMENT
// =============================================================================
void fragment() {
    vec2 uv = UV - 0.5;
    uv.x *= aspect_ratio;

    float anim_t  = TIME * time_scale;
    float color_t = TIME * color_speed;

    // Euler circle parameter: c(t) = c_radius * e^(i*anim_t)
    vec2 c = c_radius * vec2(cos(anim_t), sin(anim_t));

    // Starting z with slow global rotation
    vec2 pan = vec2(pan_x, pan_y);
    vec2 z   = euler_rot(uv / zoom + pan, anim_t * 0.04);
    vec2 z0  = z;

    // =========================================================================
    // ITERATION LOOP
    // =========================================================================
    float smooth_n   = 0.0;
    float trap_circ  = 1e9;
    float trap_axis  = 1e9;
    float trap_diag  = 1e9;
    float trap_minr  = 1e9;
    float stripe_acc = 0.0;
    float angle_sum  = 0.0;
    bool  escaped    = false;

    for (int i = 0; i < 300; i++) {
        if (i >= max_iter) { break; }

        float r2 = dot(z, z);

        if (r2 > 256.0) {
            escaped = true;
            float log_r = log(r2) * 0.5;
            float nu    = log(log_r / log(2.0)) / log(2.0);
            smooth_n    = float(i) + 1.0 - nu;
            break;
        }

        float r = sqrt(r2);

        trap_circ = min(trap_circ, abs(r - 1.0));
        trap_axis = min(trap_axis, abs(z.x));
        trap_diag = min(trap_diag, abs(z.x - z.y) * 0.707);
        trap_minr = min(trap_minr, r);

        float arg_z  = atan(z.y, z.x);
        angle_sum   += arg_z;
        stripe_acc  += 0.5 * sin(arg_z * 5.0 + anim_t * 1.6) + 0.5;

        // Euler twist: z -> z^2 * e^(i*tau*|z|^2) + c
        float twist_angle = euler_twist * r2 * 0.12;
        z = euler_rot(csq(z), twist_angle) + c;
    }

    // =========================================================================
    // DERIVED SIGNAL VALUES
    // =========================================================================
    float iter_f = float(max_iter);
    float fn     = smooth_n / iter_f;

    float fc = clamp(trap_circ * 3.0, 0.0, 1.0);
    float fa = clamp(trap_axis * 4.0, 0.0, 1.0);
    float fd = clamp(trap_diag * 4.0, 0.0, 1.0);
    float fr = clamp(trap_minr * 1.5, 0.0, 1.0);

    float fs = stripe_acc / max(smooth_n + 1.0, 1.0);
    float fw = sin(angle_sum * 0.08 + anim_t) * 0.5 + 0.5;

    float final_arg = atan(z.y, z.x) / TAU + 0.5;

    // =========================================================================
    // LOW-ITERATION ENRICHMENT
    // =========================================================================
    float low_mix   = clamp(1.0 - (iter_f - 5.0) / 35.0, 0.0, 1.0);
    float domain_a  = atan(z0.y, z0.x) / TAU + 0.5;
    float domain_r  = clamp(length(z0) / 2.0, 0.0, 1.0);
    float potential = exp(-trap_minr * 2.5) * 0.8;

    float ta_in = mix(fn,                   domain_a + domain_r * 0.3, low_mix);
    float tb_in = mix(fc,                   1.0 - fr,                  low_mix);
    float tc_in = mix(fs + final_arg * 0.4, domain_a + fw * 0.3,       low_mix);

    // =========================================================================
    // PALETTE LOOKUP
    // =========================================================================
    vec3 pA, pB, pC;
    get_palettes(color_theme,
        ta_in + color_t * 0.10,
        tb_in + color_t * 0.07,
        tc_in + color_t * 0.12,
        pA, pB, pC);

    // =========================================================================
    // COLORING
    // =========================================================================
    vec3 col;

    if (escaped) {
        col = pA;
        col = mix(col, pB, smoothstep(0.0, 0.7, 1.0 - fc) * 0.50);
        col = mix(col, pC, (1.0 - fd) * 0.20);
        col = mix(col, pC, smoothstep(0.1, 0.6, fs) * 0.35);
        col = mix(col, pC * 1.1, final_arg * 0.15 * (1.0 - fn));
        col += potential * low_mix * pB * 0.6;
        float edge = exp(-fn * iter_f * 0.10);
        col += edge * glow_strength * mix(pB, pC, 0.4) * 1.2;

    } else {
        float int_sig = fc * 0.5 + fa * 0.3 + fw * 0.2 + (1.0 - fr) * 0.4;
        vec3 hi_col   = mix(vec3(0.0), pA, clamp(int_sig, 0.0, 1.0)) * 0.55;
        hi_col += fw * 0.07 * pC;

        float dom   = domain_a + domain_r * 0.4 + color_t * 0.08;
        vec3 lo_col = pal(dom, pA * 0.5, pB * 0.8, pC, vec3(0.0));
        lo_col *= 0.7 + potential * 0.6;
        lo_col += (1.0 - fr) * 0.3 * pC;

        col = mix(hi_col, lo_col, low_mix);
        col += fw * 0.09 * pB;
    }

    // =========================================================================
    // POST-PROCESSING
    // =========================================================================

    // Balatro shimmer
    float shx = sin(UV.x * 14.0 + anim_t * 2.3) * 0.5 + 0.5;
    float shy = sin(UV.y * 11.0 + anim_t * 1.8) * 0.5 + 0.5;
    col += shx * shy * shimmer_strength * 0.04 * pB;

    // Hue drift - skipped for B&W theme to keep it pure greyscale
    if (color_theme != 10) {
        float hue_drift = sin(anim_t * 0.38) * 0.06;
        col = mix(col, col.gbr, hue_drift);
    }

    // Radial vignette
    float dist2    = dot(uv, uv);
    float vignette = 1.0 - smoothstep(0.30, 1.30, dist2 * vignette_strength);
    col *= mix(0.15, 1.0, vignette);

    // Gaussian center boost
    col *= 1.0 + 0.28 * exp(-dist2 * 3.0);

    // Strip colour for B&W - convert to luminance after all processing
    // so every lighting / glow effect still runs, then desaturate
    if (color_theme == 10) {
        float luma = dot(col, vec3(0.299, 0.587, 0.114));
        col = vec3(luma);
    }

    // Filmic Reinhard tone map
    col = col / (col + vec3(0.75));

    // Gamma correction (gamma 2.2)
    col = pow(max(col, vec3(0.0)), vec3(0.4545));

    COLOR = vec4(col, 1.0);
}
Live Preview
Tags
Mandelbrot Fractal
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments