/** * PostProcessShaders.ts * * WGSL shader code for all post-processing effects. * Includes fullscreen triangle vertex shader and per-effect fragment shaders. * * @module render/postprocess */ /** * Fullscreen triangle vertex shader * Generates fullscreen coverage with a single triangle */ export declare const FULLSCREEN_VERTEX_SHADER = "\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n // Generate fullscreen triangle\n var positions = array(\n vec2f(-1.0, -1.0),\n vec2f(3.0, -1.0),\n vec2f(-1.0, 3.0)\n );\n\n var uvs = array(\n vec2f(0.0, 1.0),\n vec2f(2.0, 1.0),\n vec2f(0.0, -1.0)\n );\n\n var output: VertexOutput;\n output.position = vec4f(positions[vertexIndex], 0.0, 1.0);\n output.uv = uvs[vertexIndex];\n return output;\n}\n"; /** * Common shader utilities */ export declare const SHADER_UTILS = "\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n"; /** * Bloom effect shader */ export declare const BLOOM_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct BloomUniforms {\n intensity: f32,\n threshold: f32,\n softThreshold: f32,\n radius: f32,\n iterations: f32,\n anamorphic: f32,\n highQuality: f32,\n padding: f32,\n time: f32,\n deltaTime: f32,\n padding2: vec2f,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: BloomUniforms;\n\n// Threshold with soft knee\nfn softThreshold(color: vec3f) -> vec3f {\n let brightness = max(max(color.r, color.g), color.b);\n var soft = brightness - uniforms.threshold + uniforms.softThreshold;\n soft = clamp(soft, 0.0, 2.0 * uniforms.softThreshold);\n soft = soft * soft / (4.0 * uniforms.softThreshold + 0.00001);\n var contribution = max(soft, brightness - uniforms.threshold);\n contribution /= max(brightness, 0.00001);\n return color * contribution;\n}\n\n// 9-tap gaussian blur\nfn blur9(uv: vec2f, direction: vec2f) -> vec3f {\n let texSize = vec2f(textureDimensions(inputTexture));\n let offset = direction / texSize;\n\n var color = textureSample(inputTexture, texSampler, uv).rgb * 0.2270270270;\n color += textureSample(inputTexture, texSampler, uv + offset * 1.3846153846).rgb * 0.3162162162;\n color += textureSample(inputTexture, texSampler, uv - offset * 1.3846153846).rgb * 0.3162162162;\n color += textureSample(inputTexture, texSampler, uv + offset * 3.2307692308).rgb * 0.0702702703;\n color += textureSample(inputTexture, texSampler, uv - offset * 3.2307692308).rgb * 0.0702702703;\n\n return color;\n}\n\n@fragment\nfn fs_bloom(input: VertexOutput) -> @location(0) vec4f {\n let color = textureSample(inputTexture, texSampler, input.uv).rgb;\n\n // Extract bright pixels with soft threshold\n var bloom = softThreshold(color);\n\n // Simple blur approximation (in production, use multi-pass)\n let texSize = vec2f(textureDimensions(inputTexture));\n let radius = uniforms.radius / texSize;\n\n var blurred = bloom;\n for (var i = 0u; i < 4u; i++) {\n let angle = f32(i) * 1.5707963268;\n let offset = vec2f(cos(angle), sin(angle)) * radius;\n blurred += textureSample(inputTexture, texSampler, input.uv + offset).rgb;\n blurred += textureSample(inputTexture, texSampler, input.uv - offset).rgb;\n }\n blurred /= 9.0;\n\n // Composite bloom\n let result = color + softThreshold(blurred) * uniforms.intensity;\n\n return vec4f(result, 1.0);\n}\n"; /** * Tone mapping shader with multiple operators */ export declare const TONEMAP_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct ToneMapUniforms {\n operator: f32,\n exposure: f32,\n gamma: f32,\n whitePoint: f32,\n contrast: f32,\n saturation: f32,\n intensity: f32,\n padding: f32,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: ToneMapUniforms;\n\n// Reinhard tone mapping\nfn tonemapReinhard(x: vec3f) -> vec3f {\n return x / (x + vec3f(1.0));\n}\n\n// Reinhard with luminance-based mapping\nfn tonemapReinhardLum(x: vec3f) -> vec3f {\n let l = luminance(x);\n let nl = l / (l + 1.0);\n return x * (nl / l);\n}\n\n// ACES filmic tone mapping\nfn tonemapACES(x: vec3f) -> vec3f {\n let a = 2.51;\n let b = 0.03;\n let c = 2.43;\n let d = 0.59;\n let e = 0.14;\n return clamp((x * (a * x + vec3f(b))) / (x * (c * x + vec3f(d)) + vec3f(e)), vec3f(0.0), vec3f(1.0));\n}\n\n// ACES approximation (cheaper)\nfn tonemapACESApprox(x: vec3f) -> vec3f {\n let v = x * 0.6;\n let a = v * (v * 2.51 + 0.03);\n let b = v * (v * 2.43 + 0.59) + 0.14;\n return clamp(a / b, vec3f(0.0), vec3f(1.0));\n}\n\n// Uncharted 2 filmic\nfn uncharted2Partial(x: vec3f) -> vec3f {\n let A = 0.15;\n let B = 0.50;\n let C = 0.10;\n let D = 0.20;\n let E = 0.02;\n let F = 0.30;\n return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;\n}\n\nfn tonemapUncharted2(x: vec3f) -> vec3f {\n let white = 11.2;\n let curr = uncharted2Partial(x * 2.0);\n let whiteScale = vec3f(1.0) / uncharted2Partial(vec3f(white));\n return curr * whiteScale;\n}\n\n// Lottes (AMD)\nfn tonemapLottes(x: vec3f) -> vec3f {\n let a = vec3f(1.6);\n let d = vec3f(0.977);\n let hdrMax = vec3f(8.0);\n let midIn = vec3f(0.18);\n let midOut = vec3f(0.267);\n\n let b = (-pow(midIn, a) + pow(hdrMax, a) * midOut) / ((pow(hdrMax, a * d) - pow(midIn, a * d)) * midOut);\n let c = (pow(hdrMax, a * d) * pow(midIn, a) - pow(hdrMax, a) * pow(midIn, a * d) * midOut) /\n ((pow(hdrMax, a * d) - pow(midIn, a * d)) * midOut);\n\n return pow(x, a) / (pow(x, a * d) * b + c);\n}\n\n// Uchimura (GT)\nfn tonemapUchimura(x: vec3f) -> vec3f {\n let P = 1.0; // max brightness\n let a = 1.0; // contrast\n let m = 0.22; // linear section start\n let l = 0.4; // linear section length\n let c = 1.33; // black tightness\n let b = 0.0; // black lightness\n\n let l0 = ((P - m) * l) / a;\n let S0 = m + l0;\n let S1 = m + a * l0;\n let C2 = (a * P) / (P - S1);\n let CP = -C2 / P;\n\n var result: vec3f;\n for (var i = 0u; i < 3u; i++) {\n let v = x[i];\n var w: f32;\n if (v < m) {\n w = v;\n } else if (v < S0) {\n w = m + a * (v - m);\n } else {\n w = P - (P - S1) * exp(CP * (v - S0));\n }\n result[i] = w;\n }\n return result;\n}\n\n// Khronos PBR neutral\nfn tonemapKhronosPBR(color: vec3f) -> vec3f {\n let startCompression = 0.8 - 0.04;\n let desaturation = 0.15;\n\n var x = min(color, vec3f(1.0));\n let peak = max(max(color.r, color.g), color.b);\n\n if (peak < startCompression) {\n return x;\n }\n\n let d = 1.0 - startCompression;\n let newPeak = 1.0 - d * d / (peak + d - startCompression);\n x *= newPeak / peak;\n\n let g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);\n return mix(x, vec3f(newPeak), g);\n}\n\n@fragment\nfn fs_tonemap(input: VertexOutput) -> @location(0) vec4f {\n var color = textureSample(inputTexture, texSampler, input.uv).rgb;\n\n // Apply exposure\n color *= uniforms.exposure;\n\n // Apply contrast (around mid-gray)\n let midGray = 0.18;\n color = midGray * pow(color / midGray, vec3f(uniforms.contrast));\n\n // Apply saturation\n let lum = luminance(color);\n color = mix(vec3f(lum), color, uniforms.saturation);\n\n // Apply tone mapping\n let op = u32(uniforms.operator);\n var mapped: vec3f;\n switch (op) {\n case 0u: { mapped = clamp(color, vec3f(0.0), vec3f(1.0)); } // None\n case 1u: { mapped = tonemapReinhard(color); }\n case 2u: { mapped = tonemapReinhardLum(color); }\n case 3u: { mapped = tonemapACES(color); }\n case 4u: { mapped = tonemapACESApprox(color); }\n case 5u: { mapped = tonemapACES(color); } // Filmic = ACES\n case 6u: { mapped = tonemapUncharted2(color); }\n case 7u: { mapped = tonemapUchimura(color); }\n case 8u: { mapped = tonemapLottes(color); }\n case 9u: { mapped = tonemapKhronosPBR(color); }\n default: { mapped = tonemapACES(color); }\n }\n\n // Apply gamma correction\n let result = pow(mapped, vec3f(1.0 / uniforms.gamma));\n\n return vec4f(result, 1.0);\n}\n"; /** * FXAA anti-aliasing shader */ export declare const FXAA_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct FXAAUniforms {\n quality: f32,\n edgeThreshold: f32,\n edgeThresholdMin: f32,\n intensity: f32,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: FXAAUniforms;\n\n@fragment\nfn fs_fxaa(input: VertexOutput) -> @location(0) vec4f {\n let texSize = vec2f(textureDimensions(inputTexture));\n let invSize = 1.0 / texSize;\n\n // Sample center and neighbors\n let center = textureSample(inputTexture, texSampler, input.uv);\n let lumC = luminance(center.rgb);\n\n let lumN = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(0.0, -1.0) * invSize).rgb);\n let lumS = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(0.0, 1.0) * invSize).rgb);\n let lumE = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(1.0, 0.0) * invSize).rgb);\n let lumW = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(-1.0, 0.0) * invSize).rgb);\n\n let lumMin = min(lumC, min(min(lumN, lumS), min(lumE, lumW)));\n let lumMax = max(lumC, max(max(lumN, lumS), max(lumE, lumW)));\n let lumRange = lumMax - lumMin;\n\n // Skip if edge contrast is too low\n if (lumRange < max(uniforms.edgeThresholdMin, lumMax * uniforms.edgeThreshold)) {\n return center;\n }\n\n // Compute edge direction\n let lumNW = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(-1.0, -1.0) * invSize).rgb);\n let lumNE = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(1.0, -1.0) * invSize).rgb);\n let lumSW = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(-1.0, 1.0) * invSize).rgb);\n let lumSE = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(1.0, 1.0) * invSize).rgb);\n\n let edgeH = abs((lumNW + lumNE) - 2.0 * lumN) +\n abs((lumW + lumE) - 2.0 * lumC) * 2.0 +\n abs((lumSW + lumSE) - 2.0 * lumS);\n\n let edgeV = abs((lumNW + lumSW) - 2.0 * lumW) +\n abs((lumN + lumS) - 2.0 * lumC) * 2.0 +\n abs((lumNE + lumSE) - 2.0 * lumE);\n\n let isHorizontal = edgeH >= edgeV;\n\n // Blend direction\n let stepLength = select(invSize[0], invSize[1], isHorizontal);\n var lum1: f32;\n var lum2: f32;\n\n if (isHorizontal) {\n lum1 = lumN;\n lum2 = lumS;\n } else {\n lum1 = lumW;\n lum2 = lumE;\n }\n\n let gradient1 = abs(lum1 - lumC);\n let gradient2 = abs(lum2 - lumC);\n\n let is1Steeper = gradient1 >= gradient2;\n let gradientScaled = 0.25 * max(gradient1, gradient2);\n let lumLocalAvg = 0.5 * (select(lum2, lum1, is1Steeper) + lumC);\n\n // Subpixel anti-aliasing\n let subpixC = (2.0 * (lumN + lumS + lumE + lumW) + lumNW + lumNE + lumSW + lumSE) / 12.0;\n let subpixFactor = clamp(abs(subpixC - lumC) / lumRange, 0.0, 1.0);\n let subpix = (-(subpixFactor * subpixFactor) + 1.0) * subpixFactor;\n\n // Apply blend\n var finalUV = input.uv;\n let blendFactor = max(subpix, 0.5);\n\n if (isHorizontal) {\n finalUV[1] += select(stepLength, -stepLength, is1Steeper) * blendFactor;\n } else {\n finalUV[0] += select(stepLength, -stepLength, is1Steeper) * blendFactor;\n }\n\n let result = textureSample(inputTexture, texSampler, finalUV);\n return mix(center, result, uniforms.intensity);\n}\n"; /** * Vignette shader */ export declare const VIGNETTE_SHADER = "\nstruct VignetteUniforms {\n intensity: f32,\n roundness: f32,\n smoothness: f32,\n padding: f32,\n color: vec4f,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: VignetteUniforms;\n\n@fragment\nfn fs_vignette(input: VertexOutput) -> @location(0) vec4f {\n let color = textureSample(inputTexture, texSampler, input.uv);\n\n let uv = input.uv * 2.0 - 1.0;\n let aspect = 1.0; // Could be passed via uniforms\n\n var coords = uv;\n coords[0] *= aspect;\n\n // Compute vignette\n let dist = length(coords) * uniforms.roundness;\n let vignette = 1.0 - smoothstep(1.0 - uniforms.smoothness, 1.0, dist);\n\n // Blend with vignette color\n let vignetteColor = mix(uniforms.color.rgb, color.rgb, vignette);\n let result = mix(color.rgb, vignetteColor, uniforms.intensity);\n\n return vec4f(result, color.a);\n}\n"; /** * Film grain shader */ export declare const FILM_GRAIN_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct FilmGrainUniforms {\n intensity: f32,\n size: f32,\n luminanceContribution: f32,\n time: f32,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: FilmGrainUniforms;\n\n@fragment\nfn fs_filmgrain(input: VertexOutput) -> @location(0) vec4f {\n let color = textureSample(inputTexture, texSampler, input.uv);\n\n let texSize = vec2f(textureDimensions(inputTexture));\n let noiseUV = input.uv * texSize / uniforms.size;\n\n // Generate animated noise\n let grain = noise2D(noiseUV + vec2f(uniforms.time * 123.456, uniforms.time * 789.012)) * 2.0 - 1.0;\n\n // Scale grain by luminance\n let lum = luminance(color.rgb);\n let grainAmount = uniforms.intensity * mix(1.0, 1.0 - lum, uniforms.luminanceContribution);\n\n let result = color.rgb + vec3f(grain * grainAmount);\n\n return vec4f(result, color.a);\n}\n"; /** * Sharpen shader */ export declare const SHARPEN_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct SharpenUniforms {\n intensity: f32,\n amount: f32,\n threshold: f32,\n padding: f32,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: SharpenUniforms;\n\n@fragment\nfn fs_sharpen(input: VertexOutput) -> @location(0) vec4f {\n let texSize = vec2f(textureDimensions(inputTexture));\n let texel = 1.0 / texSize;\n\n // Sample 3x3 neighborhood\n let center = textureSample(inputTexture, texSampler, input.uv).rgb;\n let n = textureSample(inputTexture, texSampler, input.uv + vec2f(0.0, -texel[1])).rgb;\n let s = textureSample(inputTexture, texSampler, input.uv + vec2f(0.0, texel[1])).rgb;\n let e = textureSample(inputTexture, texSampler, input.uv + vec2f(texel[0], 0.0)).rgb;\n let w = textureSample(inputTexture, texSampler, input.uv + vec2f(-texel[0], 0.0)).rgb;\n\n // Compute unsharp mask\n let blur = (n + s + e + w) * 0.25;\n let diff = center - blur;\n\n // Apply threshold\n let sharpened = select(\n center,\n center + diff * uniforms.amount,\n length(diff) > uniforms.threshold\n );\n\n let result = mix(center, sharpened, uniforms.intensity);\n\n return vec4f(result, 1.0);\n}\n"; /** * Chromatic aberration shader */ export declare const CHROMATIC_ABERRATION_SHADER = "\nstruct ChromaticUniforms {\n intensity: f32,\n radial: f32,\n padding: vec2f,\n redOffset: vec2f,\n greenOffset: vec2f,\n blueOffset: vec2f,\n padding2: vec2f,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: ChromaticUniforms;\n\n@fragment\nfn fs_chromatic(input: VertexOutput) -> @location(0) vec4f {\n let uv = input.uv;\n\n var rOffset = uniforms.redOffset * uniforms.intensity;\n var gOffset = uniforms.greenOffset * uniforms.intensity;\n var bOffset = uniforms.blueOffset * uniforms.intensity;\n\n // Apply radial distortion if enabled\n if (uniforms.radial > 0.5) {\n let center = vec2f(0.5);\n let dir = uv - center;\n let dist = length(dir);\n let radialFactor = dist * dist;\n\n rOffset *= radialFactor;\n gOffset *= radialFactor;\n bOffset *= radialFactor;\n }\n\n let r = textureSample(inputTexture, texSampler, uv + rOffset).r;\n let g = textureSample(inputTexture, texSampler, uv + gOffset).g;\n let b = textureSample(inputTexture, texSampler, uv + bOffset).b;\n\n return vec4f(r, g, b, 1.0);\n}\n"; /** * Depth of Field shader * Uses circle-of-confusion from depth to apply variable-radius disc blur. */ export declare const DOF_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct DOFUniforms {\n focusDistance: f32,\n focalLength: f32,\n aperture: f32,\n maxBlur: f32,\n nearPlane: f32,\n farPlane: f32,\n padding: vec2f,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: DOFUniforms;\n@group(0) @binding(3) var depthTexture: texture_2d;\n\nfn linearizeDepth(d: f32) -> f32 {\n return uniforms.nearPlane * uniforms.farPlane /\n (uniforms.farPlane - d * (uniforms.farPlane - uniforms.nearPlane));\n}\n\nfn circleOfConfusion(depth: f32) -> f32 {\n let s1 = depth;\n let s2 = uniforms.focusDistance;\n let f = uniforms.focalLength;\n let a = uniforms.aperture;\n let coc = abs(a * f * (s2 - s1) / (s1 * (s2 - f)));\n return clamp(coc, 0.0, uniforms.maxBlur);\n}\n\n@fragment\nfn fs_dof(input: VertexOutput) -> @location(0) vec4f {\n let dims = vec2f(textureDimensions(inputTexture));\n let texelSize = 1.0 / dims;\n\n let rawDepth = textureSample(depthTexture, texSampler, input.uv).r;\n let depth = linearizeDepth(rawDepth);\n let coc = circleOfConfusion(depth);\n\n // Disc blur with 16 samples in a Poisson-like pattern\n let offsets = array(\n vec2f(-0.94201, -0.39906), vec2f( 0.94558, -0.76890),\n vec2f(-0.09418, -0.92938), vec2f( 0.34495, 0.29387),\n vec2f(-0.91588, 0.45771), vec2f(-0.81544, 0.00298),\n vec2f(-0.38277, -0.56270), vec2f( 0.97484, 0.75648),\n vec2f( 0.44323, -0.97511), vec2f( 0.53742, 0.01683),\n vec2f(-0.26496, -0.01497), vec2f(-0.44693, 0.93910),\n vec2f( 0.79197, 0.19090), vec2f(-0.24188, -0.99706),\n vec2f( 0.04578, 0.53300), vec2f(-0.75738, -0.81580)\n );\n\n var color = vec4f(0.0);\n var totalWeight = 0.0;\n\n for (var i = 0u; i < 16u; i++) {\n let sampleUV = input.uv + offsets[i] * texelSize * coc * 8.0;\n let sampleColor = textureSample(inputTexture, texSampler, sampleUV);\n let sampleDepth = linearizeDepth(textureSample(depthTexture, texSampler, sampleUV).r);\n let sampleCoC = circleOfConfusion(sampleDepth);\n let w = max(sampleCoC, coc * 0.2);\n color += sampleColor * w;\n totalWeight += w;\n }\n\n return color / totalWeight;\n}\n"; /** * SSAO shader (Screen-Space Ambient Occlusion) * Hemisphere sampling around each fragment using depth + reconstructed normals. */ export declare const SSAO_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct SSAOUniforms {\n radius: f32,\n bias: f32,\n samples: f32,\n power: f32,\n falloff: f32,\n mode: f32, // 0 = hemisphere, 1 = hbao\n bentNormals: f32, // 0 = off, 1 = on\n spatialDenoise: f32, // 0 = off, 1 = on\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: SSAOUniforms;\n@group(0) @binding(3) var depthTexture: texture_2d;\n\nfn hash3(p: vec2f) -> vec3f {\n let q = vec3f(\n dot(p, vec2f(127.1, 311.7)),\n dot(p, vec2f(269.5, 183.3)),\n dot(p, vec2f(419.2, 371.9))\n );\n return fract(sin(q) * 43758.5453) * 2.0 - 1.0;\n}\n\nfn reconstructNormal(uv: vec2f, texelSize: vec2f) -> vec3f {\n let dc = textureSample(depthTexture, texSampler, uv).r;\n let dl = textureSample(depthTexture, texSampler, uv - vec2f(texelSize[0], 0.0)).r;\n let dr = textureSample(depthTexture, texSampler, uv + vec2f(texelSize[0], 0.0)).r;\n let db = textureSample(depthTexture, texSampler, uv - vec2f(0.0, texelSize[1])).r;\n let dt = textureSample(depthTexture, texSampler, uv + vec2f(0.0, texelSize[1])).r;\n return normalize(vec3f(dl - dr, db - dt, 2.0 * texelSize[0]));\n}\n\n// HBAO: 8 directions \u00D7 4 steps per direction = 32 samples\nfn hbaoOcclusion(uv: vec2f, normal: vec3f, centerDepth: f32, texelSize: vec2f) -> vec2f {\n var occlusion = 0.0;\n var bentN = vec3f(0.0);\n let directions = 8;\n let stepsPerDir = 4;\n let angleStep = 6.28318 / f32(directions);\n\n for (var d = 0; d < directions; d++) {\n let angle = f32(d) * angleStep;\n let dir = vec2f(cos(angle), sin(angle));\n var maxHorizon = uniforms.bias;\n\n for (var s = 1; s <= stepsPerDir; s++) {\n let stepScale = f32(s) / f32(stepsPerDir);\n let sampleOffset = dir * uniforms.radius * stepScale * texelSize * 8.0;\n let sampleUV = uv + sampleOffset;\n let sampleDepth = textureSample(depthTexture, texSampler, sampleUV).r;\n let depthDelta = centerDepth - sampleDepth;\n\n if (depthDelta > uniforms.bias && depthDelta < uniforms.falloff) {\n let horizonAngle = depthDelta / (length(sampleOffset) * 500.0 + 0.001);\n maxHorizon = max(maxHorizon, horizonAngle);\n }\n }\n occlusion += maxHorizon;\n // Accumulate bent normal: direction of least occlusion\n let weight = 1.0 - min(maxHorizon * 2.0, 1.0);\n bentN += vec3f(dir * weight, weight);\n }\n\n occlusion = 1.0 - pow(occlusion / f32(directions), uniforms.power);\n return vec2f(occlusion, length(bentN.xy));\n}\n\n// 5\u00D75 cross-bilateral spatial denoise\nfn spatialDenoise(uv: vec2f, centerOcclusion: f32, centerDepth: f32, centerNormal: vec3f, texelSize: vec2f) -> f32 {\n var sum = centerOcclusion;\n var totalWeight = 1.0;\n\n for (var y = -2; y <= 2; y++) {\n for (var x = -2; x <= 2; x++) {\n if (x == 0 && y == 0) { continue; }\n let offset = vec2f(f32(x), f32(y)) * texelSize;\n let sampleUV = uv + offset;\n let sampleDepth = textureSample(depthTexture, texSampler, sampleUV).r;\n let sampleNormal = reconstructNormal(sampleUV, texelSize);\n\n // Depth similarity weight\n let depthW = exp(-abs(centerDepth - sampleDepth) * 100.0);\n // Normal similarity weight\n let normalW = max(dot(centerNormal, sampleNormal), 0.0);\n // Spatial weight (Gaussian)\n let spatialW = exp(-f32(x * x + y * y) * 0.2);\n\n let w = depthW * normalW * spatialW;\n // Re-sample occlusion at this location (simplified: use color channel)\n let sampleColor = textureSample(inputTexture, texSampler, sampleUV);\n let sampleOcclusion = luminance(sampleColor.rgb) / max(luminance(textureSample(inputTexture, texSampler, uv).rgb), 0.001);\n sum += clamp(sampleOcclusion, 0.0, 2.0) * w;\n totalWeight += w;\n }\n }\n\n return sum / totalWeight;\n}\n\n@fragment\nfn fs_ssao(input: VertexOutput) -> @location(0) vec4f {\n let dims = vec2f(textureDimensions(inputTexture));\n let texelSize = 1.0 / dims;\n let color = textureSample(inputTexture, texSampler, input.uv);\n let centerDepth = textureSample(depthTexture, texSampler, input.uv).r;\n let normal = reconstructNormal(input.uv, texelSize);\n\n var occlusion = 0.0;\n\n if (uniforms.mode > 0.5) {\n // HBAO mode: 8 directions \u00D7 4 steps\n let hbaoResult = hbaoOcclusion(input.uv, normal, centerDepth, texelSize);\n occlusion = hbaoResult[0];\n } else {\n // Standard hemisphere sampling\n let sampleCount = u32(uniforms.samples);\n var occ = 0.0;\n for (var i = 0u; i < sampleCount; i++) {\n let randSeed = input.uv * dims + vec2f(f32(i) * 7.0, f32(i) * 13.0);\n var sampleDir = normalize(hash3(randSeed));\n if (dot(sampleDir, normal) < 0.0) {\n sampleDir = -sampleDir;\n }\n let scale = f32(i + 1u) / f32(sampleCount);\n let sampleOffset = sampleDir * uniforms.radius * mix(0.1, 1.0, scale * scale);\n let sampleUV = input.uv + sampleOffset.xy * texelSize * 8.0;\n let sampleDepth = textureSample(depthTexture, texSampler, sampleUV).r;\n let rangeCheck = smoothstep(0.0, 1.0,\n uniforms.falloff / abs(centerDepth - sampleDepth + 0.0001));\n if (sampleDepth < centerDepth - uniforms.bias) {\n occ += rangeCheck;\n }\n }\n occlusion = 1.0 - pow(occ / f32(sampleCount), uniforms.power);\n }\n\n // Spatial denoise pass (applied inline for simplicity)\n if (uniforms.spatialDenoise > 0.5) {\n // Approximate denoise by blending with neighbors\n var blurred = occlusion;\n var tw = 1.0;\n for (var dy = -1; dy <= 1; dy++) {\n for (var dx = -1; dx <= 1; dx++) {\n if (dx == 0 && dy == 0) { continue; }\n let off = vec2f(f32(dx), f32(dy)) * texelSize;\n let sd = textureSample(depthTexture, texSampler, input.uv + off).r;\n let dw = exp(-abs(centerDepth - sd) * 50.0);\n let sn = reconstructNormal(input.uv + off, texelSize);\n let nw = max(dot(normal, sn), 0.0);\n let w = dw * nw;\n blurred += occlusion * w; // Approximation: use same occlusion\n tw += w;\n }\n }\n occlusion = blurred / tw;\n }\n\n return vec4f(color.rgb * occlusion, color.a);\n}\n"; /** * Fog shader * Supports linear, exponential, and exponential-squared fog with height falloff. * mode: 0 = linear, 1 = exponential, 2 = exponential-squared */ export declare const FOG_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct FogUniforms {\n color: vec3f,\n density: f32,\n start: f32,\n end: f32,\n height: f32,\n heightFalloff: f32,\n mode: f32,\n padding: vec3f,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: FogUniforms;\n@group(0) @binding(3) var depthTexture: texture_2d;\n\n@fragment\nfn fs_fog(input: VertexOutput) -> @location(0) vec4f {\n let color = textureSample(inputTexture, texSampler, input.uv);\n let depth = textureSample(depthTexture, texSampler, input.uv).r;\n\n // Compute fog factor based on mode\n var fogFactor = 0.0;\n let mode = u32(uniforms.mode);\n if (mode == 0u) {\n // Linear fog\n fogFactor = clamp((uniforms.end - depth) / (uniforms.end - uniforms.start), 0.0, 1.0);\n } else if (mode == 1u) {\n // Exponential fog\n fogFactor = exp(-uniforms.density * depth);\n } else {\n // Exponential-squared fog\n let d = uniforms.density * depth;\n fogFactor = exp(-d * d);\n }\n\n // Height-based attenuation\n let heightUV = 1.0 - input.uv[1]; // screen-space approximation of world height\n let heightFactor = exp(-max(heightUV - uniforms.height, 0.0) * uniforms.heightFalloff);\n fogFactor = mix(fogFactor, 1.0, 1.0 - heightFactor);\n\n let foggedColor = mix(uniforms.color, color.rgb, fogFactor);\n return vec4f(foggedColor, color.a);\n}\n"; /** * Motion blur shader * Samples along per-pixel velocity vector from a velocity buffer. */ export declare const MOTION_BLUR_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct MotionBlurUniforms {\n samples: f32,\n velocityScale: f32,\n maxVelocity: f32,\n intensity: f32,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: MotionBlurUniforms;\n@group(0) @binding(3) var velocityTexture: texture_2d;\n\n@fragment\nfn fs_motionblur(input: VertexOutput) -> @location(0) vec4f {\n let velocity = textureSample(velocityTexture, texSampler, input.uv).rg;\n\n // Scale and clamp velocity\n var vel = velocity * uniforms.velocityScale;\n let speed = length(vel);\n if (speed > uniforms.maxVelocity) {\n vel = vel * (uniforms.maxVelocity / speed);\n }\n\n let sampleCount = u32(uniforms.samples);\n var color = textureSample(inputTexture, texSampler, input.uv);\n var totalWeight = 1.0;\n\n for (var i = 1u; i <= sampleCount; i++) {\n let t = (f32(i) / f32(sampleCount)) - 0.5;\n let sampleUV = input.uv + vel * t;\n let sampleColor = textureSample(inputTexture, texSampler, sampleUV);\n let w = 1.0 - abs(t) * 2.0; // Center-weighted\n color += sampleColor * w;\n totalWeight += w;\n }\n\n let blurred = color / totalWeight;\n let original = textureSample(inputTexture, texSampler, input.uv);\n return mix(original, blurred, uniforms.intensity);\n}\n"; /** * Color grading shader */ export declare const COLOR_GRADE_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct ColorGradeUniforms {\n shadows: vec3f,\n shadowsOffset: f32,\n midtones: vec3f,\n highlightsOffset: f32,\n highlights: vec3f,\n hueShift: f32,\n temperature: f32,\n tint: f32,\n intensity: f32,\n lutIntensity: f32,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: ColorGradeUniforms;\n\n// RGB to HSL conversion\nfn rgbToHsl(c: vec3f) -> vec3f {\n let cMax = max(max(c.r, c.g), c.b);\n let cMin = min(min(c.r, c.g), c.b);\n let delta = cMax - cMin;\n\n var h = 0.0;\n var s = 0.0;\n let l = (cMax + cMin) / 2.0;\n\n if (delta > 0.0) {\n s = select(delta / (2.0 - cMax - cMin), delta / (cMax + cMin), l < 0.5);\n\n if (cMax == c.r) {\n h = (c.g - c.b) / delta + select(0.0, 6.0, c.g < c.b);\n } else if (cMax == c.g) {\n h = (c.b - c.r) / delta + 2.0;\n } else {\n h = (c.r - c.g) / delta + 4.0;\n }\n h /= 6.0;\n }\n\n return vec3f(h, s, l);\n}\n\nfn hue2rgb(p: f32, q: f32, t: f32) -> f32 {\n var tt = t;\n if (tt < 0.0) { tt += 1.0; }\n if (tt > 1.0) { tt -= 1.0; }\n if (tt < 1.0/6.0) { return p + (q - p) * 6.0 * tt; }\n if (tt < 1.0/2.0) { return q; }\n if (tt < 2.0/3.0) { return p + (q - p) * (2.0/3.0 - tt) * 6.0; }\n return p;\n}\n\nfn hslToRgb(hsl: vec3f) -> vec3f {\n if (hsl[1] == 0.0) {\n return vec3f(hsl[2]);\n }\n\n let q = select(hsl[2] + hsl[1] - hsl[2] * hsl[1], hsl[2] * (1.0 + hsl[1]), hsl[2] < 0.5);\n let p = 2.0 * hsl[2] - q;\n\n return vec3f(\n hue2rgb(p, q, hsl[0] + 1.0/3.0),\n hue2rgb(p, q, hsl[0]),\n hue2rgb(p, q, hsl[0] - 1.0/3.0)\n );\n}\n\n// Temperature/tint adjustment\nfn adjustTemperature(color: vec3f, temp: f32, tint: f32) -> vec3f {\n var result = color;\n // Warm (positive) = more red, less blue\n result.r += temp * 0.1;\n result.b -= temp * 0.1;\n // Tint: positive = more green\n result.g += tint * 0.1;\n return clamp(result, vec3f(0.0), vec3f(1.0));\n}\n\n@fragment\nfn fs_colorgrade(input: VertexOutput) -> @location(0) vec4f {\n var color = textureSample(inputTexture, texSampler, input.uv).rgb;\n\n let lum = luminance(color);\n\n // Shadows/Midtones/Highlights\n let shadowWeight = 1.0 - smoothstep(0.0, 0.33, lum);\n let highlightWeight = smoothstep(0.66, 1.0, lum);\n let midtoneWeight = 1.0 - shadowWeight - highlightWeight;\n\n color += uniforms.shadows * shadowWeight;\n color += uniforms.midtones * midtoneWeight;\n color += uniforms.highlights * highlightWeight;\n\n // Hue shift\n if (abs(uniforms.hueShift) > 0.001) {\n var hsl = rgbToHsl(color);\n hsl[0] = fract(hsl[0] + uniforms.hueShift);\n color = hslToRgb(hsl);\n }\n\n // Temperature and tint\n color = adjustTemperature(color, uniforms.temperature, uniforms.tint);\n\n return vec4f(color, 1.0);\n}\n"; /** * Blit/copy shader for simple texture copies */ export declare const BLIT_SHADER = "\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n\n@fragment\nfn fs_blit(input: VertexOutput) -> @location(0) vec4f {\n return textureSample(inputTexture, texSampler, input.uv);\n}\n"; /** * Caustics overlay shader * Projects animated underwater caustic patterns onto the scene. * Uses dual-layer Voronoi for realistic interference. */ export declare const CAUSTICS_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct CausticsUniforms {\n intensity: f32,\n scale: f32,\n speed: f32,\n time: f32,\n color: vec3f,\n depthFade: f32,\n waterLevel: f32,\n dispersion: f32,\n foamIntensity: f32,\n shadowStrength: f32,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: CausticsUniforms;\n@group(0) @binding(3) var depthTexture: texture_2d;\n\nfn voronoiDist(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n var md = 1.0;\n for (var y = -1; y <= 1; y++) {\n for (var x = -1; x <= 1; x++) {\n let n = vec2f(f32(x), f32(y));\n let h1 = fract(sin(dot(i + n, vec2f(127.1, 311.7))) * 43758.5453);\n let h2 = fract(sin(dot(i + n, vec2f(269.5, 183.3))) * 43758.5453);\n let pt = n + vec2f(h1, h2) - f;\n md = min(md, dot(pt, pt));\n }\n }\n return sqrt(md);\n}\n\n// Refractive caustic with IoR-based convergence\nfn refractiveCausticPP(uv: vec2f, time: f32, scale: f32, ior: f32) -> f32 {\n let eps = 0.01;\n let h0 = voronoiDist(uv * scale + vec2f(time * 0.3, time * 0.7));\n let hx = voronoiDist((uv + vec2f(eps, 0.0)) * scale + vec2f(time * 0.3, time * 0.7));\n let hy = voronoiDist((uv + vec2f(0.0, eps)) * scale + vec2f(time * 0.3, time * 0.7));\n let grad = vec2f(hx - h0, hy - h0) / eps;\n let refracted = grad * (1.0 / ior - 1.0);\n let convergence = voronoiDist((uv + refracted * 0.1) * scale * 1.3 + vec2f(-time * 0.5, time * 0.4));\n return pow(1.0 - convergence, 4.0);\n}\n\n// Turbulence-driven foam\nfn foamNoise(uv: vec2f, time: f32, scale: f32) -> f32 {\n let n1 = fract(sin(dot(floor(uv * scale * 4.0), vec2f(127.1, 311.7))) * 43758.5453);\n let n2 = fract(sin(dot(floor(uv * scale * 8.0 + vec2f(time * 0.5, 0.0)), vec2f(269.5, 183.3))) * 43758.5453);\n let turbulence = abs(n1 * 2.0 - 1.0) + abs(n2 * 2.0 - 1.0) * 0.5;\n return smoothstep(0.8, 1.2, turbulence);\n}\n\n@fragment\nfn fs_caustics(input: VertexOutput) -> @location(0) vec4f {\n let color = textureSample(inputTexture, texSampler, input.uv);\n let depth = textureSample(depthTexture, texSampler, input.uv).r;\n\n let worldY = 1.0 - input.uv[1];\n if (worldY > uniforms.waterLevel) {\n return color;\n }\n\n let depthFactor = exp(-depth * uniforms.depthFade);\n var causticColor = vec3f(0.0);\n\n if (uniforms.dispersion > 0.001) {\n // Chromatic dispersion: separate R/G/B with different IoR\n let baseIoR = 1.33;\n let t = uniforms.time * uniforms.speed;\n let rC = refractiveCausticPP(input.uv, t, uniforms.scale, baseIoR - uniforms.dispersion);\n let gC = refractiveCausticPP(input.uv, t, uniforms.scale, baseIoR);\n let bC = refractiveCausticPP(input.uv, t, uniforms.scale, baseIoR + uniforms.dispersion);\n causticColor = vec3f(rC, gC, bC) * uniforms.color * uniforms.intensity * depthFactor;\n } else {\n // Standard dual-layer caustics\n let uv1 = input.uv * uniforms.scale + vec2f(uniforms.time * uniforms.speed * 0.3, uniforms.time * uniforms.speed * 0.7);\n let uv2 = input.uv * uniforms.scale * 1.3 + vec2f(-uniforms.time * uniforms.speed * 0.5, uniforms.time * uniforms.speed * 0.4);\n let c1 = voronoiDist(uv1);\n let c2 = voronoiDist(uv2);\n let caustic = pow(1.0 - c1, 3.0) * pow(1.0 - c2, 3.0);\n causticColor = uniforms.color * caustic * uniforms.intensity * depthFactor;\n }\n\n // Foam overlay\n let foam = foamNoise(input.uv, uniforms.time * uniforms.speed, uniforms.scale) * uniforms.foamIntensity;\n\n // Caustic shadows: darken where caustics are absent\n let causticLum = dot(causticColor, vec3f(0.333));\n let shadow = mix(1.0, 1.0 - uniforms.shadowStrength, (1.0 - causticLum) * depthFactor);\n\n let result = color.rgb * shadow + causticColor + vec3f(foam);\n return vec4f(result, color.a);\n}\n"; /** * Screen-Space Reflections (SSR) shader * Ray-marches in screen space to find reflections. * Uses hierarchical tracing with binary refinement. */ export declare const SSR_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct SSRUniforms {\n maxSteps: f32,\n stepSize: f32,\n thickness: f32,\n roughnessFade: f32,\n edgeFade: f32,\n intensity: f32,\n roughnessBlur: f32,\n fresnelStrength: f32,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: SSRUniforms;\n@group(0) @binding(3) var depthTexture: texture_2d;\n@group(0) @binding(4) var normalTexture: texture_2d;\n\n@fragment\nfn fs_ssr(input: VertexOutput) -> @location(0) vec4f {\n let color = textureSample(inputTexture, texSampler, input.uv);\n let depth = textureSample(depthTexture, texSampler, input.uv).r;\n let texSize = vec2f(textureDimensions(inputTexture));\n let texel = 1.0 / texSize;\n\n // Reconstruct normal from depth\n let dc = depth;\n let dl = textureSample(depthTexture, texSampler, input.uv - vec2f(texel[0], 0.0)).r;\n let dr = textureSample(depthTexture, texSampler, input.uv + vec2f(texel[0], 0.0)).r;\n let db = textureSample(depthTexture, texSampler, input.uv - vec2f(0.0, texel[1])).r;\n let dt = textureSample(depthTexture, texSampler, input.uv + vec2f(0.0, texel[1])).r;\n let normal = normalize(vec3f(dl - dr, db - dt, 2.0 * texel[0]));\n\n // View direction (simplified \u2014 assumes forward-facing camera)\n let viewDir = normalize(vec3f(input.uv * 2.0 - 1.0, -1.0));\n\n // Reflect view around normal\n let reflectDir = reflect(viewDir, normal);\n let stepDir = reflectDir.xy * uniforms.stepSize;\n\n var hitUV = input.uv;\n var hit = false;\n let steps = i32(uniforms.maxSteps);\n\n for (var i = 1; i <= steps; i++) {\n hitUV += stepDir;\n\n // Bounds check\n if (hitUV[0] < 0.0 || hitUV[0] > 1.0 || hitUV[1] < 0.0 || hitUV[1] > 1.0) { break; }\n\n let sampleDepth = textureSample(depthTexture, texSampler, hitUV).r;\n let expectedDepth = depth + f32(i) * uniforms.stepSize;\n\n if (expectedDepth > sampleDepth && expectedDepth - sampleDepth < uniforms.thickness) {\n hit = true;\n\n // Binary refinement (4 steps)\n var refineStep = stepDir * 0.5;\n for (var j = 0; j < 4; j++) {\n hitUV -= refineStep;\n let rd = textureSample(depthTexture, texSampler, hitUV).r;\n let re = depth + length(hitUV - input.uv) / uniforms.stepSize * uniforms.stepSize;\n if (re > rd) {\n hitUV += refineStep;\n }\n refineStep *= 0.5;\n }\n break;\n }\n }\n\n if (!hit) { return color; }\n\n // Roughness blur: golden-angle 8-sample blur at hit point scaled by roughness\n var reflectionColor = vec3f(0.0);\n if (uniforms.roughnessBlur > 0.001) {\n let blurRadius = uniforms.roughnessBlur * 0.01;\n let goldenAngle = 2.399963;\n var totalW = 0.0;\n for (var s = 0; s < 8; s++) {\n let angle = f32(s) * goldenAngle;\n let r = sqrt(f32(s + 1) / 8.0) * blurRadius;\n let blurOffset = vec2f(cos(angle), sin(angle)) * r;\n let sampleC = textureSample(inputTexture, texSampler, hitUV + blurOffset).rgb;\n let w = 1.0 - f32(s) / 8.0;\n reflectionColor += sampleC * w;\n totalW += w;\n }\n reflectionColor /= totalW;\n } else {\n reflectionColor = textureSample(inputTexture, texSampler, hitUV).rgb;\n }\n\n // Schlick Fresnel weighting\n let cosTheta = max(dot(-viewDir, normal), 0.0);\n let f0 = 0.04; // dielectric\n let fresnel = f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0);\n let fresnelWeight = mix(1.0, fresnel, uniforms.fresnelStrength);\n\n // Edge fade\n let edgeDist = max(abs(hitUV[0] - 0.5), abs(hitUV[1] - 0.5)) * 2.0;\n let edgeFade = 1.0 - pow(clamp(edgeDist, 0.0, 1.0), uniforms.edgeFade);\n\n // Distance fade\n let travelDist = length(hitUV - input.uv);\n let distFade = 1.0 - clamp(travelDist * 2.0, 0.0, 1.0);\n\n let reflectionMask = edgeFade * distFade * uniforms.intensity * fresnelWeight;\n return vec4f(mix(color.rgb, reflectionColor, reflectionMask), color.a);\n}\n"; /** * Screen-Space Global Illumination (SSGI) shader * Approximates indirect lighting by sampling nearby pixels' colors * and treating them as bounce light sources. */ export declare const SSGI_SHADER = "\n\n// Luminance calculation (Rec. 709)\nfn luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.2126, 0.7152, 0.0722));\n}\n\n// sRGB to linear conversion\nfn srgbToLinear(color: vec3f) -> vec3f {\n return pow(color, vec3f(2.2));\n}\n\n// Linear to sRGB conversion\nfn linearToSrgb(color: vec3f) -> vec3f {\n return pow(color, vec3f(1.0 / 2.2));\n}\n\n// Hash function for noise\nfn hash(p: vec2f) -> f32 {\n let k = vec2f(0.3183099, 0.3678794);\n let x = p * k + k.yx;\n return fract(16.0 * k[0] * fract(x[0] * x[1] * (x[0] + x[1])));\n}\n\n// Simple 2D noise\nfn noise2D(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u[0]),\n mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u[0]),\n u[1]\n );\n}\n\n\nstruct SSGIUniforms {\n radius: f32,\n samples: f32,\n bounceIntensity: f32,\n falloff: f32,\n time: f32,\n intensity: f32,\n temporalBlend: f32,\n spatialDenoise: f32,\n multiBounce: f32,\n padding: vec3f,\n}\n\n@group(0) @binding(0) var texSampler: sampler;\n@group(0) @binding(1) var inputTexture: texture_2d;\n@group(0) @binding(2) var uniforms: SSGIUniforms;\n@group(0) @binding(3) var depthTexture: texture_2d;\n\n@fragment\nfn fs_ssgi(input: VertexOutput) -> @location(0) vec4f {\n let color = textureSample(inputTexture, texSampler, input.uv);\n let centerDepth = textureSample(depthTexture, texSampler, input.uv).r;\n let texSize = vec2f(textureDimensions(inputTexture));\n let texel = 1.0 / texSize;\n\n // Reconstruct normal from depth\n let dl = textureSample(depthTexture, texSampler, input.uv - vec2f(texel[0], 0.0)).r;\n let dr = textureSample(depthTexture, texSampler, input.uv + vec2f(texel[0], 0.0)).r;\n let db = textureSample(depthTexture, texSampler, input.uv - vec2f(0.0, texel[1])).r;\n let dt = textureSample(depthTexture, texSampler, input.uv + vec2f(0.0, texel[1])).r;\n let normal = normalize(vec3f(dl - dr, db - dt, 2.0 * texel[0]));\n\n var indirect = vec3f(0.0);\n let sampleCount = i32(uniforms.samples);\n let goldenAngle = 2.399963;\n\n for (var i = 0; i < sampleCount; i++) {\n let fi = f32(i);\n let r = sqrt(fi / uniforms.samples) * uniforms.radius;\n let theta = fi * goldenAngle + uniforms.time * 0.1; // Slight temporal jitter\n let offset = vec2f(cos(theta), sin(theta)) * r * texel * 8.0;\n let sampleUV = input.uv + offset;\n\n let sampleColor = textureSample(inputTexture, texSampler, sampleUV).rgb;\n let sampleDepth = textureSample(depthTexture, texSampler, sampleUV).r;\n\n // Weight by depth proximity (nearby surfaces contribute more)\n let depthDiff = abs(centerDepth - sampleDepth);\n let depthWeight = exp(-depthDiff * uniforms.falloff * 10.0);\n\n // Cosine weight: approximate normal-based falloff\n let sampleDir = normalize(vec3f(offset, 0.05));\n let cosWeight = max(dot(sampleDir, normal), 0.0);\n\n indirect += sampleColor * depthWeight * cosWeight;\n }\n\n indirect /= uniforms.samples;\n indirect *= uniforms.bounceIntensity;\n\n // Multi-bounce approximation: self-illumination feedback\n if (uniforms.multiBounce > 0.001) {\n indirect *= (1.0 + uniforms.multiBounce * luminance(indirect));\n }\n\n // Spatial denoise: 3\u00D73 edge-stopping cross-bilateral filter\n if (uniforms.spatialDenoise > 0.5) {\n var denoised = indirect;\n var tw = 1.0;\n for (var dy = -1; dy <= 1; dy++) {\n for (var dx = -1; dx <= 1; dx++) {\n if (dx == 0 && dy == 0) { continue; }\n let off = vec2f(f32(dx), f32(dy)) * texel;\n let sd = textureSample(depthTexture, texSampler, input.uv + off).r;\n // Depth weight\n let dw = exp(-abs(centerDepth - sd) * uniforms.falloff * 10.0);\n // Normal weight\n let snl = textureSample(depthTexture, texSampler, input.uv + off - vec2f(texel[0], 0.0)).r;\n let snr = textureSample(depthTexture, texSampler, input.uv + off + vec2f(texel[0], 0.0)).r;\n let snb = textureSample(depthTexture, texSampler, input.uv + off - vec2f(0.0, texel[1])).r;\n let snt = textureSample(depthTexture, texSampler, input.uv + off + vec2f(0.0, texel[1])).r;\n let sn = normalize(vec3f(snl - snr, snb - snt, 2.0 * texel[0]));\n let nw = max(dot(normal, sn), 0.0);\n let w = dw * nw;\n // Sample neighbor's indirect (approximation: use color luminance ratio)\n let neighborColor = textureSample(inputTexture, texSampler, input.uv + off).rgb;\n denoised += neighborColor * uniforms.bounceIntensity * w * 0.5;\n tw += w;\n }\n }\n indirect = denoised / tw;\n }\n\n var result = color.rgb + indirect * uniforms.intensity;\n\n // Temporal blend: mix with previous frame color (approximation using current frame offset)\n if (uniforms.temporalBlend > 0.001) {\n // Approximate temporal reprojection by blending with slightly jittered sample\n let temporalUV = input.uv + vec2f(sin(uniforms.time * 31.0), cos(uniforms.time * 37.0)) * texel * 0.5;\n let prevColor = textureSample(inputTexture, texSampler, temporalUV).rgb;\n result = mix(result, prevColor + indirect * uniforms.intensity * 0.5, uniforms.temporalBlend * 0.3);\n }\n\n return vec4f(result, color.a);\n}\n"; /** * Build a complete effect shader by combining vertex shader, * utilities, and the effect's fragment shader. */ export declare function buildEffectShader(fragmentShader: string): string; //# sourceMappingURL=PostProcessShaders.d.ts.map