shader_legends_2 · LorenzAttractor3D
Lorenz Attractor (3D ODE) — strange-attractor trajectory with depth shading
The Lorenz system (1963) is the canonical strange-attractor: x' = sigma(y - x); y' = x(rho - z) - y; z' = xy - beta*z. With the classical parameters (sigma=10, rho=28, beta=8/3) the trajectory spirals around two unstable foci and oscillates between them indefinitely without ever repeating — a deterministic, bounded, non-periodic curve shaped like a butterfly. This formula integrates the ODE with RK4 and renders the resulting 3D curve as a fading trail on a 2D canvas, using z for depth-shaded brightness and a per-step cosine-palette hue along the trajectory length.
Original: https://en.wikipedia.org/wiki/Lorenz_system
▶ 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.
Math essence
Initial condition (x0, y0, z0) ~ (1, 1, 25). RK4 integration of the three ODEs over trajectory_length steps with fixed dt. Orthographic project (rotate by 'rotation' about y, drop z'). z_norm = (z - z_bias*22)/24 normalised into [0, 1]; per-step hue = index/N mapped through a + b*cos(2pi*(c*(h + palette_offset) + d)); pixels splatted with line_glow halo; depth = sigmoid(2.5*z_norm).
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 360 | 120–720 | Square output size in pixels. |
sigma | float | 10 | 2–30 | Prandtl number; standard value is 10. |
rho | float | 28 | 5–80 | Rayleigh number; standard value 28 — values < ~13.9 give a stable focus, between produce pre-chaotic orbits, above are chaotic. |
beta | float | 2.6666666 | 0.5–8 | Geometric factor; standard value 8/3 ~ 2.667. |
trajectory_length | int | 4096 | 256–16384 | Number of RK4 integration steps before rendering. |
dt | float | 0.012 | 0.001–0.05 | RK4 time step. |
line_glow | float | 1.6 | 0.3–4 | Strength of the per-pixel neighbour halo. |
z_bias | float | 0.5 | -1–2 | Bias of the z normalisation midpoint (higher shifts the focus nearer/farther). |
rotation | float | 0.6 | -3.14–3.14 | Rotation of (x, z) about y before projection. |
palette_offset | float | 0.5 | 0–1 | Phase offset for the per-step cosine palette. |
time_speed | float | 0 | -0.2–0.2 | Drift speed of the initial-condition orbit per step (0 = static frame). |
Reference
First 60 lines of reference.py. Full source:
library_external/shader_legends_2/LorenzAttractor3D/reference.py
from __future__ import annotations
from typing import ClassVar
import numpy as np
def _rk4_lorenz(
x0: float, y0: float, z0: float,
sigma: float, rho: float, beta: float,
dt: float, steps: int,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""RK4 integration of the Lorenz system
x' = sigma (y - x)
y' = x (rho - z) - y
z' = x y - beta z
Returns arrays (X, Y, Z) of the trajectory. Steps use fixed
dt and no early termination; callers can downsample the
trajectory for rendering.
"""
X = np.empty(steps, dtype=np.float32)
Y = np.empty(steps, dtype=np.float32)
Z = np.empty(steps, dtype=np.float32)
X[0] = x0
Y[0] = y0
Z[0] = z0
x, y, z = x0, y0, z0
def f(x: float, y: float, z: float) -> tuple[float, float, float]:
return (
sigma * (y - x),
x * (rho - z) - y,
x * y - beta * z,
)
for k in range(1, steps):
k1x, k1y, k1z = f(x, y, z)
k2x, k2y, k2z = f(x + 0.5 * dt * k1x, y + 0.5 * dt * k1y, z + 0.5 * dt * k1z)
k3x, k3y, k3z = f(x + 0.5 * dt * k2x, y + 0.5 * dt * k2y, z + 0.5 * dt * k2z)
k4x, k4y, k4z = f(x + dt * k3x, y + dt * k3y, z + dt * k3z)
x = x + (dt / 6.0) * (k1x + 2.0 * k2x + 2.0 * k3x + k4x)
y = y + (dt / 6.0) * (k1y + 2.0 * k2y + 2.0 * k3y + k4y)
z = z + (dt / 6.0) * (k1z + 2.0 * k2z + 2.0 * k3z + k4z)
X[k] = x
Y[k] = y
Z[k] = z
return X, Y, Z
class LorenzAttractor3D:
"""Lorenz attractor, 2D-projected with depth shading.
The Lorenz system (1963, E. N. Lorenz) is a 3D ODE that emerges
in atmospheric convection. It is the canonical "strange
attractor" — a deterministic, bounded, non-periodic trajectory
shaped like a butterfly that chaos theory has used as an icon
for six decades.
Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/shader_legends_2/LorenzAttractor3D/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from shader legends II
Hex Grid Tiling — axial-coord tessellation with fBM-filled cells
shader_legends_2 · HexGridTiling
A hexagonal grid is the canonical 6-fold-symmetric alternative to the square-grid hash tiling.
IQ Domain Warping — fbm composed with itself at 2/3 levels
shader_legends_2 · IQDomainWarping
Inigo Quilez's domain warping trick: feed a noise function *itself* as input coordinates, producing organic flowing patterns that no plain noise function can match.
IQ Mandelbox (2D edition) — iterated box-fold / ball-fold / scale / translate
shader_legends_2 · IQMandelbox2D
The Mandelbox is a 3D fractal defined by the iterate v := scale * box_fold(ball_fold(v)) + c, parametrised by a 'scale' fold factor and the (r_min, r_fixed) ball-fold thresholds.
IQ Voronoi (cellular noise) — F1 / F2 distance field
shader_legends_2 · IQVoronoi
Inigo Quilez's classical 2D Voronoi / cellular noise: for each integer cell of the plane a single feature point is placed at cell_centre + jitter*(hash-0.5).
Menger Sponge, 2D Carpet edition — recursive 3x3 / centre-removal
shader_legends_2 · MengerSponge2D
The Menger sponge (1926) is a 3D fractal formed by sub-dividing each cube into 3x3x3 and removing the centre plus the six face-centres.