结合Projector和Rendertexture实现实时阴影及软阴影效果

说到实时阴影的实现,一般比较容易想到使用ShadowMap,通过投射灯光空间的深度图,并在投射物体上进行深度比较,判断是否处于阴影的范围,以此来渲染阴影。深度图投射到接受阴影的物体上的效果如图所示:


深度图投射

你所需要做的就是在灯光空间渲染一张深度纹理,并投射到接受阴影的物体上,并和接受阴影的物体上对应像素位置的深度(灯光空间)进行比较,来确定当前像素是否处于阴影即可,此外还要考虑深度图的精度以及以此会造成的ZFighting等,当然这并不是本文讨论的重点。

本文主要介绍一种直接投射灯光空间摄像机的Rendertexture来实现阴影的方法,并将在稍后将其和projector结合。当然同时熟悉这两项技术的开发者应该已经清楚,使用projector实现阴影意味着你将会消耗额外的drawcall,实际上被投射projector并且未在shader中使用"IgnoreProjector"="true"的物体都会在自身shader渲染完(也可能是渲染前,具体看自身渲染队列和projector的shader的渲染队列的先后顺序)后再次使用projector的shader渲染一次。

首先比较一下这种技术和shadowmap技术,实际上个人感觉很大程度上两者的技术其实差不多,都需要用到屏幕投影,只不过shadowmap投射的是深度图(深度缓冲),而本文介绍的是直接投射屏幕纹理(帧缓冲),因此投射的纹理是带Alpha通道的,


注意灯光空间的摄像机背景颜色的Alpha因为0,否则会把背景色也投射到接受阴影的物体上

和shadowmap不同的是,灯光空间的摄像机应该只看到投射阴影的物体:


此时投射后的效果大致如图所示:


当然使用这种方式投射rendertexture必然造成的一个问题是,由于没有投射接受阴影的平面,导致一旦投射阴影的物体穿透接受阴影的物体时会造成阴影的穿帮:


阴影投射物体穿透阴影接受物体

接受阴影物体Shader主要实现代码:


其中viewMatrix为灯光空间摄像机的worldToCameraMatrix,projMatrix为灯光空间摄像机的投影矩阵。

当然使用这种方式实现阴影的不足之处在于需要明确的知道投射阴影的物体和接受阴影的物体。

接下来将尝试将其与Projector结合,注意之前已经讨论过,使用projector意味着额外的drawcall,尤其是场景中物件很多且全部都是分离的物体时,不建议使用这种方式。当然如果场景中只有极少部分物体需要接受阴影,比如只有主要地形,则不妨可以尝试使用这种方式,因为使用projector,你可以很方便的在shader中加入IgnoreProjector标签来忽略投影机的作用,或者直接在projector上修改projector影响的层。

从unity标准资源包中的projector shader我们大致可以了解,projector shader中需要两个4阶矩阵,分别为_Projector和_ProjectorClip,其中后者主要用于近远裁面的淡入淡出,并不是必须的。而前者的_Projector,注意这个矩阵应该区别于摄像机的projection矩阵(尽管摄像机和projector在很多参数上很相似),原因是官方的projector shader中直接通过:o.uvShadow = mul (_Projector, vertex);计算得到投影纹理坐标,这意味着_Projector矩阵应该同时实现将vertex转换到世界空间,再转换到projector的局部空间,最后转换到projector的投影空间的功能,所以其性质应该类似UNITY_MATRIX_MVP矩阵,所以使用projector实现投射rendertexture的效果,只需要添加一个脚本,其会创建一个摄像机,并使用projector的参数,并将这个摄像机的rendertexture传递给projector的material,具体实现如下:


其中_FadeTex是一张表示阴影衰减的贴图,其r、g通道效果如下:


这是实现后的阴影效果:


这里由于使用的RT是原始摄像机尺寸的1/2所以阴影质量有所下降

另外由于投射的是带Alpha通道的Rendertexture,意味着可以方便的对其使用模糊shader完成模糊效果,这里是我自己编写的模糊脚本效果图:


推荐阅读更多精彩内容