要渲染一个点光源在场景中投射出的阴影,需要Shadow Mapping 技术
Shadow Mapping
2-pass
的算法
pass1
:我们从Light处看向场景,每个像素记录它所看见的最浅深度,输出一个深度纹理图pass2
:从camera处看向场景,渲染一遍。拿着pass1得到的深度图,可以检测当前pass任何一个像素点是否在阴影里关于渲染中提到的名词pass
1、一个pass就是走完一个渲染流程,而得到一帧数据的过程。
2、一个pass生成的纹理可以提供给之后的pass使用。
3、多个pass不同的分工,最终共同渲染出一帧图像
Shadow Mapping实现步骤
(1)从light处出发看向场景,生成一张纹理记录每个像素中最近物体深度
(2)我们从camera出发,渲染一遍场景。对每个像素对应的场景中的物体坐标,判断是否能被light照到,具体就是计算该着色点与光源的距离,与pass1的深度图对应像素存放的深度做对比,如果相等则不在阴影里,反之则位于阴影中。
Shadow Mapping的效果
有阴影的明显更加层次分明
注意事项
关于深度,我们在101中讨论过一个问题,在做透视投影时,先挤压后正交投影。挤压操作后,位于视锥体远近平面内的点是会被推向远处一些的。所以MVP变换之后,物体的深度是会变的,最终得到的深度并不是原本世界空间中物体到相机的距离,在做阴影映射的时候,比较两个pass的深度图时,应该做到一致性。
(1)都用投影后的z值
(2)都用实际的距离,pass1的深度图记录实际的深度,pass2用着色点投影前的坐标到光源的空间坐标的距离
至此回顾结束,下面202内容正式开始
定义一个变量bias,值为下面黄色区域长度。定义 深度差值
小于这个值,就不算遮挡,只有大于这个值的才算遮挡
不同于闫老师所讲的另一种理解方式
float bias = 0.005;
bool shadow = currentDepth - bias > closestDepthInShadowMap ? true : false;
掠射角度依然自遮挡的解决办法: 根据入射光与法线的夹角,动态修改bias
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
虽然解决自遮挡问题,但又引出了另一个问题:detach shadow
\:\:\:\:\:\:\:\: 由于bias变量的存在,深度差在bias范围内的不算遮挡,不产生阴影,以至于真正的能够被场景物体遮挡的部分由于深度差并不大,也被判断为不在阴影里。这就出现了该出现阴影的地方没有阴影的情况。
解决方案:看这篇回答 ,总而言之还是比较麻烦的。
其实一般来说不仔细看是看不出来会有detach shadow的,比如下面这张图,你能看出来吗?放大之后,看一下其实就很小一点点。基本只要找到一个更合适的bias值 这种现象就能很好的被遮掩住。
在Light点做pass1深度测试,不仅存最小的深度,还要存第二小的深度,然后用最小深度和第二小深度中间的深度来作比较,一共是产生了3张深度图。
如下图所示,假设光垂直向下照射,左1图存放最浅深度,中间图是第二深度,最终我们需要的深度图是右1图,从左到右整个鞋子的每个横轴刻度都对应了一个深度值
但是实际中不会使用这个技术
实时渲染不相信复杂度,只相信绝对的速度
任何一种效果,在实时渲染中所分配到的时间,都是精确到1ms
因为深度贴图有一个固定的分辨率,多个片段对应于一个纹理像素。结果就是多个片段会从深度贴图的同一个深度值进行采样,这几个片段便得到的是同一个阴影,这就会产生锯齿边。
在微积分中有很多学了也不知道有什么软用的知识,类似下面这俩不等式
但是在实时渲染中,我们只关心近似相等,我们不考虑不等的情况,因此我们将这些不等式当约等式来使用.
实时渲染最重要的一个约等式(乘积的积分 ≈ 积分的乘积)
这个约等式的作用就是简化定积分的计算,将复杂的被积函数拆开,分别计算积分值,并且结果还能近似相等
为什么右边第一个函数多了个分母?—— 为了保证左右能量相等而做的类似归一化的操作
我们假设f(x)是一个常值函数f(x) = 2,我们的积分域为一维的(0-3),那么约等式左边,把f(x) = 2代入,则可以提出来变为2倍的g(x)积分,而等式右侧第一个函数代入f(x)的积分是2 * 3 =6,分母的积分是3,结果也正好是2.正好也是2倍的g(x)积分。
什么情况下约等式结果更加准确? —— 满足以下两个条件之一
很快啊,这个约等式就能应用到渲染方程中
首先在实时渲染中的渲染方程略微不同
最终结果:红色区域部分是可见性,另外一边就是shading的结果。这也正是Shadow Mapping的思想(2pass渲染,这里相当于先进行着色点渲染,之后再把Visibility乘上去即可)
什么时候这个约等式比较准确? —— 前面说过,满足两个条件之一
因此很多论文通常会说,本论文的shadow mapping方法在glossy环境时不准确(因为glossy材质的BRDF并不“光滑”)
PCSS不是在面光源的基础上生成软阴影,光源依然是点光源,只是模拟面光源的软阴影
软阴影和硬阴影的区别在于有没有明显的边界
软阴影现象很形象的例子,在GAMES101的课程中提过,这里复习一下
Umbra区域(阴影),太阳完全被月亮遮挡
Penumbra区域(半阴影),太阳被部分遮挡
PCF是一个工具,这个工具最早是用来做抗锯齿的
把PCF工具用来生成软阴影,这种技术就被称作PCSS (Percentage Closer Soft Shadows)
PCF具体的做法
我们之前在做点是否在阴影中时,把shading point连向light然后跟Shadow map对应的这一点深度比较判断是否在阴影内,之前我们是做一次比较。
这里的区别是,对于这个shading point我们仍要判断是否在阴影内,但是我们把其投影到light之后不再只找其对应的单个像素,而是找其周围一圈的像素,把周围像素深度比较的结果
加起来平均一下,就得到一个0-1之间的数,就得到了一个模糊的结果。
如图,蓝点是本来应该找的单个像素,现在我们对其周围3x3个像素的范围进行比较,由于是在Shadow map上,因此每个像素都代表一个深度,我们让在shadow map上范围内的每个像素都与shading point的实际深度进行一下比较,如果shadow map上范围内的像素深度小于shading point的实际深度,则输出1,否则输出0.
最终得到的结果是3x3个01,算出这9个数的平均值0.667作为shading point的可见性。在计算阴影的时候我们就拿这个作系数来绘制阴影。也可以加权平均,权值分布可以是越靠近中间的深度值越高。
所以再次强调:PCSS不是对深度图做模糊,也不是对最后已经生成好的阴影做模糊,而是NxN个比较结果做模糊
这个技术十分耗费性能,本来硬阴影每个着色点会比较1次深度,但是软阴影的生成要比较的次数就是几十上百次
选择filter size 的不同有什么影响?—— filter size取得越大,阴影就越软
那么回顾一下,软阴影到底怎么来的,就是在硬阴影的深度比较的时候,选择一个大的filtering。
如果取1x1的就还是硬阴影。
提出问题:
看下面一个例子
此时一个关键思想出现:Filter size <=> blocker distance
下图很经典,右上角是一块面光源(2维下为一条线段),中间的Blocker是一个点,左下角WPenumbra为照射产生的半影区域,即软阴影长度。如果光源换成点光源,很明显往Blocker方向去的光会在Receiver上产生一个点的硬阴影,而不是一块半影区域
如果我们将blocker点的位置移动一下,比如越靠近receiver,我们会发现WPenumbra区域也就会越小,反之越大,而W的大小就反应了软阴影软的程度。
这里根据相似三角形可以推出一个公式:
W P e n u m b r a W L i g h t = d R e c e i v e r − d B l o c k e r d B l o c k e r \displaystyle\Large \frac{W_{Penumbra}}{W_{Light}} = \frac{d_{Receiver} - d_{Blocker}}{d_{Blocker}} WLightWPenumbra=dBlockerdReceiver−dBlocker W P e n u m b r a = ( d R e c e i v e r − d B l o c k e r ) × W L i g h t d B l o c k e r \displaystyle\Large W_{Penumbra} = \frac{(d_{Receiver} - d_{Blocker}) \times W_{Light}}{d_{Blocker}} WPenumbra=dBlocker(dReceiver−dBlocker)×WLight
PCF时,用到的filter区域的大小取决于Blocker到Receiver的距离(和光源大小),所以就有filter size <=> blocker distance
我们想要计算遮挡物到阴影接收物的距离,需要先得到 遮挡物与光源的距离
,
即下面的Blocker search
遮挡物的平均深度(Average Blocker Depth)
补充:遮挡物的平均深度
比如着色点深度为7,shadow map上对应一个5x5区域存的深度如下
我们要计算的平均深度是蓝色区域,因为红色部分深度都>7,不是遮挡物。
前面说了最后一个问题了,这里还有最最最后的一个问题
为了做PCF,我们需要知道第二步中的filter size大小。为了知道filter size大小,我们还需要知道第一步某块特定区域到底选多大,那这块特定区域又怎么选呢- -?
近平面
覆盖多少区域,就用这个区域计算平均深度。这是很对的思想
,因为理论上只有这个棱锥内部的物体才有可能挡住着色点
开销极其恐怖,却又广泛应用于实时渲染的软阴影生成中,怎么办到的?在202第四堂课中介绍
总结三步PCSS
这块知识确实很难理解,反复看视频,几百个字写了好几个小时。可能要等到写过具体代码才能验证自己的理解是否正确,希望看到这篇笔记的人,能有自己的正误判断,如果我理解有错也欢迎评论指出一下,谢谢了。
部分课堂问题: