Skip to content
Sample of Mandelbulb power-8 2D projection

fractals_3d_webgl · Mandelbulb2DProjection

Mandelbulb power-8 2D projection

A 2D orthographic projection of the iconic power-8 Mandelbulb escape-time field. The full 3D iteration w = w^8 + c is evaluated on a (dim x dim) regular grid in spherical coordinates (Quilez's equal-length-exponentiation / equal-angle-multiplication scheme, intentionally algebraically inconsistent), averaged over a 3-sample Z slab, and coloured with Quilez's three orbit-trap channels (min |w_x|, |w_y|, |w_z| reached along each orbit).

Original: https://iquilezles.org/articles/mandelbulb/

▶ Try the live demo (JS port) ▶ Run reference.py via Pyodide 10 formulas have instant JS ports. The Pyodide path runs the actual Python in your browser — first load is ~6 MB, then instant.

Family
fractals_3d_webgl
Source
fractals_3d_webgl
Evolution
fractal
Render
scalar_field
Complexity
high
Agents
1
Year
2010
Author
Inigo Quilez (iquilezles.org) / Tom Beddard (whitefall, 2010)

Math essence

In spherical coordinates (r, theta, phi) Quilez's iteration is r' = r^8, theta' = 8 theta, phi' = 8 phi followed by w' = w' + c. The 2D projection samples the field on a regular (u, v) grid, applies a pitch/yaw rotation and zoom, averages 3 z-planes, and mixes the per-axis orbit-trap minima with a slow colour ramp so the surface tessellation of the bulb is visible. step(n) morphs camera_pitch/yaw/zoom sinusoidally.

Parameters

Name Type Default Range Description
dim int 256 128–512 Square canvas side length in pixels.
power int 8 2–16 Mandelbulb polynomial power (8 = the iconic bulb).
max_iter int 16 4–64 Escape-time iteration cap.
scale float 1.25 0.5–3 World-units-to-canvas scale.
camera_pitch float 0.35 -1.2–1.2 Camera pitch (radians), animated by step().
camera_yaw float 0.25 -1.2–1.2 Camera yaw (radians), animated by step().
zoom_step float 0.02 0–0.1 Per-step zoom amplitude.
ramp tuple 255,220,140 — Orbit-trap highlight colour.
void_color tuple 12,8,22 — Background colour (escaped points).
surface_color tuple 245,110,50 — Surface fill colour (orbit-interior points).
seed int 7 0–999 RNG seed.

Reference

First 60 lines of reference.py. Full source: library_external/fractals_3d_webgl/Mandelbulb2DProjection/reference.py

from __future__ import annotations

from typing import ClassVar

import numpy as np


def _mandelbulb_pow8_iterate(
    cx: np.ndarray, cy: np.ndarray, cz: np.ndarray, max_iter: int, escape: float
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """Quilez's power-8 Mandelbulb iteration, fully vectorized.

    For each starting point c = (cx, cy, cz), iterate w = w^8 + c using
    Quilez's equal-length-exponentiation / equal-angle-multiplication scheme
    (iquilezles.org/articles/mandelbulb). Concretely: in spherical
    coordinates (wr, wo=acos(y/r), wi=atan2(x,z)) update
        wr <- wr^8
        wo <- 8 * wo
        wi <- 8 * wi
    and convert back to Cartesian, then add c. This is *not* a quaternionic
    square (it is intentionally algebraically inconsistent) but produces the
    iconic bulb geometry.

    Returns (iters, ox, oy, oz): iteration count to escape, plus the
    *minimum* (orbit-trap) values of |w_x|, |w_y|, |w_z| seen during the
    orbit. Those three orbit traps are the same ones Quilez uses to colour
    the bulb: closer to a coordinate plane => lower trap value => a
    different "region" of the surface.
    """
    x = cx.copy()
    y = cy.copy()
    z = cz.copy()
    iters = np.zeros(cx.shape, dtype=np.int32)
    ox = np.full(cx.shape, np.inf, dtype=np.float32)
    oy = np.full(cx.shape, np.inf, dtype=np.float32)
    oz = np.full(cx.shape, np.inf, dtype=np.float32)
    escape2 = escape * escape
    for _ in range(max_iter):
        r2 = x * x + y * y + z * z
        mask = r2 < escape2
        if not np.any(mask):
            break
        iters[mask] += 1
        ox = np.minimum(ox, np.abs(x).astype(np.float32))
        oy = np.minimum(oy, np.abs(y).astype(np.float32))
        oz = np.minimum(oz, np.abs(z).astype(np.float32))
        r = np.sqrt(r2[mask])
        # Spherical update
        wr = r ** 8
        theta = np.arccos(np.clip(y[mask] / r, -1.0, 1.0)) * 8.0
        phi = np.arctan2(x[mask], z[mask]) * 8.0
        sin_t = np.sin(theta)
        nx = wr * sin_t * np.sin(phi) + cx[mask]
        ny = wr * np.cos(theta) + cy[mask]
        nz = wr * sin_t * np.cos(phi) + cz[mask]
        x[mask] = nx
        y[mask] = ny
        z[mask] = nz
    ox = np.where(np.isfinite(ox), ox, 1.0)
    oy = np.where(np.isfinite(oy), oy, 1.0)

Try running it locally

The reference implementation is self-contained Python with numpy. Install the package and run:

python library_external/fractals_3d_webgl/Mandelbulb2DProjection/reference.py

Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.

More from fractals_3d_webgl

See all fractals_3d_webgl artworks →