# Advanced Texture Mapping Techniques ## Use Cases - Texturing 3D surfaces without UV seams (triplanar/biplanar mapping) - Eliminating visible tiling repetition on large surfaces - Proper texture filtering in ray-marched scenes (mip-level selection) - Combining procedural and sampled textures ## Techniques ### 1. Biplanar Mapping (Optimized Triplanar) Uses only 2 texture fetches instead of 3, selecting the two most relevant projection axes: ```glsl vec4 biplanar(sampler2D sam, vec3 p, vec3 n, float k) { vec3 dpdx = dFdx(p); vec3 dpdy = dFdy(p); n = abs(n); // Determine major, minor, median axes ivec3 ma = (n.x > n.y && n.x > n.z) ? ivec3(0,1,2) : (n.y > n.z) ? ivec3(1,2,0) : ivec3(2,0,1); ivec3 mi = (n.x < n.y && n.x < n.z) ? ivec3(0,1,2) : (n.y < n.z) ? ivec3(1,2,0) : ivec3(2,0,1); ivec3 me = ivec3(3) - mi - ma; // Two texture fetches (major and median projections) vec4 x = textureGrad(sam, vec2(p[ma.y], p[ma.z]), vec2(dpdx[ma.y], dpdx[ma.z]), vec2(dpdy[ma.y], dpdy[ma.z])); vec4 y = textureGrad(sam, vec2(p[me.y], p[me.z]), vec2(dpdx[me.y], dpdx[me.z]), vec2(dpdy[me.y], dpdy[me.z])); // Blend weights with local support vec2 w = vec2(n[ma.x], n[me.x]); w = clamp((w - 0.5773) / (1.0 - 0.5773), 0.0, 1.0); // 0.5773 = 1/sqrt(3) w = pow(w, vec2(k / 8.0)); return (x * w.x + y * w.y) / (w.x + w.y); } // Usage: vec4 col = biplanar(tex, worldPos * scale, worldNormal, 8.0); ``` **Why biplanar over triplanar**: Saves one texture fetch (bandwidth-bound advantage), with k=8 visually equivalent to triplanar. The `dFdx/dFdy` gradient propagation prevents mipmap seams at axis-switching boundaries. ### 2. Texture Repetition Avoidance Three approaches to eliminate visible tiling patterns: #### Method A: Per-Tile Random Offset (4 fetches) ```glsl vec4 textureNoTile(sampler2D sam, vec2 uv) { vec2 iuv = floor(uv); vec2 fuv = fract(uv); // Generate 4 random offsets for the 4 surrounding tiles vec4 ofa = hash42(iuv + vec2(0, 0)); vec4 ofb = hash42(iuv + vec2(1, 0)); vec4 ofc = hash42(iuv + vec2(0, 1)); vec4 ofd = hash42(iuv + vec2(1, 1)); // Transform UVs per tile vec2 uva = uv + ofa.xy; vec2 uvb = uv + ofb.xy; vec2 uvc = uv + ofc.xy; vec2 uvd = uv + ofd.xy; // Blend near borders with smooth weights vec2 b = smoothstep(0.25, 0.75, fuv); return mix(mix(texture(sam, uva), texture(sam, uvb), b.x), mix(texture(sam, uvc), texture(sam, uvd), b.x), b.y); } ``` #### Method B: Virtual Pattern (2 fetches, cheapest) ```glsl vec4 textureNoTileCheap(sampler2D sam, vec2 uv) { float k = texture(iChannel1, 0.005 * uv).x; // low-freq variation index float index = k * 8.0; float i = floor(index); float f = fract(index); // Two offset lookups based on index vec2 offa = sin(vec2(3.0, 7.0) * (i + 0.0)); vec2 offb = sin(vec2(3.0, 7.0) * (i + 1.0)); return mix(texture(sam, uv + offa), texture(sam, uv + offb), smoothstep(0.2, 0.8, f)); } ``` ### 3. Ray Differential Texture Filtering For ray-marched scenes, compute proper mip levels using ray differentials: ```glsl // After finding hit point pos with normal nor: // 1. Compute neighbor pixel ray directions vec3 rdx = normalize(rd + dFdx(rd)); // x-neighbor ray vec3 rdy = normalize(rd + dFdy(rd)); // y-neighbor ray // 2. Intersect neighbors with tangent plane at hit point float dt_dx = -dot(pos - ro, nor) / dot(rdx, nor); float dt_dy = -dot(pos - ro, nor) / dot(rdy, nor); vec3 posDx = ro + rdx * dt_dx; vec3 posDy = ro + rdy * dt_dy; // 3. World-space position derivatives = pixel footprint vec3 dposdx = posDx - pos; vec3 dposdy = posDy - pos; // 4. Transform to texture derivatives and use textureGrad // For simple planar mapping (e.g. ground plane): vec2 duvdx = dposdx.xz * textureScale; vec2 duvdy = dposdy.xz * textureScale; vec4 color = textureGrad(tex, pos.xz * textureScale, duvdx, duvdy); ``` This provides correct mip-level selection for procedural and sampled textures on ray-marched surfaces, eliminating shimmer and aliasing at distance. → For deeper details, see [reference/texture-mapping-advanced.md](../reference/texture-mapping-advanced.md)