Unity3D 基于ShadowMap的平滑硬阴影
前言
传统的ShadowMap在明暗边缘处都会有很难看的锯齿,因此一般得到的结果会比较难看,常规的解决办法都会在使用ShadowMap渲染阴影的时候通过背面剔除把这种缺陷隐藏掉,最后剩下一个影子。但是这样一来,自阴影就会丢失,因而传统的做法又会通过局部光照来重新为这个物体添加上部分自阴影,也就是咱们常见的Phone光照模型、Blinn-Phone光照模型。而本文决定通过文献[1]的一个平滑方法把ShadowMap在明暗边缘处的锯齿消除,并和光照模型求并,最后得到了一个包含丰富平滑自阴影效果。
本文读者默认为有图形学基础、编写Shader基础和ShadowMap基本原理,若没有请先去把这些基础学习一下,再来阅读本文,否则可能会有阅读障碍。
一、ShadowMap和局部光照模型的缺陷
(1) 传统ShadowMap能产生丰富的自阴影,但在明暗边缘处都会有很难看的锯齿和走样问题,如图1.1。
图1.1 ShadowMap的缺陷
(2) 局部光照模型在在明暗边缘处能产生非常平滑的阴影,但只能产生有限的自阴影,缺陷表现为丢失部分自阴影和投射阴影,如图1.2。
图1.2 局部光照模型的缺陷
二、ShadowMap缺陷分析
ShadowMap产生这种锯齿缺陷的原因是,光照摄像机的方向和模型中边缘处的三角面接近平行,导致这些三角面没有映射到任何像素点。如下图2.1、图2.2
图2.1 以光照摄像机的正交投影视角观察需产生阴影的物体
图2.2 以自由的正交投影视角观察需产生阴影的物体
图2.1渲染出来的图像就是ShadowMap,图2.2是以不同的角度观察ShadowMap渲染的物体。
两张图都是是同一个渲染方法,只是观察角度不同。即:把光照方向和三角面法向量的点乘结果大于0的三角面渲染出来的结果,也就是隐藏对于光源摄像机不可见部分。这样大致上能模拟得出构成ShadowMap的所有必须的三角面。
从人的视觉上看图2.1很完美,理论上也就认为能产生完美的阴影,但实际操作的时候就会发现结果和自己想的不一样。结合两张图得出,造成ShadowMap明暗边缘锯齿的罪魁祸首是模型的三角化导致的。因此不管怎么增加ShadowMap的分辨率都是没用的。
图2.3 ShadowMap的缺陷指示图
目前市面上的模型基本都是三角面构成的,不可能因为这个问题就废弃掉。虽然学术上有一种把三角面模型体素化的方法把模型转换成控件中的一个个有体积微小正方体,但貌似并不常用。因此问题怎么消除这些锯齿是本文的重点。
三、ShadowMap和局部光照模型的并集
那么仔细观察两种模型产生的阴影缺陷后,把两者求并集后是否就能即拥有局部光照般的边缘平滑度,又有ShadowMap丰富的阴影呢?立马动手实现,如下图3.1
图3.1 ShadowMap和局部光照模型求并
如果这样的效果能接受的话,那么到此就结束了。本人在翻了一番国内学术后发现,也是到这一步就结束了,后续貌似没人再做更多的工作。但其实还可以进一步把平滑做得更完美。
四、ShadowMap明暗边界的平滑
4.1 构造明暗边界线
ShadowMap的锯齿原因是由于在明暗边界的地方三角面不完整,导致深度呈锯齿状起伏,因此只要把明暗交接的地方的深度值(像素值)用同一个深度值覆盖就能获取到非常平滑的明暗边界。
在文献[1]~文献[3]中都阐述到了同一种,方向向量与模型网格(Mesh)在边缘处求边缘线的方法。由于本人未对其做深入研究,仅知道通过其提供的公式即可求出边缘线,进而可构造出比较完美的明暗边界边,理论就不多说了免得班门弄斧,建议直接去看原文,不看那就直接抄本人写的代码。效果如下图4.1~图4.3:
图4.1 ShadowMap+明暗交界线(红色)
图4.2 局部光照+明暗交界线(红色)
图4.2 复杂模型+局部光照+明暗交界线(红色)
此边缘线基本上就是局部光照模型的明暗交接的比较完美的逼近了,甚至还比局部光照还能更平滑,这都是得益于数学上的赫米特(Hiemite)插值法。
4.2 平滑ShadowMap明暗边界的深度值
实际上通过文献[1]~文献[3]求出来的是一个一个轮廓三角形上的一条线段,最后把所有这些线段合并起来就得到了明暗边界线,那么我们就可以通过这些点构造出一条针对于光照摄像机的可控粗细的线条。如下图4.2.1、图4.2.2
图 4.2.1 其他视角
图 4.2.2 光照摄像机视角
具体实现步骤如下:
1.通过明暗边界线的2个点的位置及其单位法向量(注:这2个数据都可以通过文献[1]~文献[3]计算得到)构造出2个各自沿着单位法向量负方向位移一段距离的点,以及2个各自沿着单位法向量正方向位移一段距离的点。
2.通过步骤1得到的4个点构造出2个三角面,进而构造出1个四角面。
这样我们就得到了一个针对于光照摄像机的可控粗细的明暗边界线,接下来就是如何进行正确的覆盖ShadowMap中锯齿状起伏的深度值。
4.3 覆盖ShadowMap中锯齿状起伏的深度值
关于这一块本人目前没有想到太好的办法,目前的做法是把这些明暗边界线往光照方向的负方向位移一段距离来覆盖锯齿状起伏的深度值,效果看起来还不错。
图4.3.1 ShadowMap+平滑明暗边界的深度值
图4.3.2 ShadowMap+平滑明暗边界的深度值+局部光照
图4.3.3 ShadowMap+平滑明暗边界的深度值+局部光照+复杂模型
图4.3.4 平滑明暗边界的ShadowMap
可以看到仅仅使用ShadowMap就非常接近局部光照阴影的平滑程度了,并且还拥有丰富的全局阴影。但由于只是简单的把这些明暗边界线往光照方向的负方向位移一段距离来覆盖锯齿状起伏的深度值,因此还是有一点点的小缺陷。如果到这里已经满足了的话,我建议再把局部光照加上去,因为局部光照算法非常简单,1次点乘+1次step即可得到结果,再与本文方法求并,就能得到效果很不错的阴影了,如图4.3.2和图4.3.3。
五、实现以及用途
说了这么多,真正动手去实现的时候会发现,并没有增加多少复杂度,仅仅在传统的ShadowMap的基础上,在渲染ShadowMap的时候增加几何着色器即可。代码部分不过多说明,后面会给出基于Unity3D的源码。
那么这种硬阴影有什么用呢,甚至不惜增加一定的复杂度?\本文认为这种阴影在卡通渲染上是十分有用的,因为卡通的颜色并不需要过多的渐变,一般只需要明暗2种颜色即可,而卡通渲染又需要丰富阴影,因此将其运用到卡通渲染上是用途之一,如下图5.1。
图5.1 本文算法+明暗贴图+复杂模型
六、结束语
虽然是这么说,但实际上二次元精美的插画都有一定程度的渐变,这是人为主观意识来添加的。在计算机上要实时实现这中渐变,并且任何角度观察都能达到插画级的精美程度是很困难的,这是因为插画的绘画人自己也说不出这个数学模型,在计算机里没有数学模型就不存在合理性,没有合理性就很难模拟,因此一般都只能用大量人力一帧一帧地把画面画出来。
目前顶尖水平的卡通渲染是以GuiltyGearXrd为首的渲染方法,使用局部光照阴影,并通过大量人力物力对某个物体在各个角度做类似法线贴图的贴图。这是无法复制的,一次创作需要消耗大量人力物力。这种方式产生的阴影在物体形变大的时候会产生凌乱的阴影。因此制作人一般都会想办法遮掩这部分缺陷,比如减少物体的形变动作,或者把镜头放到你看不到这些缺陷的地方。如果哪天实时全局光照烂大街了,那么插画级卡通渲染或许就会来临吧。
感谢学术界大佬们的精彩文章,本文到此结束,谢谢。
附:源码
参考文献
Silhouette Smoothing for Real-time Rendering of Mesh Surfaces
基于GPU的网格模型平滑阴影的实时绘制
三角网格模型平滑阴影的实时绘制