pub const SSGI: &str = "// Screen-space global illumination. One fragment per compile, selected by a\n// define so each variant declares exactly the resources it binds (Metal and\n// DXIL indices are assigned in declaration order, so an unused declaration\n// would shift the live ones):\n//\n// SSGI_GATHER - per pixel, cast cosine-weighted hemisphere rays around the\n// surface normal, screen-march each against the SSR pre-pass\n// G-buffer, and accumulate the lit scene colour at each\n// on-screen hit. Misses contribute nothing (the IBL ambient\n// already covers the off-screen / sky term). The\n// cosine-weighted importance sampling folds the cos(theta) /\n// pdf factor away, so the estimate of the (albedo-free)\n// indirect irradiance is just the mean hit radiance.\n// SSGI_COMPOSITE - a depth-aware box blur of that noisy gather output, which\n// the pipeline additively blends into the scene.\n//\n// Pairs with `fullscreen_vertex` in fullscreen.slang.\n\n{POST_COMMON}\n\n// Layout matches `SsgiParams` in render_types.rs (32 B). The composite reads\n// only `intensity`, but the whole block is shared with the gather.\nstruct SsgiParams\n{\n float intensity;\n float max_distance;\n float tan_half_fov_y;\n float aspect;\n float stride;\n float thickness;\n // Rays cast per pixel over the hemisphere, and march samples per ray. Both\n // arrive as floats and are read as int loop bounds; the Rust side derives\n // the stride from `steps`.\n float rays;\n float steps;\n};\n\n[[vk::push_constant]]\nConstantBuffer<SsgiParams> params;\n\n#if defined(SSGI_GATHER)\n\n// binding 0 = lit scene radiance (the bounce-radiance source); binding 1 = the\n// SSR pre-pass G-buffer (rgb = view normal, a = linear view depth).\n[[vk::binding(0, 0)]] Sampler2D<float4> scene;\n[[vk::binding(1, 0)]] Sampler2D<float4> gbuffer;\n\n// Origin offset along the surface normal (x stride) so a ray does not\n// immediately self-intersect the surface it starts on.\nstatic const float SSGI_NORMAL_BIAS = 0.5;\nstatic const float SSGI_PI = 3.14159265359;\n\n// Rebuild a view-space position from a UV and its linear (view-space) depth.\n// Matches ssr_view_pos / ssao_view_pos.\nfloat3 ssgi_view_pos(float2 uv, float depth, float tan_y, float aspect)\n{\n float2 ndc = float2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);\n return float3(ndc.x * tan_y * aspect, ndc.y * tan_y, -1.0) * depth;\n}\n\n// Project a view-space point (z < 0, in front of the camera) to a screen UV.\nfloat2 ssgi_project(float3 q, float tan_y, float aspect)\n{\n float inv = 1.0 / max(-q.z, 1e-4);\n float2 ndc = float2(q.x * inv / (tan_y * aspect), q.y * inv / tan_y);\n return float2(ndc.x * 0.5 + 0.5, 1.0 - (ndc.y * 0.5 + 0.5));\n}\n\n// Interleaved gradient noise: a cheap per-pixel hash in [0, 1). Decorrelates\n// the hemisphere sampling spatially; the depth-aware blur + TAA clean up the\n// residual high-frequency noise.\nfloat ssgi_ign(float2 p)\n{\n return frac(52.9829189 * frac(dot(p, float2(0.06711056, 0.00583715))));\n}\n\n// Van der Corput radical inverse (base 2), for the low-discrepancy ray set.\nfloat ssgi_vdc(uint bits)\n{\n bits = (bits << 16u) | (bits >> 16u);\n bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);\n bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);\n bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);\n bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);\n return float(bits) * 2.3283064365386963e-10; // / 2^32\n}\n\n[shader(\"fragment\")]\nfloat4 ssgi_gather_fragment(\n [[vk::location(0)]] float2 uv : TEXCOORD0,\n float4 pixel : SV_Position) : SV_Target\n{\n float4 c = gbuffer.Sample(uv);\n float depth = c.a;\n if (depth <= 0.0)\n {\n return float4(0.0, 0.0, 0.0, 1.0); // background / sky\n }\n\n float3 n = normalize(c.xyz);\n float3 p = ssgi_view_pos(uv, depth, params.tan_half_fov_y, params.aspect);\n\n // Orthonormal basis around the view-space normal.\n float3 up = abs(n.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0);\n float3 t = normalize(cross(up, n));\n float3 b = cross(n, t);\n\n float jitter = ssgi_ign(pixel.xy);\n float3 origin = p + n * (params.stride * SSGI_NORMAL_BIAS);\n\n int rays = max(1, int(params.rays));\n int steps = max(1, int(params.steps));\n\n float3 indirect = float3(0.0);\n for (int i = 0; i < rays; i++)\n {\n // Stratified cosine-weighted hemisphere sample, jittered per pixel.\n float u1 = (float(i) + jitter) / float(rays);\n float u2 = frac(ssgi_vdc(uint(i + 1)) + jitter);\n float r = sqrt(u1);\n float phi = 2.0 * SSGI_PI * u2;\n float3 d_t = float3(r * cos(phi), r * sin(phi), sqrt(max(0.0, 1.0 - u1)));\n float3 d = normalize(t * d_t.x + b * d_t.y + n * d_t.z);\n\n float3 step_v = d * params.stride;\n float3 q = origin;\n for (int s = 0; s < steps; s++)\n {\n q += step_v;\n if (q.z >= 0.0) break; // crossed camera plane\n float2 hit_uv = ssgi_project(q, params.tan_half_fov_y, params.aspect);\n if (hit_uv.x < 0.0 || hit_uv.x > 1.0 || hit_uv.y < 0.0 || hit_uv.y > 1.0) break;\n float scene_depth = gbuffer.Sample(hit_uv).a;\n if (scene_depth <= 0.0) continue; // sky here - keep marching\n float diff = (-q.z) - scene_depth; // > 0: ray is behind the surface\n if (diff > 0.0 && diff < params.thickness)\n {\n indirect += scene.Sample(hit_uv).rgb; // bounced radiance\n break;\n }\n }\n }\n\n indirect *= (1.0 / float(rays));\n return float4(indirect, 1.0);\n}\n\n#elif defined(SSGI_COMPOSITE)\n\n// binding 0 = the noisy gather output; binding 1 = the SSR pre-pass G-buffer\n// (depth in .a) for the depth-similarity weighting.\n[[vk::binding(0, 0)]] Sampler2D<float4> gi_tex;\n[[vk::binding(1, 0)]] Sampler2D<float4> gbuffer;\n\n// Depth-aware blur footprint: a (2R+1)^2 box weighted by depth similarity, so\n// the indirect term denoises without bleeding across silhouettes.\nstatic const int SSGI_BLUR_RADIUS = 2;\n\n[shader(\"fragment\")]\nfloat4 ssgi_composite_fragment([[vk::location(0)]] float2 uv : TEXCOORD0) : SV_Target\n{\n float center_depth = gbuffer.Sample(uv).a;\n if (center_depth <= 0.0)\n {\n return float4(0.0, 0.0, 0.0, 1.0);\n }\n\n float2 texel = 1.0 / combined_size(gi_tex);\n float3 sum = float3(0.0);\n float wsum = 0.0;\n for (int dy = -SSGI_BLUR_RADIUS; dy <= SSGI_BLUR_RADIUS; dy++)\n {\n for (int dx = -SSGI_BLUR_RADIUS; dx <= SSGI_BLUR_RADIUS; dx++)\n {\n float2 tap = uv + float2(float(dx), float(dy)) * texel;\n float d = gbuffer.Sample(tap).a;\n if (d <= 0.0) continue; // skip background taps\n // Depth-similarity weight: taps on a different surface fall off\n // sharply so the indirect term does not bleed across edges.\n float dd = abs(d - center_depth);\n float w = exp2(-dd * 8.0);\n sum += gi_tex.Sample(tap).rgb * w;\n wsum += w;\n }\n }\n float3 gi = wsum > 0.0 ? sum / wsum : gi_tex.Sample(uv).rgb;\n return float4(gi * params.intensity, 1.0);\n}\n\n#else\n#error \"ssgi.slang: define SSGI_GATHER or SSGI_COMPOSITE\"\n#endif\n";Expand description
ssgi.slang.