Files
skills/shader-dev/reference/texture-mapping-advanced.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

3.3 KiB
Raw Blame History

Advanced Texture Mapping Detailed Reference

Prerequisites

  • Screen-space derivatives (dFdx, dFdy)
  • textureGrad() function usage
  • Basic ray marching

Triplanar vs Biplanar Cost Analysis

Aspect Triplanar Biplanar
Texture fetches 3 2
ALU operations Lower Higher (axis selection)
Bandwidth Higher Lower
Visual quality Baseline Equivalent (k≥8)
Best for Bandwidth-rich GPUs Mobile, bandwidth-limited

Modern GPUs are typically bandwidth-limited rather than ALU-limited, making biplanar the better default choice.

Weight Remapping Mathematics

The biplanar weight formula clamp((w - 0.5773) / (1.0 - 0.5773), 0, 1) ensures:

  • At normals aligned with one axis: weight = 1.0 (clean projection)
  • At 45° diagonals where 2 axes are equal: smooth transition
  • At the cube diagonal (1/√3 ≈ 0.5773): weight = 0.0, but this is the point where the third (discarded) projection would be needed — biplanar's approximation error is maximal here but visually acceptable

Gradient Propagation

Using textureGrad() instead of texture() is essential because:

  1. Axis selection (ma, me) creates UV discontinuities at projection boundaries
  2. Hardware texture() computes mip from implicit derivatives, which spike at discontinuities → visible seams
  3. textureGrad() with manually propagated dFdx(p), dFdy(p) bypasses this, keeping gradients smooth across boundaries

Ray Differential Mathematics

Problem Statement

In rasterization, dFdx/dFdy of texture coordinates work naturally because adjacent pixels map to nearby surface points. In ray marching, adjacent pixels may hit completely different objects → broken mip selection.

Solution: Tangent Plane Intersection

Given:

  • Primary ray hits surface at pos with normal nor
  • Neighbor pixel ray rd_neighbor originates from ro_neighbor

The neighbor ray's intersection with the tangent plane at pos:

t_neighbor = dot(pos - ro_neighbor, nor) / dot(rd_neighbor, nor)
pos_neighbor = ro_neighbor + rd_neighbor * t_neighbor

The difference pos_neighbor - pos gives the world-space footprint of one pixel at the hit point.

For Perspective Cameras (Common Case)

ro is the same for all pixels, only rd varies:
dposdx = t * (rdx * dot(rd, nor) / dot(rdx, nor) - rd)
dposdy = t * (rdy * dot(rd, nor) / dot(rdy, nor) - rd)

Where rdx = rd + dFdx(rd) and rdy = rd + dFdy(rd).

Chain Rule for Texture Coordinates

If texture mapping function is uv = f(pos):

duvdx = Jacobian(f) × dposdx
duvdy = Jacobian(f) × dposdy

For simple planar mapping uv = pos.xz:

duvdx = dposdx.xz
duvdy = dposdy.xz

Texture Repetition Theory

Why Tiling is Visible

Human vision excels at detecting:

  1. Periodic patterns: Regular grid alignment
  2. Unique features: Distinctive spots/marks that repeat identically
  3. Phase alignment: All tiles start at the same phase

Breaking Repetition

Each method targets different cues:

  • Random offset (Method A): Breaks phase alignment, 4 fetches
  • Voronoi blend: Breaks grid structure entirely, 9 fetches (expensive)
  • Virtual pattern (Method B): Breaks unique features cheaply, 2 fetches

Method B is preferred for real-time use — the low-frequency index variation is cache-friendly and the two texture fetches share locality.