(论文)[2019-RTGEM] Ray Traced Shadows: Maintaining Real-Time Frame Rates

Ray Traced Shadows

  • 适应性采样:通过局部变化量来计算采样样本数
    • 极差 => 空间滤波 => 时间平均
  • 后处理
    • 时域平均 + 空间(可变半径高斯双边滤波)【半径由局部变化量决定】

Introduction

  • 光栅化阴影的问题
    • Perspective aliasing【锯齿】
    • 自遮挡问题【shadow acne,peter panning】
    • 软阴影
    • 半透明物体
  • 这个论文对 DI 的近似
    • 【式子没有考虑自发光,每个光源单独计算】
    • 这里阴影的定义就是 area-domain 的平均
    • 着色使用光源中心计算

\[ L_d(P,\omega_o) \approx f(P,\omega_C,\omega_o) L_a(C,\omega_C) \frac{ (\omega_C\cdot\hat{\mathbf{n}}_P) (-\omega_C\cdot\hat{\mathbf{n}}_C) }{ \left\Vert P-C\right\Vert^2 } \frac{1}{|S|} \sum_{X\in S} v(P,X) \]

Adaptive Sampling

Temporal Reprojection

  • 两个目的
    • 估计 visibility variation【可见性变化程度】,从而计算光线数
    • 决定后处理的 kernel size
  • 一共保存 4 帧的样本作为 cache
  • 将当前帧重投影回前 3 帧【reverse reprojection】
    • 【clip space,投影矩阵变换后未归一化的空间】
    • 假设世界空间位置不变

\[ \bar{P}_{t-1} = C_{t-1} V_{t-1} V_t^{-1} C_t^{-1} P_t \]

  • 通过深度连续性判断是否保留
    • \(c_1=0.003,c_2=0.017\)
    • \(\hat{\mathbf{n}}_z\):view space normal
    • \(P^{z}\):深度【只保留了上一帧就足够】
    • 我看实现代码,就是把 \(\epsilon=0.003\)

\[ \left| 1-\frac{\bar{P}_{t-1}^{z}}{P_{t-1}^{z}} \right| < \varepsilon, \quad \varepsilon = c_1+c_2\left|\hat{\mathbf{n}}_z\right| \]

半影区域判定

  • 启发式的使用 temporal variation measure
    • 四帧历史的 visibility 极差【存在一个 float4 中】
    • 历史 visibility 是重投影后的

\[ \Delta v_t(x) = \max\left( v_{t-1}(x),\ldots,v_{t-4}(x) \right) - \min\left( v_{t-1}(x),\ldots,v_{t-4}(x) \right) \]

  • 维持时域稳定,对 \(\Delta v_t\) 进行 spatial filter 得到 \(\widetilde{\Delta v_t}\)
    • 5x5 maximum filter【2 pass】
    • 13x13 tent filter【倒三角权重形式】
      • 实现代码:9x9 average filter【2 pass】
  • 和历史保存信息平均

\[ \overline{\Delta v}_t = \frac{1}{2} \left( \widetilde{\Delta v}_t + \frac{1}{4} \left( \Delta v_{t-1} + \Delta v_{t-2} + \Delta v_{t-3} + \Delta v_{t-4} \right) \right) \]

分配样本

  • 通过阈值 \(\delta\) 动态调整样本个数【实现用的 0.1】
  • \(s_{\max}\):标准 5,高质量模式 8
  • 调整
    • \(\overline{\Delta v}_t>\delta\)\(s_{t}(x)=\min\left(s_{t-1}(x)+1,s_{\max}\right)\)
    • \(\overline{\Delta v}_t<\delta\)\(s_{t}(x)=\max\left(s_{t-1}(x)-1,0\right)\)
  • 比较稳定
  • 如果重投影失效,那么就重置为当前帧结果;设置为 \(s_{\max}\)

Sampling Mask

  • variation 为 0 表示全白/全黑;直接使用历史信息
    • 相机慢速 zoom、光源快速移动可能出问题【重投影成功,但是 visibility 变化】
  • 实现:4 个像素至少发一条光线
    • 直接这么做性能低,warp 依赖的问题【和都发一条性能差不多】
    • 采样一个 block \(n_b\times n_b\) 【8x8 效果不错】
      • 只有被选中的 block 内部,variation = 0,此时才不发光线
    • 实现上使用一个 4x4 的 block mask【循环】,用于标记当前 block 是否被选中【block 对应的 mask = frameCount%4 则被选中】

计算最终值

  • temporal filtering + spatial filtering
    • 相关工作:SVGF,Optix
    • 这里使用简单的实现方案
  • 时域

\[ \tilde{v}_t = \frac{1}{4} \left( v_t + v_{t-1} + v_{t-2} + v_{t-3} \right) \]

  • 空域:可变半径双边滤波【1x1 => 9x9】【2 pass】
    • 半径和 \(\widetilde{\Delta v}_t\) 线性相关【kernel 半径:\(\widetilde{\Delta v}_t/\eta\times4\)
      • 把最大半径对应到 \(\widetilde{\Delta v}_t=\eta=0.4\)
    • 防止抖动:预定义高斯权重,然后从相邻的两级高斯进行线性插值【根据半径】
    • 判断当前像素能否复用
      • view-space normal 差距【点积 > 0.9】
      • 深度差异:通过视线方向插值【掠射角取最大阈值,正射最小阈值】【判断是不是属于一个表面】
        • 阈值:0.003;0.02
1
2
3
4
5
6
7
8
9
10
bool isValidTap(float tapDepth, float centerDepth, float3 tapNormal, float3 centerNormal)
{
const float3 centerNormalV = normalize(mul((float3x3)gViewMat, centerNormal));
const float dotViewNormal = abs(centerNormalV.z);
const float depthEpsilon = lerp(gDepthEpsilonMax, gDepthEpsilonMin, dotViewNormal);
if (centerDepth <= 0.f || abs(1.f - tapDepth / centerDepth) > depthEpsilon) {
return false;
}
return dot(tapNormal, centerNormal) >= gDotNormalsEpsilon;
}
  • 这样的空域滤波变成不可分的,但是还是 2 个 pass 实现,效果还行

实现

  • 对 Spherical Light 内部采样进行了优化【蓝噪声类似,球内分布更加均匀】
  • 光源剔除:到光源距离
  • 总样本数控制:通过计算 \(\overline{\Delta v}_t\) 的和【自定义层级 mipmap 计算】
    • 对最后得到的样本数进行 scale
  • 整体 pipeline