#version 450 core #include "common.glsl" // Half-res world-space hemisphere SSAO. Normals are reconstructed from depth // (picking the flatter neighbour per axis to avoid smearing across edges). in vec2 vUV; layout(binding = 0) uniform sampler2D uDepth; uniform float uRadius; uniform float uIntensity; layout(location = 0) out vec4 fragColor; const int SAMPLES = 14; vec3 posAt(vec2 uv) { return worldFromDepth(uv, texture(uDepth, uv).r); } void main() { float d = texture(uDepth, vUV).r; if (d >= 1.0) { fragColor = vec4(1.0); return; } vec3 P = worldFromDepth(vUV, d); vec2 t = 1.0 / vec2(textureSize(uDepth, 0)); vec3 px = posAt(vUV + vec2(t.x, 0)), nx = posAt(vUV - vec2(t.x, 0)); vec3 py = posAt(vUV + vec2(0, t.y)), ny = posAt(vUV - vec2(0, t.y)); vec3 dx = length(px - P) < length(P - nx) ? px - P : P - nx; vec3 dy = length(py - P) < length(P - ny) ? py - P : P - ny; vec3 N = normalize(cross(dx, dy)); if (dot(N, uCamPos.xyz - P) < 0.0) N = -N; float rot = ign(gl_FragCoord.xy) * 6.2831853; vec3 T = normalize(cross(N, abs(N.y) < 0.9 ? vec3(0, 1, 0) : vec3(1, 0, 0))); vec3 B = cross(N, T); float centerLin = linearDepth(d); // Larger radius further away keeps the effect stable on screen. float radius = uRadius * (0.8 + centerLin * 0.01); float occ = 0.0; for (int i = 0; i < SAMPLES; ++i) { float fi = (float(i) + 0.5) / float(SAMPLES); float a = float(i) * 2.399963 + rot; float r = sqrt(fi); float z = sqrt(max(0.0, 1.0 - r * r)); vec3 k = (T * cos(a) * r + B * sin(a) * r + N * z) * mix(0.15, 1.0, fi * fi); vec3 S = P + N * 0.03 + k * radius; vec4 clip = uViewProj * vec4(S, 1.0); vec2 suv = clip.xy / clip.w * 0.5 + 0.5; if (any(lessThan(suv, vec2(0.0))) || any(greaterThan(suv, vec2(1.0)))) continue; float sceneLin = linearDepth(texture(uDepth, suv).r); float range = smoothstep(0.0, 1.0, radius / max(abs(centerLin - sceneLin), 1e-3)); occ += step(sceneLin, clip.w - 0.02) * range; } float ao = 1.0 - occ / float(SAMPLES); fragColor = vec4(pow(clamp(ao, 0.0, 1.0), uIntensity * 1.6)); }