shader_legends · ShaneOrganicNoise
Shane Organic Noise — layered Perlin with rotation and twist
Shane Olsson's 'organic noise' Shadertoy technique: a base Perlin-style gradient noise is sampled in multiple octaves, with each successive layer rotated by an angle proportional to the layer index and twisted by sinusoidal warping about the canvas centre. The summed scalar field is mapped through a cosine palette (Quilez) for the biomorphic, flesh-like textures Shane uses across his Shadertoy catalogue.
Original: https://www.shadertoy.com/view/3fcyz8
▶ 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
Per layer k: rotate (x, y) by angle k*twist_per_layer, displace by sin/cos of the polar angle for an organic warp, then sample Perlin gradient noise at scale*(x', y') + offset_k. Layers are summed with amp = 0.62^k. A radial vignette (1 - r*0.85) dampens the field near the edges. The final scalar is fed through the IQ cosine palette a + b*cos(2π*(c*t + d)) with offset drift over time.
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 360 | 120–800 | Square output size in pixels. |
scale | float | 2.6 | 0.5–8 | Perlin input scale per layer (larger = finer features). |
octaves | int | 4 | 1–8 | Number of stacked layers. |
twist_per_layer | float | 0.6 | -1.4–1.4 | Per-layer rotation in radians added to the polar angle. |
distortion | float | 0.45 | 0–2.5 | Sinusoidal warp strength applied to each layer. |
contrast | float | 1.25 | 0.25–3 | Post-field contrast multiplier. |
palette_offset | float | 0.55 | 0–1 | Phase offset for the IQ cosine palette. |
time_speed | float | 0.04 | -0.4–0.4 | Drift speed of the warp and palette offset per step. |
Reference
First 60 lines of reference.py. Full source:
library_external/shader_legends/ShaneOrganicNoise/reference.py
from __future__ import annotations
from typing import ClassVar
import numpy as np
_PERMUTATION = (
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,
247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,
57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,
74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,
60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,
65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200,
196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52,
217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207,
206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119,
248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129,
22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218,
246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,
81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
)
_P = np.zeros(512, dtype=np.int32)
for _i in range(256):
_P[_i] = _PERMUTATION[_i]
_P[256 + _i] = _PERMUTATION[_i]
def _fade(t: np.ndarray) -> np.ndarray:
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
def _lerp(t: np.ndarray, a: np.ndarray, b: np.ndarray) -> np.ndarray:
return a + t * (b - a)
def _grad2(hash_idx: np.ndarray, x: np.ndarray, y: np.ndarray) -> np.ndarray:
h = hash_idx & 7
u = np.where(h < 4, x, y)
v = np.where(h < 4, y, x)
sign_u = np.where((h & 1) == 0, 1.0, -1.0)
sign_v = np.where((h & 2) == 0, 1.0, -1.0)
return sign_u * u + sign_v * v
def _perlin2(x: np.ndarray, y: np.ndarray, seed: int) -> np.ndarray:
"""Vectorised Ken-Perlin improved noise in 2D (z=0)."""
xi = np.floor(x).astype(np.int32) & 255
yi = np.floor(y).astype(np.int32) & 255
xf = x - np.floor(x)
yf = y - np.floor(y)
u = _fade(xf)
v = _fade(yf)
perm = _P if seed == 0 else np.roll(_P, seed & 255)
A = (perm[xi] + yi) & 255 Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/shader_legends/ShaneOrganicNoise/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from shader legends
Domain Repetition — Inigo Quilez tile-with-warp
shader_legends · IQDomainRepetition
Inigo Quilez's 'Domain Repetition' technique from his 2008 article.
Hash without Sine — Dave Hoskins hash art
shader_legends · TaterHashArt
Dave Hoskins's celebrated 'Hash without Sine' family of cross-platform-stable pseudo-random hash functions, presented as art.
Improved Noise — Ken Perlin (SIGGRAPH 2002, faithful reimplementation)
shader_legends · KenPerlinNoisePure
Ken Perlin's Improved Noise (SIGGRAPH 2002) ported faithfully to numpy.
Pixel City — top-down procedural city
shader_legends · PolygoncityPixelCity
A top-down 50x50 procedural city built from a deterministic hash of grid cells.
Shane Flow Field — particles in an fBM flow
shader_legends · ShaneFlowFieldFBM
Inspired by Shane's Noisey Image Flow Lines (Shadertoy 7sSSzw).