Shadow Mapping——阴影
一、SM的数学基础
图形学上经常经常使用不等式的约等式来进行计算(当满足特定条件下不等式几乎取得等号,相当于约等式),其中一个比较常见的约等式:
其中的分母可以看做一个归一化常数。
二、影阴影
对于点光源或者方向光,照射到观察点p的方向是恒定的,因此不存在本影和半影,就是硬阴影。
换句话说,在点光源与方向光照射下,只存在能看到和不能看到,并没有中间过渡带。
还记得我们在上一篇介绍的渲染方程吗?
这是一个很长的积分式,我们解释过V代表是否可见,则其余部分就是实际光照产生的影响,我们不妨将上述公式进行拆解:
拆解开的公式就很明了了,后边表示对于着色点p,实际的光照出射,但同时要考虑前边V(可见项)的值(来源于SM),对于硬阴影而言就是非0即1,表示可见与不可见,由此形成硬阴影。
补充:如何确定0还是1?
SM的基本原理,从光源观察生成深度图1(depth1),从摄像机观察生成深度图2(depth2)。对于p所在像素,如果depth1<depth2(人能看见但光不能看见),即为阴影,V=0;否则V=1;
因此确定V实际就是在光源方向上查询对应像素的两张深度图。
能够使用约等吗?
一般满足两个条件约等成立:
1.积分限很小。这里引入unity shader的一个说法,精确光源,换句话说我们积分的方向往往就是一个极小的角度范围,所以第一条满足;
2.g(x)足够光滑,也就是足够光滑。先看第一项,对于来自小方向的光,到达p是逐步平滑衰减的,甚至方向光都没有衰减;然后是BRDF项,对于diffuse的光是非常平滑的,specular比较锐利,但是在小范围,小角度变化也不算太大。
综上,使用约等式是合理的。
三、软阴影
与硬阴影的差别就在于阴影的边缘,软阴影是渐进的,是过渡的。所以回到我们的渲染方程:
既然硬阴影是由于V的非0即1进行的计算,那么要实现软阴影,得到平滑的过渡,则,此时的V不再是非0即1的二进制,而是根据周边环境确定的浮点数,根据浮点数的大小确定光照对像素着色的贡献。因此问题转化为浮点数如何得到?
PCF:
很简单的一个思路——卷积!
确实,这是一个很类似于抗锯齿的做法,叫做PCF(Percentage Closer Filtering),核心思想如下图:
我们首先还是需要SM来标记像素着色的非0即1作为原始数据,然后就是使用一个卷积核(或者叫做模板)基于像素中心固定尺寸采点,加权,计算得到一个0-1的可见性占比值V(Games例子):
打破了非0即1的限制,也就打破了硬阴影与软阴影之间的界限。But, cost?
是的相比于我们制作硬阴影时只用查询像素对应的SM,使用PCF要取决于卷积核的大小,比如上边就需要查询9次,甚至为了达到更佳的效果需要25,49,81...这个开销是很大的,并且到底使用多大的卷积核对结果也有影响,太大甚至导致场景虚化,范围而不真实(太大就完全没有本影区都是有可能的)。对此,提出了另一个变种PCSS。
PCSS:
所谓PCSS(Percentage Closer Soft Shadow),我把它称为自适应filtering size的PCF。他的自适应逻辑是基于距离的,在Games中提到下图:
可以看出靠近笔尖的地方几乎是硬阴影,远离笔尖的阴影更倾向于软阴影。这里所说的靠近与远离是指阴影投射物(钢笔)与阴影接受物(纸)在光照方向上的距离,距离越大越倾向于软阴影,那么我们的思路就是:
距离与filtering size正相关!
使用如图所示相似三角形解决size的确定问题:
结果就匹配我们的size(必须是奇数个像素大小),此时使用自适应的size对SM进行批量化的处理。可以看出,size与光源大小(影响半影区)还有blocker distance(本体与阴影在光源方向距离)有关。那么问题随之而来,如何确定blocker distance,换句话说也就是公式中的或者是。
最开始我认为一个像素对应一个深度,但是这样做是比较草率的:
我们只能从像素的中心去观察,那么由于像素是离散的,得到的距离也就是离散的,特别是一些法线与光线向量近乎垂直的面,相邻像素的深度变化非常大,导致size可能突变,结果不尽如人意,因此我们不能使用一个像素的深度,而应该基于一种平均深度的思想,使变化趋于平缓。
如果要使用平均深度,基本思路也是卷积核,两种方案:
1.最简单:固定卷积核尺寸,得到中心像素平均深度,代入上式匹配size;
2.二次自适应:通过观察点p与light边界点的连线得到一个在SM上的投射区域,那么这个区域范围得到一个自适应的卷积核,计算得到平均平均深度。
从上边的过程我们不难发现:频繁操作SM,这都是相比硬阴影增加的开销,这是比较让人头疼的。
对此,一个简单的策略就是随机采样,减少采样数(统计到的0,1值进行平均)。这种人为操作会导致噪声的产生,对于噪声,现在图像空间降噪技术发展得不错,只要是我们可以快速得到一张基本正确的带噪声的渲染图,利用图像空间的后处理也能得到不错的效果。
还有另外一种处理办法那就是Variance Soft Shadow Mapping(VSSM)基于方差的软阴影。
VSSM:
我们提到PCSS的最大问题是采样带来的巨大开销:
Step1:根据SM标记不可见0,可见1,标记原始的V;(SM逐像素采样,判断)
Step2:计算filtering size,并采样SM获得平均深度作为blocker depth;(SM采样,每个像素进行固定次数或者根据光源边界自适应采样次数)
Step3:对Step1标记的0或1进行filtering size次采样,产生最终的V;
我们的目的都是得到V,现将第一步与第三步合起来看,两步都是得到V的数值,有没有办法不采样就得呢?
V=像素可见的概率!
既然提到了概率,那就使用概率论来解决。首先,为了好理解我们以SM在一个范围内的深度值满足正态分布为例(随机分布),要表示正态分布,只需要均值和方差:
x为自光源看过去的SM深度值:
1.均值:MipMap或者SAT;
2.方差:
对于方差,我们需要在存储depth的SM中,使用其他通道存储就能计算得到方差。
有了均值与方差我们就得到一个正态分布:
他表示的是光源角度下的SM,一定考察范围内深度值的分布规律,那么此时我将对应像素自摄像机看过来的深度放入进行比较,就可以得到在这个范围内,SM中depth小于摄像机中depth的概率P,也就是我们在硬阴影所说的depth1<depth2的概率P,也就是说待着色像素被其他像素挡住的概率P,即像素对光源不可见的概率P:
P=1-V;
由此得到V。我们现在仅仅是在Step2确定范围的时候对SM有采样,就得到了V,直接取消了Step1和Step3的采样过程,实际上Step2也可以不采样(等下再说)。回到我们对Step1和Step3的处理,是基于正态分布进行的,计算正态分布实际是一种查表的方式,很多语言都支持,erf函数正是如此:
double cdf=erf(0.5);
但是,还有更加绝妙的做法:切比雪夫不等式
根据方差与均值,直接给出的概率的上届,当我们使用约等式的时候,也就是说估算:
将t看做从相机看过来的深度值,方差与均值为SM一定范围的(MipMap)查询结果,则概率表示SM中深度大于摄像机深度t的概率P,也就是depth1>depth2的概率P,即像素可见的概率P:
P=V;
注意切比雪夫与我们假设的正态分布的结果表达上可能不同,但是结果意义完全相同,使用切比雪夫不需要查找正态分布表就能完成计算,但是要求才准确。
对于Step1和Step3的改进到此为止,我们没有多次采样SM却得到了V。现在来看看Step2,需要通过采样SM,通过遮挡物的平均深度表示blocker depth,确定filtering size。这里要强调遮挡物这个概念,比如,我们在摄像机视角看到的深度是8,而从SM看过来的范围内的值采样值如图:
那么我们就任务只有小于8的位置才是遮挡物,是要求解遮挡物的平均而不是整个的平均。对于整个的平均值可以使用MipMap或者SAT获得,但是不能直接用。同样我们也可以获得其方差,只要我们存储了,那么能获得均值与方差有什么用呢?还是切比雪夫不等式当做约等式使用:
是否意味着t=8取得的概率为,对应的就会有
那么我们基于加权的思想:
这中间、以及(整体均值)都是已知的,我们要求解的是。二元一次方程。有一个大胆的假设。将未遮挡的平均深度假设为我们比较的初始值,这样做的基本思路源于观察:我们的投影面是一个平行于摄像机的平面(或者小范围连续光滑面)。那么就能如愿以偿的得到遮挡物平均深度,带入相似三角形,结合光源大小得到filtering size。
看看,我们从待着色点对应像素出发(使用固定范围或者基于光源大小确定一个在SM上求解平均遮挡深度的范围),并不需要逐像素采样,而是根据MipMap或者SAT得到均值与方差,结合切比雪夫估算概率,假设非遮挡平均深度就得到了遮挡平均深度,进而得到blocker depth。没有一次采样就解决了这个问题。
归纳一下,VSSM的基本过程:
1.从shading point出发,在SM圈定一个对深度的查找范围,并获得这个范围的均值,方差,结合切比雪夫得到平均blocker depth,然后得到filtering size;
2.根据filtering size在SM上估算depth的分布情况,引入摄像机观察到的深度depth2,确定像素可见的概率V;
事到如今,没有采样,通过不断地假设与估算,我们获得了V,带入渲染方程就可以得到软阴影。但是这个方法查询均值与方差,必定需要增加存储空间。由于动态物体阴影也要动态,对应的SM及其MipMap或者SAT都要动态生成,开始会有一定的开销。
最后,在VSSM这里还要解决一个问题,就是我们获得均值的办法(隐含获得方差的方法(计算式基于均值))。
MipMap:
这个方法大家都比较熟悉,特点就是:快速,近似,方形,如果要得到非方形(毕竟基于光源边界确定blocker depth的取样空间,可能出现非方向),就需要各向异性的MipMap,但还是近似的。对此就有了另一个方式:SAT。
SAT:
所谓SAT(Summed Area Table),是一种数据结构,是基于前缀和(记录自输入开始到当前位置的总和):
这有什么作用呢?如果我们知道几项的和,难道还不知道他们的均值吗?通过上述1D的结构我们可以迅速拓展处2D的SAT结构:
从输入开始到当前位置的所有总和。
一个用他查询一个小范围总和的例子如图:
每个方格右下角点记录的数值就是自左上角点到此的前缀和,这样可以立马得到待求区域总和15,而不需要进行逐像素采样(还是有4次SAT空间的采样) 。
MSM:
最后一个话题MSM(Moment Shadow Mapping),这个方法诞生的根本目的就是为了解决VSSM中采样大多近似,概率估计的不准确问题。但是它本身也是近似的概率估计,所不同的是,他的估计更准。
在VSSM中,我们使用均值,平方的均值,这叫做二阶矩。有大佬就发现如果使用高阶矩会得到更好的估计结果,由此诞生了MSM,比如下图是使用不同的矩得到的对概率估计CDF的拟合效果:m阶矩就有0.5*m个台阶。
可以看出PCF是有很多台阶的,当m=4,使用4阶矩的时候就已经可以获得一个比较可观的结果了。但是这个高阶矩的函数比较复杂,等我以后真的有机会搞清楚再来续写吧。
OK,Have a good time!