Fractals
Mandelbrot, Julia sets, and escape-time rendering
Some shapes contain infinite detail. No matter how far you zoom, new structures emerge. These are fractals: patterns that repeat at every scale, revealing complexity from simplicity.
The mathematics is deceptively simple. Take a number, square it, add a constant, and repeat. Whether this process spirals off to infinity or stays bounded determines whether a point belongs to the fractal. The boundary between these two behaviors is where the magic lives.
The Mandelbrot Set
The Mandelbrot set is perhaps the most famous fractal. For each point in the complex plane, we iterate:
Starting with , we ask: does this sequence escape to infinity, or does it stay bounded?
Points where the sequence stays bounded form the Mandelbrot set, typically colored black. Points that escape are colored based on how quickly they escape. The boundary between escape and capture is infinitely complex.
vec2 complexSquare(vec2 z) {
return vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
}
float mandelbrot(vec2 c, int maxIter) {
vec2 z = vec2(0.0);
for (int i = 0; i < maxIter; i++) {
z = complexSquare(z) + c;
if (dot(z, z) > 4.0) {
return float(i) / float(maxIter);
}
}
return 0.0;
}The dot(z, z) > 4.0 check is an optimization. Once , the sequence will definitely escape to infinity. We can stop iterating and record how many iterations it took.
The Mandelbrot Set
Drag to pan, scroll to zoom. Increase iterations for deeper zooms.
Drag to pan around the fractal. Zoom in on the boundary and you will discover spirals within spirals, miniature copies of the whole set, and patterns that never quite repeat.
Julia Sets
The Mandelbrot set varies the constant while starting from . Julia sets do the opposite: they fix and vary the starting point .
Each point in the complex plane becomes the initial value, and we iterate:
The same escape-time algorithm applies, but now each choice of produces a different fractal. Some are connected, intricate shapes. Others shatter into disconnected dust called Fatou dust.
Julia Set Explorer
Adjust c to explore different Julia sets
Drag the crosshair to change . Notice how the Julia set's shape relates to where lies relative to the Mandelbrot set. Points inside the Mandelbrot set produce connected Julia sets; points outside produce disconnected ones.
Understanding Iteration
What does it mean for a point to "escape"? Let us visualize the iteration process step by step.
Iteration Visualizer
Click to set c (yellow). Green = start, blue = iterations, red = final. Circle at radius 2 is escape threshold.
For points deep inside the set, the iteration settles into a cycle or stays near the origin. For points outside, each iteration pushes further away until it crosses our escape threshold. The boundary points hover on the edge, taking many iterations before their fate becomes clear.
Smooth Coloring
Counting iterations produces banding artifacts: sudden color jumps between regions that escaped at iteration 50 versus iteration 51. For smoother gradients, we use smooth iteration count:
This formula interpolates between integer iteration counts based on how far past the escape radius the point traveled.
float smoothMandelbrot(vec2 c, int maxIter) {
vec2 z = vec2(0.0);
for (int i = 0; i < maxIter; i++) {
z = complexSquare(z) + c;
if (dot(z, z) > 256.0) {
float smooth = float(i) - log2(log2(dot(z, z))) + 4.0;
return smooth / float(maxIter);
}
}
return 0.0;
}We use a larger escape radius (256 instead of 4) to ensure accurate smooth coloring.
Smooth vs Banded Coloring
Toggle to compare smooth interpolation vs integer iteration counts
The smooth version produces beautiful gradients that reveal the fractal's structure without distracting color bands.
Color Mapping
The iteration count gives us a single value per pixel. Mapping this to color is where artistry enters. Common approaches:
Gradient mapping: Map iteration count through a color gradient. Sine waves with different phases create rainbow effects:
vec3 palette(float t) {
return 0.5 + 0.5 * cos(6.28 * (t + vec3(0.0, 0.33, 0.67)));
}Histogram equalization: Distribute colors based on how many pixels fall in each iteration range, ensuring the full palette is used.
Orbit traps: Color based on how close the orbit came to certain shapes (circles, lines, or other points), not just when it escaped.
Fractal Explorer
Drag to pan, scroll to zoom. Try different fractals and color palettes.
Experiment with different palettes and zoom levels. The fractal contains infinite variety.
Precision Limits
As you zoom deeper, floating-point precision becomes a limiting factor. Standard 32-bit floats have about 7 decimal digits of precision. After zooming by a factor of or so, the fractal starts looking blocky.
For deeper exploration, techniques include:
- Double precision: Using 64-bit floats (when available) doubles the zoom depth
- Perturbation theory: Computing deltas from a reference orbit
- Arbitrary precision: Software floating point for unlimited depth
In WebGL, we are typically limited to 32-bit floats, so our zoom depth is constrained. Even so, there is an enormous amount of detail to explore.
Beyond Mandelbrot
The Mandelbrot set uses , but other formulas produce other fractals:
- Burning Ship: Uses and in the iteration
- Multibrot sets: Use for different powers
- Tricorn: Uses the complex conjugate:
- Newton fractals: Based on Newton's method for finding roots
Each formula creates its own universe of patterns.
Key Takeaways
- Fractals arise from simple iteration rules applied to complex numbers
- The Mandelbrot set varies ; Julia sets fix and vary the starting point
- Escape-time coloring: count iterations until , or mark as inside the set
- Smooth iteration count eliminates color banding with logarithmic interpolation
- Color palettes transform iteration counts into visual art
- Floating-point precision limits zoom depth, but the detail is effectively infinite
- The boundary between escape and capture contains the fractal's infinite complexity