Rendering the Mandelbrot set fast comes down to one insight: every pixel is an independent calculation, so a GPU can compute all of them at once. This is a walkthrough of how Vortexia renders the set at 60fps inside a browser tab using a WebGL2 fragment shader — the same approach behind the live demos on this site. It assumes you know the definition (the set of c for which z → z² + c stays bounded) and want to see how that becomes pixels.
Why a fragment shader
A fragment shader is a tiny program the GPU runs once per pixel, all pixels in parallel. The Mandelbrot escape-time test is embarrassingly parallel — no pixel depends on any other — so it maps onto the GPU almost perfectly. A CPU loops over a million pixels one at a time; the GPU evaluates thousands simultaneously. That is the entire reason a browser tab can zoom a fractal smoothly.
Step 1 — map each pixel to a complex number
The shader receives the pixel's screen coordinate and must convert it into a point c on the complex plane. You center the screen on zero, scale by the zoom, then shift by the view's center offset. In Vortexia the core of that mapping looks like this:
vec2 c = (screenPos - 0.5) * 2.0; // center the screen on (0,0)
c *= 3.0 / u_zoom; // scale: half-height = 3.0 / zoom
c += u_offset; // recenter on (cx, cy)
The 3.0 factor means that at zoom = 1 the visible window is roughly 6 units tall (−3 to 3) — comfortably framing the whole set. Double the zoom and you halve the window, magnifying the center. u_offset is the (cx, cy) you pass in the demo URL.
Step 2 — the escape-time loop
With c in hand, run the iteration. Start z at zero and repeatedly apply z → z² + c. Squaring a complex number (a + bi)² gives (a² − b²) + (2ab)i, so the loop is just a few multiplies and adds. Bail out the moment |z|² exceeds 4 (that is |z| > 2, the escape threshold), or when you hit the iteration cap:
vec2 z = vec2(0.0);
int i = 0;
for (; i < maxIter; i++) {
float x2 = z.x * z.x, y2 = z.y * z.y;
if (x2 + y2 > 4.0) break; // escaped
z = vec2(x2 - y2 + c.x, 2.0 * z.x * z.y + c.y);
}
Step 3 — color it smoothly
If the loop ran to maxIter without escaping, the point is (treated as) inside the set — paint it black. Otherwise color it by how fast it escaped. Using the raw integer iteration count produces visible bands, so apply the smooth-iteration correction, which turns the count into a continuous value:
float smooth = float(i) + 1.0 - log(log(length(z))) / log(2.0);
float hue = smooth * 0.02 + u_colorShift / 360.0;
Feed that hue into an HSV-to-RGB conversion and you get the glowing continuous gradient instead of stair-stepped rings. Exposing u_colorShift as a uniform is what lets you recolor the whole fractal instantly from a slider — or from the color parameter in a demo URL.
The catch — single precision caps your zoom
Browser GPUs compute in 32-bit single-precision floats, which carry only about 7 significant decimal digits. Once you zoom past roughly a million times, neighboring pixels round to the same float value and the image dissolves into blocky mush. This is why real-time WebGL fractals live in the shallow, beautiful range — and why the truly extreme zoom videos on the internet are pre-rendered offline with arbitrary-precision or double-float emulation. Knowing this limit up front saves you from chasing a zoom depth the hardware simply cannot deliver at 60fps.
- Keep zoom in the shallow range (up to a few hundred thousand) for crisp real-time results
- Raise the iteration cap as you zoom — deep boundary points need 400–500 iterations to resolve
- Prefer x² + y² > 4 over a square root for the escape test
- Apply smooth-iteration coloring to kill the banding
- Expose zoom, center, iterations and color as uniforms so the view is fully URL-driven
From renderer to instrument
Once the fractal is a shader driven entirely by uniforms, making it audio-reactive is a small step: sample the microphone with the Web Audio API, push the frequency data into the shader as a texture, and let bass nudge the zoom pulse while treble shifts the hue. That is the leap from "a Mandelbrot renderer" to Vortexia — a live visual instrument. The math in this article is exactly what powers it.
Watch the shader described above running live:
- Open the live Mandelbrot renderer — Seahorse Valley at 400 iterations — the exact escape-time loop from this article, running on your GPU.