Skip to content
Sample of IQ Domain Warping — fbm composed with itself at 2/3 levels

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.

Family
shader_legends_2
Source
shader legends II
Evolution
noise_field
Render
scalar_field
Complexity
medium
Agents
0
Year
2008
Author
Inigo Quilez

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

Sample of Hex Grid Tiling — axial-coord tessellation with fBM-filled cells

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.

View →
Sample of IQ Mandelbox (2D edition) — iterated box-fold / ball-fold / scale / translate

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.

View →
Sample of IQ Voronoi (cellular noise) — F1 / F2 distance field

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).

View →
Sample of Lorenz Attractor (3D ODE) — strange-attractor trajectory with depth shading

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.

View →
Sample of Menger Sponge, 2D Carpet edition — recursive 3x3 / centre-removal

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.

View →

See all shader_legends_2 artworks →