Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 4 min read

How I Built a 60 FPS Real-Time Fractal Explorer in WebGL 2.0 & Vectorized NumPy Library for 27 Complex Systems

How I Built a 60 FPS Real-Time Fractal Explorer in WebGL 2.0 & Vectorized NumPy Library for 27 Complex Systems πŸŒ€βš‘ Fractals are infinite mathematical universes generated by deceptively simple rules. Most of us have seen

How I Built a 60 FPS Real-Time Fractal Explorer in WebGL 2.0 & Vectorized NumPy Library for 27 Complex Systems πŸŒ€βš‘

Fractals are infinite mathematical universes generated by deceptively simple rules. Most of us have seen the classical Mandelbrot set ($z_{n+1} = z_n^2 + c$), but the mathematical universe of dynamical systems is vastly largerβ€”spanning non-analytic systems, rational physics maps, and complex analytic continuations.

Over the past week, I set out to build FantasticFractals: a dual-engine project consisting of:

  1. A 60 FPS interactive web visualizer powered by custom WebGL 2.0 GPU fragment shaders running in under 3 milliseconds per frame.
  2. A standalone Python scientific library on PyPI (fantasticfractals) with vectorized NumPy compute kernels, Hausdorff box-counting dimension analysis, and 16K Ultra-HD export.

Here is the engineering journey of how it was built, why standard canvas loops melt the CPU, and how GLSL shaders and NumPy array broadcasting make infinite mathematical art run seamlessly.

πŸ’₯ The Problem: Why Traditional Canvas Loops Fail

At 1080p resolution ($1920 \times 1080$), a single frame contains 2,073,600 pixels.

If you calculate an escape-time fractal with an iteration ceiling of 250 steps, computing one frame on a standard CPU thread requires up to 518,400,000 operations.

CPU Canvas 2D Loop:
  For every Y (1080):
    For every X (1920):
       While |z| < 2 and n < 250:
          z = z^2 + c
       -> Takes 8,000ms - 12,000ms (10+ seconds of UI freezing!)

When I tested this with serverless edge functions and CPU workers, dragging the canvas or zooming triggered severe lag (10+ seconds per render). It was completely unusable for interactive exploration.

⚑ The Solution: Hardware GPU Acceleration via WebGL 2.0 (2ms Compute)

To achieve silky-smooth 60 frames per second, we offloaded the entire mathematical calculation to the GPU using WebGL 2.0 fragment shaders.

In WebGL, your graphics card executes thousands of pixel computations concurrently across its stream processors. Instead of computing pixels one by one in a serial loop, every pixel on your screen computes its own escape trajectory simultaneously in under 3 milliseconds.

The Core GLSL Escape Loop

Here is how the complex arithmetic executes directly inside our fragment shader:

#version 300 es
precision highp float;

in vec2 vUv;
out vec4 fragColor;

uniform vec2 u_center;
uniform float u_zoom;
uniform int u_max_iterations;
uniform sampler2D u_palette_lut;

void main() {
    // Map normalized screen coordinates to complex plane
    vec2 c = u_center + (vUv - 0.5) * (3.0 / u_zoom);
    vec2 z = c;

    int iter = 0;
    float smoothVal = 0.0;

    for (int i = 0; i < 500; i++) {
        if (i >= u_max_iterations) break;

        // Complex multiplication: (x + iy)^2 = (x^2 - y^2) + i(2xy)
        float x2 = z.x * z.x;
        float y2 = z.y * z.y;

        if (x2 + y2 > 4.0) {
            // Smooth continuous exterior potential:
            // nu = n + 1 - ln(ln|z|) / ln(2)
            float log_zn = log(x2 + y2) / 2.0;
            float nu = log(log_zn / log(2.0)) / log(2.0);
            smoothVal = float(i) + 1.0 - nu;
            iter = i;
            break;
        }

        z = vec2(x2 - y2, 2.0 * z.x * z.y) + c;
    }

    // Sample color from precomputed 256-color LUT texture
    float u = fract(smoothVal / 40.0);
    fragColor = texture(u_palette_lut, vec2(u, 0.5));
}

By uploading a 1D palette Lookup Table (LUT) as a GPU texture, color interpolation and continuous shading happen at native hardware speed.

πŸŒ€ Beyond Mandelbrot: 27 Mathematical Fractals

Most visualizers stop at Mandelbrot and Julia. FantasticFractals provides complete analytical support for 27 distinct mathematical fractal systems organized into 7 mathematical families:

Category Famous Examples Mathematical Formulation
Classical Escape Mandelbrot, Julia $z_{n+1} = z_n^2 + c$
Non-Analytic Burning Ship, Buffalo, Celtic $z_{n+1} = (
Conjugate Escape Tricorn (Mandelbar), Multibar $z_{n+1} = \bar{z}_n^2 + c$
Higher Order Multibrot 3, Multibrot 4 $z_{n+1} = z_n^d + c$ ($d=3, 4$)
Convergence Basins Newton-Raphson, Nova $z_{n+1} = z_n - R \frac{z_n^3 - 1}{3z_n^2} + c$
Physics & Rational Magnet 1 & 2 (Potts model), Feather $z_{n+1} = \left(\frac{z^2 + c - 1}{2z + c - 2}\right)^2$
Geometric & Dynamical Sierpinski, Koch, Collatz, Lyapunov Analytic continuation of $3n+1$ into $\mathbb{C}$

🎬 The Evolution Slideshow Engine

Fractals are iterative limits ($n \to \infty$). But what does a fractal look like at iteration 1? At iteration 5? At iteration 35?

We built a uniform Evolution Slideshow Engine that lets you scrub through time:

  • As the iteration ceiling steps forward ($1 \to 2 \to 3 \to \dots \to 250$), you watch geometric boundaries fold, branch, and crystallize into chaotic attractors.
  • Includes an interactive scrubber, variable speed playback (5 to 60 iterations/sec), and Ping-Pong oscillation mode.

🐍 Building the Python Scientific Library (pip install fantasticfractals)

To bring these mathematical systems to data science notebooks, research pipelines, and high-resolution rendering, we packaged a standalone Python library and published it to PyPI:

pip install fantasticfractals

1. Vectorized NumPy Broadcasting

Instead of slow nested Python loops, algorithms are implemented using NumPy array broadcasting:

from fantasticfractals import render_fractal, export_image

# Render a 4K Burning Ship wallpaper
img = render_fractal(
    fractal_type="burning_ship",
    width=3840,
    height=2160,
    palette="cyberpunk",
    max_iterations=300
)
img.save("burning_ship_4k.png")

2. Empirical Hausdorff / Minkowski-Bouligand Box Dimension

The library includes automated box-counting dimension analysis $D = \lim_{\epsilon \to 0} \frac{\log N(\epsilon)}{\log(1/\epsilon)}$:

from fantasticfractals import FractalEngine, compute_box_dimension

engine = FractalEngine()
grid = engine.render_raw("sierpinski_triangle", width=1024, height=1024, max_iter=25)
dim, r_squared, scales, counts = compute_box_dimension(grid)

print(f"Calculated Box Dimension: {dim:.4f} (RΒ² = {r_squared:.4f})")
# Expected for Sierpinski Triangle: log(3)/log(2) β‰ˆ 1.5850

🌐 Try It Out (Links & Open Source)

Everything is open source under the MIT License:

If you enjoy mathematical art and creative coding, feel free to give the repository a star or try zooming into the Burning Ship antenna! What fractal system should we implement next? Let me know in the comments below!

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.