【实时渲染】SVGF Spatiotemporal Variance-Guided Filtering

在这里插入图片描述
流程主要就是昨天Games202上介绍的三个步骤,理清这三步怎么做的就可以了。下面分析的是Nvidia的实现代码。

Pass1 Reprojection

输入:Color,Emission,Albedo
输出 illumination,moments,historylength

(1)当前帧的illumination:

i l l u m i n a t i o n C u r r e n t = g C o l o r − g E m i s s i o n m a x ( g A l b e d o , e p s ) illuminationCurrent=\frac{gColor-gEmission}{max(gAlbedo,eps)} illuminationCurrent=max(gAlbedo,eps)gColorgEmission

(2)取上一帧的illumination,moments,historylength

(2.1)如果上一帧对应位置无效则仅以当前帧信息计算

(2.2)如果有效:

1.计算historyLength
historyLength = historyLength + 1.0f

2.计算当前帧的momentCurrent

    float2 momentCurrent;
    momentCurrent.r = luminance(illumination);
    momentCurrent.g = momentCurrent.r * momentCurrent.r;

插值得到moments结果

moments = lerp(prevMoments, momentCurrent, 1/historylength);

3.插值得到当前帧的illuminationCurrent
illumination=lerp(prevIllumination, illuminationCurrent, 1/historylength);

返回结果,
1.返回float historyLength
2.返回float3 moments
3.返回float4 Out_illumination

Out_illumination.xyz=illumination
Out_illumination.w=variance=moments.y - moments.x * moments.x

Pass2 Filter moments and illumination

当historyLength>4时不需要filter了直接将输入输出,如果<4需要filter

Filter过程:
对周围7x7区域加权即可。

权重计算公式如下:

float computeWeight(
    float depthCenter, float depthP, float phiDepth,
    float3 normalCenter, float3 normalP, float phiNormal,
    float luminanceIllumCenter, float luminanceIllumP, float phiIllum)
{
    const float weightNormal  = pow(saturate(dot(normalCenter, normalP)), phiNormal);
    const float weightZ       = (phiDepth == 0) ? 0.0f : abs(depthCenter - depthP) / phiDepth;
    const float weightLillum  = abs(luminanceIllumCenter - luminanceIllumP) / phiIllum;

    const float weightIllum   = exp(0.0 - max(weightLillum, 0.0) - max(weightZ, 0.0)) * weightNormal;

    return weightIllum;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结下来就是如果法线相差,深度,illumination相差越大,filter的权重越小。

两种例外:
第一个,当两个采样点深度相差大但是位于同一表面上时p对中心点的权重应该更大,解决方法是除以phiDepth,它的计算方式是两者采样点之间的距离在深度的导数上的"投影",zCenterDerivat * length(float2(xx, yy))

第二个,两个采样点的illumination相近,原先的权重会很大,但是中心点illumination可能是由于噪声产生过亮导致的和另一采样点相近,所以权重应该更小。修正方法是,如果该点illumination方差过大,则权重应该降低。

由于Pass2中不含illumination方差信息(illumination方差信息是Pass2的输出),所以第二个例外没有考虑,在Pass3中才被考虑

Pass3 最后filter

为了提高效率,使用a-trous wavelet transform进行filter
下面的步骤迭代多次,每次进行5x5的滤波,每次滤波间隔为 2 i 2^i 2i
在这里插入图片描述

1.将Pass2输出的方差在进行3*3的高斯模糊

float computeVarianceCenter(int2 ipos)
{
    float sum = 0.f;

    const float kernel[2][2] = {
        { 1.0 / 4.0, 1.0 / 8.0  },
        { 1.0 / 8.0, 1.0 / 16.0 }
    };
    const int radius = 1;
    for (int yy = -radius; yy <= radius; yy++)
    {
        for (int xx = -radius; xx <= radius; xx++)
        {
            const int2 p = ipos + int2(xx, yy);
            const float k = kernel[abs(xx)][abs(yy)];
            sum += gIllumination.Load(int3(p, 0)).a * k;
        }
    }
    return sum;
}

2.将步骤1的结果进行5x5的filter,方式和Pass2中filter的方式一样,不过这次需要考虑例外2。

步骤一将Pass2的方差进行了filter,用filter的结果计算:
在这里插入图片描述

σ l g 3 × 3 V a r ( l i ( c e n t e r ) ) + e p s \sigma_l\sqrt{g_{3\times 3}Var(l_i(center))}+eps σlg3×3Var(li(center)) +eps

对应代码:

 const float phiLIllumination   = gPhiColor * sqrt(max(0.0, epsVariance + var.r));

Pass4 输出结果

float4 main(FullScreenPassVsOut vsOut) : SV_TARGET0
{
    const int2 ipos = int2(vsOut.posH.xy);

    return gAlbedo[ipos] * gIllumination[ipos] + gEmission[ipos];
}

Pass1代码


PS_OUT main(FullScreenPassVsOut vsOut)
{
    const float4 posH = vsOut.posH;
    const int2 ipos = posH.xy;

    float3 illumination = demodulate(gColor[ipos].rgb - gEmission[ipos].rgb, gAlbedo[ipos].rgb);
    if (isNaN(illumination.x) || isNaN(illumination.y) || isNaN(illumination.z))
    {
        illumination = float3(0, 0, 0);
    }

    float historyLength;
    float4 prevIllumination;
    float2 prevMoments;
    bool success = loadPrevData(posH.xy, prevIllumination, prevMoments, historyLength);
    historyLength = min(32.0f, success ? historyLength + 1.0f : 1.0f);

    const float alpha        = success ? max(gAlpha,        1.0 / historyLength) : 1.0;
    const float alphaMoments = success ? max(gMomentsAlpha, 1.0 / historyLength) : 1.0;

    float2 moments;
    moments.r = luminance(illumination);
    moments.g = moments.r * moments.r;
    moments = lerp(prevMoments, moments, alphaMoments);

    float variance = max(0.f, moments.g - moments.r * moments.r);

    PS_OUT psOut;
    psOut.OutIllumination = lerp(prevIllumination,   float4(illumination,   0), alpha);

    psOut.OutIllumination.a = variance;
    psOut.OutMoments = moments;
    psOut.OutHistoryLength = historyLength;

    return psOut;
}

Pass3代码


float4 main(FullScreenPassVsOut vsOut) : SV_TARGET0
{
    const int2 ipos       = int2(vsOut.posH.xy);
    const int2 screenSize = getTextureDims(gAlbedo, 0);

    const float epsVariance      = 1e-10;
    const float kernelWeights[3] = { 1.0, 2.0 / 3.0, 1.0 / 6.0 };

    const float4 illuminationCenter = gIllumination.Load(int3(ipos, 0));
    const float lIlluminationCenter = luminance(illuminationCenter.rgb);

    const float var = computeVarianceCenter(ipos);

    const float historyLength = gHistoryLength[ipos].x;

    const float2 zCenter = gLinearZAndNormal[ipos].xy;
    if (zCenter.x < 0)
    {
        return illuminationCenter;
    }
    const float3 nCenter = oct_to_ndir_snorm(gLinearZAndNormal[ipos].zw);

    const float phiLIllumination   = gPhiColor * sqrt(max(0.0, epsVariance + var.r));
    const float phiDepth     = max(zCenter.y, 1e-8) * gStepSize;

    float sumWIllumination   = 1.0;
    float4  sumIllumination  = illuminationCenter;

    for (int yy = -2; yy <= 2; yy++)
    {
        for (int xx = -2; xx <= 2; xx++)
        {
            const int2 p     = ipos + int2(xx, yy) * gStepSize;
            const bool inside = all(p >= int2(0,0)) && all(p < screenSize);

            const float kernel = kernelWeights[abs(xx)] * kernelWeights[abs(yy)];

            if (inside && (xx != 0 || yy != 0)) 
            {
                const float4 illuminationP = gIllumination.Load(int3(p, 0));
                const float lIlluminationP = luminance(illuminationP.rgb);
                const float zP = gLinearZAndNormal[p].x;
                const float3 nP = oct_to_ndir_snorm(gLinearZAndNormal[p].zw);

                const float2 w = computeWeight(
                    zCenter.x, zP, phiDepth * length(float2(xx, yy)),
                    nCenter, nP, gPhiNormal,
                    lIlluminationCenter, lIlluminationP, phiLIllumination);

                const float wIllumination = w.x * kernel;

                sumWIllumination  += wIllumination;
                sumIllumination   += float4(wIllumination.xxx, wIllumination * wIllumination) * illuminationP;
            }
        }
    }

    float4 filteredIllumination = float4(sumIllumination / float4(sumWIllumination.xxx, sumWIllumination * sumWIllumination));

    return filteredIllumination;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值