Chapter 61: Screen-Space Effects
屏幕空间效果(Screen-Space Effects)把已经渲染出来的一帧当作输入数据,再补充近似遮蔽、反射、接触阴影、边缘增强、景深或其他后处理结果。本章只讨论两个最常见、最能体现屏幕空间边界的效果:屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)和屏幕空间反射(Screen-Space Reflections, SSR)。读完本章后,读者应能定位这些效果在 frame 中读取哪些 buffer,判断它们在哪些画面区域成立,并按证据排查 banding、halo、ghosting、edge leak 和 undersampling。
贯穿本章的材料是一帧室内走廊画面:相机看向一根靠墙立柱,地面有一片粗糙度较低的积水,墙角存在细窄缝隙,远处有一扇窗。基础 deferred pass 已经生成 depth、normal、base color、roughness、motion vector 和直接光照 color buffer。后处理阶段希望加入墙角遮蔽、立柱脚下的接触暗部和地面积水中的反射。这个例子覆盖 SSAO 与 SSR 的共同输入,也能暴露屏幕空间效果最常见的失败边界。
屏幕空间技术的核心结论可以先给出:它的有效域等于当前相机已经写入屏幕 buffer 的信息范围。它可以快速复用现成 depth、normal 和 color,降低额外几何提交与场景查询成本;它也会天然缺失屏幕外物体、被遮挡物体、背面几何和历史帧中已经失效的信息。工程上使用屏幕空间效果时,正确问题是“这一帧的哪些像素能被当前 buffer 支撑”,再继续问“缺失区域用什么 fallback 与稳定策略接住”。
本章的可复用判断顺序是:先确认输入 buffer 的坐标空间和精度,再确认采样或 ray marching 的搜索范围,接着观察边界像素和运动区域,最后把视觉伪影对应到资源分辨率、深度比较、temporal history、blur、upsample 或 fallback。这个顺序比单独调参数更稳定,因为屏幕空间问题通常来自输入信息缺失、坐标空间不一致或历史数据失效。
61.1 屏幕空间技术的数据来源与适用边界
屏幕空间技术把一帧中已有的 per-pixel 数据当作局部场景表示。这里的 per-pixel 数据指每个屏幕像素关联的 depth、normal、material、motion vector 和 color。它们来自 G-buffer、depth pre-pass、lighting pass 或上一帧 history buffer。SSAO 主要读取 depth 与 normal,输出一张单通道 AO mask;SSR 读取 depth、normal、roughness、直接光照 color 和 motion vector,输出反射 radiance 或 reflection factor。
在走廊画面中,立柱脚下的接触暗部可以通过 depth 与 normal 推断。当前像素如果位于地面,附近采样点投影到墙面或立柱表面,并且深度更靠近相机,SSAO 就把这些采样视为局部遮蔽贡献。地面积水反射可以通过 normal 计算反射方向,再沿屏幕投影方向查找深度交点,并从 color buffer 读取被反射物体的颜色。两者都没有重新遍历完整场景,它们只查询当前帧已经写入的纹理。
屏幕空间效果的输入链可以用下面的 frame path 表示。图中每个节点代表可观察资源或 pass,箭头代表当前帧中的数据依赖。
这条路径说明了一个工程事实:SSAO 与 SSR 的质量先受输入 buffer 控制,再受算法采样控制。depth 的非线性分布、reverse-Z 约定、normal 的编码范围、roughness 的含义、motion vector 的方向定义,都会直接改变最终画面。AMD 的 FidelityFX CACAO 1.4 文档把 depth 作为必需输入,并把 normal 作为可选输入;没有 normal 时可从 depth 的邻域偏导推导 implied normal。这个设计说明 AO 的最小输入可以压缩到 depth,但单靠 depth 推导法线会对几何边缘和薄物体更敏感。
depth reconstruction 是屏幕空间技术的第一道检查点。GPU depth buffer 存储的是投影后的深度值,shader 需要把像素 UV 和 depth 还原为 view space position。常见流程是把 uv 变成 normalized device coordinates,再乘 inverse projection,最后除以 w。如果工程使用 reverse-Z、无限远平面或 API 特定的 depth range,重建公式要和 projection matrix 的生成方式保持一致。公式写错时,SSAO radius 会随距离异常放大或缩小,SSR ray marching 会在本应命中的表面前后漂移。
屏幕空间效果的有效边界来自当前 buffer 的可见性。相机没有看到的窗外物体不会出现在 color buffer 中,积水中本应反射的窗外上沿就会缺失。被立柱遮挡的墙面细节也不会出现在当前 color 中,SSR 查询到立柱边缘后没有办法从当前帧恢复遮挡后的颜色。背面几何和透明物体也需要单独处理,因为常规 depth buffer 只保留一个最靠近相机的表面。结论是屏幕空间结果适合补充局部细节和低成本近似,远距离、大面积镜面、屏幕边缘反射和多层透明区域需要 probe、planar reflection、ray tracing 或 baked data 接管。
调试屏幕空间效果时,先看资源可视化,再看最终画面。对走廊 frame,应打开 depth linearization view、normal view、roughness view、motion vector view、SSAO raw mask、SSR hit/miss mask 和 composite view。raw mask 能回答算法是否在正确像素工作;composite view 能回答混合是否过强、过弱或历史数据失效。只观察最终 LDR 画面会把输入错误、采样错误和混合错误叠在一起。
61.2 SSAO(屏幕空间环境光遮蔽)实现原理
SSAO 的目标是估计当前表面点周围半球方向中有多少方向被近处几何遮住。环境光遮蔽(Ambient Occlusion, AO)在本章中的工作定义是:对一个表面点,统计半球范围内可被环境光到达的程度,并把不可达比例转成暗化系数。SSAO 把这个统计限制在屏幕 buffer 中完成,因此它读取 depth 与 normal,围绕当前像素构造一组局部采样点,用深度比较估计遮蔽。
在走廊 frame 中,墙角缝隙附近的 SSAO 应变暗,因为地面、墙面和立柱的深度在局部半球内互相靠近。空旷地面中央的 AO 应接近白色,因为邻域采样点大多落在同一平面或远离当前点。这个视觉目标决定了 SSAO 的关键参数:radius 控制查询半径,bias 控制自遮蔽容忍度,sample count 控制噪声,blur 控制稳定性,normal buffer 控制半球方向。
一个典型 SSAO pass 的输入是 view space position 或 depth、view space normal、一组 hemisphere sample kernel、一张小尺寸 noise texture、projection matrix 和 radius。若工程没有 position buffer,shader 可以用 depth reconstruction 得到当前点 P。sample kernel 通常先在 tangent space 的半球内生成,再根据当前 normal 构造 TBN 矩阵旋转到 view space。LearnOpenGL 的 SSAO 示例使用 normal-oriented hemisphere,并说明该做法把采样方向放在表面法线指向的半球内;这正好对应 SSAO 对“当前表面上方局部空间”的近似。
下面的伪代码展示 SSAO 的核心计算路径。代码省略 API 绑定和资源格式,只保留当前像素如何从 depth、normal 和 kernel 得到 AO factor。
float ComputeSSAO(float2 uv)
{
float depth = DepthTexture.Sample(pointSampler, uv).r;
float3 P = ReconstructViewPosition(uv, depth);
float3 N = normalize(DecodeNormal(NormalTexture.Sample(pointSampler, uv)));
float3 randomDir = NoiseTexture.Sample(wrapSampler, uv * noiseScale).xyz;
float3 tangent = normalize(randomDir - N * dot(randomDir, N));
float3 bitangent = cross(N, tangent);
float3x3 tbn = float3x3(tangent, bitangent, N);
float occlusion = 0.0;
for (int i = 0; i < SampleCount; ++i)
{
float3 samplePos = P + mul(Kernel[i], tbn) * Radius;
float4 clip = mul(Projection, float4(samplePos, 1.0));
float2 sampleUv = clip.xy / clip.w * 0.5 + 0.5;
float sampleDepth = DepthTexture.Sample(pointSampler, sampleUv).r;
float3 scenePos = ReconstructViewPosition(sampleUv, sampleDepth);
float rangeWeight = smoothstep(0.0, 1.0, Radius / abs(P.z - scenePos.z));
occlusion += (scenePos.z >= samplePos.z + Bias ? 1.0 : 0.0) * rangeWeight;
}
return 1.0 - occlusion / SampleCount;
}
这段代码的输入与输出关系很直接:当前像素先还原 view space 表面点,再沿 normal 半球生成若干候选点,接着把候选点投影回屏幕读取真实 depth,最后比较候选点深度与场景深度。若真实场景在候选点之前出现,说明该方向存在局部遮挡。Bias 用来压住同一平面上的自遮蔽,Radius 用来限制 AO 的空间尺度,rangeWeight 用来让远离当前点的深度差贡献变弱。
SSAO 的噪声来自低采样数。实时渲染通常不会为每个像素做大量采样,于是使用随机旋转 kernel,把低采样数误差变成高频噪声,再用 depth-aware blur 或 bilateral blur 平滑。这里的 blur 需要同时读取 AO、depth 和 normal。相邻像素深度连续且法线接近时可以混合;跨越立柱边缘、墙角和地面交界时应降低混合权重。blur 过强会让墙角暗部扩散到空旷地面,blur 过弱会保留明显颗粒。
SSAO 的 radius 应在 view space 或 world space 中定义,并和相机距离保持可解释关系。若 radius 在屏幕像素单位中直接固定,远处缝隙和近处缝隙会得到相同屏幕尺度的遮蔽,视觉上会出现距离不一致。若 radius 过大,墙角 AO 会跨过本地结构,形成一团灰暗阴影;若 radius 过小,立柱脚下接触暗部会断裂。工程上可以从真实尺度反推初值:角色脚边接触 AO 使用几十厘米量级,室内墙角 AO 可以稍大,城市远景 AO 应交给低频 GI、baked AO 或 probe。
SSAO 的 debug view 应至少包含 raw AO、blurred AO、depth discontinuity mask 和 normal discontinuity mask。走廊画面中,如果 raw AO 在墙角有噪声但形状正确,问题多半在 sample count、noise texture 或 blur;如果 raw AO 已经在整面墙发灰,问题通常来自 kernel 方向、depth reconstruction、bias 或 radius;如果立柱轮廓周围出现黑边,问题常来自跨边缘 blur 或深度比较过敏。这个判断顺序把视觉症状落回了数据、采样和后处理三个层级。
61.3 屏幕空间反射(SSR)算法与边界
SSR 的目标是在当前屏幕可见内容中查找反射命中点,并把命中像素的颜色混合到反射表面上。它的输入比 SSAO 更多:当前像素的 depth 和 normal 用来确定反射起点与方向,roughness 用来控制反射清晰度与 fallback 权重,color buffer 提供被反射的 radiance,motion vector 与 history buffer 提供 temporal reuse。AMD 的 FidelityFX SSSR 1.5 文档把 SSSR 描述为层级屏幕空间 traversal kernel,并使用 hierarchical depth、tile classification、blue noise、indirect dispatch 和 denoising 来组织工程实现。
在走廊 frame 中,地面积水中的立柱反射是 SSR 的理想场景:反射物体位于当前屏幕内,地面 normal 稳定,roughness 较低,depth buffer 中也存在立柱表面。窗外上沿或相机背后的灯带则属于缺失场景,因为当前 color buffer 没有这些 radiance。SSR 可以命中可见立柱,也会在屏幕边缘、遮挡背后和高 roughness 区域交给 reflection probe、skybox、planar reflection 或 ray tracing fallback。
SSR 的核心计算从反射向量开始。当前像素还原出 view space position P 和 normal N 后,view direction 可以写成从相机指向 P 的单位向量,反射方向 R 由 reflect(viewDir, N) 得到。ray marching 沿 P + t * R 前进,把每个候选点投影到屏幕,读取对应 depth,再比较 ray 深度与场景深度。当 ray 深度穿过场景深度,并且差值落在 thickness 范围内时,算法认为命中。
下面的伪代码展示最小 SSR ray marching。实际工程会加入 hierarchical depth、binary search、粗糙度采样、tile classification 和 denoiser,这里只保留命中判断的主干。
bool TraceSSR(float2 uv, out float2 hitUv)
{
float depth = DepthTexture.Sample(pointSampler, uv).r;
float3 P = ReconstructViewPosition(uv, depth);
float3 N = normalize(DecodeNormal(NormalTexture.Sample(pointSampler, uv)));
float3 V = normalize(P);
float3 R = normalize(reflect(V, N));
float t = StepLength;
for (int stepIndex = 0; stepIndex < MaxSteps; ++stepIndex)
{
float3 rayPos = P + R * t;
float4 clip = mul(Projection, float4(rayPos, 1.0));
float2 probeUv = clip.xy / clip.w * 0.5 + 0.5;
if (any(probeUv < 0.0) || any(probeUv > 1.0))
return false;
float probeDepth = DepthTexture.Sample(pointSampler, probeUv).r;
float3 scenePos = ReconstructViewPosition(probeUv, probeDepth);
float depthDelta = rayPos.z - scenePos.z;
if (depthDelta > 0.0 && depthDelta < Thickness)
{
hitUv = probeUv;
return true;
}
t += StepLength;
}
return false;
}
这段伪代码中,Thickness 是 SSR 的边界参数。depth buffer 只存一层可见表面,ray 与 depth 的交点常常落在薄物体附近。没有 thickness 时,ray 容易穿过栏杆、桌腿和门框;thickness 过大时,反射会吸附到错误表面,积水中可能出现墙面颜色贴到立柱脚下的现象。工程上应根据场景尺度、depth precision、ray step 和 roughness 分层设置 thickness,并用 hit distance view 观察命中位置是否稳定。
SSR 的性能优化通常从 ray 数量和步进结构入手。全屏每像素长距离 marching 会带来大量 depth texture 读取。现代实现通常先做 tile classification,只让低 roughness、高反射权重、法线方向合理的像素发射 ray;再生成 hierarchical depth buffer,用粗 mip 快速跳过空区域;命中后再做小范围 refinement。FidelityFX SSSR 的文档中,hierarchical depth generation 会从应用提供的 depth 生成 depth pyramid,并在 traversal 检测到可用粗 mip 时加速 raymarching;tile classification 则根据 roughness 等参数决定哪些像素需要射线。
SSR 的 roughness 处理决定反射是否可信。镜面或低 roughness 地面可以使用较清晰的 hit color;中等 roughness 表面需要对反射做模糊、stochastic sampling 或 denoise;高 roughness 表面继续 ray marching 的收益很低,probe 或 prefiltered environment 通常更稳定。roughness 还影响 temporal accumulation:粗糙表面可以接受更多历史混合,镜面边缘需要更严格的 history rejection,因为轻微错误会在直线反射中非常明显。
SSR 的失败边界应以 hit/miss mask 观察。屏幕边缘 miss 属于视域外缺失,使用 edge fade 和 environment fallback 接住;遮挡背后 miss 属于当前 color 缺失,使用 probe 或 ray tracing fallback 接住;薄物体抖动属于 thickness 和 depth precision 问题;运动物体拖影属于 motion vector、history clamp 或 disocclusion mask 问题。走廊积水中的立柱反射若在立柱边缘断开,先看 hit/miss mask;若 hit 稳定但颜色错位,再看 motion vector 和 color buffer 的采样时机。
61.4 在游戏管线中集成屏幕空间效果
把 SSAO 与 SSR 放进游戏管线时,重点是 pass 顺序、资源生命周期和 debug view。SSAO 通常位于 G-buffer 或 depth pre-pass 之后、lighting 或 ambient lighting 合成之前。SSR 通常位于直接光照 color 可用之后、透明合成和 tone mapping 之前。这样安排可以让 AO 影响 ambient 或 indirect 分量,也让 SSR 读取 HDR lighting color,并在 tone mapping 前保持能量范围。
走廊 frame 的一个可执行 pass 顺序如下表。表中的“检查证据”列用于说明每个 pass 应输出什么 debug 结果,方便定位资源与算法问题。
| 顺序 | Pass | 主要输入 | 主要输出 | 检查证据 |
|---|---|---|---|---|
| 1 | Depth / G-buffer | geometry、material | depth、normal、roughness、motion vector | linear depth、normal、roughness view |
| 2 | Direct lighting | G-buffer、lights、shadow | HDR direct color | lighting-only view |
| 3 | SSAO raw | depth、normal、kernel、noise | raw AO | AO mask、sample radius view |
| 4 | SSAO blur / upsample | raw AO、depth、normal | filtered AO | edge-preserving blur view |
| 5 | SSR trace | depth、normal、roughness、HDR color | hit UV、hit radiance、miss mask | hit/miss、ray length、roughness mask |
| 6 | SSR temporal / denoise | SSR result、motion vector、history | stable reflection | history weight、rejection mask |
| 7 | Composite | lighting、AO、reflection、material | HDR composite | component toggle view |
| 8 | Post process | HDR composite | display color | exposure、tone mapped view |
SSAO 是否半分辨率执行取决于目标瓶颈。AO 是低频遮蔽信号,半分辨率常能节省采样成本;但墙角、立柱脚、细缝会暴露 upsample 问题。半分辨率方案需要保留全分辨率 depth,在上采样时按深度和法线选择邻域,减少跨边缘扩散。若场景以大平面和粗几何为主,半分辨率 AO 很合适;若画面充满细线、台阶、栏杆和近景道具,quarter-resolution AO 会让边缘暗部漂移。
SSR 是否半分辨率执行更依赖 roughness 与反射用途。粗糙反射可以低分辨率 trace 后用 bilateral upsample 与 denoise 处理;镜面水面、地板或金属面更容易暴露分辨率不足。工程中可以根据 roughness 分层:低 roughness 使用 full-rate 或 checkerboard,高 roughness 使用 half-rate 或 probe fallback。FidelityFX SSSR 文档中的 tile classification 思路正是把“哪些像素值得发射 ray”变成一张可调度的 workload,从而减少默认全屏同成本处理带来的浪费。
Temporal reuse 是屏幕空间效果稳定性的关键路径。SSAO 的 raw noise 可以通过 history accumulation 稳定,但要使用 motion vector 把上一帧 AO reproject 到当前帧,并用 depth、normal 和 color neighborhood 做 rejection。SSR 的 temporal reuse 更敏感,因为 hit UV 代表反射中的另一个表面,当前像素运动和被反射物体运动都可能改变结果。走廊中,地面静止但立柱旁有移动角色经过时,SSR history 需要同时检测反射表面当前像素和 hit point 附近的变化。
Debug view 应作为管线正式资源,并纳入固定调试入口。推荐为 SSAO 提供 ao_raw、ao_filtered、ao_radius、ao_edge_weight;为 SSR 提供 ssr_hit_mask、ssr_ray_steps、ssr_hit_distance、ssr_history_weight、ssr_fallback_weight。这些 debug view 可以直接回答工程问题:效果是否在正确像素工作,失败区域是否已进入 fallback,temporal rejection 是否过严或过松,分辨率策略是否造成边缘泄漏。
屏幕空间效果还要处理透明物体与后处理顺序。常规 opaque depth 只覆盖不透明表面,透明玻璃、水体、粒子和折射材质可能在 depth 中缺失或单独写入 depth。若水面本身是透明 pass,SSR 需要在水面 shading 时单独读取 opaque color 与 opaque depth,或者把水面纳入专门 reflection pass。若先做 tone mapping 再做 SSR,反射采样会损失 HDR 能量,亮窗和灯带在积水中会显得灰。正确顺序应由资源语义决定:SSR 读取的是线性 HDR lighting,合成后再进入曝光与 tone mapping。
61.5 性能优化与伪影减少策略
屏幕空间效果的性能成本主要来自纹理读取、随机访问、ray marching 循环、blur 或 denoise、history buffer 带宽和 upsample。SSAO 的成本近似为像素数乘 sample count,再加 blur;SSR 的成本近似为参与 ray tracing 的像素数乘平均 step count,再加 hierarchical depth、denoise 与 temporal pass。优化时先用 GPU marker 把 SSAO raw、SSAO blur、SSR trace、SSR denoise 分开计时,再根据瓶颈选择降采样、减少采样、缩短 ray、提升早退或调整 workload。
伪影应按“症状 → 数据原因 → 管线证据 → 修复动作”排查。下面的表把常见问题放回具体资源路径。
| 伪影 | 常见症状 | 数据原因 | 检查证据 | 修复动作 |
|---|---|---|---|---|
| banding | AO 或反射出现条带 | sample pattern 重复、step 太大、随机旋转不足 | raw AO、ray step view | 使用 blue noise、随机旋转、jitter、更多 step refinement |
| halo | 物体边缘出现亮圈或黑圈 | blur / upsample 跨越 depth discontinuity | depth edge mask、blur weight view | 增加 depth 与 normal 权重,收紧跨边缘混合 |
| ghosting | 运动区域残留上一帧阴影或反射 | motion vector 错误、history rejection 过松 | history weight、rejection mask | 使用 depth、normal、roughness 与 color clamp 共同拒绝历史 |
| edge leak | 屏幕边缘反射拉伸或突然消失 | ray 走出屏幕,fallback 混合过硬 | hit/miss mask、edge fade mask | 加 edge fade,提升 probe / sky fallback 权重 |
| undersampling | AO 颗粒、SSR 闪烁 | sample count 或 ray count 太低 | sample count heatmap、ray counter | 分层采样、temporal accumulation、按 roughness 分配 ray |
| self-occlusion | 平面发灰或立柱表面脏 | SSAO bias 太低,normal 或 depth 重建错误 | normal view、linear depth view | 调整 bias,修正 depth reconstruction,限制同平面贡献 |
| reflection swimming | 反射随相机微动漂移 | hit point 不稳定,ray step 量化明显 | hit UV view、ray length view | hierarchical traversal、binary refinement、temporal clamp |
Banding 的根源通常是采样结构被观众看见。SSAO 中,固定 kernel 如果没有 per-pixel random rotation,会在墙面形成重复纹理。SSR 中,固定 step ray marching 会在斜面反射上形成等距条带。解决思路是把低频结构打散为高频噪声,再用 temporal 或 spatial filter 收敛。blue noise 常用于这类场景,因为它把误差推向视觉上更易过滤的频段。
Halo 来自跨边缘混合。走廊立柱边缘如果 AO blur 读取到背景墙面,立柱外侧会出现黑边;SSR upsample 如果把地面反射扩散到墙面,墙脚会出现亮线。修复动作应落到边缘权重上:比较中心像素与邻域像素的 linear depth、normal、roughness 和 material id,只有几何与材质连续时才混合。若使用半分辨率效果,upsample 阶段比 raw pass 更容易制造 halo。
Ghosting 来自历史帧复用失效。temporal accumulation 的输入是上一帧结果、当前 motion vector、当前 depth 和当前 normal。只用 motion vector 做 reprojection 时,遮挡关系变化会把旧结果带回当前帧。SSR 尤其敏感,因为反射命中点可能属于另一个移动物体。可靠做法是为 history 提供 rejection mask:当前像素 depth 差异过大、normal 变化过大、roughness 分层变化、hit distance 跳变或 color neighborhood 超出 clamp 范围时,降低 history 权重。
Edge leak 来自屏幕空间可见域结束。SSR ray 走出屏幕后,算法已经没有新的 color 和 depth 可查询。直接保留最后一次采样会产生边缘拉伸,直接置零会产生突然消失。更稳定的做法是把 miss 原因分开:屏幕边缘 miss 使用 edge fade 过渡到 probe 或 sky;roughness 过高的 miss 直接增加 prefiltered environment 权重;被遮挡区域 miss 可使用 ray tracing、probe 或低频 reflection fallback。这样输出的反射能量在边界处连续变化。
Undersampling 与性能预算紧密相连。SSAO 可以通过 sample count、半分辨率、temporal accumulation 和 adaptive radius 平衡;SSR 可以通过 roughness classification、max ray distance、hierarchical depth、ray budget 和 denoise 平衡。调参时应先固定目标帧预算,例如 SSAO raw 0.5ms、blur 0.2ms、SSR trace 1.0ms、denoise 0.4ms,再观察质量。如果预算已固定,增加质量必须从减少无效像素开始,例如只在低 roughness 且 reflection weight 足够的 tile 发射 ray。
最终的屏幕空间效果质量来自三层约束同时成立:输入 buffer 的语义正确,采样或 ray marching 能覆盖目标现象,filter 与 fallback 能稳定处理缺失区域。走廊 frame 中,墙角暗部、立柱脚接触阴影和积水反射都可以由屏幕空间效果快速完成;窗外缺失反射、背后物体反射和高速运动拖影需要 fallback 与 history rejection 接管。工程判断的重点是识别当前像素是否仍在屏幕空间有效域内。
最小自检任务
给定一帧 deferred renderer 的走廊画面:opaque pass 已输出 depth、normal、roughness、motion vector 和 HDR direct color。画面中有一根立柱、一个墙角、一片低 roughness 积水和一个正在穿过画面的角色。要求设计 SSAO 与 SSR 的 pass 顺序,并说明如何排查三类问题:墙角 AO 颗粒明显、积水反射在屏幕边缘拉伸、角色经过后反射残留上一帧影子。
答案要点
pass 顺序应从 depth / G-buffer 开始,接着生成 direct lighting color,再执行 SSAO raw、SSAO edge-aware blur 或 upsample、SSR trace、SSR temporal / denoise、HDR composite,最后进入 tone mapping。SSAO 读取 depth 与 normal,输出 AO mask;SSR 读取 depth、normal、roughness、HDR direct color 和 motion vector,输出 reflection radiance、hit/miss mask 与 fallback weight。AO 应在 ambient 或 indirect 分量中合成,SSR 应在 HDR 阶段采样 lighting color。
墙角 AO 颗粒明显时,先看 raw AO。若形状正确但颗粒高,检查 sample count、noise rotation、temporal accumulation 和 blur;若整面墙发灰,检查 depth reconstruction、normal orientation、radius 和 bias。积水反射在屏幕边缘拉伸时,先看 SSR hit/miss mask 与 edge fade mask;ray 出屏幕后应提高 probe、sky 或 environment fallback 权重,并让边缘区域逐渐 fade。角色经过后反射残留上一帧影子时,先看 motion vector、history weight 和 rejection mask;当前 depth、normal、roughness、hit distance 或 color neighborhood 变化明显时,应降低 history 权重并使用当前帧结果。
这个任务的核心判断是把每个伪影映射到资源路径。AO 颗粒属于采样与过滤问题;边缘拉伸属于屏幕空间可见域结束;运动残影属于 temporal history 失效。三个问题都应先通过 debug view 定位,再调整采样、filter、rejection 或 fallback。
本章知识点总结
- 有效域:屏幕空间效果只能查询当前帧或历史帧中已经写入 buffer 的信息。
- 输入语义:depth、normal、roughness、color 和 motion vector 的坐标空间与编码决定效果上限。
- 深度重建:depth reconstruction 必须匹配 projection、reverse-Z 和 API depth range。
- SSAO 输入:SSAO 主要读取 depth 与 normal,并围绕当前表面点估计局部半球遮蔽。
- SSAO 参数:radius、bias、sample count、noise rotation 和 blur 共同决定 AO 的尺度、噪声和边缘稳定性。
- SSR 输入:SSR 读取 depth、normal、roughness、HDR color 与 motion vector,沿反射方向查找屏幕内命中点。
- 厚度判断:SSR 的 thickness 控制 ray 与 depth surface 的命中容忍度,过小会断裂,过大会吸附错误表面。
- 粗糙度分层:roughness 应影响 SSR ray 预算、denoise 强度和 fallback 权重。
- 半分辨率:AO 与粗糙反射适合降分辨率执行,但 upsample 必须受 depth 与 normal 约束。
- 历史复用:temporal accumulation 需要 motion vector、depth、normal、roughness 和 color clamp 共同控制 history 权重。
- 边界缺失:屏幕边缘、遮挡背后、背面几何和透明层会让屏幕空间查询缺失结果。
- 调试顺序:先看 input view,再看 raw mask,再看 filter / temporal,再看 composite。
- 伪影定位:banding、halo、ghosting、edge leak 和 undersampling 都能映射到具体 pass 与资源证据。
- 工程结论:屏幕空间效果适合低成本补充局部细节,稳定成品需要 fallback、debug view 和清晰的资源生命周期。