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