Files
skills/shader-dev/reference/procedural-2d-pattern.md
shihao 6487becf60 Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:52:49 +08:00

440 lines
16 KiB
Markdown

# 2D Procedural Patterns — Detailed Reference
This document is a complete supplement to [SKILL.md](SKILL.md), containing prerequisites, detailed explanations for each step, variant descriptions, in-depth performance analysis, and combination example code.
---
## Prerequisites
- **GLSL Basic Syntax**: uniform, varying, built-in functions
- **Vector Math**: `dot`, `length`, `normalize`, `atan`
- **Coordinate Space Concepts**: UV normalization, aspect ratio correction
- **Basic Math Functions**: `sin`/`cos`, `fract`/`floor`/`mod`, `smoothstep`, `pow`
- **Polar Coordinates**: `atan(y,x)` returns angle, `length` returns radial distance
---
## Core Principles in Detail
The essence of 2D procedural patterns is the combination of **domain transforms + distance fields + color mapping**:
1. **Domain Repetition**: use `fract()`/`mod()` to fold an infinite plane into finite cells, each cell independently rendering the same (or variant) pattern
2. **Cell Identification**: use `floor()` to extract the integer coordinates of the current cell as a hash seed to generate pseudo-random numbers, driving independent variations per cell
3. **Distance Fields (SDF)**: use mathematical functions to compute the distance from a pixel to geometric shapes (circles, hexagons, line segments, arcs), converting to crisp or soft edges via `smoothstep`
4. **Color Mapping**: Cosine palette `a + b*cos(2pi(c*t+d))` or HSV mapping, converting scalar values to rich colors
5. **Layered Compositing**: results from multiple loops or multi-layer passes are combined through addition, multiplication, or `mix` to build visual complexity
---
## Implementation Steps in Detail
### Step 1: UV Coordinate Normalization and Aspect Ratio Correction
**What**: Convert pixel coordinates to normalized coordinates centered on the screen with Y-axis range [-1, 1]
**Why**: A unified coordinate system ensures patterns don't distort with resolution changes; using Y-axis as reference maintains square pixels
```glsl
vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;
```
### Step 2: Domain Repetition — Dividing Space into Repeating Cells
**What**: Scale UV coordinates and take the fractional part to generate repeating local coordinates; simultaneously extract cell IDs using `floor`
**Why**: `fract()` folds an infinite plane into a repeating [0,1) space, `floor()` provides a unique cell identifier for subsequent randomization. Subtracting 0.5 centers the origin
```glsl
#define SCALE 4.0 // Tunable: repetition density, higher = more cells
vec2 cell_uv = fract(uv * SCALE) - 0.5;
vec2 cell_id = floor(uv * SCALE);
```
For hexagonal grids, domain repetition requires special handling (two offset rectangular grids, taking the nearest):
```glsl
const vec2 s = vec2(1, 1.7320508); // 1 and sqrt(3)
vec4 hC = floor(vec4(p, p - vec2(0.5, 1.0)) / s.xyxy) + 0.5;
vec4 h = vec4(p - hC.xy * s, p - (hC.zw + 0.5) * s);
// Take the nearest hexagonal center
vec4 hex_data = dot(h.xy, h.xy) < dot(h.zw, h.zw)
? vec4(h.xy, hC.xy)
: vec4(h.zw, hC.zw + vec2(0.5, 1.0));
```
### Step 3: Cell Randomization
**What**: Use cell IDs to generate pseudo-random numbers, giving each cell different attributes (size, position, color offset)
**Why**: Pure repetition looks mechanical; randomization gives patterns a "procedural yet lively" quality
```glsl
float hash21(vec2 p) {
return fract(sin(dot(p, vec2(141.173, 289.927))) * 43758.5453);
}
float rnd = hash21(cell_id);
float radius = 0.15 + 0.1 * rnd; // Tunable: base radius and random range
```
### Step 4: Distance Field Shape Rendering
**What**: Compute the distance from the pixel to the target shape, then convert to visualization using `smoothstep`
**Why**: SDF is the cornerstone of procedural graphics — a single scalar value simultaneously encodes shape, edges, and glow effects
```glsl
// Circle SDF
float d = length(cell_uv) - radius;
// Hexagon SDF
float hex_sdf(vec2 p) {
p = abs(p);
return max(dot(p, vec2(0.5, 0.866025)), p.x);
}
// Line segment SDF (for networks/grid lines)
float line_sdf(vec2 a, vec2 b, vec2 p) {
vec2 pa = p - a, ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h);
}
// Anti-aliased rendering with smoothstep
float shape = 1.0 - smoothstep(radius - 0.008, radius + 0.008, length(cell_uv));
```
### Step 5: Polar Coordinate Conversion and Ring/Arc Patterns
**What**: Convert Cartesian coordinates to polar coordinates, using radial distance to draw concentric rings and angle to draw sectors/arc segments
**Why**: Polar coordinates are naturally suited for radar sweeps, concentric circles, spirals, and other radially symmetric patterns
```glsl
vec2 polar = vec2(length(uv), atan(uv.y, uv.x));
float ring_id = floor(polar.x * NUM_RINGS + 0.5) / NUM_RINGS; // Tunable: NUM_RINGS ring count
// Concentric rings
float ring = 1.0 - pow(abs(sin(polar.x * 3.14159 * NUM_RINGS)) * 1.25, 2.5);
// Arc segment clipping
float arc_end = polar.y + sin(iTime + ring_id * 5.5) * 1.52 - 1.5;
ring *= smoothstep(0.0, 0.05, arc_end);
```
### Step 6: Cosine Palette
**What**: Generate a continuous rainbow color mapping function using four vec3 parameters
**Why**: A single line of code generates infinite smooth color schemes, more flexible and GPU-friendly than lookup tables
```glsl
vec3 palette(float t) {
// Tunable: modify a/b/c/d to change color scheme
vec3 a = vec3(0.5, 0.5, 0.5); // Brightness offset
vec3 b = vec3(0.5, 0.5, 0.5); // Amplitude
vec3 c = vec3(1.0, 1.0, 1.0); // Frequency
vec3 d = vec3(0.263, 0.416, 0.557); // Phase offset
return a + b * cos(6.28318 * (c * t + d));
}
```
### Step 7: Iterative Stacking and Glow Effects
**What**: Repeatedly perform domain repetition + distance field calculation in a loop, accumulating color; use `pow(1/d)` to produce glow
**Why**: A single layer pattern is too simple; multi-layer iterative stacking produces fractal-like visual complexity with minimal code. Exponentially decaying glow gives patterns a neon light feel
```glsl
#define NUM_LAYERS 4.0 // Tunable: number of iteration layers, more = more complex
vec3 finalColor = vec3(0.0);
vec2 uv0 = uv; // Preserve original UV for global coloring
for (float i = 0.0; i < NUM_LAYERS; i++) {
uv = fract(uv * 1.5) - 0.5; // Tunable: 1.5 is the scale factor
float d = length(uv) * exp(-length(uv0));
vec3 col = palette(length(uv0) + i * 0.4 + iTime * 0.4);
d = sin(d * 8.0 + iTime) / 8.0; // Tunable: 8.0 is the ripple frequency
d = abs(d);
d = pow(0.01 / d, 1.2); // Tunable: 0.01 is glow width, 1.2 is decay exponent
finalColor += col * d;
}
```
### Step 8: Trigonometric Interference Patterns
**What**: Use `sin`/`cos` to mutually perturb coordinates in iterations, generating water caustic-like interference patterns
**Why**: Superposition of trigonometric functions produces complex Moire-like interference patterns; a few iterations yield highly organic visual effects
```glsl
#define MAX_ITER 5 // Tunable: iteration count, more = richer detail
vec2 p = mod(uv * TAU, TAU) - 250.0; // TAU period ensures tileability
vec2 i = p;
float c = 1.0;
float inten = 0.005; // Tunable: intensity coefficient
for (int n = 0; n < MAX_ITER; n++) {
float t = iTime * (1.0 - 3.5 / float(n + 1));
i = p + vec2(cos(t - i.x) + sin(t + i.y),
sin(t - i.y) + cos(t + i.x));
c += 1.0 / length(vec2(p.x / (sin(i.x + t) / inten),
p.y / (cos(i.y + t) / inten)));
}
c /= float(MAX_ITER);
c = 1.17 - pow(c, 1.4); // Tunable: 1.4 is the contrast exponent
vec3 colour = vec3(pow(abs(c), 8.0));
```
### Step 9: Multi-Layer Depth Compositing
**What**: Render the same pattern at different zoom levels, using depth fade-in/out to simulate parallax
**Why**: Multi-scale stacking breaks the mechanical feel of a single scale, producing a pseudo-3D depth effect
```glsl
#define NUM_DEPTH_LAYERS 4.0 // Tunable: number of depth layers
float m = 0.0;
for (float i = 0.0; i < 1.0; i += 1.0 / NUM_DEPTH_LAYERS) {
float z = fract(iTime * 0.1 + i);
float size = mix(15.0, 1.0, z); // Dense far away, sparse up close
float fade = smoothstep(0.0, 0.6, z) * smoothstep(1.0, 0.8, z); // Fade at both ends
m += fade * patternLayer(uv * size, i, iTime);
}
```
### Step 10: Post-Processing Pipeline
**What**: Apply gamma correction, contrast enhancement, saturation adjustment, and vignette in sequence
**Why**: Post-processing transforms "technically correct" output into "visually pleasing" final results
```glsl
// Gamma correction
col = pow(clamp(col, 0.0, 1.0), vec3(1.0 / 2.2));
// Contrast enhancement (S-curve)
col = col * 0.6 + 0.4 * col * col * (3.0 - 2.0 * col);
// Saturation adjustment
col = mix(col, vec3(dot(col, vec3(0.33))), -0.4); // Tunable: -0.4 increases saturation, positive reduces it
// Vignette
vec2 q = fragCoord / iResolution.xy;
col *= 0.5 + 0.5 * pow(16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y), 0.7);
```
---
## Common Variants in Detail
### Variant 1: Hexagonal Grid + Truchet Arcs
**Difference from base version**: Replaces the square grid with a hexagonal grid coordinate system, drawing three randomly oriented arcs within each hexagonal cell; arcs form maze-like continuous paths between cells
**Key modified code**:
```glsl
// Hexagon distance field
float hex(vec2 p) {
p = abs(p);
return max(dot(p, vec2(0.5, 0.866025)), p.x);
}
// Hexagonal grid coordinates (returns xy=cell-local coords, zw=cell ID)
const vec2 s = vec2(1.0, 1.7320508);
vec4 getHex(vec2 p) {
vec4 hC = floor(vec4(p, p - vec2(0.5, 1.0)) / s.xyxy) + 0.5;
vec4 h = vec4(p - hC.xy * s, p - (hC.zw + 0.5) * s);
return dot(h.xy, h.xy) < dot(h.zw, h.zw)
? vec4(h.xy, hC.xy)
: vec4(h.zw, hC.zw + vec2(0.5, 1.0));
}
// Truchet three-arc: one arc for each of three directions
float r = 1.0;
vec2 q1 = p - vec2(0.0, r) / s;
vec2 q2 = rot2(6.28318 / 3.0) * p - vec2(0.0, r) / s;
vec2 q3 = rot2(6.28318 * 2.0 / 3.0) * p - vec2(0.0, r) / s;
// Take nearest arc
float d = min(min(length(q1), length(q2)), length(q3));
d = abs(d - 0.288675) - 0.1; // 0.288675 = sqrt(3)/6, arc radius
```
### Variant 2: Water Caustic Interference Pattern
**Difference from base version**: Does not use domain repetition grids; instead generates full-screen interference textures through trigonometric iteration, seamlessly tileable
**Key modified code**:
```glsl
#define TAU 6.28318530718
#define MAX_ITER 5 // Tunable: iteration count
vec2 p = mod(uv * TAU, TAU) - 250.0;
vec2 i = p;
float c = 1.0;
float inten = 0.005;
for (int n = 0; n < MAX_ITER; n++) {
float t = iTime * (1.0 - 3.5 / float(n + 1));
i = p + vec2(cos(t - i.x) + sin(t + i.y),
sin(t - i.y) + cos(t + i.x));
c += 1.0 / length(vec2(p.x / (sin(i.x + t) / inten),
p.y / (cos(i.y + t) / inten)));
}
c /= float(MAX_ITER);
c = 1.17 - pow(c, 1.4);
vec3 colour = vec3(pow(abs(c), 8.0));
colour = clamp(colour + vec3(0.0, 0.35, 0.5), 0.0, 1.0); // Aquatic color shift
```
### Variant 3: Polar Concentric Rings + Animated Arc Segments
**Difference from base version**: Uses polar coordinates instead of Cartesian grids, drawing concentric ring arc segments with independent animation, suitable for radar/HUD style
**Key modified code**:
```glsl
#define NUM_RINGS 20.0 // Tunable: ring count
#define PALETTE vec3(0.0, 1.4, 2.0) + 1.5
vec2 plr = vec2(length(p), atan(p.y, p.x));
float id = floor(plr.x * NUM_RINGS + 0.5) / NUM_RINGS;
// Each ring rotates independently
p *= rot2(id * 11.0);
p.y = abs(p.y); // Mirror symmetry
// Concentric ring SDF
float rz = 1.0 - pow(abs(sin(plr.x * 3.14159 * NUM_RINGS)) * 1.25, 2.5);
// Arc segment animation
float arc = plr.y + sin(iTime + id * 5.5) * 1.52 - 1.5;
rz *= smoothstep(0.0, 0.05, arc);
// Per-ring coloring
vec3 col = (sin(PALETTE + id * 5.0 + iTime) * 0.5 + 0.5) * rz;
```
### Variant 4: Multi-Layer Depth Parallax Network
**Difference from base version**: Renders grid nodes and connections at multiple zoom levels, using depth fade-in/out to produce a pseudo-3D effect
**Key modified code**:
```glsl
#define NUM_DEPTH_LAYERS 4.0 // Tunable: number of depth layers
// Random vertex position within each cell
vec2 GetPos(vec2 id, vec2 offs, float t) {
float n = hash21(id + offs);
return offs + vec2(sin(t + n * 6.28), cos(t + fract(n * 100.0) * 6.28)) * 0.4;
}
// Line segment SDF
float df_line(vec2 a, vec2 b, vec2 p) {
vec2 pa = p - a, ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h);
}
// Multi-layer compositing
float m = 0.0;
for (float i = 0.0; i < 1.0; i += 1.0 / NUM_DEPTH_LAYERS) {
float z = fract(iTime * 0.1 + i);
float size = mix(15.0, 1.0, z);
float fade = smoothstep(0.0, 0.6, z) * smoothstep(1.0, 0.8, z);
m += fade * NetLayer(uv * size, i, iTime);
}
```
### Variant 5: Fractal Apollian Pattern
**Difference from base version**: Uses iterative fold-and-invert transforms to generate infinitely detailed aperiodic fractal patterns, combined with HSV coloring
**Key modified code**:
```glsl
float apollian(vec4 p, float s) {
float scale = 1.0;
for (int i = 0; i < 7; ++i) { // Tunable: iteration count (5~12)
p = -1.0 + 2.0 * fract(0.5 * p + 0.5); // Space folding
float r2 = dot(p, p);
float k = s / r2; // Tunable: s is scaling factor (1.0~1.5)
p *= k; // Inversion mapping
scale *= k;
}
return abs(p.y) / scale;
}
// 4D slice animation for smooth morphing
vec4 pp = vec4(p.x, p.y, 0.0, 0.0) + offset;
pp.w = 0.125 * (1.0 - tanh(length(pp.xyz)));
float d = apollian(pp / 4.0, 1.2) * 4.0;
// HSV coloring
float hue = fract(0.75 * length(p) - 0.3 * iTime) + 0.3;
float sat = 0.75 * tanh(2.0 * length(p));
vec3 col = hsv2rgb(vec3(hue, sat, 1.0));
```
---
## In-Depth Performance Optimization
### 1. Control Iteration Count
The iteration loop is the biggest performance bottleneck. Increasing `NUM_LAYERS` from 4 to 8 halves performance. On mobile, keep it at 3 or fewer layers.
### 2. Avoid Branching
Replace `if/else` with branchless `step()`/`smoothstep()`/`mix()` alternatives:
```glsl
// Bad: if(rnd > 0.5) p.y = -p.y;
// Good: p.y *= sign(rnd - 0.5); // or use mix
```
### 3. Merge Distance Field Calculations
Combine multiple shape SDFs using `min()`/`max()` and apply a single `smoothstep`, rather than rendering each shape separately.
### 4. Precompute Constants
Compute `sin`/`cos` pairs (e.g., rotation matrices) once outside the loop; write irrational numbers like `1.7320508` (sqrt(3)) as direct constants.
### 5. Minimize `atan` Calls
`atan` is an expensive function. If you only need periodic angular variation, consider approximating with `dot`.
### 6. LOD Strategy
Reduce iteration count at distance/when zoomed out:
```glsl
int iters = int(mix(3.0, float(MAX_ITER), smoothstep(0.0, 1.0, 1.0 / scale)));
```
### 7. Use `smoothstep` Instead of `pow`
`pow(x, n)` is slower than `smoothstep` on some GPUs, and `smoothstep` naturally clamps to [0,1].
---
## Complete Combination Suggestion Examples
### 1. + Noise Texture
Overlay Perlin/Simplex noise perturbation on distance fields to give geometric patterns an organic/eroded feel. Triangle noise (as used in "Overly Satisfying") is an efficient low-cost alternative:
```glsl
d += triangleNoise(uv * 10.0) * 0.05; // Noise perturbation amount is tunable
```
### 2. + Post-Processing Cross-Hatch
Overlay cross-hatching effects on patterns to simulate hand-drawn/printmaking style (as used in "Hexagonal Maze Flow"):
```glsl
float gr = dot(col, vec3(0.299, 0.587, 0.114)); // Grayscale
float hatch = (gr < 0.45) ? clamp(sin((uv.x - uv.y) * 125.6) * 2.0 + 1.5, 0.0, 1.0) : 1.0;
col *= hatch * 0.5 + 0.5;
```
### 3. + SDF Boolean Operations
Combine multiple base patterns through `min` (union), `max` (intersection), and subtraction into complex geometry:
```glsl
float d = max(hexSDF, -circleSDF); // Hexagon minus circle = hexagonal ring
```
### 4. + Domain Warping
Apply sin/cos distortion to UVs before domain repetition, producing flowing/swirling effects:
```glsl
uv += 0.05 * vec2(sin(uv.y * 5.0 + iTime), sin(uv.x * 3.0 + iTime));
```
### 5. + Radial Blur / Motion Blur
Average multiple samples in the polar coordinate direction on the final color, producing rotational motion blur to enhance dynamism.
### 6. + Pseudo-3D Lighting
Use SDF gradients as normals and add simple diffuse/specular lighting to give 2D patterns a relief/embossed appearance (as in "Apollian with a twist" shadow casting method).