Screen Space Techniques — Why & What屏幕空间技术 — 为什么以及是什么

Screen space techniques are a family of rendering algorithms that approximate global effects (reflections, ambient occlusion, contact shadows) using only the data already rendered in the current frame's GBuffer and depth buffer — no additional geometry traversal, no BVH, no ray tracing hardware required. They trade physical accuracy for speed: only surfaces visible on screen can contribute, and results break at screen edges.

屏幕空间技术是一类渲染算法,仅使用当前帧 GBuffer 和深度缓冲区中已渲染的数据 来近似全局效果(反射、环境光遮蔽、接触阴影)——无需额外的几何体遍历, 无需 BVH,无需光线追踪硬件。它们以物理准确性换取速度: 只有屏幕上可见的表面才能贡献效果,在屏幕边缘结果会失效。

Screen Space Pipeline Position (in deferred rendering): GBuffer Pass → Depth Buffer → ┬→ SSAO (AO from depth + normals) ├→ SSR (reflections from depth + normals + color) └→ Contact (shadow from depth along light dir) All three read: - SceneDepth (reconstruct world position) - GBufferA.rgb (world normal) - GBufferB (roughness for SSR importance) SSR additionally reads: - SceneColor (the "mirror" image to reflect)

SSR — Screen Space Reflections via Hi-Z Ray MarchingSSR — 通过 Hi-Z 光线步进实现屏幕空间反射

Screen Space Reflections (SSR) computes specular indirect lighting by tracing reflection rays in screen space. Instead of tracing into a 3D scene, the ray marches through a Hi-Z mipmap of the depth buffer — much faster than BVH traversal.

屏幕空间反射(SSR)通过在屏幕空间追踪反射光线来计算镜面间接光照。 不是追踪到 3D 场景,而是光线步进穿过深度缓冲区的 Hi-Z Mipmap—— 比 BVH 遍历快得多。

Hi-Z AccelerationHi-Z 加速

The Hi-Z (Hierarchical Z) buffer is a depth buffer mip chain where each level stores the maximum depth of its 2×2 children. A ray step can first test against a coarse mip level — if the ray is entirely in front of the maximum depth at that level, it cannot possibly intersect anything in that region. This allows large empty regions to be skipped in one step.

Hi-Z(分层 Z)缓冲区是深度缓冲区的 Mip 链,每个级别存储其 2×2 子像素的最大深度。 光线步进可以先针对粗糙的 Mip 级别进行测试——如果光线在该级别最大深度的完全前方, 它不可能与该区域的任何物体相交。这允许在一步中跳过大片空白区域。

// ScreenSpaceRayTracing.cpp — HiZ ray march (conceptual)

bool CastSSRRay(
    float3 RayOrigin,    // world space position of reflective pixel
    float3 ReflectDir,   // mirror reflection direction
    out float2 HitUV,    // screen UV of hit (if found)
    out float HitMask)   // confidence [0,1]
{
    // Project ray into clip space for screen-space traversal
    float3 RayStartScreen = WorldToScreen(RayOrigin);
    float3 RayDirScreen   = normalize(WorldToScreen(RayOrigin + ReflectDir) - RayStartScreen);

    float3 RayPos = RayStartScreen;
    int MipLevel = 0;

    LOOP for (int i = 0; i < MAX_STEPS && MipLevel >= 0; ++i)
    {
        // Step size scales with current mip level (coarser = bigger step)
        float CellSize = pow(2.0f, float(MipLevel)) * InvScreenSize;

        // Advance to next cell boundary
        RayPos += RayDirScreen * CellSize;

        // Read Hi-Z depth at current mip level
        float SceneDepth = HiZBuffer.SampleLevel(PointSampler, RayPos.xy, MipLevel);

        if (RayPos.z > SceneDepth)
        {
            // Ray is behind scene — intersection found!
            if (MipLevel == 0) {
                HitUV   = RayPos.xy;
                HitMask = 1.0f;
                return true;
            }
            MipLevel--;  // refine at finer mip level
        }
        else
        {
            MipLevel = min(MipLevel + 1, MAX_MIP);  // advance at coarser mip
        }
    }
    return false;  // no intersection found within max steps
}

Once a hit is found, UE5 applies a fade mask at screen edges (reflections that point off-screen are invalid) and fades out reflections based on roughness (rough surfaces = wide cone = SSR breaks down, fallback to reflection captures).

一旦找到命中,UE5 在屏幕边缘应用衰退遮罩(指向屏幕外的反射无效), 并根据 roughness 使反射衰退 (粗糙表面 = 宽锥体 = SSR 失效,回退到反射捕获)。

SSR Tile Classification & Temporal DenoisingSSR 瓦片分类与时域降噪

The SSR pass uses tile classification (ScreenSpaceReflectionTiles.h) to skip tiles that have no reflective pixels (roughness too high, or not in a reflective material). This can save 30–60% of SSR compute on typical scenes where most surfaces are rough.

SSR 通道使用瓦片分类ScreenSpaceReflectionTiles.h) 跳过没有反射像素的瓦片(粗糙度过高或非反射材质)。 在大多数表面粗糙的典型场景中,这可以节省 30–60% 的 SSR 计算量。

SSR is inherently noisy at 1 sample/pixel. UE5 accumulates SSR results over multiple frames using temporal reprojection (the same mechanism as TAA), storing a history buffer with the previous frame's SSR. Pixels that fail temporal reprojection (disocclusion, fast-moving objects) fall back to a lower-quality single-frame estimate.

SSR 在每像素 1 个样本时本质上是有噪声的。UE5 使用时域重投影(与 TAA 相同的机制) 跨多帧累积 SSR 结果,存储包含上一帧 SSR 的历史缓冲区。 时域重投影失败的像素(遮挡、快速移动物体)回退到质量较低的单帧估计。

SSAO — Horizon-Based Ambient OcclusionSSAO — 基于水平线的环境光遮蔽

Screen Space Ambient Occlusion (SSAO) approximates how much ambient light a surface point receives — specifically, how much of its hemisphere of directions is occluded by nearby geometry. It produces characteristic soft contact shadows in corners and crevices.

屏幕空间环境光遮蔽(SSAO)近似表面点接收多少环境光—— 具体来说,其半球方向上有多少被附近几何体遮蔽。 它在角落和缝隙中产生特有的柔和接触阴影。

Algorithm: GTAO (Ground Truth AO approximation)算法:GTAO(地面真实 AO 近似)

UE5 uses GTAO (Ground Truth Ambient Occlusion), a horizon-based method that samples the depth buffer in multiple directions around each pixel to find the maximum elevation angles (horizons). The integral of blocked directions over the hemisphere gives the AO term.

UE5 使用 GTAO(地面真实环境光遮蔽),这是一种基于水平线的方法, 在每个像素周围的多个方向上采样深度缓冲区以找到最大仰角(水平线)。 在半球上被遮挡方向的积分给出 AO 项。

// PostProcessAmbientOcclusion.cpp — GTAO concept

float ComputeGTAO(float2 PixelUV, float3 WorldPos, float3 WorldNormal)
{
    float AO = 0;
    const int NumDirections = 4;    // 4 slice directions around hemisphere
    const int NumStepsPerDir = 4;   // 4 depth samples per direction

    LOOP for (int d = 0; d < NumDirections; ++d)
    {
        // Random rotation per pixel (blue noise to avoid structured patterns)
        float Angle = (float(d) + BlueNoise(PixelUV)) / float(NumDirections) * PI;
        float2 SliceDir = float2(cos(Angle), sin(Angle));

        // Find max horizon angle in this slice direction
        float HorizonAngle = -PI / 2.0f;  // start at -90°

        LOOP for (int s = 1; s <= NumStepsPerDir; ++s)
        {
            float2 SampleUV = PixelUV + SliceDir * float(s) * AORadius * InvScreenSize;
            float3 SamplePos = ReconstructWorldPos(SampleUV);
            float3 HorizonVec = SamplePos - WorldPos;
            float  HorizonAngleCandidate = atan2(HorizonVec.z,
                                                   length(HorizonVec.xy));
            HorizonAngle = max(HorizonAngle, HorizonAngleCandidate);
        }

        // Bent normal contribution: how much of this slice's hemisphere is visible
        float NormalBias = dot(WorldNormal, float3(SliceDir, 0));
        AO += saturate(1.0f - sin(HorizonAngle)) * NormalBias;
    }

    return 1.0f - AO / float(NumDirections);  // normalize to [0,1]
}

The GTAO result is temporally accumulated using a history buffer (same reprojection as SSR). The accumulated AO is then composited with the scene: it multiplies the ambient/sky light contribution, darkening corners and contact areas.

GTAO 结果使用历史缓冲区在时域中累积(与 SSR 相同的重投影)。 累积的 AO 然后与场景合成:它乘以环境/天空光贡献,使角落和接触区域变暗。

Contact Shadows — Micro-Geometry Self-Shadowing接触阴影 — 微几何体自阴影

Standard shadow maps have a minimum resolution limit — they cannot represent the thin shadow cast by a character's shoe touching the floor, or the shadow under a pebble. Contact shadows solve this by tracing a short ray in screen space toward the light source for each pixel. If the ray intersects scene depth before reaching the light, the pixel is in contact shadow.

标准阴影贴图有最小分辨率限制——它们无法表示角色鞋子接触地板投射的细薄阴影, 或小石子下的阴影。接触阴影通过对每个像素在屏幕空间中 朝向光源追踪短光线来解决这个问题。 如果光线在到达光源之前与场景深度相交,则该像素在接触阴影中。

// ScreenSpaceShadows.cpp — per-light contact shadow ray

float ComputeContactShadow(
    float2 PixelUV, float3 WorldPos,
    float3 LightDir,              // direction from pixel to light
    float ContactShadowLength)    // max ray length (world units)
{
    // Project ray into screen space
    float3 RayEnd     = WorldPos + LightDir * ContactShadowLength;
    float2 RayEndScreen = WorldToScreenUV(RayEnd);
    float2 RayDirUV   = RayEndScreen - PixelUV;
    float  RayLength  = length(RayDirUV);

    const int NumSteps = 8;
    float StepSize = 1.0f / float(NumSteps);

    LOOP for (int i = 1; i <= NumSteps; ++i)
    {
        float t = float(i) * StepSize;
        float2 SampleUV = PixelUV + RayDirUV * t;
        float3 SamplePos = ReconstructWorldPos(SampleUV);

        // Is this sample position behind the scene?
        if (SamplePos.z < WorldToLinearDepth(SamplePos))
        {
            return 0.0f;  // in shadow
        }
    }
    return 1.0f;  // lit
}

Contact shadows are enabled per-light (not globally). Each light with contact shadows enabled adds one screen-space ray pass per frame. They work best for small distances (0.1–0.5m). For larger distances, they exhibit the characteristic "stretching" artifact near screen edges.

接触阴影逐灯光启用(而非全局)。每个启用接触阴影的灯光每帧添加一次屏幕空间光线通道。它们在短距离(0.1–0.5m)效果最好。对于较大距离,在屏幕边缘附近会出现特有的"拉伸"伪影。

Screen Space Denoising屏幕空间降噪

Both SSR and SSAO are noisy at 1 sample/pixel. UE5 runs a spatiotemporal denoiser (ScreenSpaceDenoise.h/.cpp) after each effect:

UE5 在每个效果后运行时空降噪器ScreenSpaceDenoise.h/.cpp):

Stage阶段Operation操作
Temporal Reprojection时域重投影Reproject previous frame's result using motion vectors. Accumulate with current frame (α = 0.1–0.2 per frame). Reject history on disocclusion (depth difference threshold).使用运动向量重投影上一帧结果。与当前帧累积(每帧 α = 0.1–0.2)。在遮挡消失时拒绝历史(深度差阈值)。
Spatial Filter空间滤波5×5 bilateral Gaussian filter: preserves edges using normal + depth discontinuity detection.5×5 双边高斯滤波:使用法线 + 深度不连续性检测保留边缘。
Variance Clipping方差裁剪Clip temporal accumulation to the current frame's neighborhood color/value range. Prevents ghosting on fast-moving objects.将时域累积裁剪到当前帧邻域颜色/值范围。防止快速移动物体上的重影。

When to Use Screen Space vs Ray Tracing何时使用屏幕空间而非光线追踪

Scenario场景 Best Option最佳选择 Why原因
Reflections on rough surfaces (Rough > 0.3)粗糙表面上的反射(粗糙度 > 0.3)Reflection Captures / Lumen反射捕获 / LumenSSR cone is too wide — noise cannot be denoised to stable resultSSR 锥体太宽——噪声无法降噪为稳定结果
Mirror/near-mirror surfaces (Rough < 0.1)镜面/近镜面表面(粗糙度 < 0.1)SSR (with Lumen fallback)SSR(带 Lumen 回退)SSR is near-perfect for mirrors, very fast, Lumen for off-screenSSR 对镜面几乎完美,非常快,Lumen 处理屏幕外
AO for hero characters (close-up)英雄角色的 AO(特写)SSAO + Capsule ShadowsSSAO + 胶囊体阴影SSAO catches detail. Capsule shadows handle large-scale self-AO.SSAO 捕获细节。胶囊体阴影处理大规模自 AO。
AO for outdoor/large scene室外/大场景的 AOLumen SSAO / DFAOLumen SSAO / DFAOSSAO radius is too small for building-scale. Use Distance Field AO instead.SSAO 半径对建筑尺度太小。改用距离场 AO。
Contact shadows for all lights所有灯光的接触阴影Contact Shadows (per-light)接触阴影(逐灯光)Cheapest way to get micro-contact shadows. Only enable on key lights.获得微接触阴影的最便宜方式。仅在关键灯光上启用。

Performance Tuning性能调优

CVarEffect效果
r.SSR.Quality0=off, 1=low (no denoiser), 2=medium, 3=high, 4=very high. Default=3.0=关,1=低(无降噪),2=中,3=高,4=非常高。默认=3。
r.SSR.MaxRoughnessMax roughness for SSR (above this = no SSR). Default=0.6.SSR 最大粗糙度(超过此值 = 无 SSR)。默认=0.6。
r.SSR.HalfResScatter1 = compute SSR at half resolution (faster, slightly less quality)1 = 以半分辨率计算 SSR(更快,质量稍低)
r.AmbientOcclusion.Method0=SSAO, 1=GTAO (default), 2=DFAO. GTAO is best quality.0=SSAO,1=GTAO(默认),2=DFAO。GTAO 质量最好。
r.AmbientOcclusionSampleSetSizeScaleScale AO sample count (0.5=half samples=2× faster, noisy)缩放 AO 样本数(0.5=一半样本=2× 更快,噪声更多)
r.ContactShadows0 = disable all contact shadows globally0 = 全局禁用所有接触阴影

Source Navigation源码导航

Unreal Engine 5.6 · Engine/Source/Runtime/Renderer/Private/
Source analysis · For learning purposes