GAMES202 笔记 – Real-time Shadows

目录

一、Shadow mapping的回顾

二、PCF-百分比渐进滤波

三、软阴影-PCSS

四、VSSM-Variance Soft Shadow Mapping

五、MSM-Moment shadow mapping

六、Distance field(SDF) soft shadows


一、Shadow mapping的回顾

Shadow mapping原理

 

  1. 我们从Light处看向场景并输出一个从light处看向场景所生成的深度图得到一张texture,也就是所谓的shadow map.
  2. 我们从camera处看向场景渲染一遍将,并参考1-pass生成的shadow map去判断物体是否在阴影中.

 左图中,点连向light,其在shadow map的最浅深度 等于 图中从点连到light处的深度,所以点不在阴影中。右图,点连向light,其在shadow map的最浅深度 小于 图中从点连到light处的深度,所以点在阴影中。

shadow mapping是一个完全在图像空间中的算法,其优点:一旦shadow Map已经生成,就可以利用shadow map来获取场景中的几何表示而不是场景中的几何.其缺点:会产生自遮挡和走样现象.

从Light处看向场景生成的是shadow map,并不是Shading的结果,shadowmap颜色深的表示值比较小,也就是离light近,颜色浅的就是离light远,值大。

Shadow Mapping问题

  1. 自遮挡

  2. 走样-锯齿

自遮挡的现象:主要是由于shadow map记录的深度的不连续造成的地板上的东西会感觉像摩尔纹,但并不是,它是由数值精度造成的一种现象。我们生成了一个Shadow map,那么这个Shadow map肯定有自己的分辨率,从Light处看向场景时,沿某一像素看过去我们看到的位置就认为是像素所代表的深度,记录的深度值是离散的,不连续的,如图中红色斜线所示。图中眼睛看到的蓝色点,实际上不会被遮挡的,但其实际深度大于shadow map上对应像素记录的深度,造成了被遮挡的假象,由此产生了自遮挡现象。在light与平面趋于平行时候最严重。

消除自遮挡现象:

  1. 使用更高分辨率的shadow map,可以从一定程度上缓解自遮挡现象,因为高分辨率会使记录的深度变化更加平滑。
  2. 在深度比较时添加bias,在深度比较时不计算图中黄色部分,就可以解决自遮挡问题。当Light处垂直于场景时,我们可以让这个遮挡区域尽可能小一点,当light趋近于平行场景时,我们让这个区域尽可能大一点。
  3. 在生成shadow map的时候,不仅记录最小深度,还需要记录第二小的深度。

     

 如果bias太大,也会出现另一个非常严重的问题——物体与阴影的割裂。

Second-depth shadow mapping

 在生成shadow map的时候,不仅记录最小深度,还需要记录第二小的深度,在深度比较时,使用前面二者的平均值,可以有效地消除自遮挡现象。此方法不使用bias。但是开销大

走样-锯齿:shadow map本身就存在分辨率,当分辨率不够大自然会看到锯齿。

Shadow mapping math

但是在实时渲染中,我们只关心近似约等,我们不考虑不等的情况,因此我们将这些不等式当约等式来使用.

  满足下列两个条件之一时,实时渲染领域认为他们近似相等:

  1.  g(x)的积分域很小
  2. g(x)足够smooth,变化不大

右边第一个函数的分母:归一化操作。

假设f(x)是一个常值函数,也就是f(x) = 2,我们的积分域恒为0-3.那么约等式左边,把f(x) = 2代入,则可以提出来变为2倍的g(x)积分,而等式右侧第一个函数代入f(x)的积分是2 * 3 =6,分母的积分是3,结果也正好是2.正好也是2倍的g(x)积分。

 积分中多了一项visibility,把visibility看作是f(x),提取出来并作归一化处理。红色区域部分时visibility,那么剩下的g(x)部分,也就是shading的结果。意义就是,我们计算每个点的shading,然后去乘这个点的visibality得到的就是最后的渲染结果。也就是shadow mapping的基本思想。

约等式成立条件判断:

  • 我们要控制积分域足够小,也就是说我们只有一个点光源或者方向光源。
  • 我们要保证shading部分足够光滑,也就是说brdf的部分变化足够小,那么这个brdf部分是diffuse的。
  • 我们还要保证光源各处的radience变化也不大,类似于一个面光源。

解决:反走样的解决方案就是滤波,简单理解就是求平均。不是对渲染结果滤波或者对shadow map滤波。首先,对渲染结果滤波只会得到模糊的锯齿,走样结果依然存在;对shadow map滤波,虽然平均了深度,但是得到的visibility仍然非0即1,仍然无法解决走样问题。我们真正的滤波对象是渲染方程中的visibility项。

二、PCF-百分比渐进滤波

PCF的初衷是为了抗锯齿,反走样,比如上面的忍者阴影出现的锯齿状,是为了解决这个现象.后来发现可以用在软阴影上,通过把shadow结果求一个加权平均(或者叫filtering).

PCF不是直接在最后生成的结果上模糊,而是在你做阴影判断时进行filtering。

如果直接在shadow map上filtering就会造成阴影和物体交界直接糊起来,而且在第二个pass上做深度测试还是非0即1的结果,最后得到的仍然是硬阴影。

PCF的思想很简单,在Shadow Mapping深度比较的阶段,不仅考虑当前着色点深度与对应shadow map上那一个像素深度值的比较,我还考虑其周围的像素值记录的深度,最终返回一个代表visibility的由0-1组成的矩阵,最后求平均值作为真正的visibility。

 

 如果滤波核的尺寸较大(比如9×9的滤波核)可以在滤波核尺寸范围内进行随机采样,并控制采样点个数,同样可以得到抗锯齿效果。

PCF做了什么事?

对于实际渲染的点X,我们找到其投影到shadow map上对应的像素点—p,我们不只考虑点p,还要考虑其周围一圈范围内的像素,区域内各像素的根据到点p的距离进行加权平均,离得远贡献小,离得近贡献大。区域内除点p的其他像素

在shadow map上记录的最小深度,也与x的实际深度进行比较,从而判断区域内有多少fragments是遮挡物(Blocker),然后算出平均的visilibity,从而得到了一个在0到1之间的软阴影效果。

 

 

我们会发现如果filter size越大,阴影本身越软,所以这个方法也就可以去绘制软阴影,也就是pcss技术。软阴影比硬阴影看起来更自然。

三、软阴影-PCSS

 光源照射下产生的阴影区域,由于光源存在一定的体积,所以主要分为本影区和半影区。在本影区中,无法接收到来自光源的任何光线,看不到光源的任何部分;而在半影区中,光源仅部分被遮挡,因此与本影相比,半影的阴影要浅得多。 

 

距遮挡物越远的阴影区域,阴影越软。 阴影接受物与阴影投射物的距离越小,阴影越锐利

左下和右上两个黄色虚线形成的三角是两个相似三角形。

 PCSS(Percentage Closer Soft Shadows)的核心思想就是控制PCF算法中滤波核的大小来生成软阴影。一个直观的观察结果是距离遮挡物越远的区域,阴影越软,需要的滤波核尺寸越大。

如何确定一个blocker距离光源的位置:不能直接使用shadow map中对应单个点的深度来代表blcoker距离,因为如果该点的深度与周围点的深度差距较大(遮挡物的表面陡峭或者对应点正好有一个孔洞),将会产生一个错误的效果,选择使用平均遮挡距离来代替,所以平常我们指的blocker depth其实是Average blocker depth。

  • 滤波核越小—–模糊程度越低—–阴影越硬(尖锐)
  • 滤波核越大—–模糊程度越高—–阴影越软

那么在PCSS做了什么?

首先将shading point点x投应到shadow map上,找到其对应的像素点p。PCSS算法的实现流程如下:

第一步:Blocker search,即获取某个区域的平均遮挡物深度(在点p附近取一个范围(这个范围是自己定义或动态计算的),将范围内各像素的最小深度与x的实际深度比较,从而判断哪些像素是遮挡物,把所有遮挡物的深度记下来取个平均值作为blocker distance。)

第二步:Penumbra estimation,使用平均遮挡物深度计算滤波核尺寸(用取得的遮挡物深度距离来算在PCF中filtering的范围。)

第三步:Percentage Closer Filtering,对应该滤波核尺寸应用PCF算法。

那么PCSS中那些步骤会导致速度变慢?

  • 第一步:多次采样查询深度信息—–速度变慢
  • 第三步:阴影越软—–滤波核尺寸越大—–采样查询次数变多—–速度变慢

如果觉得区域过大不想对每一个texels都进行比较,就可以通过随机采样其中的texels,而不是全部采样,会得到一个近似的结果,近似的结果就可能会导致出现噪声。工业的处理的方式就是先稀疏采样得到一个有噪声的visibility的图,接着再在图像空间进行降噪。

四、VSSM-Variance Soft Shadow Mapping

Variance soft shadow mapping主要解决了PCSS中第一步和第三步慢。第三步其实就是应用PCF算法,而PCF算法的核心就是对渲染方程中的visibility项进行滤波,也就是求平均。从另一个角度考虑,我们想要得到的是某个区域中,深度大于当前着色点深度的范围占整个区域的比例。

从统计角度来看,假设查询区域的深度满足某个概率密度函数,对于当前着色点的深度d,我们希望得到的是概率P(x>t),而根据单边切比雪夫不等式可得:

 VSSM的key idea是快速计算出某一区域内的均值和方差。在VSSM中,我们认为上述不等式两边相等。所以不论查询范围内深度符合哪种概率分布,只要知道该范围深度的均值和方差,就可以直接得到visibility项的平均。

用这个公式的原因是:

在shadow map中我们存储的是depth,因此depth也就是公式中的x,在指定区域范围后,可以快速的求出区域范围的平均值(期望)。

还需要额外生成一张shadow map,但是这张图上存depth^2,即square-depth map。

加速第三步总结:

  1. 我们通过生成shadow map和square-depth map得到期望值的平方和平方值的期望再根据公式 得到方差
  2. 通过mipmap或者SAT得到期望
  3. 得到期望和方差之后,根据切比雪夫不等式近似得到一个depth大于shading point点深度的面积.,也就是求出了未遮挡Shading point的概率,从而可以求出一个在1-0之间的visilibity.也就是省去了在这个范围内进行采样或者循环的操作,大大加速了第三步。

第一步的加速方法:

第一步中,我们需要获得的仍然是深度平均值,但注意此处的平均值是shadow map上某个区域中特定值的平均值,即区域中遮挡物的深度平均值,并不是整个区域的深度平均值。

假设着色点距光源的深度z = 7,shadow map上需要查询的区域如下图所示,那么我们需要的是图中蓝色区域(遮挡物)的深度平均值。

  • 非遮挡物的深度平均值—Z_{unocc}  采样点中非遮挡物数量—N_1
  • 遮挡物的深度平均值—Z_{occ}  采样点中遮挡物数量—N_2 
  • 整个区域的深度平均值—Z_{aug}  采样点数量—N

上述参数存在以下关系:

\\frac{N_1}{N}Z_{unocc}+\\frac{N_2}{N}Z_{occ}=Z_{aug}

 根据第三步的加速方案中的单边切比雪夫不等式,我们可以得到下式,且数据都可知。

\\frac{N_1}{N}=P(x>t) ,\\frac{N_2}{N}=1-P(x>t)

假设非遮挡物深度全部等于当前着色点深度t,即Z_{unocc}=t,可以求出Z_{occ}。但是接受平面是曲面或者与光源不平行的时候就会出问题。

VSSM的做法采用了非常多的大胆假设,同时非常的快,没有任何噪声,本质上其实也没有用正态分布,是直接用切比雪夫不等式来进行近似。但是现在最主流的方法仍然是PCSS。

如何在区域范围内快速的求出均值?

  1. MIPMAP
  2. Summed-Area Variance Shadow Maps-SAT

他是一个快速的,近似的,正方形的范围查询,由于他要做插值,因此即便是方形有时也会不准确。

SAT是百分百准确的一个数据结构,在1维情况下其实就是一维数组,SAT这种数据结构就是做了预处理,类似前缀和。

 对于二维数组的情况,我们需要计算区域蓝的和,只需要这样计算:蓝=大绿-橘1-橘2+小绿,也就是说,如果我们生成了二维数组对应的SAT,只需要查询四次,每次查询图中白色点所在位置对应的值,然后求解。

 生成二维数组SAT的方法:对于一个NxN的二维数组,首先,按行生成每行对应的一维SAT,N行计算完毕之后,形成一个新的NxN的二维数组;在新数组的基础上,按列生成每列对应的一维SAT,最终可以得到初始二维数组对应的二维SAT。

五、MSM-Moment shadow mapping

VSSM是为了解决PCSS的问题,但vssm由于做了很多假设,当假设不对的时候会有问题。当范围深度的分布并不是单峰的分布(如下图左边所示),而是多峰分布时,甚至是一些比较简单的分布时(如下图右边所示),使用单边切比雪夫不等式同样有可能是不准的。

 

为了避免VSSM中不是正态分布情况下的问题,就引入了更高阶的moments来得到更加准确的深度分布情况.想要描述的更准确,就要使用更高阶的moment(矩),矩的定义有很多,最简单的矩就是记录一个数的次方,VSSM 就等于用了前两阶的矩。

越多的阶数就和原本的分布越拟合。一般来说4阶就够用。

六、Distance field(SDF) soft shadows

Distance Function就是空间中任何一点,到某个物体的最小距离。

 Signed的意思就是加入规定负数代表在物体内部,正数则表示在物体外部.这样不止定义了距离,还定义了方向。

 

  SDF的应用:

  1. Ray Marching
  2. 生成软阴影

Ray Marching:假设我们已经知道场景的SDF,现在有一根光线,我们试图让光线和SDF所表示的隐含表面进行求交,也就是我们要用sphere tarcing(ray marching)进行求交.

任意一点的SDF我们是已知的,因此在点1时,我们以它的SDF(1)为半径做一个圆(此处假设在2D内,如果在3D内则是一个球),在这个圆内无论是哪个方向前进,只要不超过半径距离,都不会碰见物体,是安全的。因此我们可以利用这个特性不断的朝一个方向前进,直到SDF足够小,也就是代表离物体表面足够接近了,则进行求交操作。如果在超一方向trace非常远的距离但仍然什么都没trace到,此时就可以舍弃这条光线,也就是停止了。

软阴影:

将安全距离的概念进行延伸,在任意一点通过sdf可以获得一个safe angle。我们取点P为shading point往一方向打出一根光线,光线上的一点a,有一个SDF值SDF(a),也就是在a点以SDF(a)为半径所做的球或圆内是安全的,不会碰到物体.

把shading point和面光源相连,所得到的安全角度越小,被遮蔽的可能越高,就可以认为

safe angle越小,阴影越黑,越趋近于硬阴影;

safe angle够大就视为不被遮挡没有阴影,也就越趋近于软阴影。

如何求出safe angle:

我们以o为起点,沿一个方向推进,仍然是ray marhcing的步骤,在p1点以SDF(p1)进行推进,其余点也是一样,最后再取其中最小的角度作为总的safe angle。

计算:

 从图我们可以知道,以p1点为例,从o点到p1的距离为斜边,sdf(p1)是直角边,因此我们用arcsin就可以求出safe angle了。

arcsin的计算量其实是十分大的,因此在shader中我们不用反三角函数,只要sdf长度除以光线走过的距离乘一个k值,再限定到1以内,就能得到遮挡值或者说是visibility,而k的大小是控制阴影的软硬程度。

SDF是一个快速的高质量的软阴影生成方法(比shadow map快是忽略了SDF生成的时间),但是在存储上的消耗非常大,而且生成SDF的花的时间也要很久,SDF是预计算,在有动态的物体的情况就得重新计算SDF。

© 版权声明
THE END
喜欢就支持一下吧
点赞286 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容