笔记
先讲两个概念
软阴影、硬阴影
硬阴影:
● 应用于点光源
● 对于每一个像素,只需要检查该像素和光源之间是否有遮挡物存在即可,因此这种和像素一对一的可见性函数结果可以如同表面颜色一样,被存储在一个阴影贴图中,这种阴影的分布在表面上是一种非黑即白的效果。
软阴影:
● 应用于真实光源(面积或体积)
● 光源对于接受点,可能是部分可见的,因此这种部分可见性必须能够以某种方式被计算,这就要求每个观察点向光源发射多条光线来进行计算
● 目前主要有两大类方法来计算软阴影:
- 基于图像的方法:通过借助标准的阴影贴图技术,然后使用一些2D的过滤器来对边缘进行模糊处理(如PCF)
- 基于几何体的方法:如基于光线追踪的软阴影实现、基于距离场的软阴影实现。这些方法不使用阴影贴图,避免了阴影贴图的走样问题,但是有较高的计算成本。
实时阴影方案
主要分为两个方案:
- 基于几何:平面投影阴影、ShadowMap、CSM(级联阴影)、PCSS、基于光线追踪的软阴影、基于距离场的软阴影
- 基于滤波/图像:PCF、CSM(卷积阴影)、VSM、ESM
基于几何的阴影方案
平面投影阴影
阴影映射(Shadow Map)
原理
- 从光源位置渲染深度图片
- 从摄像机位置去渲染整个场景,将每一个像素点转换到光源坐标系下
- 与之前的深度图对比,当该片元的深度值大于深度图的深度时,也即产生了遮挡
阴影映射优化——自阴影
自阴影原理
由于Shadow Map的分辨率有限,离散的采样点以及数值上的偏差可能造成不正确的自阴影,也被称为Z-fighting或者阴影粉刺(Surface Acne)
解决方法:当比较深度时,需要设置容错阈值
● 深度偏移:增加深度偏移会使该像素向光源靠近
● 法线偏移:沿表面法线方向向外偏移
偏移单位是阴影映射的纹素
但是当深度偏移设置过大时,会导致漏光现象。即阴影与投影者之间发生脱节,也叫做Peter Panning问题
偏移原理
对于视锥的右侧蓝色点,加了偏移后可以认为不被红色点相应像素点遮挡,但是对于红点左侧,阴影就可能产生脱节,产生Peter Panning问题。
Unity中的偏移优化
- Shadow Caster阶段基于顶点的Normal Bias
- 在Shadow Caster阶段,让遮挡物进行反向偏移
● 性能高了
● 精准度低了
阴影映射优化——透视走样
透视走样,全称perspective aliasing
原理:在左图中,A、B点在shadow map中都占用1个像素,从摄像机位置观察A、B,会有更多的需要渲染的屏幕像素去shadow map B对应的pixel里对比。也即越靠近观察者的元素(对应多个像素)所用到采样点越少,从而产生右图的走样效果
解决思路:
-
在生成shadow map的时候,进行一次透视投影,减少远近不同造成的信息差。
例如PSM(透视阴影映射):
将Shadow map透视变换
-
根据观察者远近,分段生成不同级别的Shadow Map
如:级联阴影映射(CSM)
阴影映射优化——重采样误差
重采样:从摄像机视角对Shadow Map重新采样
重采样,也即会产生纹理采样的误差(像素点不可能完全对应的上Shadow Map中的有限信息),通过滤波,可以减少该误差,如PCF滤波
滤波
● 图像处理中,通过滤波强调一些特征或者去除图像中一些不需要的部分(强调如Bloom,去除如高斯模糊)
● 滤波是一个领域操作算子,利用给定像素周围的像素的值来决定此像素最终的输出值
PCSS
全称Percentage-Closer Soft Shadows。
PCSS通过一个和光源位置相关的相似三角形来控制软阴影的采样搜索范围。下面这个图展示了PCSS如何根据光源,遮挡物,和阴影投射目标三者确定搜索范围
PCSS首先假定光源是一个区域光(area light)。传统的点光源,聚光灯和平行光,其实都不过是某种模拟,比如点光源模拟小灯泡,聚光灯模拟手电筒,平行光模拟太阳,其实严格来说这些都是区域光,软阴影形成的很大一个原因就是区域光的存在。
为了在旧的光源体系中加入区域的概念,我们假定光源是一个始终平行于接收面的圆形,并用uniform变量控制这个圆形的半径
这样,我们就可以估算根据光源,遮挡物,被遮挡物三者的距离估算阴影的软硬程度
计算公式如下:
PCSS的主要贡献在于形成了所谓“动态”的阴影,PCSS确定的这个搜索范围,也可以看做是某种模糊半径,或者卷积核的大小,并没有要求一定按照多重采样(PCF)的方式来实现,因此可以很好地和VSM,ESM等技术结合
基于光线跟踪的软阴影
● 密度缓存:记录光线的可见性值,其值为0或者1,相当于能够提取“硬阴影”的边界;可以认为是可见性的密度,或者光照的密度
● 距离缓存:像素到最近遮挡物的距离
● 步骤:
用距离缓存中的值作为一个核函数的窗宽,对密度函数中的值执行过滤,即可得到半影(被投射阴影表面上的一块面积)区域的阴影过渡
优点
● 该方法是通过上面两个缓存,基于屏幕空间来做的,所以没有Shdaow Map的分辨率问题
● 没有由于采样不足导致的噪点、瑕疵等问题
● 不会有偏差问题,因为是直接对几何体执行了光线跟踪来检测像素是否位于阴影区域
基于距离场的软阴影
● 距离场:记录了3D空间中规则网格(或者其它结构体)内的点到物体表面的最近距离
● 步骤:
- 从像素点的三维空间,做圆锥跟踪(cone-tracing):通过距离场,以非常低的成本近似计算一个圆锥(cone)形状与场景的相交情况
- 以步进的方式,找出圆锥的中心离到最近表面的距离Dmin(下图黄线)
- 计算公式:Shadow = 1 - Dmin / Cone Radius
- 其中Cone Radius 是光源的定义,用来指示光源的扩展面积(Cone Radius越小/Cone的角度越小,阴影越硬)
优点
● 距离场的信息成本,如果还需要用来做AO等其它应用,是相对较低的
● 适用于计算较远区域的软阴影,因为这些区域需要分辨率非常大的Shadow Map,
基于滤波的阴影方案
PCF
PCF不同实现:
- 采样数K:
规则滤波,33 或 55
采用泊松分布的形式来分布一定数量的采样点 - 滤波核函数:高斯函数作为滤波函数
缺点:
- 多重采样非常影响性能
- 不支持pre-filtering(在渲染阴影纹理的时候就进行filter滤波处理),无法使用mipmap做完美的三线性插值
- 基于采样的阴影模糊一般都会有二次走样的问题,这是基于sampling的方法无法绕过的问题
CSM(卷积阴影)
卷积阴影CSM,全称Convolution Shadow Mapping
原理
使用卷积计算:
其中
d是这一点到光源的距离,z是遮挡物的深度
Ck = π(2k-1)
当M取不同的值时,f(d,z)曲线如下:
考虑到效率和视线效果,M应该取值大于4
当d=z时,表面应该是无遮挡的,f(d,z)应该等于1,因此需要加一点偏移offset = -0.032:
缺点
抖光、漏光。
从前面的曲线可看出,抖光跟漏光会呈反比(M=1相比M=16,漏光多,抖光少)
优点
支持pre-filtering,M越大,效率越低,但是比起PCF效率高很多
VSM
深度概率分布,全称Variance Shadow Mapping
原理
为了分析阴影边缘分布的统计规律,我们考虑光源视角的深度为一个随机变量x。根据切比雪夫不等式的单边公式,深度大于给定值t的概率有一个上界
这个上界有多接近于p(x>=t)?我们可以假设一个被称为single-bounded的理想模型。
假设把深度为d1的平面投影到深度为d2的平行平面,那么在深度为d1的平面边缘,可以假设投影深度正好为d2的概率为p(这个p也可以认为是pcf的值)那么可以得到深度的期望为
而
期望的方差:
这样我们就可以根据上面提到的切比雪夫的单边公式来计算深度小于d2的概率的上界
这个结果刚好就是p的值, 这表明Pmax和p在理想情况下是一样的
虽然这是一种非常特殊的情况,只有一次投影,一个遮挡物,但是这个结果表明,由切比雪夫不等式计算出来的上界很可能只有一点点的偏移,我们可以直接采用这个上界作为阴影测试函数f
然而,当对VSM应用一个较大核的滤波,也就是像基于VSM生成软阴影的时候,将会导致结果有较大偏差
实现
VSM的实现关键在于渲染到深度纹理的同时渲染深度的平方,并在shading pass计算期望和方差。这个深度贴图会占用比之前多一倍的内存
实现需要注意几个地方:一是在shadow pass 的时候就利用mipmap,高斯模糊等技术做好预处理,减少锯齿和突变。二是在shading 的时候,注意只有深度大于期望的时候才利用切比雪夫公式进行近似计算(这是由于计算的深度是一个上界)
优缺点
存储:32+32
VSM同样支持pre-filtering但效率比CSM稳定,开销也更小
ESM
原理
显然这个表达式是单边有效的,对于d<z的情况,e-cdez将会爆炸式增长,我们暂时先假定d<z时f(d,z)=1
得到ESM的图像表示:
有了这个表达式,我们就有可能实现pre-filtering,从而提高性能。
下面的式子证明了经过ESM处理之后filtering和pre-filtering是等价的
常数指数c的选择会影响ESM的 s(x) 曲线:c 越大,s(x) 越陡峭,也就越接近真实的阴影;当c的值偏小的时候,会造成漏光。
c 的上限受到浮点数精度的影响。32位float对应的c的经验最佳值大概在 80 左右,这个值生成的阴影比 M =16 的 CSM 更好
缺点
下面这个图很好的反应了由shadow map精度带来的问题
观察图中的边缘,发现有过亮的现象产生,我们用一张图可以清晰明了的解释
对于阴影纹理,存储和采样点都在纹素中心,当我们计算d(x)-z§时就会出现小于0的情况。于是我们得到一个全白的结果,而正确结果应该是有50%的阴影。
这种情况出现在多重阴影的边缘,原论文中有两种解决方案,一种是增加一个Zmax的pre-pass,还有一种是对e-cdez设置一个阈值 1+ σ。这两者都很好理解,Zmax的方法通过找找到附近的最大深度值来判断是否取样到了比d还远的表面,阈值检查e-cdez是否超过某个值,若超过某个值就有可能遇到了上面的情况
总的来说,阈值 1+ σ 的效率比 Zmax 要好,没有多出来的pre-pass,更何况要计算 Zmax 免不了要采样多次;阈值的方法虽然不能保证所有的情况都能处理,但胜在效率高,存储小。
优点
ESM只需要一个32F的浮点纹理,存储e-cdez的值,之后可以直接对阴影纹理进行线性插值,高斯模糊等操作
ESM比之前提到的几乎所有方法都要好,不需要MRT,支持pre-filtering,没有震荡现象,可以说是最优的方案
作业
总结实时阴影的优化方案
- 自阴影:通过深度偏移/法线偏移解决
- 透视走样:CSM
- 重采样误差:PCF
尝试实现一套阴影系统
背面的块状阴影内的插值好像还是有点问题,并且深度偏移看起来有点差劲
参考资料
https://www.bilibili.com/video/BV1Jf4y1P7ch?p=2
PSM:https://jankautz.com/courses/ShadowCourse/03-PerspectiveSM.pdf
VSM,ESM,PCSS:https://zhuanlan.zhihu.com/p/26853641
PCSS:https://developer.download.nvidia.com/shaderlibrary/docs/shadow_PCSS.pdf
基于光线跟踪的软阴影:https://www.imaginationtech.com/blog/implementing-fast-ray-traced-soft-shadows-in-a-game-engine/
基于距离场的软阴影:《全局光照技术》