Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
382
shader-dev/techniques/texture-sampling.md
Normal file
382
shader-dev/techniques/texture-sampling.md
Normal file
@@ -0,0 +1,382 @@
|
||||
**IMPORTANT - GLSL Type Strictness**:
|
||||
- GLSL is a strongly-typed language and does not support the `string` type (you cannot define `string var`)
|
||||
- `vec2`/`vec3`/`vec4` are vector types and cannot be directly assigned a float (e.g., `vec2 a = 1.0` must be `vec2 a = vec2(1.0)`)
|
||||
- Array indices must be integer constants or uniform variables; runtime-computed floats cannot be used
|
||||
- Avoid uninitialized variables — GLSL default values are undefined
|
||||
|
||||
# Texture Sampling
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Post-processing effects**: Blur, bloom, dispersion, chromatic aberration
|
||||
- **Procedural noise**: FBM layering from noise textures to generate terrain, clouds, fire
|
||||
- **PBR/IBL**: Cubemap environment lighting, BRDF LUT lookup
|
||||
- **Simulation/feedback systems**: Reaction-diffusion, fluid simulation multi-buffer feedback
|
||||
- **Data storage**: Textures used as structured data (game state, keyboard input)
|
||||
- **Temporal accumulation**: TAA, motion blur, previous frame reading
|
||||
|
||||
## Core Principles
|
||||
|
||||
| Function | Coordinate Type | Filtering | Typical Use |
|
||||
|----------|----------------|-----------|-------------|
|
||||
| `texture(sampler, uv)` | Float UV `[0,1]` | Hardware bilinear | General texture reading |
|
||||
| `textureLod(sampler, uv, lod)` | Float UV + LOD | Specified mip level | Control blur level / avoid auto mip |
|
||||
| `texelFetch(sampler, ivec2, lod)` | Integer pixel coordinates | No filtering | Exact pixel data reading |
|
||||
|
||||
Key mathematics:
|
||||
1. **Hardware bilinear interpolation**: `texture()` automatically linearly blends between 4 adjacent texels
|
||||
2. **Quintic Hermite smoothing**: `u = f^3(6f^2 - 15f + 10)`, C2 continuous (eliminates hardware linear interpolation seams)
|
||||
3. **LOD control**: `textureLod` third parameter selects mipmap level, `lod=0` is original resolution, each +1 halves resolution
|
||||
4. **Coordinate wrapping**: `fract(uv)` implements torus boundary, equivalent to `GL_REPEAT`
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Basic Sampling and UV Normalization
|
||||
|
||||
```glsl
|
||||
vec2 uv = fragCoord / iResolution.xy;
|
||||
vec4 col = texture(iChannel0, uv);
|
||||
```
|
||||
|
||||
### Step 2: textureLod for Mipmap Control
|
||||
|
||||
```glsl
|
||||
// In ray marching: force LOD 0 to avoid artifacts
|
||||
vec3 groundCol = textureLod(iChannel2, groundUv * 0.05, 0.0).rgb;
|
||||
|
||||
// Depth of field blur: LOD varies with distance
|
||||
float focus = mix(maxBlur - coverage, minBlur, smoothstep(.1, .2, coverage));
|
||||
vec3 col = textureLod(iChannel0, uv + normal, focus).rgb;
|
||||
|
||||
// Bloom: sample high mip levels
|
||||
#define BLOOM_LOD_A 4.0 // adjustable: bloom first mip level
|
||||
#define BLOOM_LOD_B 5.0
|
||||
#define BLOOM_LOD_C 6.0
|
||||
vec3 bloom = vec3(0.0);
|
||||
bloom += textureLod(iChannel0, uv + off * exp2(BLOOM_LOD_A), BLOOM_LOD_A).rgb;
|
||||
bloom += textureLod(iChannel0, uv + off * exp2(BLOOM_LOD_B), BLOOM_LOD_B).rgb;
|
||||
bloom += textureLod(iChannel0, uv + off * exp2(BLOOM_LOD_C), BLOOM_LOD_C).rgb;
|
||||
bloom /= 3.0;
|
||||
```
|
||||
|
||||
### Step 3: texelFetch for Exact Pixel Reading
|
||||
|
||||
```glsl
|
||||
// Data storage addresses
|
||||
const ivec2 txBallPosVel = ivec2(0, 0);
|
||||
const ivec2 txPaddlePos = ivec2(1, 0);
|
||||
const ivec2 txPoints = ivec2(2, 0);
|
||||
const ivec2 txState = ivec2(3, 0);
|
||||
|
||||
vec4 loadValue(in ivec2 addr) {
|
||||
return texelFetch(iChannel0, addr, 0);
|
||||
}
|
||||
|
||||
void storeValue(in ivec2 addr, in vec4 val, inout vec4 fragColor, in ivec2 fragPos) {
|
||||
fragColor = (fragPos == addr) ? val : fragColor;
|
||||
}
|
||||
|
||||
// Keyboard input
|
||||
float key = texelFetch(iChannel1, ivec2(KEY_SPACE, 0), 0).x;
|
||||
```
|
||||
|
||||
### Step 4: Manual Bilinear + Quintic Hermite Smoothing
|
||||
|
||||
```glsl
|
||||
float noise(vec2 x) {
|
||||
vec2 p = floor(x);
|
||||
vec2 f = fract(x);
|
||||
vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); // C2 continuous
|
||||
|
||||
#define TEX_RES 1024.0 // adjustable: noise texture resolution
|
||||
float a = texture(iChannel0, (p + vec2(0.0, 0.0)) / TEX_RES).x;
|
||||
float b = texture(iChannel0, (p + vec2(1.0, 0.0)) / TEX_RES).x;
|
||||
float c = texture(iChannel0, (p + vec2(0.0, 1.0)) / TEX_RES).x;
|
||||
float d = texture(iChannel0, (p + vec2(1.0, 1.0)) / TEX_RES).x;
|
||||
|
||||
return a + (b - a) * u.x + (c - a) * u.y + (a - b - c + d) * u.x * u.y;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: FBM Texture Noise
|
||||
|
||||
```glsl
|
||||
#define FBM_OCTAVES 5 // adjustable: number of layers
|
||||
#define FBM_PERSISTENCE 0.5 // adjustable: amplitude decay rate
|
||||
|
||||
float fbm(vec2 x) {
|
||||
float v = 0.0;
|
||||
float a = 0.5;
|
||||
float totalWeight = 0.0;
|
||||
for (int i = 0; i < FBM_OCTAVES; i++) {
|
||||
v += a * noise(x);
|
||||
totalWeight += a;
|
||||
x *= 2.0;
|
||||
a *= FBM_PERSISTENCE;
|
||||
}
|
||||
return v / totalWeight;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: Separable Gaussian Blur
|
||||
|
||||
```glsl
|
||||
#define BLUR_RADIUS 4 // adjustable: blur radius
|
||||
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||
vec2 uv = fragCoord / iResolution.xy;
|
||||
vec2 d = vec2(1.0 / iResolution.x, 0.0); // horizontal pass; for vertical pass change to vec2(0, 1/iResolution.y)
|
||||
float w[9] = float[9](0.05, 0.09, 0.12, 0.15, 0.16, 0.15, 0.12, 0.09, 0.05);
|
||||
|
||||
vec4 col = vec4(0.0);
|
||||
for (int i = -4; i <= 4; i++) {
|
||||
col += w[i + 4] * texture(iChannel0, fract(uv + float(i) * d));
|
||||
}
|
||||
col /= 0.98;
|
||||
fragColor = col;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 7: Dispersion Sampling
|
||||
|
||||
```glsl
|
||||
#define DISP_SAMPLES 64 // adjustable: sample count
|
||||
|
||||
vec3 sampleWeights(float i) {
|
||||
return vec3(i * i, 46.6666 * pow((1.0 - i) * i, 3.0), (1.0 - i) * (1.0 - i));
|
||||
}
|
||||
|
||||
vec3 sampleDisp(sampler2D tex, vec2 uv, vec2 disp) {
|
||||
vec3 col = vec3(0.0);
|
||||
vec3 totalWeight = vec3(0.0);
|
||||
for (int i = 0; i < DISP_SAMPLES; i++) {
|
||||
float t = float(i) / float(DISP_SAMPLES);
|
||||
vec3 w = sampleWeights(t);
|
||||
col += w * texture(tex, fract(uv + disp * t)).rgb;
|
||||
totalWeight += w;
|
||||
}
|
||||
return col / totalWeight;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 8: IBL Environment Sampling
|
||||
|
||||
```glsl
|
||||
#define MAX_LOD 7.0 // adjustable: cubemap max mip level
|
||||
#define DIFFUSE_LOD 6.5 // adjustable: diffuse sampling LOD
|
||||
|
||||
vec3 getSpecularLightColor(vec3 N, float roughness) {
|
||||
vec3 raw = textureLod(iChannel0, N, roughness * MAX_LOD).rgb;
|
||||
return pow(raw, vec3(4.5)) * 6.5; // HDR approximation
|
||||
}
|
||||
|
||||
vec3 getDiffuseLightColor(vec3 N) {
|
||||
return textureLod(iChannel0, N, DIFFUSE_LOD).rgb;
|
||||
}
|
||||
|
||||
// BRDF LUT lookup
|
||||
vec2 brdf = texture(iChannel3, vec2(NdotV, roughness)).rg;
|
||||
vec3 specular = envColor * (F * brdf.x + brdf.y);
|
||||
```
|
||||
|
||||
## Complete Code Template
|
||||
|
||||
iChannel0 bound to a noise texture (e.g., "Gray Noise Medium"), with mipmap enabled.
|
||||
|
||||
```glsl
|
||||
// === Texture Sampling Comprehensive Demo ===
|
||||
// iChannel0: noise texture (requires mipmap enabled)
|
||||
|
||||
#define TEX_RES 256.0
|
||||
#define FBM_OCTAVES 6
|
||||
#define FBM_PERSISTENCE 0.5
|
||||
#define CLOUD_LAYERS 4
|
||||
#define CLOUD_SPEED 0.02
|
||||
#define DOF_MAX_BLUR 5.0
|
||||
#define DOF_FOCUS_DIST 0.5
|
||||
#define BLOOM_STRENGTH 0.3
|
||||
#define BLOOM_LOD 4.0
|
||||
|
||||
float noise(vec2 x) {
|
||||
vec2 p = floor(x);
|
||||
vec2 f = fract(x);
|
||||
vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
|
||||
|
||||
float a = textureLod(iChannel0, (p + vec2(0.0, 0.0)) / TEX_RES, 0.0).x;
|
||||
float b = textureLod(iChannel0, (p + vec2(1.0, 0.0)) / TEX_RES, 0.0).x;
|
||||
float c = textureLod(iChannel0, (p + vec2(0.0, 1.0)) / TEX_RES, 0.0).x;
|
||||
float d = textureLod(iChannel0, (p + vec2(1.0, 1.0)) / TEX_RES, 0.0).x;
|
||||
|
||||
return a + (b - a) * u.x + (c - a) * u.y + (a - b - c + d) * u.x * u.y;
|
||||
}
|
||||
|
||||
float fbm(vec2 x) {
|
||||
float v = 0.0;
|
||||
float a = 0.5;
|
||||
float w = 0.0;
|
||||
for (int i = 0; i < FBM_OCTAVES; i++) {
|
||||
v += a * noise(x);
|
||||
w += a;
|
||||
x *= 2.0;
|
||||
a *= FBM_PERSISTENCE;
|
||||
}
|
||||
return v / w;
|
||||
}
|
||||
|
||||
float cloudLayer(vec2 uv, float height, float time) {
|
||||
vec2 offset = vec2(time * CLOUD_SPEED * (1.0 + height), 0.0);
|
||||
float n = fbm((uv + offset) * (2.0 + height * 3.0));
|
||||
return smoothstep(0.4, 0.7, n);
|
||||
}
|
||||
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||
vec2 uv = fragCoord / iResolution.xy;
|
||||
float aspect = iResolution.x / iResolution.y;
|
||||
|
||||
// 1. Procedural sky
|
||||
vec3 sky = mix(vec3(0.1, 0.15, 0.4), vec3(0.5, 0.7, 1.0), uv.y);
|
||||
|
||||
// 2. FBM cloud layers
|
||||
vec3 col = sky;
|
||||
for (int i = 0; i < CLOUD_LAYERS; i++) {
|
||||
float h = float(i) / float(CLOUD_LAYERS);
|
||||
float density = cloudLayer(vec2(uv.x * aspect, uv.y), h, iTime);
|
||||
vec3 cloudCol = mix(vec3(0.8, 0.85, 0.9), vec3(1.0), h);
|
||||
col = mix(col, cloudCol, density * (0.3 + 0.7 * h));
|
||||
}
|
||||
|
||||
// 3. textureLod depth of field blur
|
||||
float dist = abs(uv.y - DOF_FOCUS_DIST);
|
||||
float lod = dist * DOF_MAX_BLUR;
|
||||
vec3 blurred = textureLod(iChannel0, uv, lod).rgb;
|
||||
col = mix(col, blurred * 0.5 + col * 0.5, 0.3);
|
||||
|
||||
// 4. Bloom
|
||||
vec3 bloom = textureLod(iChannel0, uv, BLOOM_LOD).rgb;
|
||||
bloom += textureLod(iChannel0, uv, BLOOM_LOD + 1.0).rgb;
|
||||
bloom += textureLod(iChannel0, uv, BLOOM_LOD + 2.0).rgb;
|
||||
bloom /= 3.0;
|
||||
col += bloom * BLOOM_STRENGTH;
|
||||
|
||||
// 5. Post-processing
|
||||
col = (col * (6.2 * col + 0.5)) / (col * (6.2 * col + 1.7) + 0.06);
|
||||
col *= 0.5 + 0.5 * pow(16.0 * uv.x * uv.y * (1.0 - uv.x) * (1.0 - uv.y), 0.2);
|
||||
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
## Common Variants
|
||||
|
||||
### Variant 1: Anisotropic Flow-Field Blur
|
||||
|
||||
```glsl
|
||||
#define BLUR_ITERATIONS 32 // adjustable: number of samples along flow field
|
||||
#define BLUR_STEP 0.008 // adjustable: UV offset per step
|
||||
|
||||
vec3 flowBlur(vec2 uv) {
|
||||
vec3 col = vec3(0.0);
|
||||
float acc = 0.0;
|
||||
for (int i = 0; i < BLUR_ITERATIONS; i++) {
|
||||
float h = float(i) / float(BLUR_ITERATIONS);
|
||||
float w = 4.0 * h * (1.0 - h);
|
||||
col += w * texture(iChannel0, uv).rgb;
|
||||
acc += w;
|
||||
vec2 dir = texture(iChannel1, uv).xy * 2.0 - 1.0;
|
||||
uv += BLUR_STEP * dir;
|
||||
}
|
||||
return col / acc;
|
||||
}
|
||||
```
|
||||
|
||||
### Variant 2: Buffer-as-Data Storage
|
||||
|
||||
```glsl
|
||||
const ivec2 txPosition = ivec2(0, 0);
|
||||
const ivec2 txVelocity = ivec2(1, 0);
|
||||
const ivec2 txState = ivec2(2, 0);
|
||||
|
||||
vec4 load(ivec2 addr) { return texelFetch(iChannel0, addr, 0); }
|
||||
|
||||
void store(ivec2 addr, vec4 val, inout vec4 fragColor, ivec2 fragPos) {
|
||||
fragColor = (fragPos == addr) ? val : fragColor;
|
||||
}
|
||||
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||
ivec2 p = ivec2(fragCoord);
|
||||
fragColor = texelFetch(iChannel0, p, 0);
|
||||
vec4 pos = load(txPosition);
|
||||
vec4 vel = load(txVelocity);
|
||||
// ... update logic ...
|
||||
store(txPosition, pos + vel * 0.016, fragColor, p);
|
||||
store(txVelocity, vel, fragColor, p);
|
||||
}
|
||||
```
|
||||
|
||||
### Variant 3: Dispersion Effect
|
||||
|
||||
```glsl
|
||||
#define DISP_SAMPLES 64 // adjustable: sample count
|
||||
#define DISP_STRENGTH 0.05 // adjustable: dispersion strength
|
||||
|
||||
vec3 dispersion(vec2 uv, vec2 displacement) {
|
||||
vec3 col = vec3(0.0);
|
||||
vec3 w_total = vec3(0.0);
|
||||
for (int i = 0; i < DISP_SAMPLES; i++) {
|
||||
float t = float(i) / float(DISP_SAMPLES);
|
||||
vec3 w = vec3(t * t, 46.666 * pow((1.0 - t) * t, 3.0), (1.0 - t) * (1.0 - t));
|
||||
col += w * texture(iChannel0, fract(uv + displacement * t * DISP_STRENGTH)).rgb;
|
||||
w_total += w;
|
||||
}
|
||||
return col / w_total;
|
||||
}
|
||||
```
|
||||
|
||||
### Variant 4: Triplanar Texture Mapping
|
||||
|
||||
```glsl
|
||||
#define TRIPLANAR_SHARPNESS 2.0 // adjustable: blend sharpness
|
||||
|
||||
vec3 triplanarSample(sampler2D tex, vec3 pos, vec3 normal, float scale) {
|
||||
vec3 w = pow(abs(normal), vec3(TRIPLANAR_SHARPNESS));
|
||||
w /= (w.x + w.y + w.z);
|
||||
vec3 xSample = texture(tex, pos.yz * scale).rgb;
|
||||
vec3 ySample = texture(tex, pos.xz * scale).rgb;
|
||||
vec3 zSample = texture(tex, pos.xy * scale).rgb;
|
||||
return xSample * w.x + ySample * w.y + zSample * w.z;
|
||||
}
|
||||
```
|
||||
|
||||
### Variant 5: Temporal Reprojection (TAA)
|
||||
|
||||
```glsl
|
||||
#define TAA_BLEND 0.9 // adjustable: history frame blend ratio
|
||||
|
||||
vec3 temporalBlend(vec2 currUv, vec2 prevUv, vec3 currColor) {
|
||||
vec3 history = textureLod(iChannel0, prevUv, 0.0).rgb;
|
||||
vec3 minCol = currColor - 0.1;
|
||||
vec3 maxCol = currColor + 0.1;
|
||||
history = clamp(history, minCol, maxCol);
|
||||
return mix(currColor, history, TAA_BLEND);
|
||||
}
|
||||
```
|
||||
|
||||
## Performance & Composition
|
||||
|
||||
**Performance Tips**:
|
||||
- Heavy sampling (e.g., 64 dispersion samples) is a bandwidth bottleneck — reduce sample count + use smart weight compensation; use `textureLod` with high LOD to reduce cache misses
|
||||
- 2D Gaussian blur uses separable two-pass (O(N^2) -> O(2N)), leveraging hardware bilinear for (N+1)/2 samples to achieve N-tap
|
||||
- Must use `textureLod(..., 0.0)` inside ray marching — the GPU cannot correctly estimate screen-space derivatives
|
||||
- Manual Hermite interpolation is ~4x slower than hardware — only use for the first two FBM octaves, fall back to `texture()` for higher frequencies
|
||||
- Each multi-buffer feedback adds one frame of latency — merge operations into the same pass; use `texelFetch` to avoid filtering overhead
|
||||
|
||||
**Composition Tips**:
|
||||
- **+ SDF Ray Marching**: Noise textures for displacement maps/materials; use `textureLod(..., 0.0)` inside ray marching
|
||||
- **+ Procedural Noise**: Hermite + FBM driving domain warping to generate terrain/clouds/fire; texture noise is faster than pure mathematical noise
|
||||
- **+ Post-Processing Pipeline**: Multi-LOD bloom → separable DOF → dispersion → tone mapping, chaining a complete post-processing pipeline
|
||||
- **+ PBR/IBL**: `textureLod` samples cubemap by roughness + BRDF LUT lookup = split-sum IBL
|
||||
- **+ Simulation/Feedback**: Multi-buffer reaction-diffusion/fluid; Buffer A state, B/C separable blur diffusion, Image visualization; `fract()` torus boundary
|
||||
|
||||
## Further Reading
|
||||
|
||||
For complete step-by-step tutorials, mathematical derivations, and advanced usage, see [reference](../reference/texture-sampling.md)
|
||||
Reference in New Issue
Block a user