shader_legends_3 · IQDisplacementSphere
Recursive Spherical Displacement Wireframe
A wireframe sphere whose surface is recursively displaced by spherical harmonics of the form amp * sin(N * theta) * cos(M * phi), rendered as overlapping latitude and meridian polylines plus random surface dots. Three nested displacement levels of decreasing amplitude produce a 'sphere on a sphere on a sphere' geometry that reads as a sea-urchin or virus shell — entirely from analytic geometry, no noise, no texture sampling.
Original: https://iquilezles.org/articles/
▶ 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 3D point p on the unit sphere, with theta = arccos(p.y) and phi = atan2(p.z, p.x), the displacement is r := 1 + amp_k * (0.5 + 0.5 * sin(N_k * theta + phase_t) * cos(M_k * phi + phase_t)). Apply this for k=1,2,3 in succession with decreasing amps, rotate the result by (0.4 t, 0.6 t, 0.3 t), then orthographically project. The wireframe samples n_lat latitude circles and n_lon meridian polylines; surface dots come from uniformly-spaced (theta, phi) draws.
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 400 | 160–800 | Square output size in pixels. |
n_lat | int | 28 | 8–80 | Number of latitude circles in the wireframe. |
n_lon | int | 56 | 16–160 | Number of meridians in the wireframe. |
n1 | int | 4 | 2–12 | Theta frequency for level-1 displacement. |
n2 | int | 3 | 2–12 | Phi frequency for level-1 displacement (theta freq for level-2). |
n3 | int | 5 | 2–12 | Phi frequency for level-2 displacement (theta freq for level-3). |
amp1 | float | 0.18 | 0.02–0.5 | Amplitude of the level-1 displacement. |
amp2 | float | 0.1 | 0.02–0.3 | Amplitude of the level-2 displacement. |
amp3 | float | 0.05 | 0.01–0.2 | Amplitude of the level-3 displacement. |
radius | float | 0.4 | 0.2–0.48 | Sphere radius in canvas units. |
rotate_speed | float | 0.18 | -0.5–0.5 | Per-step Euler rotation rate. |
dot_density | float | 0.18 | 0–0.6 | Random surface-dot density. |
line_color | rgb | 0.45,0.85,1 | null–null | Wireframe colour. |
fill_color | rgb | 0.05,0.1,0.18 | null–null | Silhouette fill tint. |
background | rgb | 0.02,0.02,0.05 | null–null | Background colour. |
Reference
First 60 lines of reference.py. Full source:
library_external/shader_legends_3/IQDisplacementSphere/reference.py
from __future__ import annotations
from typing import ClassVar
import numpy as np
def _hash21(x: np.ndarray, y: np.ndarray, seed: int) -> np.ndarray:
"""Deterministic 2D hash → [0, 1)."""
x = np.asarray(x, dtype=np.int64)
y = np.asarray(y, dtype=np.int64)
h = (x * np.int64(374761393) + y * np.int64(668265263) + np.int64(seed)) & np.int64(0xFFFFFFFF)
h = (h ^ (h >> np.int64(13))) * np.int64(1274126177) & np.int64(0xFFFFFFFF)
h = h ^ (h >> np.int64(16))
return (h & np.int64(0xFFFFFFFF)).astype(np.float32) / np.float32(0xFFFFFFFF)
class IQDisplacementSphere:
"""Recursive spherical displacement — Iñigo Quilez (Shadertoy).
IQ's classic *displacement* shader (Shadertoy) is a deceptively
short program: a single sphere is recursively *displaced* by
other smaller spheres on its surface, then those smaller
spheres are again displaced, and so on, producing a wireframe
of nested spherical "growths" that looks like a sea-urchin or
a virus. The math essence is:
1. start with a base point on the unit sphere,
2. apply ``displacement = amp * sin(N * theta) * cos(M * phi)``,
3. rotate the result by a time-varying Euler triple,
4. project to 2D with simple orthographic projection.
The shader has no texture sampling, no fBM, and no lighting.
Its richness comes entirely from the recursive spherical
geometry. This implementation flattens the recursion into a
single nested series of polar displacements: three
*generations* of ``amp * sin(N * theta) * cos(M * phi)`` of
decreasing amplitude, rendered as a wireframe of latitude
lines + dots, with an optional second concentric wireframe
that adds the "sphere on a sphere" look.
"""
family: ClassVar[str] = "shader_legends_3"
sketch: ClassVar[str] = "iqdisplacementsphere"
def __init__(
self,
dim: int = 400,
n_lat: int = 28,
n_lon: int = 56,
n1: int = 4,
n2: int = 3,
n3: int = 5,
amp1: float = 0.18,
amp2: float = 0.10,
amp3: float = 0.05,
radius: float = 0.40,
rotate_speed: float = 0.18,
dot_density: float = 0.18,
line_color: tuple[float, float, float] = (0.45, 0.85, 1.00), Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/shader_legends_3/IQDisplacementSphere/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from shader_legends_3
Animated FBM Nebula with Chromatic Aberration
shader_legends_3 · AshleyGAnimNebula
A multi-coloured, slowly rotating cosmic gas cloud built from 5 layers of FBM noise at different scales, each layer rotated by a different angle that varies with time.
Atmospheric Perspective Landscape
shader_legends_3 · HubertyAtmospheric
A painterly 2D landscape built from depth-graded FBM bands: a soft sky gradient with a sun disc and cloud streaks, a low-amp haze band in the upper-middle, rolling green midground hills, and a sharp foreground silhouette of trees and grass.
Bokeh Blur with Light Leaks
shader_legends_3 · TaterBokeh
A 2D bokeh effect built by stamping 50-200 soft Gaussian discs onto a near-black backdrop, each at a hash-determined centre, radius, and warm-to-cool colour.
Hash-driven Procedural City Skyline
shader_legends_3 · DaveHoskinsHashCity
A 2D orthographic night-time cityscape whose per-column building heights come from 1D FBM noise and whose lit windows are a per-cell hash threshold.
Volumetric Clouds from Cellular + FBM
shader_legends_3 · BigWingsClouds
A painterly cumulus-cloud sky built from a Worley/cellular noise layer (cloud puffs) gated by a low-frequency FBM (large-scale cloud coverage) and a high-frequency FBM (in-cloud detail).