A 3D fractal, ray marched pixel by pixel on the CPU.
The Mandelbulb extends the Mandelbrot set to three dimensions using spherical coordinates. Each pixel marches a ray forward by a distance estimate, which says how far it can safely step without hitting the surface. When it lands, the program estimates the surface normal, ambient occlusion and a soft shadow, and colors it by how close the iteration came to the origin. Ray marching every pixel every frame is too slow for a 2D canvas, so each pixel remembers the 3D point it hit; as the camera orbits, those points are re-projected into the new view, and the frame's time goes to filling gaps and refreshing the oldest pixels, so the image sharpens whenever things hold still.
Try it. Drag to orbit. Scroll or pinch to zoom. Press the arrow keys to change the fractal's power.
Paste this into Claude Code, Codex or any coding agent to get a simple version running, then take it wherever you like.
Build a ray marched Mandelbulb, the 3D cousin of the Mandelbrot set, with JavaScript and the HTML canvas element. Put everything in a single index.html file with no libraries or build step, so I can open it directly in a browser.
Start simple:
- Render into a small offscreen canvas (about 200 by 130) with ImageData and draw it scaled up to fill the window. Ray marching on the CPU is slow, so resolution matters.
- Write the power 8 distance estimator: iterate z = z^8 + c in spherical coordinates (convert to radius and two angles, raise the radius to the 8th power and multiply both angles by 8, convert back), tracking the running derivative dr = 8 * r^7 * dr + 1. The distance estimate is 0.5 * log(r) * r / dr.
- For each pixel, march a ray from an orbiting camera: step forward by the estimated distance until it is closer than a small epsilon (a hit) or passes a bounding sphere of radius 1.2 (a miss).
- Shade hits with a normal from finite differences of the distance estimator and simple diffuse lighting, and paint misses with a dark gradient.
Once that works, make it beautiful:
- Render progressively: draw a coarse version every frame while the camera moves, then fill in a sharper one a few rows per frame when it rests.
- Add ambient occlusion by sampling the distance estimator a few times along the normal, and a soft shadow by marching toward the light and keeping the smallest distance-to-travel ratio.
- Color the surface with an orbit trap (the smallest |z| reached while iterating) mapped through a warm palette, and add a glow for rays that passed close to the surface.
Explain the key ideas in short code comments. When you're done, tell me how to open it and suggest three directions I could take it next, such as animating the power, a trig-free formula for power 8, or reusing pixels from the previous frame while the camera moves.