Mandelbrot Set
A real-time WebGL fractal explorer. For every pixel c, the sequence z ↦ z² + c starts at z = 0 and is iterated to see whether it escapes or stays bounded forever. Drag to pan, scroll to zoom, or click anywhere on the boundary to preview the Julia set it produces.
Un esploratore di frattali in tempo reale con WebGL. Per ogni pixel c, la sequenza z ↦ z² + c parte da z = 0 e viene iterata per vedere se sfugge o rimane limitata per sempre. Trascina per traslare, scorri per ingrandire, o fai clic ovunque sul confine per visualizzare l'insieme di Julia che produce.
Your browser doesn't support WebGL, so this simulation can't run here.
Notable locations:
Preview the Julia set for a point
Click anywhere on the fractal above to pick a value of c. The mini map shows the Julia set for that c — the boundary is connected exactly when c lies inside the Mandelbrot set (the dark region); points picked from the wispy filaments outside it produce dust-like, disconnected Julia sets.
z₀ = 0, zn+1 = z_n² + c
The Mandelbrot set, named after Benoît Mandelbrot, is the set of complex numbers c for which this sequence never escapes to infinity. Each pixel here is colored by an escape-time count — how many iterations it survives before crossing the bailout radius — smoothed to avoid banding. Points that never escape (within the iteration budget) are rendered as the dark background: that's the set itself.
L'insieme di Mandelbrot, dal nome di Benoît Mandelbrot, è l'insieme dei numeri complessi c per i quali questa sequenza non scappa mai all'infinito. Ogni pixel qui è colorato in base al numero di iterazioni di fuga — quante iterazioni sopravvive prima di attraversare il raggio limite — livellato per evitare banding. I punti che non scappano mai (entro il budget di iterazioni) sono renderizzati come sfondo scuro: questo è l'insieme stesso.
Understanding the Mandelbrot Set
Capire l'insieme di Mandelbrot
The Mandelbrot set is a fractal — an infinite pattern that looks similar at every scale. Let's walk through the mathematical ideas step by step, starting from the basics.
L'insieme di Mandelbrot è un frattale — un motivo infinito che appare simile a ogni scala. Percorriamo le idee matematiche passo dopo passo, partendo dalle basi.
Step 1: What are complex numbers?
Passo 1: Cosa sono i numeri complessi?
A complex number combines two parts: a real part and an imaginary part.
We write it as a + bi,
where a is real
and i is the imaginary unit.
What's i? The letter i is defined as the square root of −1.
In normal math, you can't square a number and get −1 (negative). But mathematicians invented
i to solve equations that would otherwise be impossible.
Think of it as a number on a separate axis, perpendicular to the real number line.
Visualizing complex numbers: We can plot them on a 2D plane. The horizontal axis is the real part,
the vertical axis is the imaginary part. So 3 + 2i is
a point at coordinates (3, 2).
Un numero complesso combina due parti: una parte reale e una parte immaginaria.
Lo scriviamo come a + bi,
dove a è reale
e i è l'unità immaginaria.
Cosa è i? La lettera i è definita come la radice quadrata di −1.
In matematica normale, non puoi elevare al quadrato un numero e ottenere −1 (negativo). Ma i matematici hanno inventato
i per risolvere equazioni che altrimenti sarebbero impossibili.
Pensa a esso come un numero su un asse separato, perpendicolare alla retta dei numeri reali.
Visualizzare i numeri complessi: Possiamo tracciarli su un piano 2D. L'asse orizzontale è la parte reale,
l'asse verticale è la parte immaginaria. Quindi 3 + 2i è
un punto alle coordinate (3, 2).
Step 2: The iteration formula
Passo 2: La formula di iterazione
To calculate the Mandelbrot set, we test each point c using this formula:
zn+1 = z_n² + c
|z|
(the distance from zero) exceeds 2, the squaring operation guarantees it will keep growing until infinity. We call 2 the bailout radius.
What does "stay bounded" mean? For some values of c, z bounces around but never gets too large. No matter how many times we
iterate, it stays within a reasonable range—say, always between −2 and 2. These are the points inside the Mandelbrot set.
Example: If c = 0, then z starts at 0, and z² + 0 = 0 every iteration. It never escapes. But if c = 1,
then z = 0 → 0² + 1 = 1 → 1² + 1 = 2 → 2² + 1 = 5 → 5² + 1 = 26... it quickly escapes! This is why different colors represent
how fast a point escapes.
Per calcolare l'insieme di Mandelbrot, testiamo ogni punto c usando questa formula:
zn+1 = z_n² + c
|z|
(la distanza da zero) supera 2, il quadrato garantisce che continuerà a crescere all'infinito. Chiamiamo 2 il raggio di fuga (bailout radius).
Cosa significa "rimanere limitato"? Per alcuni valori di c, z rimbalza ma non diventa mai troppo grande.
Non importa quante volte iteriamo, rimane entro un intervallo ragionevole—ad esempio, sempre tra −2 e 2. Questi sono i punti dentro l'insieme di Mandelbrot.
Esempio: Se c = 0, allora z parte da 0, e z² + 0 = 0 ad ogni iterazione. Non scappa mai. Ma se c = 1,
allora z = 0 → 0² + 1 = 1 → 1² + 1 = 2 → 2² + 1 = 5 → 5² + 1 = 26... scappa velocemente! Questo è il motivo per cui colori diversi rappresentano
la velocità con cui un punto sfugge.
Step 3: Points inside vs outside
Passo 3: Punti dentro vs fuori
Inside the set (dark regions): If we iterate a point c hundreds of times and it never escapes beyond radius 2, we consider it part of the Mandelbrot set. These points are drawn in dark (almost black). Outside the set (colored regions): If a point escapes, we record how many iterations it took before reaching radius 2. A point that escapes in 5 iterations gets colored differently than one that takes 50 iterations. This creates the beautiful colored bands around the set boundary. The boundary between inside and outside is infinitely detailed — no matter how much you zoom in, you'll always see more structure. That's what makes it a fractal!
Dentro l'insieme (regioni scure): Se iteriamo un punto c centinaia di volte e non scappa mai oltre il raggio 2, lo consideriamo parte dell'insieme di Mandelbrot. Questi punti sono disegnati in scuro (quasi nero). Fuori l'insieme (regioni colorate): Se un punto scappa, registriamo quante iterazioni ci vollero prima di raggiungere il raggio 2. Un punto che scappa in 5 iterazioni viene colorato diversamente da uno che ne impiega 50. Questo crea le belle bande colorate intorno al confine dell'insieme. Il confine tra interno e esterno è infinitamente dettagliato — non importa quanto ingrandisci, vedrai sempre più struttura. Ecco cosa lo rende un frattale!
Step 4: Turning escape time into color
Passo 4: Trasformare il tempo di fuga in colore
If we just color pixels by raw iteration count, we get harsh, jagged stripes (called "banding"). To smooth this, we apply a smoothing algorithm that uses the magnitude of z when it escapes. Once we have a smooth iteration count, we cycle through a color palette — a carefully chosen gradient of colors. In this explorer, the palette transitions through deep blue, cyan, yellow, and red as the value increases. The Color density slider controls how quickly the palette cycles. A higher value means colors change faster, creating tighter bands. A lower value spreads the palette out over a wider range.
Se coloriamo semplicemente i pixel in base al conteggio di iterazioni raw, otteniamo strisce dure e frastagliate (chiamate "banding"). Per levigare questo, applichiamo un algoritmo di levigatura che usa la grandezza di z quando scappa. Una volta che abbiamo un conteggio di iterazioni levigato, facciamo scorrere una tavolozza di colori — un gradiente di colori accuratamente scelto. In questo esploratore, la tavolozza passa attraverso blu profondo, ciano, giallo e rosso man mano che il valore aumenta. Il cursore Densità colore controlla la velocità con cui la tavolozza scorre. Un valore più alto significa che i colori cambiano più velocemente, creando bande più strette. Un valore più basso diffonde la tavolozza su una gamma più ampia.
Step 5: The escape time algorithm in detail
Passo 5: L'algoritmo del tempo di fuga nel dettaglio
Here's the actual process for a single pixel:
Ecco il processo effettivo per un singolo pixel:
- Convert the pixel's screen position into a complex number (c)
- Set z = 0 + 0i (starting point)
- For each iteration (up to the max):
- Calculate: z = z² + c (using complex number arithmetic)
- If |z| > 2, the point has escaped — record the iteration count and stop
- Otherwise, continue to the next iteration
- Color the pixel based on the total iteration count
- Converti la posizione dello schermo del pixel in un numero complesso (c)
- Imposta z = 0 + 0i (punto di partenza)
- Per ogni iterazione (fino al massimo):
- Calcola: z = z² + c (usando l'aritmetica dei numeri complessi)
- Se |z| > 2, il punto è scappato — registra il conteggio delle iterazioni e fermi
- Altrimenti, continua alla successiva iterazione
- Colora il pixel in base al conteggio totale delle iterazioni
Why does zooming reveal infinite structure?
Perché l'ingrandimento rivela una struttura infinita?
The Mandelbrot set is self-similar — smaller copies of the entire set appear within itself at smaller scales. The boundary between the set and the empty space is infinitely complex. When you zoom in, you're not just magnifying the same pattern; you're discovering entirely new patterns that were hidden at larger scales. This infinite complexity from a simple rule is the defining characteristic of fractals.
L'insieme di Mandelbrot è auto-simile — copie più piccole dell'intero insieme appaiono al suo interno su scale più piccole. Il confine tra l'insieme e lo spazio vuoto è infinitamente complesso. Quando ingrandisci, non stai solo ingrandendo lo stesso schema; stai scoprendo schemi completamente nuovi che erano nascosti su scale più grandi. Questa complessità infinita da una regola semplice è la caratteristica distintiva dei frattali.
How this page works
Come funziona questa pagina
The fractal is computed entirely on the GPU, one pixel at a time, inside a WebGL fragment shader — there's no per-pixel loop on the CPU. Here's the code broken into its main blocks.
Il frattale è calcolato interamente sulla GPU, un pixel alla volta, all'interno di un fragment shader WebGL — non c'è nessun loop per-pixel sulla CPU. Ecco il codice suddiviso nei suoi blocchi principali.
1. Map each pixel to a point in the complex plane
1. Mappare ogni pixel a un punto nel piano complesso
The canvas is a single fullscreen quad (two triangles, four vertices). The vertex shader just
forwards each corner's [-1, 1]
coordinate to the fragment shader as vUv.
The fragment shader then turns that into a complex number using the current pan/zoom state:
Il canvas è un singolo quad a schermo intero (due triangoli, quattro vertici). Il vertex shader
semplicemente inoltra la coordinata [-1, 1]
di ogni angolo al fragment shader come vUv.
Il fragment shader la trasforma in un numero complesso utilizzando lo stato attuale di pan/zoom:
vec2 coord = uCenter + vec2(vUv.x * uAspect, vUv.y) * uScale;
2. Simulate the escape: iterate z ↦ z² + c until it flies off
2. Simulare la fuga: iterare z ↦ z² + c finché non scappa
For the main view, z starts
at 0 and c is
that pixel's coordinate — that's the Mandelbrot set. For the Julia preview it's flipped:
z starts at the
pixel's coordinate and c is
the picked point. Both share one shader, switched by a
uMode uniform.
This loop runs independently for every pixel, in parallel, every frame — that's the "simulation":
Per la vista principale, z parte
da 0 e c è
la coordinata di quel pixel — questo è l'insieme di Mandelbrot. Per l'anteprima di Julia è invertito:
z parte dalla
coordinata del pixel e c è
il punto scelto. Entrambi condividono un shader, commutato da
un uniforme uMode.
Questo loop funziona indipendentemente per ogni pixel, in parallelo, ogni frame — questa è la "simulazione":
for (int i = 0; i < MAX_ITERATIONS; i++) {
if (float(i) >= uMaxIter) break;
float x2 = z.x * z.x - z.y * z.y + c.x;
float y2 = 2.0 * z.x * z.y + c.y;
z = vec2(x2, y2);
if (dot(z, z) > 4.0) break; // escaped past radius 2
iter += 1.0;
} 3. Turn the iteration count into a color
3. Trasformare il conteggio delle iterazioni in un colore
A raw integer count bands harshly, so it's smoothed with a logarithmic correction
(smoothIter) and fed through
a 5-stop gradient (palette()).
Pixels that never escape within the iteration budget are painted the solid dark background instead.
Un conteggio intero raw crea bande dure, quindi viene levigato con una correzione logaritmica
(smoothIter) e fatto passare attraverso
un gradiente a 5 tappe (palette()).
I pixel che non scappano mai entro il budget di iterazioni vengono dipinti con lo sfondo scuro solido.
4. Drive the shader from TypeScript
4. Guidare lo shader da TypeScript
createFractalRenderer() compiles
and links the shader once per canvas, then exposes a single
draw(uniforms) call that resizes
the canvas, uploads c,
center,
scale and the rest as
uniforms, and issues one draw call. The exact same function renders both the main canvas
(mode 1) and the Julia preview (mode 0) — only the uniforms passed in differ.
createFractalRenderer() compila
e collega lo shader una volta per canvas, quindi espone una singola
chiamata draw(uniforms) che ridimensiona
il canvas, carica c,
center,
scale e il resto come
uniformi, e rilascia una chiamata di disegno. La stessa funzione esatta renderizza sia il canvas principale
(mode 1) che l'anteprima Julia (mode 0) — solo i uniformi passati differiscono.
A requestAnimationFrame loop
redraws the main canvas every frame. Pointer drag events convert pixel deltas into a
complex-plane pan; the wheel handler zooms by scaling
scale while re-solving
center so the point under the
cursor doesn't move. A click that doesn't drag converts the pointer position back into a
complex number, redraws the Julia preview for it, and updates the "open in Julia Set
explorer" link with that point encoded as ?re=&im= query
parameters.
Un ciclo requestAnimationFrame
ridisegna il canvas principale ogni frame. Gli eventi di trascinamento del puntatore convertono i delta dei pixel in una
traslazione del piano complesso; il gestore della rotella esegue lo zoom ridimensionando
scale mentre ricalcola
center in modo che il punto sotto il
cursore non si sposti. Un clic che non trascina converte la posizione del puntatore in un
numero complesso, ridisegna l'anteprima Julia per esso e aggiorna il collegamento "apri in Julia Set
explorer" con quel punto codificato come parametri di query ?re=&im=.