2018-11月份写的阴影技术总结
背景
阴影在游戏的表现上的重要性无需多言,本文主要介绍及实现一些常用的阴影绘制技巧。
原理
平面阴影
平面阴影(Planar Shadow)原理为将模型上所有顶点投射到投影面上(如地面),由于只需计算将模型顶点投影到投影面上的投影矩阵,无需另外的rt消耗,所以很多手游使用此方法来绘制阴影,如王者的角色阴影:
阴影贴图投射
平面阴影虽然实现简单,但有一个表现上的问题,就是无法在曲面上显示阴影,如王者的人物在凸起的石头上没有阴影。
一种修复这种缺陷的最简单的办法就是将人物的阴影渲染出一张贴图,然后将这张贴图“投射”到物体中,原理如下图:
第1个pass是先在光源空间(light space)渲染出一张阴影贴图,第2个pass是在主视角空间渲染时,将要渲染的像素世界坐标转成光源空间下的裁剪坐标,然后通过该坐标的xy读取阴影贴图,所得的值即阴影颜色,将阴影颜色与原来的接收阴影的物体(Receiver)颜色叠加即可。
在游戏中也有很多应用,比如某手游的云阴影的渲染就是将用这种方法将贴图“投射”到地面上和其他物体上。
Shadow Map
Shadow Map是在光源空间下利用深度缓冲得出一张深度贴图,如下图(左),在渲染某个像素时,先获取该像素在光源空间下的深度值,这个像素的深度值如果比对应在深度图的深度值大,就说明该像素在阴影内,如下图(右)。
受限于贴图的精度,有时候多个片元可能从深度贴图的同一个值中去采样,这个时候需要增加阴影偏移(shadow bias)来处理
具体ps()的shader伪代码如下:
PCF
PCF(Percentage-Closer Filtering)是一种基于Shadow Map的阴影绘制方法,它是为了解决Shadow Map绘制阴影时在边缘有软化的效果,同时也起到一种抗锯齿的效果。
PCF的原理也比较简单,就是不直接读深度图的深度值,而是将统计该像素周围的NN的深度值并取其均值作为最终的深度值。如下图为77的情况。
PCF相对于普通Shadow Map,主要开销在于采样(Sample)的次数。在《Gpu Gems 1-Chapter 11. Shadow Map Antialiasing》介绍了一种用在16个像素中挑4个像素采样这种部分采样的方式,来替代4*4的方式。仿照书中的代码实现的效果如下:
可以看到,虽然采样数一样,但PCF 4 Sample效果也未必优于PCF 22Sample,而44方式也相较于其他阴影效果更加柔和,所以要用多少个采样数要具体情况具体分析。
VSM
由于PCF的采样开销是一个费时又费力的过程,而通过VSM(Variance Shadow Maps)可以大大减少采样次数所带来的消耗。
VSM背后有一个概率学原理:切比雪夫不等式
PSSM
PSSM(Parallel-Split Shadow Maps)是在Shadow Map的基础上,将摄像机视锥体切分开n子视锥体,这样就得到n个深度图,并且通过用物体包围盒的方式将计算出一个CropMatrix,然后通过lightViewMatrix * lightProjMatrix * cropMatrix的方式将渲染像素转到光源空间,这个目的是为了增加精度,在计算阴影的时候通过这n个深度图分别计算阴影。如图是n=3的情况。
增加多个深度图的好处增大了采样的精度,由于多个深度图是相互独立,所以可以配合GPU进行并行采样。
原因如下图所示,对于ds*ds的像素,即阴影图的最小分辨率,所对应的视平面区域dp包含多个像素点,那么这些像素点就都会取相同的值,这样就产生了锯齿状的阴影边界,对于走样的度量可以定义为dp/ds:
而PSSM的子视锥体要怎么分割,《GPU Gems 3》推荐的做法是用均分+对数的方式来分割,具体原因为一系列数学推导,大致思路就是使[图片上传失败...(image-e4244f-1572685793501)] 尽可能的小。
在Unity中的Cascade Shadow就是通过PSSM来实现,默认视锥体分割大小为4。
PCSS
上述介绍的PCF和VSM虽然能使阴影边缘有模糊的效果,但因为它们都是通过类似滤波的方式来处理阴影边缘,都不是真正意义上的软阴影。真正的软阴影应考虑光源区域和以及如何渲染半影(Penumbra)。
PCSS(Percentage-Closer Soft Shadows)是一种基于Shadow Map以及光源区域的思想来实现绘制软阴影的算法。绘制过程共需要2个pass,在第1个pass得到Shadow Map,第2个pass获得一个区域大小并通过PCF获得阴影值,后者主要步骤分为三步:
下图为通过PCSS实现的阴影效果,在边缘处理上比PCF、VSM的方式更加柔和。
技术缺陷
关于技术缺陷,其实在上面的介绍有提及一些,
下面主要罗列一些其他没提及的问题:
1.VSM的光渗现象(light bleeding)
此现象主要源于式(2.2)中[图片上传失败...(image-308da0-1572685793501)] 永远非负,导致式右端永远不可能为0,表现上就是在一些被完全遮挡的物体中有可能会有光渗出的情况。
2.近平面锯齿问题
大部分基于光源空间的阴影绘制在近平面都可能有阴影锯齿严重的情况,原因是dp/ds与z成反比,[图片上传失败...(image-293aca-1572685793501)] 越小,就代表锯齿越严重。
3.PCSS的Zavg不准确问题
性能
手游对于阴影的表现优先级一般不是很高,大部分只要做到有就行,比如最简单的点阴影。而实时阴影,大部分都是通过平面阴影和曲面阴影投射的方式得出,而对于一些手游上的建筑,很多都是静态阴影+AO的方式绘制。
虽然深度检测在移动端也是个很费时的过程,但一些RPG游戏中为在曲面上有好的阴影效果也会结合深度图做一些阴影的绘制,如楚留香在人物阴影的绘制有用到Shadow Map及一些软阴影的绘制。
而对于上述介绍的算法,除了平面阴影,其他都需要额外的一张贴图。而PCF的采样由于消耗比较大,《Real Time Shadow》介绍了一种用对数在(0,1)区间内模拟生成过度自然的阴影值的方法,即用Log2(shade)来代替shade 。
而对于PSSM,由于视锥体的分割,会生成n张贴图,rt的来回切换和SetTex在移动端吃不消,一种省内存的做法是将n张贴图合在一张中。
在很久之前崩坏3中关于角色渲染的分享,也提及到上述讲述的PSSM,VSM,PCSS:)
参考
1.《GPU Gem》
2.《Real-Time Shadow》