前言

本文总结了the surge、ue、寒霜关于ssr的技术分享并加以实现

两种算法

镜面反射

  • 先拿到color buffer、normal buffer、depth buffer的信息

    normal buffer:用于计算反射

    depth buffer:用于判断raymarching的光线是否与场景相交,相交则有贡献

  • 沿着反射射线方向追踪

    1. 每次步进固定长度得到test point,把该test point转换到屏幕空间坐标(u,v,z)
    2. 使用该z值,与用(u, v)采样得到的深度图d,进行比较。若z > d,说明光线和场景有相交部分,并返回该test point的信息;否则,重复上述步骤
  • 用交点的颜色作为反射颜色

UE和the surge都使用的这种方法

随机反射

如下图所示,雨天中,在地面的水潭的反射效果在垂直方向存在拉伸,且在反射平面上也存在较高频率的粗糙度与法线的变化。针对这种情况,寒霜提出了随机屏幕空间反射
1733070959042.png

寒霜的效果预期如下:

  • 支持清晰与模糊的反射
  • 在接触区域有比较清晰的反射表现
  • 为了提升真实感,需要纵向的拉伸效果
  • 支持逐像素的粗糙度与法线反射调整

The Surge SSR

总体流程如下:

1733071393544.png

分块分类(tile-based classification)->Raymarching->对得到的结果进行多次模糊,每次都作为反射结果的mipmap->去噪->异步处理(优化)

Tile-Based classification

1733071639268.png

因为不一定每个uv都会有反射效果,所以需要识别屏幕中需要SSR的区域,并将之划分成不同的tile。划分后,对每个tile评估,计算每个tile反射对应所需要的rays count

Ray Marching

1733071881662.png

  • 由于SSR Raymarching的耗能一般都较大,想要使用较大的步长且较小的消耗就需要降分辨率,且the surge在低分辨率下还将交错奇数偶数块分为两帧

  • The surge抖动了normal ——即用到了GGX重要性采样

  • 在计算反射数据时,ray march的步数是固定的,且抖动了ray,以避免步进次数不够高,造成的锯齿
  • 利用到了时空抗锯齿,即复用前一帧的反射数据
  • 在一个2x2的核中,计算高分辨率图的目标pixel对应的depth和该pixel在低分辨率旁边四个pixel的depth差,选择最接近全分辨率深度并返回该样本对应的低分辨率颜色
  • 超出屏幕的反射射线,不做SSR,而是采样反射探针

GGX重要性采样和Mask效果如下:

1733078465772.png

其中Mask还做了边缘的消散

Resolve

Blur生成的mipmap效果如下:

1733078559176.png

Deinterleave and Reproject

  • 空间滤波:利用临近的hit点做混合
  • 时间滤波:利用上一帧的同一个uv数据做混合

1733078807593.png

寒霜 SSR

寒霜提出的流程和The surge差不多,不同之处在于用Hi Z加速Ray March。虽然寒霜的效果更好,但消耗也更大,下面来看几个重要部分

Hi Z

Hi-Z将屏幕深度的层次关系存储在MipMap层级中,用于加速反射光线的求交

Hi-Z的构建时,对屏幕尺寸的深度进行滤波,每次保存2x2像素中最浅的像素

在比较相机视角的场景深度和反射光线的深度时,若反射光线的深度小于场景深度,mipMap Level++;若反射光线的深度大于等于场景深度,mipMap Level--。随后继续步进,重复上述步骤,直至mipMap Level小于0,当前得到的场景深度即为想要的

两种Ray Tracing方式

  • 对于光滑的反射平面,需要较为清晰的反射效果,寒霜选择使用精确的HiZ tracing,这种方法得到给出反射射线的精确交点

  • 对于粗糙的反射平面,使用性能较好但反射效果较为粗糙的线性ray tracing方法,这种方式得到的反射结果通常会需要经过模糊处理来掩盖数据的瑕疵

    通常会用到对步长抖动来掩盖锯齿瑕疵

1733080558149.png

Patiofilter

重要性采样积分次数不够会导致噪点过多,可以采用空间滤波降噪。当计算出反射光线与场景交叉时,可以用它相邻的uv数据假装成它发射的光线,从而得到更多的样本

随后乘以权重

需要注意的是:因为各个点的PDF是不同的。若几个点的θ_h为半程向量(即采样方向)与宏观法线夹角差异较大,得到的PDF的差异也会较大,那么反射结果就存在噪点

为了避免当俯角差异过大导致的PDF差异过大,可以对随机数进行截断(截断较小的值)并重映射,进而得到更为平缓的PDF值

实现

这里仅仅展示重要实现部分。由于笔者还没了解过tile-based classification,因此这部分暂时省略,以后填坑

对不同roughness采用不同方案

对于较为光滑的物体应该走完全的镜面反射,而非随机的,笔者采用的方案是roughness小于0.1的,走镜面反射,大于的走随机反射

if(roughness > 0.1)
{
    float2 E = _BlueNoiseTex.SampleLevel(Smp_RepeatU_RepeatV_Linear, screenUV + Rand1SPPDenoiserInput(screenUV), 0).xy;
    E.x = lerp(E.x, 0.f, _BRDFBias);
    // 获取当前法线在切线空间的3个正交的基向量
    float3x3 TangentBasis = GetTangentBasis( normalWS );
    // 切线空间的viewDir向量
    float3 TangentV = mul( TangentBasis, viewDirWS );
    float4 halfVectorOS = ImportanceSampleVisibleGGX(E, pow2(roughness), TangentV );
    PDF = halfVectorOS.w;
    float4 H = float4(mul(halfVectorOS.xyz, TangentBasis ), halfVectorOS.w);
    reflectDir = reflect(-viewDirWS, H.xyz);
}
else
{
    reflectDir = reflect(-viewDirWS, normalWS);
    PDF = 1;
}

在计算反射颜色阶段会用到空间滤波,复用当前帧临近的反射数据,并加权平均,这会加强随机反射的效果。因此笔者采用的方案是,roughness小于0.1的,不使用空间滤波;大于的复用周围4个或8个像素点的反射数据

uint numRays = 1;

half totalWeight = 0;
half4 totalColor = 0, currColor = 0;
if(roughness > 0.1)
{
    #if defined (_SSR_QUALITY_LOW)
        numRays = 4;
    #elif defined (_SSR_QUALITY_MIDDLE)
        numRays = 6;
    #elif defined (_SSR_QUALITY_HIGH)
        numRays = 8;
    #endif
}
else
{
    numRays = 1;
}

for(uint i = 0; i < numRays; ++i)
{
    float2 offsetUV = mul(offsetRotationMatrix, offset[i] * _RayMarchTexSize.zw);
    float2 neighborUV = uv + offsetUV;

    float4 hitData  = _SSRHitDataTex.SampleLevel(Smp_ClampU_ClampV_Linear, neighborUV, 0);
    float hitPDF    = _SSRHitPDFTex.SampleLevel(Smp_ClampU_ClampV_Linear, neighborUV, 0);
    float2 hitUV    = hitData.rg;
    float hitDepth  = hitData.b;
    float hitMask   = hitData.a;

    float4 hitPositionNDC = GetPositionNDC(hitUV, hitDepth);
    float3 hitPositionVS = GetPositionVS(hitPositionNDC, Matrix_I_P);

    half currWeight = BRDF_UE4(normalize(-positionVS), normalize(hitPositionVS - positionVS), normalVS, roughness) / max(1e-5, hitPDF);

    currColor.rgb = _CameraColorTexture.SampleLevel(Smp_ClampU_ClampV_Linear, hitUV, 0);
    currColor.a = hitMask;

    totalColor += currColor * currWeight;
    totalWeight += currWeight;
}

时空滤波会使得画面变糊,对于光滑的地面,应该尽可能使前一帧的占比尽量低。因此笔者采用的方案是,roughness小于0.1的,上一帧仅仅占0.1的比例

降采样

这里采用UE的思路,升采样时计算高分辨率图的目标pixel对应的depth和该pixel在低分辨率旁边四个pixel的depth差,选择最接近全分辨率深度并返回该样本对应的低分辨率颜色

1733081424064.png

升采样前和升采样后效果分别如下图所示:

1733081088404.png

1733081123532.png

Bounded VNDF Sampling GGX重要性采样

这里采样Bounded VNDF Sampling GGX重要性采样,该方法渲染耗时略高,但能在有限的采样率下更好地还原高粗糙度表面的外观。效果区别如下:

1733081663364.png

Mip map Blur

为了实现随着roughness的增加,反射效果变得更加模糊,需要对反射RT进行模糊,每次模糊都作为一层mipmap。在最后合并时,让roughness和blur的强度挂钩即可

这里模糊采用The Surge提到的7x7模糊

模糊前和模糊后效果如下:

1733146834295.png

1733146868306.png

最终效果

1733148182139.png

Reference

基于屏幕空间的实时全局光照(Real-time Global Illumination Based On Screen Space)

GDC 2015 Stochastic Screen-Space Reflections

Visible NDF重要性采样实践

Unity StochasticSSR-03

剖析虚幻渲染体系(07)- 后处理

UE SSR Realization

基于预计算的实时环境光照(Real-time Environment Lighting Based On Precomputation)


他们曾如此骄傲的活过,贯彻始终