shader_legends_2 · IQDomainWarping
IQ Domain Warping — fbm composed with itself at 2/3 levels
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. The canonical 3-level chain q = fbm(p + fbm(p)); r = fbm(p + 4q + fbm(p + 4q)); v = fbm(p + 4r); gives the iconic 'warped fractal' look used in many of IQ's most-loved art shaders. This formula renders the 1/2/3-level warp with adjustable warp_strength; the output is fed through the IQ cosine palette with time-evolving phase.
Original: https://iquilezles.org/articles/warp/
▶ 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
p = (x, y)*scale + 0.3*t. qx, qy = fbm(p_x, p_y), fbm(p_x + 5.2, p_y + 1.3). If depth>=2: r_x, r_y = fbm(p + 4*q_x, p + 4*q_y + ...); if depth>=3: s_x, s_y = fbm(p + 4*r_x, p + 4*r_y + ...). Final sample = fbm(p + 4*r/s, ...) mapped through a + b*cos(2pi*(c*(h + palette_offset) + d)).
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 360 | 120–720 | Square output size in pixels. |
scale | float | 1.8 | 0.4–6 | Spatial scale of the underlying fBM. |
octaves | int | 4 | 1–7 | Octaves for each individual fbm call in the chain. |
warp_strength | float | 4 | 0–8 | Magnitude of the per-level coordinate warp (Q canonical value is 4). |
warp_depth | int | 2 | 1–3 | Number of nested warp levels (1, 2, or 3). 3 matches the canonical IQ chain. |
contrast | float | 1.1 | 0.25–3 | Post-field contrast multiplier. |
palette_offset | float | 0.5 | 0–1 | Phase offset for the IQ cosine palette. |
time_speed | float | 0.03 | -0.3–0.3 | Drift speed of the warp offset and palette phase per step. |
Reference
First 60 lines of reference.py. Full source:
library_external/shader_legends_2/IQDomainWarping/reference.py
from __future__ import annotations
from typing import ClassVar
import numpy as np
def _hash21(x: np.ndarray, y: np.ndarray) -> np.ndarray:
p = 127.1 * x + 311.7 * y
return (np.sin(p) * 43758.5453123 - np.floor(np.sin(p) * 43758.5453123)).astype(np.float32)
def _value_noise(x: np.ndarray, y: np.ndarray, seed: int) -> np.ndarray:
xi = np.floor(x).astype(np.int64)
yi = np.floor(y).astype(np.int64)
xf = (x - xi).astype(np.float32)
yf = (y - yi).astype(np.float32)
sx = float((seed * 17) & 0xFFFFF)
sy = float((seed * 31) & 0xFFFFF)
u = xf * xf * (3.0 - 2.0 * xf)
v = yf * yf * (3.0 - 2.0 * yf)
n00 = _hash21(xi.astype(np.float32) + sx, yi.astype(np.float32) + sy)
n10 = _hash21(xi.astype(np.float32) + 1.0 + sx, yi.astype(np.float32) + sy)
n01 = _hash21(xi.astype(np.float32) + sx, yi.astype(np.float32) + 1.0 + sy)
n11 = _hash21(xi.astype(np.float32) + 1.0 + sx, yi.astype(np.float32) + 1.0 + sy)
nx0 = n00 * (1.0 - u) + n10 * u
nx1 = n01 * (1.0 - u) + n11 * u
return nx0 * (1.0 - v) + nx1 * v
def _fbm(x: np.ndarray, y: np.ndarray, seed: int, octaves: int = 4) -> np.ndarray:
f = 1.0
a = 1.0
total = np.zeros_like(x, dtype=np.float32)
norm = 0.0
for k in range(octaves):
total += a * _value_noise(x * f + 31.0 * k, y * f - 17.0 * k, seed + k)
norm += a
f *= 2.0
a *= 0.55
return total / max(norm, 1e-6)
class IQDomainWarping:
"""Domain Warping — Inigo Quilez (https://iquilezles.org/articles/warp/).
Domain warping is Quilez's elegant trick to break out of the
"looks like noise" cliché. You feed a noise function *itself*
as input coordinates:
q = fbm(p + fbm(p))
r = fbm(p + 4*q + fbm(p + 4*q))
v = fbm(p + 4*r)
Each warp level stretches, folds, and curls the field into
organic shapes that no noise primitive can produce on its own.
Increasing warp depth produces increasingly
painterly/flame-like output; the canonical 3-warp chain (an "fbm
of fbm of fbm") gives IQ's classic *warped fractal noise* look.
Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/shader_legends_2/IQDomainWarping/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 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).
Lorenz Attractor (3D ODE) — strange-attractor trajectory with depth shading
shader_legends_2 · LorenzAttractor3D
The Lorenz system (1963) is the canonical strange-attractor: x' = sigma(y - x); y' = x(rho - z) - y; z' = xy - beta*z.
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.