Shadow
Shadow Map
- 解决了什么问题?
解决了渲染中阴影如何渲染的问题 - 如何做的?
- 我们需要从光源作为观察点,光源方向作为观察方向渲染一张深度贴图
- 正常相机位置渲染,再把像素点投影到光源空间,和深度贴图对比,如果深于深度贴图的深度,那么就被遮挡
- 引入了什么问题?
- 存储在深度贴图中的深度是离散的(像是深度图高度*深度图宽度多的四面体柱拟合的深度),而真正的模型并不是这样的,这会导致很多不自然的斑马纹样的阴影(Shadow Acne)出现(在阳光打向一个平面的时候尤其明显,可以通过微调深度贴图避免这种情况,但是会引发模型影子不贴合(detached shadow)的情况出现)。
- 阴影锯齿明显,这是由于场景过大而深度贴图分辨率不足导致的
- 存储在深度贴图中的深度是离散的(像是深度图高度*深度图宽度多的四面体柱拟合的深度),而真正的模型并不是这样的,这会导致很多不自然的斑马纹样的阴影(Shadow Acne)出现(在阳光打向一个平面的时候尤其明显,可以通过微调深度贴图避免这种情况,但是会引发模型影子不贴合(detached shadow)的情况出现)。
CSM
- 解决了什么问题?
CSM的精髓是Cascaded,对于普通的SM,它的问题是只生成一个深度贴图,这会导致离观察位置进的部分锯齿严重(因为深度贴图分辨率有限,四面体柱划分不够精细)。CSM就认为,离得近的部分需要更精细的深度贴图,离得远的部分仅需要普通SM级的精度就好(这里引入LOD会有更好的优化效果)。
CSM这样做就解决了贴图锯齿的问题。
-
如何做的?
注意到需要Cascaded,我们按照视锥体来划分。
- 将视锥切成多个小视锥
- 得到小视锥的AABB
- 根据AABB得到深度贴图
- 对每一个像素选择合适的SM生成阴影。
- 引入了什么问题?
- 突变
原因是视锥生成的贴图质量是有跳跃的,可以根据mipmap的三线性插值原理通过重叠部分插值解决 - 抖动
原因就很简单了,是视锥体的AABB对视角切换敏感,每次切换视角AABB的贴图都要重新生成,但是浮点数精度有限,不能够满足敏感变化的阴影贴图。
解决方案是引入对视角变化不敏感的包围球。更好的解决方法是在Shader X7中的Practical Cascaded Shadow Maps中提到的渐进式变换Frustum的方法,而且也能同时在一定程度上解决Rasterization所带来的抖动问题。 - 漏阴影
这个原因也很简单,因为我们的视锥体总是有一个近远平面的,如果天空中有只鸟飞过正好在我们视锥外,这样我们的地面就无法看到鸟的阴影。更为合理的方法是使用两端扩展的光线视锥体来与场景求交计算出合理的远近平面。
- 突变
PCF & PCSS
- 解决了什么问题?
前两种生成阴影的办法是硬阴影,我们在自然条件下看到的阴影都是软阴影。以下几种方法是生成软阴影的办法。
-
怎么做的?
一般来讲,PCSS = PCF + 遮挡物深度估计。首先说遮挡物深度估计,不能直接使用shadow map中对应单个点的深度来代表blcoker距离,因为如果该点的深度与周围点的深度差距较大(遮挡物的表面陡峭或者对应点正好有一个孔洞),将会产生一个错误的效果,我们选择使用平均遮挡距离来代替,所以平常我们指的blocker depth其实是Average blocker depth.
之后我们假设,如果shading point的深度大于这个shadow map上点对应的深度,则说明shadow map上的点就是一个Blocker,然后我们取shadow map上这个点(像素)周围的一些像素,找出能够挡住shading point的点的像素,并求出他们的深度平均值作为blocker的深度。
第二步是PCF,我们比较shading point的深度和shadow map上一个范围的深度,得到通过率的平均值,从而得到[0,1]的阴影。- 根据光源大小,得到遮挡物深度估计的卷积核从而得到遮挡物的深度
- 根据三角形相似,从遮挡物的深度得到卷积核的范围
- 得到卷积核内的深度通过率,从而得到非0即1的深度
- 引入了什么问题?
- 离谱的纹理采样
我们每次计算平均Blocker深度需要采样,并且每次计算PCF都需要采样。带来的开销不可接受。
- 离谱的纹理采样
VSSM
- 解决了什么问题?
前两种生成阴影的办法是硬阴影,我们在自然条件下看到的阴影都是软阴影。
-
怎么做的?
注意到PCSS的离谱开销主要体现在采样上,我们要做的避免大量采样。VSSM的做法是用MRT和概率论的方式避免采样。
- MRT
因为我们知道一个理论,就是可以用方差和期望来估计一个分布,对于方差而言,可以用一二阶矩来得到。
一二阶矩可以直接通过MRT输出。
因为获取一二阶矩需要均值,我们可以通过mipmap或SAT(Summed Area Tables). - 使用切比雪夫不等式避免Blocker深度估计
我们得到局部的方差和期望后,我们就可以通过切比雪夫不等式得到小于这个值的比例。然后假设不是blocker的深度就是shading point的深度。我们就可以得到blocker的深度了。 - 使用切比雪夫不等式避免PCF的采样
使用之前的切比雪夫排名机制即可。
- MRT
- 引入了什么问题?
- 漏光
原因是两阶矩对分布的拟合过于简单了。会导致阴影不准确。
- 切比雪夫不等式的问题
不等式要求的值大于平均值的时候才准。
- 漏光
MSSM
- 解决了什么问题?
VSSM的估计不准导致的漏光问题。
-
怎么做的?
采用了更高阶的矩而不是VSSM的二阶矩来评估一个分布。这样即使分布是多峰的也可以被较为精确的估计了。
-
引入了什么问题?
产生的纹理更多,计算量更大。
SDFSS
-
解决了什么问题?
更为物理的阴影。
-
怎么做的?
首先我们存一张距离场贴图,然后我们从相机到shading point连线,因为我们已知距离场(Signed Distance Function),我们就可以安全的步进,并且求得光线和最近blocker的夹角(实际工业上并不会直接计算arcsin值而是通过线性乘系数k控制软硬阴影)。我们就可以通过这个夹角渲染软硬阴影。
-
引入了什么问题?
SDF是一个快速的高质量的软阴影生成方法(比shadow map快是忽略了SDF生成的时间),但是在存储上的消耗非常大,而且生成SDF的花的时间也要很久,SDF是预计算,在有动态的物体的情况就得重新计算SDF。