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

shader_legends_2 · HexGridTiling

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

A hexagonal grid is the canonical 6-fold-symmetric alternative to the square-grid hash tiling. Following IQ's coordinate trick (and Shane's *Hexagon* Shadertoy https://www.shadertoy.com/view/Xljczw), each pixel is mapped to axial hex coordinates (q, r) by snapping to one of two interleaved square lattices. Per-cell colour comes from hash21(cell_id) and per-cell interior from an fBM noise field, giving a tessellated landscape with the geometry of a honeycomb and the texture of a biomorphic noise pattern.

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

▶ 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
tiling
Render
scalar_field
Complexity
medium
Agents
0
Year
2017
Author
Inigo Quilez (after Shane / The Art of Code)

Math essence

For each pixel p: re-coordinate into u = x/s, v = y/(s*sqrt(3)). Try both grid origins (offset by 0.5) and pick the cell whose centre is nearest. Get axial cell id (q, r). Look up hash21 -> hue and saturation; look up fbm at the local hex position * noise_scale + t -> interior noise. Hex boundary is computed analytically as max(|lq|, |lr|, 0.5*(|lq+lr|)) for an antialiased edge.

Parameters

Name Type Default Range Description
dim int 360 120–720 Square output size in pixels.
size float 0.05 0.02–0.3 Hex tile size in canvas units (smaller = more hexes).
octaves int 4 1–7 Octaves of fBM noise inside each hex.
noise_scale float 18 2–60 Spatial scale for the per-hex fBM input.
edge_gain float 7 1–30 Hex boundary darkness; higher = harder edges.
palette_offset float 0.45 0–1 Phase offset for the IQ cosine palette.
time_speed float 0.03 -0.3–0.3 Drift speed of the per-hex fBM offset per step.

Reference

First 60 lines of reference.py. Full source: library_external/shader_legends_2/HexGridTiling/reference.py

from __future__ import annotations

from typing import ClassVar

import numpy as np


def _hash21(x: np.ndarray, y: np.ndarray) -> np.ndarray:
    """1-component hash on integer (x,y) -> [0,1)."""
    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 HexGridTiling:
    """Hex-grid tiling with fBM-filled cells — Inigo Quilez / Shane.

    A hexagonal grid is the canonical 6-fold-symmetric alternative
    to the square-grid hash tiling.  The classic trick (Inigo Quilez
    https://iquilezles.org/articles/distance/ ; Shane *Hexagon*
    https://www.shadertoy.com/view/Xljczw) is to first re-coordinate
    the pixel to a skewed frame, snap to the nearest of two
    interleaved square-lattice offsets, then convert the cell id
    into *axial* hex coordinates (q, r):

        q = (2/3 * x),  r = (-1/3 * x + sqrt(3)/3 * y)
        cells in row y : floor(q), shifted by (r & 1)

    Once we have the (q, r) of the pixel's containing hex, we look
    up a per-tile (hash21-driven) base colour and add fBM noise

Try running it locally

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

python library_external/shader_legends_2/HexGridTiling/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 IQ Domain Warping — fbm composed with itself at 2/3 levels

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.

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 →