lecture 2
实时渲染的特点: 不做与真实或者离线完全一致的渲染,而是选择相似度比较高的方式来进行,以此来节约时间和达到实时的目的。
全局光照: 光照分直接光照,就是光线通过光源打到点上,然后再反射到人眼或者相机,这种叫直接光照;间接光照,就是光打到A点,B点接受来自A点的光,B点反射再打到相机或者人眼。
实时渲染里,实时全局光照中一般只做一次间接光照,而不是按照真实的光线传播那样一直递归和损耗,因为两者效果区别并不大,但是不做间接光照和做的区别很大。
这是不做间接光照的照片:
这是只做一次间接光照的照片:
这是做两次的bounce的实时光照:
可以看到区别不大。
Lecture 3 shadow mapping
为什么做阴影?
因为阴影让物体更真实,而非有一种贴图感。
在渲染里,做阴影的思路是:先从光源出发,做一张深度图,记录它所能到达空间内的每个位置的最浅深度。再从摄像机出发,从摄像机所看到的点,计算这个点到光源的深度,与光源第一次记录这个位置的深度相对比后,如果发现其大于光源记录的深度,则说明,摄像机所照进去的这个点在阴影里,不然则说明这个点可以被光源看到。
shadow mapping 的问题有两个,一个是自遮挡,另一个是走样。
3.1shadow mapping 的自遮挡问题
当光源与所照射的物体倾斜角度越大的时候,一个像素更容易遮挡住另一个像素,用它的深度覆盖另一个像素的深度。
//这部分转载自知乎 zilch,因为他讲的这部分我个人感觉更好懂。
/*
作者:zilch
链接:zhuanlan.zhihu.com/p/370951892…
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
3.1.1 引起shadow acne的原因
-
ShadowMap的分辨率有限
-
因此ShadowMap中的一个像素,对应到场景上是一片区域。
-
在ShadowMap生成阶段(Shadow Caster),该区域内的点(假设都暴露在光照下)最终会汇集到ShadowMap上的同一个像素,对应一个深度值D,该深度值在光栅化阶段由插值得到。对于平面来说,深度值应当恰好等于区域的中心。
-
在阴影计算阶段(Shadow Receiver),当我们将该区域内的点投影到ShadowMap中进行深度对比时,会发现其中一些点的深度大于D,而其中一些小于D,因此形成了亮暗条纹。
用一张图来解释:
图片来自learnOpenGL
箭头为入射的平行光,每一条斜黄线断代表ShadowMap贴图中的一个像素,对应到水平地面上可能覆盖多个像素。多个像素里有些深度较大,有些深度较小,因而产生了条状瑕疵。
我们可以使用一个Shadow Debug Pass,将ShadowMap的分辨率以红黑格子的形式,投影到场景中绘制出来如下:
ShadowMap分辨率投影效果
可以看得出来,自遮挡引起的阴影瑕疵条纹与shadowmap的分辨率格子大小是一致的。
凑近了观察一下
ShadowMap分辨率投影效果
红黑格子为shadowmap投下的分辨率(平行光以45度照射,因此格子呈现1:2的长方形)。黑白条纹会自遮挡引起的阴影瑕疵。我们可以看出,从左至右,一个分辨率格子内包含了黑白条纹各一条。
接下来我们会将以上的阴影自遮挡问题,转化为一个纯粹的几何问题,来用数学进行精确描述。这将解释:
- 为什么瑕疵条纹与ShadowMap的分辨率会呈现以上的关系
- 如何精确的计算Bias的数值来修正自遮挡问题。
几何模型
首先看下图:
此图为Shadow Map数学几何模型
- 橙色线条为平行光视角的近平面,记为L,这个近平面最终将映射为一张ShadowMap深度图。
- 蓝色线条为接受光照的平面。
我们暂且假设Light对应的正交矩阵其宽高相同(实际上URP中就是这样的),记为frustumSize。ShadowMap贴图的尺寸记为shadowMapSize。
设图中AB线段代表ShadowMap贴图上单位像素在Light近平面上对应的尺寸。那么我们有以下公式:
|AB| = frustumSize / shadowMapSize
从AB作橙线的垂线,相交场景蓝色平面于CD两点。易知,CD范围内的所有像素均投影到平面L上的AB区域。
由于AB代表了ShadowMap上的单个像素,因此AB像素中存储的深度值应当是CD中点F到平面L的距离,即|FE|(实际上是归一的)。
我们记:
Distance(X,L) - 表示任意点X到光源近平面L的距离。
那么显然:
- 对任意点X属于CF,有Distance(X,L) < |FE|,因此判定为不在阴影中
- 对任意点X属于DF,有Distance(X,L) > |FE|,因此判定为在阴影。
于是在CD区域中会呈现出一半白,一半黑的阴影瑕疵,这就是Shadow Acne,其以CD长度为周期在平面上循环交替,而|CD|正是ShadowMap中单个像素投射在场景上覆盖的区域。
作者:zilch
链接:zhuanlan.zhihu.com/p/370951892…
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\
3.1.2 如何解决shadow ance
使用DepthBias
过F点做L的平行线p,如下图:
ShadowMap数学几何模型示意图
我们只要将CD中的点,往光照方向平移一定距离到GH上即可修正误差。这个移动的距离即称为Depth Bias。不妨先考察D点。 |DG|即是要修正的Depth Bias,于是我们有:
那么对于CD上的任意点X,我们可以使用相似三角形的原理,从DepthBias(D)乘以对应的比例就可以了。
伪代码如下:
shadowUV = shadowProj(X);
percent = (frac(shadowUV) - 0.5) / 0.5;
DepthBias_X = DepthBias_D * percent;
实际上在大多数引擎的实现中,并不会这么精确的去计算每个点的bias数值,而是对所有的点执行一个固定的Depth Bias。很明显,D点的误差是最大的,因此只要对所有的点都使用D点的bias数值,就可以修正自遮挡的问题。
但是固定的depth bias也会引起其他的问题。
3.1.3 漏光问题
考虑点C前面有个遮挡物。
ShadowMap Bias 遮挡物情形示意图
本来C点应该是处于阴影中的。但是我们通过depth bias将C点往光源方向进行了长度为|DG|的偏移,那么C点就变到了遮挡物前面去了。这就是固定depth bias引起的漏光现象。
3.1.4 bias趋向无穷大的问题
当入射光线与平面夹角趋于0,即 趋于90度时,线段DG长度会趋于无穷大。从|DG|的公式也可以看出:
在角度趋于90度时,会变得无穷大。我们不能对像素应用过大的偏移,否则漏光现象会变得非常严重。因此固定尺度的depth bias在
趋于90度时,将会趋于失效。
过大depth bias导致漏光现象
3.1.5 Normal Bias
为了解决Depth Bias的缺陷,于是人们提出了Normal Bias。顾名思义,既然往光源方向偏移有问题,那么我就往法线方向偏移呗。
Normal Bias几何示意图
将CD平面按照法线方向,平移到C'D',平移距离为G到CD的距离即|GM|。从图中可以看出,经过法线方向平移后:
- C'G段的点,深度均小于|EF|,因此光亮
- D'G段的点,移动到了隔壁的像素区间,易知也是小于隔壁像素深度的,同样呈现光亮。
这样同样可以解决shadow acne。通过简单的几何知识,我们可以写出关于Normal Bias的公式,即对任意点X属于CD有:
于Depth Bias相比,其优点在于当 趋于90度时,bias趋于
{frustumSize/(2 * shadowMapSize)},而不是无穷大。也即,normal bias的最大值只与shadowMap的分辨率有关。
3.1.6 Normal Bias的漏光问题
Normal Bias同样会存在漏光问题,并且是两头漏光。考虑有个遮挡物如下:
Normal Bias遮挡物示意图
该遮挡本应该在CD区域投下阴影。但由于normal bias的存在,我们在进行深度判定的时候将CD移到了C'D',因此CD平面左侧会有少部分位于遮挡物上方,从而漏光。而右侧的GD'部分,也因为进入了隔壁的像素地盘,形成了漏光。
但在实际应用中,由于Normal Bias的最大值相对可控(只要提升ShadowMap分辨率即可),因此漏光问题并不太严重。
3.2 阴影的走样
解决办法有:
利用PCF(Percentage Closer Filtering)直接对阴影信号 进行滤波,就可以实现平滑阴影边缘。
工业界采用动态分辨率的方法。
3.3 硬阴影与软阴影
硬阴影是左上,阴影非常棱角分明,而软阴影如左下,更为贴近真实。软阴影有过度,一般是面光源,更自然,有一个半阴影的区域,从此看光源发现光源被部分遮挡。
PCF是做抗锯齿的,而PCSS是用来做软阴影的。
最早开始研究的是pcf,他们两者的原理都是,做滤波,也就是卷积。
此处转载知乎insulation的部分笔记,用来解释硬阴影与软阴影
作者:insulation
链接:zhuanlan.zhihu.com/p/359377010…
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
为了实现软阴影的效果,我们可以修改一下采样比较shadow map时的方式,不是直接地采样得到shadow map中对应点的深度与实际到光源深度进行比较,而是采用所谓的filter的方式,在shadow map中对对应点的周围的数个像素采样,将采样得到的深度与对应点实际到光源深度进行比较,从而得到或0或1的值,然后对这些值取平均,作为阴影绘制的依据。如图中我们绘制P点阴影时,可以假设在shadow map中以P点为中心取到3x3的区域。
以P点为中心在shadow map中采样
假设这是我们在shadow map中获取到的深度值,而P点得到的实际到光源深度为0.5,这时所有的在shadow map中获取到的深度值要与P点得到的实际到光源深度,即0.5进行比较,所有大于0.5的像素我们输出0,反之则输出1。
采样得到的结果
可以得到:
比较后的结果
接下来,将得到的值求平均,得到4/9=0.44,作为阴影输出,这样我们就得到了一个软阴影,而这就是PCF的核心。 这样的计算可以得到软阴影效果,而且锯齿也不再那么明显了,但是要得到一个看上去不错的软阴影效果,需要采样的区域很可能是16x16甚至于64x64的,这样对每个点进行计算,将是非常大的性能消耗,而且对于每个点都进行PCF,最后将得到一个“糊掉”的阴影,这都取决于我们进行filter的范围,在我们仔细观察下,一个阴影可以分为软+硬的两部分。
闫老师自称很喜欢的一张图
在这张图中,我们可以看到,这支笔的阴影在笔尖的部分比较符合“硬阴影”,而在较远处则更符合“软阴影”,注意观察钢笔到纸面的距离,不难发现阴影的接收物到阴影的投射物越远(也就是笔和笔的阴影),阴影就越“软”,反之则越“硬”。以此为原理,我们可以计算出我们要进行filter的范围,这就是PCSS。、
我们可以用下面这张图来说明,Wlight为我们模拟出的光源的“大小”(我们使用的点光源其实并不存在大小一说,这里是将其模拟为面光源来处理 ),Blocker为遮挡物,Receiver为我们接收光源的面,下面的W就是软阴影,也就是filter的范围。
我们很容易就可以发现一对相似三角形,根据这个相似三角形,可以得到如下图的等式
相似三角形在图中标出
由相似三角形推出的等式。计算得到的Wp 与filter的大小有关,如果越大,则filter也越大,卷积变大,阴影边缘变的模糊;越小,说明遮挡物与阴影越近,阴影越清晰。
在等式右侧这些值中,除了Blocker到光源的竖直距离,其他我们都可以轻松得到,这个距离如何界定呢?首先不能直接使用shadow map中对应单个点的深度,因为这样如果该点的深度与周围点的深度差距较大(遮挡物的表面陡峭或者对应点正好有一个孔洞),将会产生一个错误的效果。我们选择使用平均遮挡距离来代替,具体方法是在shadow map上采样该点周围取许多点来计算各自的遮挡距离后求平均。
立体示意图
这样就又涉及到了一个问题,采样的范围该如何界定?一种方法是采用固定的范围,例如4x4、16x16。另一种更好的方法是动态计算遮挡范围,我们计算shadow map的时候在光源处设置过相机,如图所示,我们把shadow map放在相机的近截面,然后将光源和要渲染的点相连,在shadow map上截出来的面就是要查询计算平均遮挡距离的部分,这部分的深度求一个均值,就是Blocker到光源的平均遮挡距离。