找回密码
 立即注册
查看: 377|回复: 2

Unity Shadow Map(一 理论)

[复制链接]
发表于 2023-4-6 13:10 | 显示全部楼层 |阅读模式
本文主要阐述理论部分,Unity源码剖析部分请参考 Unity Shadow Map(二 实践) - 知乎 (zhihu.com)
关于shadow map 的参考资料:图形学中级学习推荐 - 知乎 (zhihu.com)
关于“卷积”的参考资料学习资料:图形学好书——虎书 - 知乎 (zhihu.com)
1.ShadowMap

1.1 ShadowMap原理

a. 光源深度图
光源被遮挡,照不到的地方,就形成阴影. 也可理解为光源“看”不到的地方.  从光源角度“看”记录那些距离光源最近的位置,其他地方都将被遮挡或者干脆在光源外. 技术角度说,这个“看”就是将摄像机放在光源处,通过摄像机的正交(平行光源)或透视矩阵将场景投影,将那些离光源最近(最小,如果是reverse-z,就是最大)的z值记录到 z-buffer中, 就得到光源深度图,也就是shadow map.
b. 比较与光源的距离
成像时,将观察点,和光源方向的,同一位置的(光源角度看),光源深度图中记录的,离光源最近的值比较,如果更远,说明在阴影中,如果更近(或同样近),说明被光源照亮. 技术角度,就是将渲染目标的坐标,从观察或世界坐标系,转换到光源坐标系,然后将z值与ShadowMap中的值比较,大于等于(如果是reverse-z,就是小于等于)时,离光源更远,有遮挡,在阴影中.
图中左边示意图表示 shadow map 记录了光源角度“观察”,距离光源最近的场景物体点. 右图中,a点在光源空间深度和shadow map记录一致,或者更小,不在阴影中,b点显然大于shadow map记录的深度,被遮挡,在阴影中.


1.2 ShadowMap 的问题

1.2.1 问题1:自阴影 Shadow acne

Shadow map 因为2个原因会产生自阴影
a. 有限精度和分辨率
深度数值的存储精度是有限的,并且这个有限精度还得分配到整个光源空间.
另外shadow map 的分辨率也是有限的,只能用一个深度值代表一个区域(shadow map的一个纹素)的深度值.
图1-2中,绿色部分代表渲染表面,红色射线表示shadow map有限分辨率时的采样中心点,采样中心两侧的黄色区域表示采样粒度,采样粒度内的深度值由采样中心的深度值代表, 蓝色阶梯表示因有限精度而不连的采样深度值,其中δ代表最小间隔. 由于采样间粒度不能无限小,深度值精度有限的原因,虽然渲染表面都暴露在光照中,但是采样区间内部,在采样中心的两侧,会造成一半区域的深度值小于采样中心深度值,被照亮,另一半区域的深度值,小于采样中心深度值,在阴影中, 形成自阴影显现.


b. Shadow map和Camera采样点不一致
光源深度的采样点和摄像机空间的采样点很难一样,加上精度有限的问题,导致摄像机的采样点和将该点转换到光源空间后,得到的深度仅仅是其所在shadow map采样区域的,中心采样点的深度.
如图1-3所示.黑色实线代表渲染表面,垂直的黄色区域光源空间采样区域, 红色线条表示采样中心, 实心圆点代表摄像机空间的采样点,同色的x表示与摄像机采样点所属于的,shadow map采样区域的中心采样点. 可以看到只有绿色点和shadow map 采样点(绿色X)比较后,判断为不在阴影中. 蓝色和橙色点虽然没有被遮挡,但和对应的shadow map 采样区域的中心点的深度相比偏大,所以判定为在阴影中. 自阴影由此产生.


自阴影消除方法
a. Constant bias
对物体表面进行施加一个向光源方向的常量偏移,如图1-4所示.
图中所物体表面,都想光源方向施加一个相同的偏移量,使得新的相机采样点比对应shadow map采样区域的中心采样点深度更小,消除了自阴影.


b. The Slope scale bias + Constant bias
超光源方向的常量偏移虽然简单,但是当物体表面完全垂直于光源方向时,其实并不需要这种偏移. 这就造成了,偏移量太小,有些采样点仍然“在阴影中”,偏移量太大,会造成其他问题(漏光和漂浮). 所以提出了根据表面与光照方向的倾斜程度,即光线和表面法线的tanθ来偏移,如图1-5所示,越是倾斜,偏移量越大,充分保证自阴影的消除. 但是这个偏移也不能太大,因为当倾斜程度和光线接近时, tanθ会非常大,所以要对最大值加以限制. 另外,当平面垂直光线时,偏移不能为0,还是要加一个常量偏移,避免因为精度问题而产生自阴影.


c. Normal offset bias
该方法是根据表面法线和光线的sinθ值的大小,将采样点沿法线法相偏移,同样可以达到表面相对光线越倾斜,偏移越多.
但是,因为偏移方向是沿着物体表面法线,而不是沿着光线,所以会造成对shadow map采样坐标(x,y)也发生变化. 可以将该方法理解为,将采样点移动到了一个虚拟的表面.


d. 偏移方式的副作用
偏移方式基本可以解决shadow map 自阴影的问题,但是当偏移量很大时,会有两个副作用: 漏光和漂浮.
漏光(Light leaks)
模型本来是封闭的,但是当偏移了迎光面的位置时,这种偏移是模型顶点沿着法线收缩(shadow cast pass),如果是突出多面体没有太大问题,但当模型是凹多面体时,模型可能出现“缝隙”,会漏光. 此时可以修改模型,也可以打开双面投影(Mesh Render->Lighting->Cast shadows->Two sided,会增加性能损耗).
漂浮(Peter Panning)
投影物和下面的阴影接受面本来是紧密接触的,比如人物站在地面上,但是由于偏移过大,地面和脚分开,产生了漂浮的问题.
e. 其他消除方式
背光面投影
还可以将物体背光面作为投影面,可消除自阴影.但是这容易造成漏光,且模型必须是密封且有一定厚度的,很薄的模型,比如植物叶子,纸片等没有效果.
中间面投影
单纯的背光面投影容易造成漏光,又提出了中见面投影,就是追踪离光源最近的两个面,取中间深度值作为投影“面”.缺点是需要额外损耗性能. 对很薄的模型仍然是无效的. 另外也有方式是,这个中间面不用计算,而是美术人员来确定.


f. Unity Shadow Map的偏移方式
Unity 使用normal offset bias 的方式来防止自阴影,但Unity 是在生成shadow map的时候,将投影面轻微远离光源,这样shadow map中深度值会比实际偏大,实际投影时,投影面的深度就会偏小,从而消除自阴影.
此外,在光源剪裁空间,Unity还会在法线偏移的基础上,直接施加一个随着与光源距离增加而减小的偏移量.
1.2.2 问题2:分辨率问题(Perspective aliasing)

与texture类似,当shadow map 纹素和相机像素能一一匹配的时候,不存在任何问题, 但这太理想了.  当相机和光源位置重合,不存在任何阴影,当两者位置不同,就会出现多个像素匹配一个shadow map纹素, 阴影就会出现锯齿或马赛克,如图1-7. 这主要是因为shadow map 的像素匹配被透视相机不同区域的不同缩放比例造成的, 如图1-8所示. 光源垂直照射投影,方格表示shadow map的纹素. 摄像机从左向右拍摄,近截面附近和远截面附近在屏幕上呈现相同的大小,但是,近界面附近对应4个shadow map纹素,远截面附近对应20个shadow map 纹素. 近截面附近的shadow map 分辨率显然是不够的,就会造成马赛克.


a. Cascaded Shadow Map(CSM)
马赛克现象可以通过提高shadow map的分辨率来解决,但这会带来更多的性能消耗. 另外,当增加Soft shadow时也会缓解失真,但也不能和那后的消除. 所以提出了更加高效的解决方法:级联阴影 即CSM, 其示意图如图1-10. 这种方式就是将camera视锥体,平行的分成几个平顶锥体(frustum),沿观察方向,下一个frustum的深度是前一个的2-3倍(Unity中默认是2倍),每个部分由光源的frustum包裹(平行光时就是长方体),并生成单独的shadow map. 如果某个渲染点被同时属于2个frustum,就选择更加靠近Camera的, 以提高分辨率.


Unity的Cascaded Shadow Map 设置如图1-12


b. 其他解决方案
除了使用级联阴影,还有一种解决方案是perspective shadow maps(PSM).这种方式使用与摄像机类似的成像原理来生成shadow map, 目的就是在靠近摄像机时提高分辨率,远离时降低分辨率. 摄像机的成像也是近大远小,越远的地方缩小越多. 具体来说,就是改变了场景投影到光源的方式,不再是根据光源观察方向对称,这个frustum可能被扭曲,缩放,平移,旋转, 如图 1-12所示. 与此类似的方式还有 TSM, LiSPSM.


1.3 Soft Shadow

软阴影就是介于完全阴影和完全照亮之间的,位于阴影边缘的半影. 软阴影可以改善shadow map 一个纹素匹配多个像素造成的马赛克问题,同时让阴影更真实,更符合实际情况.
1.3.1 Percentage Closer Filter(PCF)

造成软阴影的原因之一是光源是有大小体积的,而不是理想化的一个点. 如果被整个光源照到,就是完全的无阴影,如果完全照不到,就是全阴影,如果被部分光源照到,就是半影,如图1-3-1. 左图中的P点就是个处于部分光源照射的半影之中,且这个半影的大小和光源与遮挡物的距离有直接关系,距离越远,半影越小. 右图中是理想的点光源,PCF的思路就是以渲染点P为中心的一个区域(在shadow map中)采样,接着将所有采样点都与P点(在光源空间的)的实际深度比较,最后在所有比较结果中计算阴影或者照亮的百分比,这个百分比就决定了P点的半影明暗程度.
注意:
a.采样区域是P点对应的shadow map 纹理采样点(u,v)的周围,而不是p点的周围,以此推测模拟p点的半影情况.
b.所有采样点都和p点的实际深度比较,相当于假设p点周围对于光源方向是个等距离的平面或者弧面.


接下来看下最简单的PCF采样滤波,均值滤波,也就是对目标项目点为中心,对目标点的shadow map采样坐标(u,v)进行偏移,然后采样. 如图1-3-2,图中对包括目标点在内的3x3个点采样,然后将采样结果和目标点的实际深度z比较,再对比较结果进行平均,得到目标点的滤波结果.


1.3.2 滤波方式

a.均值滤波:
这个是最简单的PCF滤波,如果用滤波和卷积的方式来解释,就是用一个box filter来对阴影进行卷积操作. 对一个二维的点进行卷积操作的公式如式1-3-1, 其中r是卷积中滤波函数,或者说是权重函数的有效半径,在这个半径之外,权重函数等于0, 而滤波的中心就是目标像素点的函数值,1-3-1中的s[i, j], 在PCF中,这个s[i,j]就是采样且深度比较的结果. 权重函数如式1-3-2,该函数在目标像素点极其周围一定半径内,对其进行加权求和,为了使得像素在滤波后不至于整体改变亮度,权重函数积分或者求和后,应该等于1. 式1-3-2中,当r=1时,ω=1/9, 其实就是求9个像素函数值的平均值.


b.三角滤波(Tent Filter):
在均值滤波中,采样半径内的所有点都是相同的权重,且滤波函数边缘是阶跃形式的. 另外一种简单,但是更为平滑的滤波函数是三角滤波,因为滤波函数看起来就像三角形, 函数如式1-3-3, 函数图像如图1-3-4. 同样,三角滤波的区域的积分或者是求和应该为1, 也就是权重和为1,以防止改变信号的整体强度.


1.3.3 Unity中三角滤波应用

当三角滤波应用在PCF中时,情况稍稍复杂. 硬件采样时,其实是以2x2为单位进行采样的,并且会对这4个采样像素进行双线性插值. Unity中3x3的滤波中,就是进行4次硬件采样,也就是采样4x4=16个像素, 然后用三角滤波在UV两个方向加权4x4个像素,每个像素的权重=U方向权重* V方向权重. 而U,V方向上单个像素所占的权重=像素对应的三角形区域的面积/三角形总面积, 如图1-3-5中,S1,S2,S3,S4区域的面积除以三角形面积,就分别代表P1,P2,P3,P4像素对应的权重大小. 因为要覆盖4个像素,所以Tent Filter的形状通常为底边长3,高为1.5的等腰三角形, 这样就能覆盖4个像素. 之所以是3, 是因为offset的偏移区间为[-0.5,0.5], 超过这个区间,就可以算作是另外4个像素的滤波, 使用底边和高的比例尺寸(不是更高,或者更矮,更大或者更小)来得到的权重来滤波,会有更好的效果, 而且 这是个等腰直角三角形,计算权重上也更加方便.


a. U和V方向加权
这4x4个像素,不看成4个孤立点,而是一块像素区域,然后用Tent Filter函数的对应面积(积分)比例表示某个像素区域的权重. Tent Filter的中心对准目标渲染点P的shadow map采样坐标u 或v,然而 u, v坐标极其可能不是某个像素块的中心,那么tent filter三角形的中心也会左右偏移(offset), 以便对齐目标采样点的U和V坐标. 但三角形偏移后,对应采样坐标中心的4个像素点比例也会变化,如图1-3-6所示.








通过Tent Filter加权,得到目标采样点附近4x4个像素的权重,每个像素的权重就是横向权重和纵向权重相乘的结果, 如图1-3-9. 实际采样过程中16个像素需要由硬件采样4次,每次得到一个2x2的group, 这个group的权重就是4个像素的权重和,如图1-3-10,黄色group的权重就是4个黄色像素权重的和. 通常对纹理采样,硬件就是采样一个2x2的group, 然后根据采样点的坐标,对group进行双线性插值,再返回插值结果. 在图1-3-10中,黄色采样点的坐标正好位于4个黄色像素的中心,那么双线性插值的结果就是4个黄色像素的平均值. 而PCF的真正目标点P,是4个双线性插值结果P1,P2,P3,P4的加权平均值, 其中各自的权重就是每个group中4个像素权重的和.


b. Group 采样坐标计算
硬件采样的2x2group, 采样点不会像图1-3-10那样始终位于group的中心, 而可能是group的任意位置, 如图1-3-11.此时可进行双线性插值得到P点的值,如式1-3-14, 1-3-15, 1-3-16,本质上就是根据s坐标与V1V2的距离,计算横向权重比,插值A1,B1得到E1值,插值C1,D1,得到F1值; 再根据t坐标与U1,U2的距离, 计算纵向权重比,插值E1,1F得到P1值. 而在3x3PCF滤波中,如前所述,已经知道了横向和纵向的插值比例,但是并不知道这个p点的坐标, 所以也无法得到P点的采样值. 所以现在的工作就是利用已知的横向纵向权重比,得到P1点的采样坐标(s1 ,t1 ),然后用UV采样.
注意:P点距离A越近,A值的比例越大,s1-u1越小,u2-s1越大,所以A的比例是u2-s1.


4x4=16个像素的最终权重,已经计算好了,如图1-3-9,所以4个2x2的Group中的权重也就确定了,但是这4个Group的采样值还不知道,所以现在需要计算每个Group的采样坐标,进而得到采样值. 由1-3-13式可得到的4个横向,4个纵向权重(ω1, ω2, ω3, ω4)和( φ1, φ2, φ3, φ4). 由1-3-14可看出,P1的插值过程中,A,B的权重比等于距离比(B1P1比A1P1),当通过Tent Filter确定A1,B1插值权重时,距离比也就确定了,现在就是要通过权重比,得到距离.


1.对P1点坐标四舍五入取整,得到采样中心参考点O的坐标
2.O点坐标平移(-1.5,-1.5)得到A点坐标
3. 利用P1在Group中的权重,得到P1相对A1点的距离(XA1P1,YA1P1),
如1-3-17和1-3-18式
4. A1相对O点坐标+P1相对A点坐标 = P1相对O点坐标,而O点坐标可通过P点得到的


根据1-3-19式,可以通过Tent Filter计算得到的4x4个权重,进一步算出A1,A2,A3,A4相对于P1,P2,P3,P4的距离,加上A1,A2,A3,A4相对O的位移,再加上O点坐标(O点坐标通过P点采样坐标取整得到), 就得到P1,P2,P3,P4的采样坐标为:


c. 加权求和
得到P1,P2,P3,P4的采样之后,剩下的就是加权求和了,它们的权重就是每个Group内2x2个像素的权重和, 如图1-3-9所示,插值如1-3-21所示.


1.3.4 阴影接受面深度偏移

之前1-2-1节已经讨论过,为了防止自阴影,对深度图进行偏移. 但是这个偏移仅仅能防止渲染点P自己对应的深度纹理比较时产生的自阴影. 但在PCF中,情况有所不同,渲染点P不仅要和自己采样坐标(u , v)对应的深度纹理值比较,还要将(u , v)附近的深度纹理采样值,和渲染点P的深度值z比较, 比较对象都是渲染点P的深度z, 如果此时光线方向和P点为中心的采样“平面”夹角较小时,也会产生自阴影,如图1-3-14.图中,P为目标渲染点,在光源空间中深度Pz,P对应的shadow纹理坐标为u,u附近的u+1,u+2为实现PCF时增加的附近采样点,深度分别为d1,d2. 按照PCF算法,应该将采样值d,d1,d2分别与P点的光源空间深度Pz比较,从图上看有,Pz<=d, Pz > d1, Pz> d2, 然而这个结果显然是错误的,因为P旁边的A,B两点显然位于光源下,而不是阴影中. 所以最后计算得到的阴影肯定是错误的. 所以就需要偏移P点的光源空间深度Pz,对于u+1采样点,Pz偏移δ1到 P’位置,就可有Pz - δ1 =Pz’, Pz’<=d1,但是对于u+2采样点,需偏移δ2,才能满足Pz - δ2=Pz”. 也就是说必须偏移P点的光源深度Z,才能消除PCF带来的自阴影,并且这个偏移量δ随着采样坐标的移动而变化.


Z(偏移量)随着采样坐标(u, v)的移动而变化, 数学描述就是z对采样坐标(u , v)的变化率,也就是z对(u , v)的导数.Unity中用离散方式(差分)计算导数的函数是ddx(),ddy(), 但是,这两个函数仅仅只能求得传入参数相对于屏幕空间坐标(x , y)的变化率(倒数/差分), 也就是说只能得到∂z/∂x, ∂z/∂y, 另外∂u/∂x, ∂u/∂y也可以得到,但是∂z/∂u, ∂z/∂v是得不到的, ddx(),ddy()只能对x , y求导. 所以问题就归结为,如何利用∂z/∂x, ∂z/∂y, ∂u/∂x, ∂u/∂y,来得到∂z/∂u, ∂z/∂v.
从已知开始, z对x,y的导数,根据链式求导,得到z对u,v的导数,再分别对x和y 求导, 如式1-3-22, 红色表示未知,绿色表示可以通过ddx(),ddy()得到.写成矩阵形式如1-3-23式




参考资料:

Real-Time Rendering, Fourth Edition 4th
Fundamentals of Computer Graphics, Fourth Edition by Marschner, Steve Shirley, Peter (http://z-lib.org)
https://docs.unity3d.com/Manual/shadow-cascades.html
https://blog.csdn.net/cgy56191948/article/details/105726682
https://zhuanlan.zhihu.com/p/369761748
https://zhuanlan.zhihu.com/p/39

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-4-6 15:03 | 显示全部楼层
厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害
发表于 2023-4-6 15:04 | 显示全部楼层
厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-4-29 23:29 , Processed in 0.121182 second(s), 28 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表