GAMES202.闫令琪.04.实时阴影(2)
- https://www.bilibili.com/video/BV1YK4y1T7yY
实时阴影
PCF/PCSS
背后的数学
PCF
- filter/convolution
- 卷积
\[ [w\ast f](p)=\sum_{q\in \mathcal{N}(p)}w(p,q)f(q) \]
PCSS
\[ V(x)=\sum_{q\in \mathcal{N}(p)}w(p,q)\cdot\chi^+[D_{SM}(q)-D_{scene}(x)] \]
- \(\chi^+(x)\)
- \(1,x\ge0\)
- \(0,x<0\)
- PCF 并不是对 SM 进行模糊
\[ V(x)\ne\chi^+\{[w\ast D_{SM}](x)-D_{scene}(x)\} \]
- PCF 也不是在图像空间(结果图)上做 filter
- 二值的结果图(也就是经典 SM 做出来的阴影效果)
\[ V(x)\ne \sum_{y\in\mathcal{N}(x)}w(x,y)V(y) \]
PCSS 的步骤
- Step 1: Blocker search
- 在某个区域内计算平均遮挡深度
- Step 2: Penumbra estimation
- 通过计算出来的平均遮挡深度来计算半影(penumbra)的大小
- Step 3: Percentage Closer Filtering
- 根据 Step 2 计算出来的大小进行 PCF
限速步
- Step 1,Step 3
- 采样会比较慢
- Softer -> larger filtering region -> slower
- 两种方式
- 对区域内的每一个像素采样
- 对区域内的像素进行随机采样
- 稀疏采样会引起噪声
- 处理噪声的方法:图像空间降噪,时间维度降噪
- 低通滤波回式低频噪声通过,帧间闪烁现象变成 boiling artifact(沸腾的现象)
- flicker:如果每一帧取得随机数不一样,会引起帧间闪烁的问题
- 针对很慢的方法,提出了 Variance Shadow Mapping
VSSM / VSM
- Variance Soft Shadow Mapping
- 针对性地解决 PCSS 在第一步、第三步慢的问题
PCF 步骤(Step 3)
- 假设均值滤波
- 等价于想要知道在指定范围内有百分之多少的 texel 是在 shading point 之前
- 等价于在指定范围内找出有多少的 texel 深度值比 shading point 要小
- 等价于想知道当前像素在指定范围内,深度排到第几(百分之几)
- 如果知道指定区域的分布,那么就容易得到结果
- 如果不需要那么准确,知道直方图也能得到结果
- 假定是正态分布,也能得到结果(相对更加不准)
- 怎么定义一个正态分布:均值、方差
key idea
- 快速求出某个区域内的均值和方差
- mean + variance
均值
- MipMap:不准确,只能求正方形
- Summed Area Tables(SAT)
方差
\[ Var(X)=E(X^2)-E^2(X) \]
- 计算两个均值即可
- 存两张图
- 怎么在 OpenGL 中实现
- 4 通道,一个通道存 \(SM\ (depth)\),一个通道保存 \(SM^2\ (depth^2)\)
计算百分之几
- CDF:求出阴影面积
- 具体实现求积分:打表
- 误差函数:error function
- c++ 中有函数
erf()
- 有数值解,没有解析解
切比雪夫不等式估计
- Chebychev’s inequality
\[ P(x>t)\le\dfrac{\sigma^2}{\sigma^2+(t-\mu)^2} \]
- 背后假设是单峰的分布
- 具体得去看证明过程
- 我们在渲染中直接作为约等号(约等式)
- 对于 \(t\ge\mu\) 效果还是不错的
- 否则估计不准确
评价
- VSSM 在查询的时候是 O(1) 的(很快),但是在生成的时候需要有些开销
- 生成 Mipmap 是硬件支持的,生成非常快
- 生成 SAT 相对较慢
- 现在解决了 PCSS 中的第三步的问题
第一步的问题(Step 1)
- blocker search
- 求出遮挡物的平均深度,而不是区域内的平均深度
- 区域内的平均深度是知道的
定义基础量
- 区域内的平均深度:\(z_{avg}\)
- blocker 的平均深度\((z<t)\):\(avg.z_{ooc}\)
- 待求的量
- non-blocker 的平均深度\((z>t)\):\(avg.z_{unooc}\)
key idea
- 满足如下的关系
\[ \dfrac{N_1}{N}z_{unooc}+\dfrac{N_2}{N}z_{ooc}=z_{avg} \]
- \(\dfrac{N_1}{N}\) 可以通过切比雪夫不等式估计
- \(\dfrac{N_2}{N}=1-\dfrac{N_1}{N}\)
- 假设 \(z_{unooc}=t\)
- 非遮挡物的深度和当前的深度相似
- 基于绝大多数阴影的接收者是一个平面
- 这种假设还是有问题的
- 接受平面是曲面
- 接受平面和光线平行
- 这种假设还是有问题的
- \(z_{avg}\) 可以通过范围查询得到
VSSM 效果
- https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch08.html
现状
- 人们会更多使用 PCSS 来做阴影
- 人们对于噪声的容忍度很高
- 图像空间降噪做得很好
- 尤其是结合时间维度之后
MipMap / SAT
- 快速范围查询
MipMap
- fast、approx、square
- 涉及到插值
- 层内插值(线性插值)
- 层间插值(三线性插值)
- 插值会引发不准确
- 正方形查询都会不准,对于矩形无法解决
- 各向异性过滤可以查询矩形
- 在软阴影范围比较小的情况下还是表现不错的
SAT
- summed area table
- 范围内元素平均和范围内元素综合是等价的(知道范围大小)
一维数组
- 前缀和
- 任意范围内的和可以通过两个前缀和相减得到
- 前缀和可以通过动态规划方法很快得到 \(O(n)\)
二维数组
- 忽略下图中的坐标系箭头,应该是左上角为原点才能和范围匹配
- 动态规划,\(dp[i][j]\) 记录范围 \((0,0),(i,j)\) 矩形内的面积
\[ S \left((a,b),(c,d)\right)=dp[c][d]-dp[c][b]-dp[a][d]+dp[a][b] \]
- 建表
- 每行做一遍,每列做一遍
- 复杂度 \(O(mn)\)
- 并行 \(O(m+n)\)
- 数值精度溢出问题:有可能(double 损失精度)
MSM
- Moment Shadow Mapping
VSSM 的问题
单峰假设不准
- 正态分布的假设可能不成立
- 带来的问题:计算出来的百分比不准确
- 问题
- 偏黑:问题不大,结果可以忍受
- 偏亮:问题很大(LIGHT LEAKING)
- 下图表示偏亮
- 下图表示偏亮带来的问题
- 镂空车车底问题
- https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch08.html
接受面不是平面
- 接收面不是平面导致假设出问题
切比雪夫不等式问题
- \(t\ge\mu\)
MSM 解决的问题
- 更精准的模拟分布
- 高阶矩(moment)描述分布
- VSSM 只使用了 \(X,X^2\)
- 保留更多阶的矩,描述的分布更准确
- 只给结论,过程复杂
- 简单来说就是某种展开,保留更多的项,结果更加准确
- first \(m\) orders of moments can represent a function with \(\dfrac{m}{2}\) steps
- 通常而言,4 阶矩效果就很好了
- 越多,存储开销越大
- 存储上工业界常用的方法:packing/unpacking
- 多个通道表示一个数
- 一个数表示多个通道
- 这样做的结果是不能直接插值的
- 怎么通过这些高阶矩计算出需要的值
- 论文:[Peters et al., Moment Shadow Mapping]
效果
- 解决了 light leaking 问题
DFSS
- Distance field soft shadows
- 生成软阴影
(Signed) Distance Function
- (有向)距离场
- At any point, giving the minimum distance (could be signed distance) to the closest location on an object
- 对于任意一个点,定义到到物体表面的最近距离
- 可以带符号
- 负号表示在物体内部
- SDF:Signed Distance Function
- GAMES101 中的例子:blend 一个边界
- 第一行是线性插值
- 第二行是使用 SDF 进行插值
- SDF 能在几何上产生非常好的过渡
- SDF 理论基础
- 最优传输理论(optimal transport)
- 顾险峰
距离场的使用
ray marching
- https://docs.unrealengine.com/en-US/BuildingWorlds/LightingAndShadows/MeshDistanceFields/index.html
- 用于光线和物体求交
- SDF 提供了一个安全距离
- 对于一个点 x,SDF(x) 表示在一个半径为 SDF(x) 的距离之内没有物体
- 也就是说光线前进 SDF(x) 这么长的路程,一定不会打到物体上
- 也就是说,光线每次前进 SDF 中记录的距离,优化求交过程
- SDF 的计算比较麻烦,存储开销很大
- 运动物体(刚体)是可以使用 SDF 的
- 如果对每个模型计算了 SDF,求解整个场景的 SDF 只需要求每个模型 SDF 的最小值即可
- 刚体运动的 SDF 比较好计算
- 形变物体的 SDF 需要重新计算
- 比较麻烦
生成软阴影
- 任意一点的 SDF 告诉了我们一个安全距离,等价于安全角度
- 安全角度越小,意味着能够看到的东西越少
- less visibility
- 在 ray marching 的过程中,每一步都计算安全角度
- 最终的安全角度就是过程中最小的安全角度
- 怎么计算安全距离
\[ \arcsin\left\{\dfrac{SDF(p)}{||p-o||}\right\} \]
- 更快的计算
- 一个近似,直接使用 \(\sin\) 的结果也可以估计出阴影的大小
- \(k\) 的作用:控制 0,1
之间的过渡带范围,也就是阴影的软硬程度
- larger k \(\leftrightarrow\) earlier cutoff of penumbra \(\leftrightarrow\) harder shadow
\[ \min\left\{\dfrac{k\cdot SDF(p)}{||p-o||},1.0\right\} \]
- 安全角度的大小决定阴影的软硬程度
- 效果
- 一个教程:https://zhuanlan.zhihu.com/p/94265891
距离场的可视化
- 很像是物体的描边
- https://docs.unrealengine.com/en-US/BuildingWorlds/LightingAndShadows/MeshDistanceFields/index.html
距离场做软阴影的评价
- 好处
- 快:在 ray marching 的同时计算出软阴影
- 生成 SDF 的过程相对耗时
- 高质量
- 快:在 ray marching 的同时计算出软阴影
- 问题
- 需要预先计算 SDF
- 需要较大的存储(3D)
- 优化存储:八叉树、kd 树等
- artifacts
其他
- https://github.com/protectwise/troika/tree/master/packages/troika-three-text
- 利用距离场实现无限分辨率的字母
- SDF 生成的物体表面非常不好贴纹理,参数化表面很复杂