Skip to content
Sample of Domain Repetition — Inigo Quilez tile-with-warp

shader_legends · IQDomainRepetition

Domain Repetition — Inigo Quilez tile-with-warp

Inigo Quilez's 'Domain Repetition' technique from his 2008 article. A base SDF (rounded box / cross) is replicated infinitely over space by wrapping the sample point with `r = p - s * round(p / s)`; to keep the SDF correct for shapes that break tile-boundary symmetry (rotation, scale, jitter) we evaluate a 2x2 ring of neighbour tiles and keep the min distance. Per-instance rotation angle, scale, and colour come from a 2D hash of the tile id. We render the resulting SDF as a heightmap tinted with Quilez's cosine palette and gently warped in time.

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

▶ 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
Source
shader legends
Evolution
cellular
Render
scalar_field
Complexity
medium
Agents
0
Year
2008
Author
Inigo Quilez

Math essence

For a query point p, take id = round(p/s). To guarantee correct distances even with size/rotation variation, scan the 2x2 ring id + di*sign(p - s*id), evaluate sdf(rx, ry) per neighbour after a per-instance rotation/scale, and keep the minimum. Inside each tile, the SDF is a rounded box minus a cross; outside any tile the SDF is positive. The cosine palette `a + b*cos(2π*(c*h + d))` colours each tile by hash(id).

Parameters

Name Type Default Range Description
dim int 360 120–800 Square output size in pixels.
scale float 6 2–24 Tile size in canvas units.
smoothness float 0.5 0–2 Smoothing amount for the SDF edge (visual).
warp float 0.35 0–2 Strength of the fBM-driven domain warp.
palette_offset float 0.5 0–1 Phase offset for the cosine palette.
time_speed float 0.05 -0.4–0.4 Drift speed of the domain warp per step.

Reference

First 60 lines of reference.py. Full source: library_external/shader_legends/IQDomainRepetition/reference.py

from __future__ import annotations

from typing import ClassVar

import numpy as np


def _hash1(x: np.ndarray, y: np.ndarray) -> np.ndarray:
    x = x.astype(np.float64)
    y = y.astype(np.float64)
    p = 127.1 * x + 311.7 * y
    return (np.sin(p) * 43758.5453123 - np.floor(np.sin(p) * 43758.5453123)).astype(np.float32)


def _hash2(x: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
    a = _hash1(x, y)
    b = _hash1(x + 17.0, y + 31.0)
    return a, b


def _value_noise2(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)
    xi_i = (xi % (1 << 24)).astype(np.int32)
    yi_i = (yi % (1 << 24)).astype(np.int32)
    sx = np.float32((seed * 17) % (1 << 20))
    sy = np.float32((seed * 31) % (1 << 20))
    u = xf * xf * (3.0 - 2.0 * xf)
    v = yf * yf * (3.0 - 2.0 * yf)
    n00 = _hash1(xi_i.astype(np.float32) + sx, yi_i.astype(np.float32) + sy)
    n10 = _hash1(xi_i.astype(np.float32) + np.float32(1) + sx, yi_i.astype(np.float32) + sy)
    n01 = _hash1(xi_i.astype(np.float32) + sx, yi_i.astype(np.float32) + np.float32(1) + sy)
    n11 = _hash1(xi_i.astype(np.float32) + np.float32(1) + sx, yi_i.astype(np.float32) + np.float32(1) + sy)
    nx0 = n00 * (1 - u) + n10 * u
    nx1 = n01 * (1 - u) + n11 * u
    return nx0 * (1 - 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_noise2(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)


def _shape_sdf(rx: np.ndarray, ry: np.ndarray) -> np.ndarray:
    """Base SDF: a rounded rectangle + a small inner triangle blob.

    Defined on (-0.5, 0.5) — domain repetition expects shapes to live
    within a single tile, with the tile size giving the period.
    """
    qx = np.abs(rx) - 0.32

Try running it locally

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

python library_external/shader_legends/IQDomainRepetition/reference.py

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

More from shader legends

See all shader_legends artworks →