Skip to main content
GameDev.net gamedev.net

Mandelbrot Explorer

by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026

Use in your engine

Run the shader to adjust these controls.

What it demonstrates

The Mandelbrot set is the shortest interesting shader there is: square a number, add a constant, repeat, and record how long it takes to run away. This version is an explorer with a movable centre, an exponential zoom, an iteration budget, and smooth colouring instead of the banded rings that a raw iteration count produces.

Source Revision 1

Author notes are linked to specific lines.

Shader inputs

void mainImage(out vec4 fragColor, in vec2 fragCoord)

Called once per pixel. Write the colour to fragColor.

iResolution vec3
Viewport size in pixels (z is the pixel aspect ratio).
iTime float
Seconds since the shader started.
iTimeDelta float
Seconds since the previous frame.
iFrameRate float
Frames per second, smoothed.
iFrame int
Frames rendered since the start.
iMouse vec4
Mouse position: xy while held, zw of the last click.
iDate vec4
Year, month, day, and seconds within the day.
iChannel0 sampler2D
Texture bound to channel 0.
iChannel1 sampler2D
Texture bound to channel 1.
iChannel2 sampler2D
Texture bound to channel 2.
iChannel3 sampler2D
Texture bound to channel 3.
iChannelResolution vec3[4]
Pixel size of each bound channel texture.
iChannelTime float[4]
Playback time of each channel, in seconds.
iSampleRate float
Audio sample rate, always 44100.
Main Image7
1 uniform vec2 uCenter; // @param -2.0..1.0 = -0.6, 0.0 "Center"
2 uniform float uZoomExponent; // @param 0.0..12.0 = 0.35 "Zoom exponent"
3 uniform int uIterations; // @param 32..400 = 180 "Iterations"
4 uniform float uPaletteShift; // @param 0.0..1.0 = 0.62 "Palette shift"
5
6 void mainImage(out vec4 fragColor, in vec2 fragCoord) {
7 vec2 screen = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
8 float span = 3.0 / exp2(uZoomExponent);
9 vec2 c = uCenter + screen * span;
10 vec2 z = vec2(0.0);
11 float count = 0.0;
12 for (int i = 0; i < 400; i++) {
13 if (i >= uIterations) break;
14 z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
15 if (dot(z, z) > 65536.0) break;
16 count += 1.0;
17 }
18 if (count >= float(uIterations)) {
19 fragColor = vec4(0.02, 0.02, 0.05, 1.0);
20 return;
21 }
22 float smoothCount = count + 1.0 - log2(max(log2(length(z)), 0.0001));
23 float t = fract(smoothCount * 0.035 + uPaletteShift);
24 vec3 color = vec3(0.52, 0.55, 0.60) + vec3(0.44, 0.40, 0.36) * cos(6.2831853 * (t + vec3(0.0, 0.14, 0.28)));
25 fragColor = vec4(color, 1.0);
26 }
27

Learn from this shader

How it works

Each fragment maps to a point on the complex plane. The complex square is written out in real arithmetic: the real part is a difference of squares, the imaginary part twice the product. The loop stops once the point has clearly escaped, tested against squared length so no square root is needed. Points that never escape are the set itself and are painted flat. For the rest, the raw count is an integer and shows up as hard bands, so the escape magnitude is folded back in through a double logarithm. That yields a continuous value across the boundary, and a cosine palette turns it into smooth colour.

Try changing

Raise Zoom exponent one step at a time and watch detail continue past every scale until single precision floats run out and the image turns blocky. That limit is why deep zoom renderers use higher precision. Raise Iterations when zooming, since the budget that resolved the whole set reads as solid dark once you are inside a filament. Past roughly six the Center sliders are coarser than the view, so a fork wants pointer panning. Palette shift rotates colour without changing the maths.

Using it in a game

Rarely a game effect directly, but the pattern is: a bounded iteration with an early exit, a squared distance test, and a continuous remap replacing an integer count. The same structure appears in raymarching loops and any per pixel search.

Explore the techniques

Continue with curated explanations and progressively related examples.

LicenseMIT
Views0
Forks0

Discussion

Loading comments...