源码 / shaders/Ch28/SimpleSSAO.glsl
shaders/Ch28/SimpleSSAO.glsl
// 案例 28.4:简化版 SSAO(法线半球采样 + 深度感知模糊)。 // Pass 0 AO:输出“可见度”(1 = 不被遮挡),与 URP 的 _ScreenSpaceOcclusionTexture 约定一致 // Pass 1/2:水平 / 竖直双边模糊;Pass 3:把 AO 乘到画面上;Pass 4:只显示 AO(调试) Shader "Cookbook/Ch28/SimpleSSAO" { SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } ZWrite Off ZTest Always Cull Off Blend Off GLSLINCLUDE #include "Ch28/ScreenSpaceCommon.glsl" uniform float _Radius; uniform float _Intensity; uniform float _SampleCount; uniform float _Bias; uniform vec4 _SSAOTexelSize; // C# 每帧设置:AO 纹理的 (1/w, 1/h, w, h) uniform sampler2D _SSAOResult; // 合成时读取的 AO 结果 float EyeDepthAt(vec2 uv) { return LinearEyeDepth(SampleSceneDepthLod(uv), _ZBufferParams); } // URP 的 LinearEyeDepth(positionWS, viewMatrix) 重载:观察空间 z 取反 float LinearEyeDepthWS(vec3 positionWS) { return -(UNITY_MATRIX_V * vec4(positionWS, 1.0)).z; } // ComputeNormalizedDeviceCoordinates:世界坐标 → 屏幕 UV vec2 ComputeNormalizedDeviceCoordinates(vec3 positionWS, mat4 vp) { vec4 positionCS = vp * vec4(positionWS, 1.0); return positionCS.xy / positionCS.w * 0.5 + 0.5; } // Random.hlsl 的 InterleavedGradientNoise float InterleavedGradientNoise(vec2 pixCoord, int frameCount) { pixCoord += float(frameCount) * vec2(2.083, 4.867); return fract(52.9829189 * fract(dot(pixCoord, vec2(0.06711056, 0.00583715)))); } // 双边模糊:高斯权重 × 深度相似度权重,跨越深度断层的样本被压低,AO 不会渗到背景上 vec4 BilateralBlur(vec2 uv, vec2 dir) { const float w[4] = float[4](0.324, 0.232, 0.0855, 0.0205); float centerEye = EyeDepthAt(uv); float sum = texture(_BlitTexture, uv).r * w[0]; float wsum = w[0]; for (int i = 1; i < 4; i++) { for (int s = -1; s <= 1; s += 2) { vec2 suv = uv + dir * float(i * s); float d = EyeDepthAt(suv); float wd = w[i] * exp(-abs(d - centerEye) / max(centerEye * 0.02, 1e-3)); sum += textureLod(_BlitTexture, suv, 0.0).r * wd; wsum += wd; } } float v = sum / wsum; return vec4(v, v, v, 1.0); } ENDGLSL Pass { Name "AO" GLSLPROGRAM #ifdef FRAGMENT vec4 FragAO() { vec2 uv = texcoord; float depth = SampleDeviceDepth(uv); if (IsSkyDepth(depth)) return vec4(1.0); vec3 positionWS = ReconstructPositionWS(uv, depth); vec3 normalWS = normalize(SampleSceneNormals(uv)); // URP 14 的法线纹理是世界空间 float centerEye = LinearEyeDepthWS(positionWS); // 每像素一个随机旋转角,把规则的采样图案打散成高频噪声,后面再模糊掉 float noise = InterleavedGradientNoise(gl_FragCoord.xy, 0); vec3 randomDir = vec3(cos(noise * TWO_PI), sin(noise * TWO_PI), 0.37); vec3 tangentWS = normalize(cross(normalWS, randomDir)); vec3 bitangentWS = cross(normalWS, tangentWS); int count = int(_SampleCount); float occlusion = 0.0; for (int i = 0; i < count; i++) { // 余弦分布的半球方向(黄金角螺旋),长度向中心聚集:近处样本更重要 float u = (float(i) + 0.5) / float(count); float phi = float(i) * 2.39996323; float cosTheta = sqrt(1.0 - u), sinTheta = sqrt(u); vec3 dirTS = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); float lenScale = mix(0.1, 1.0, fract(u * 7.31 + noise) * fract(u * 7.31 + noise)); vec3 offsetWS = (tangentWS * dirTS.x + bitangentWS * dirTS.y + normalWS * dirTS.z) * (lenScale * _Radius); vec3 sampleWS = positionWS + offsetWS; // 投影回屏幕,读那里的场景深度 vec2 sampleUV = ComputeNormalizedDeviceCoordinates(sampleWS, UNITY_MATRIX_VP); float sampleEye = LinearEyeDepthWS(sampleWS); float sceneEye = EyeDepthAt(sampleUV); // 场景表面在样本点前面 → 样本点被埋住 → 记一次遮挡 float occluded = sceneEye < sampleEye - _Bias ? 1.0 : 0.0; // 范围检查:深度差远大于半径的(背景前的前景边缘)不算,避免黑边光晕 float rangeCheck = smoothstep(0.0, 1.0, _Radius / max(abs(centerEye - sceneEye), 1e-4)); occlusion += occluded * rangeCheck; } float visibility = saturate(1.0 - occlusion / float(count) * _Intensity); return vec4(visibility, visibility, visibility, 1.0); } void main() { fragColor = FragAO(); } #endif ENDGLSL } Pass { Name "BlurH" GLSLPROGRAM #ifdef FRAGMENT void main() { fragColor = BilateralBlur(texcoord, vec2(_SSAOTexelSize.x, 0.0)); } #endif ENDGLSL } Pass { Name "BlurV" GLSLPROGRAM #ifdef FRAGMENT void main() { fragColor = BilateralBlur(texcoord, vec2(0.0, _SSAOTexelSize.y)); } #endif ENDGLSL } Pass { Name "CompositeMultiply" GLSLPROGRAM #ifdef FRAGMENT void main() { vec4 color = SamplePointClamp(_BlitTexture, texcoord); float ao = texture(_SSAOResult, texcoord).r; fragColor = vec4(color.rgb * ao, color.a); } #endif ENDGLSL } Pass { Name "ShowAO" GLSLPROGRAM #ifdef FRAGMENT void main() { float ao = texture(_SSAOResult, texcoord).r; fragColor = vec4(ao, ao, ao, 1.0); } #endif ENDGLSL } } }