pub const SSAO: &str = "// Screen-space ambient occlusion (GTAO): the horizon-search kernel and the\n// depth-aware blur that cleans up its noise. One fragment per compile, selected\n// by a define so each variant declares exactly the resources it binds (Metal\n// and DXIL indices are assigned in declaration order, so an unused declaration\n// would shift the live ones):\n//\n// SSAO_KERNEL - horizon search over the G-buffer -> raw occlusion.\n// SSAO_BLUR - depth-aware 5x5 box blur of that raw occlusion.\n//\n// Both read the unified G-buffer pre-pass (rgb = unit view normal, a = linear\n// view depth) and write a single-channel occlusion target. Pairs with\n// `fullscreen_vertex` in fullscreen.slang.\n\n{POST_COMMON}\n\n#if defined(SSAO_KERNEL)\n\n// Layout matches `SsaoParams` in render_types.rs (16 B).\nstruct SsaoParams\n{\n float radius;\n float intensity;\n float tan_half_fov_y;\n float aspect;\n};\n\n[[vk::binding(0, 0)]] Sampler2D<float4> gbuffer;\n\n[[vk::push_constant]]\nConstantBuffer<SsaoParams> params;\n\nstatic const int SSAO_SLICES = 3;\nstatic const int SSAO_STEPS = 6;\nstatic const float SSAO_PI = 3.14159265359;\nstatic const float SSAO_HALF_PI = 1.57079632679;\n// Cap on the kernel\'s UV footprint so geometry right in front of the camera\n// does not blow the search radius out to most of the screen.\nstatic const float SSAO_MAX_UV = 0.2;\n\n// Rebuild a view-space position from a UV and its linear (view-space) depth.\nfloat3 ssao_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[shader(\"fragment\")]\nfloat ssao_kernel_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 1.0; // background - no geometry, fully lit\n }\n\n float3 n_vec = normalize(c.xyz);\n float3 p = ssao_view_pos(uv, depth, params.tan_half_fov_y, params.aspect);\n float3 v = normalize(-p); // p is in view space; camera is origin\n\n // UV-space radius of the world-space search radius at this depth. The\n // viewport spans 2*tan_half_fov*depth view units vertically.\n float radius_uv = params.radius / max(2.0 * params.tan_half_fov_y * depth, 1e-4);\n radius_uv = min(radius_uv, SSAO_MAX_UV);\n\n // Interleaved gradient noise: a per-pixel slice rotation + step jitter that\n // trades banding for high-frequency noise the blur pass then cleans up.\n float ign = frac(52.9829189 * frac(dot(pixel.xy, float2(0.06711056, 0.00583715))));\n\n float visibility = 0.0;\n for (int s = 0; s < SSAO_SLICES; s++)\n {\n float ang = (float(s) + ign) * (SSAO_PI / float(SSAO_SLICES));\n float2 dir = float2(cos(ang), sin(ang));\n\n // Slice plane: spanned by v and the screen direction lifted to view\n // space. The projected surface normal and both horizons are measured\n // inside this plane.\n float3 dir_vs = normalize(float3(dir, 0.0));\n float3 plane_n = normalize(cross(dir_vs, v));\n float3 proj_n = n_vec - plane_n * dot(n_vec, plane_n);\n float proj_len = length(proj_n);\n if (proj_len < 1e-4)\n {\n continue;\n }\n float3 tangent = cross(plane_n, v);\n float n = atan2(dot(proj_n, tangent), dot(proj_n, v));\n\n // Horizon search: march both screen directions, keeping the widest\n // horizon cosine, distance-attenuated so far occluders fade out.\n float cos_plus = -1.0;\n float cos_minus = -1.0;\n for (int step = 1; step <= SSAO_STEPS; step++)\n {\n float t = (float(step) - 0.5 + ign) / float(SSAO_STEPS);\n float2 off = dir * radius_uv * t;\n\n float2 uvp = uv + off;\n float dp = gbuffer.Sample(uvp).a;\n if (dp > 0.0)\n {\n float3 sp = ssao_view_pos(uvp, dp, params.tan_half_fov_y, params.aspect) - p;\n float lp = length(sp);\n float fo = saturate(1.0 - lp / max(params.radius, 1e-4));\n cos_plus = lerp(cos_plus, max(cos_plus, dot(sp / max(lp, 1e-5), v)), fo);\n }\n float2 uvm = uv - off;\n float dm = gbuffer.Sample(uvm).a;\n if (dm > 0.0)\n {\n float3 sm = ssao_view_pos(uvm, dm, params.tan_half_fov_y, params.aspect) - p;\n float lm = length(sm);\n float fo = saturate(1.0 - lm / max(params.radius, 1e-4));\n cos_minus = lerp(cos_minus, max(cos_minus, dot(sm / max(lm, 1e-5), v)), fo);\n }\n }\n\n // Horizon angles, clamped into the hemisphere around the projected\n // normal, then the GTAO cosine-weighted arc integral for the slice.\n float h1 = -acos(clamp(cos_minus, -1.0, 1.0));\n float h2 = acos(clamp(cos_plus, -1.0, 1.0));\n h1 = n + max(h1 - n, -SSAO_HALF_PI);\n h2 = n + min(h2 - n, SSAO_HALF_PI);\n float sin_n = sin(n);\n float cos_n = cos(n);\n float a1 = 0.25 * (-cos(2.0 * h1 - n) + cos_n + 2.0 * h1 * sin_n);\n float a2 = 0.25 * (-cos(2.0 * h2 - n) + cos_n + 2.0 * h2 * sin_n);\n visibility += proj_len * (a1 + a2);\n }\n\n visibility = saturate(visibility / float(SSAO_SLICES));\n // `intensity` sharpens the contact darkening; 1.0 is the integrated amount.\n return pow(visibility, max(params.intensity, 0.0));\n}\n\n#elif defined(SSAO_BLUR)\n\n[[vk::binding(0, 0)]] Sampler2D<float4> ao_raw;\n[[vk::binding(1, 0)]] Sampler2D<float4> gbuffer;\n\n// Depth-aware 5x5 box blur. Weighting each tap by view-depth similarity keeps\n// the noisy GTAO output from bleeding occlusion across silhouette edges.\n[shader(\"fragment\")]\nfloat ssao_blur_fragment([[vk::location(0)]] float2 uv : TEXCOORD0) : SV_Target\n{\n float2 texel = 1.0 / combined_size(ao_raw);\n float center_depth = gbuffer.Sample(uv).a;\n if (center_depth <= 0.0)\n {\n return 1.0;\n }\n float sum = 0.0;\n float wsum = 0.0;\n for (int y = -2; y <= 2; y++)\n {\n for (int x = -2; x <= 2; x++)\n {\n float2 tap = uv + float2(float(x), float(y)) * texel;\n float d = gbuffer.Sample(tap).a;\n // Depth-similarity weight; background taps (d <= 0) drop out.\n float w = (d > 0.0)\n ? exp(-abs(d - center_depth) * 8.0 / max(center_depth, 1e-3))\n : 0.0;\n sum += ao_raw.Sample(tap).r * w;\n wsum += w;\n }\n }\n return (wsum > 1e-4) ? (sum / wsum) : ao_raw.Sample(uv).r;\n}\n\n#else\n#error \"ssao.slang: define SSAO_KERNEL or SSAO_BLUR\"\n#endif\n";Expand description
ssao.slang.