Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
439
shader-dev/reference/procedural-2d-pattern.md
Normal file
439
shader-dev/reference/procedural-2d-pattern.md
Normal file
@@ -0,0 +1,439 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user