今天我们来讨论一下游戏中多光源的应用,为了有更好的光照效果,引擎就必须对多光源进行支持。实现方式大致有以下几种:
1、传统的前身渲染(Forward rendering)。
这个一般在国内的游戏引擎中比较常见,国外引擎中也有使用,比如最近的使命召唤系统,为了保证帧率就使用了前向渲染来支持有限个数的点光源。这个一般也有两种做法,一种是对场景渲染多遍,每遍计算一个光源信息。另一种方法是只画一遍,在shader里面处理多个光源的情况。但他们对每个物体支持的光源个数均有限。一般最多支持四个光源(不包括主方向光)。它的主要缺点就是光照计算跟场景复杂度和光源个数有很大关系。假设有n个物体,m个光源,且每个每个物体受所有光源的影响,那么复杂度就是O(m*n)。
2、延迟光照 (Deferred lighting)DL
这个在早期的CryEngine[1]版本中应用比较成熟,第一遍主要是把法线写到一张RT上,然后再进行光照的计算(主要是点光源 聚光灯 环境光),最后再把整个场景画一遍,取上一步计算好的光照,然后再计算主方向光的光照,得到最终计算好的结果。这种方法的优点是光照计算(点光源、聚光灯的计算)跟场景复杂度无关,可以支持任意数量的光源,而且对帧缓冲区带宽的压力也比较小,但一个比较大的缺点是需要对整个场景画两遍,如果场景中物体比较多的话(经过各种剔除和合批之后),这种方式并不是特别合适。
具体流程如下所示:[2]
3、延迟着色 (Deferred shading)DS
这个技术在很多商业引擎中都有应用,包括UE和一些国外比较出名的自研引擎。它跟延迟光照的最大区别在于场景只需要画一遍,但是在第一遍输出的信息就会多一些,除了法线一般还会输出漫反射颜色、高光强度、光泽度等,根据引擎的不同需要,这个根据项目或者引擎的具体实现不同也会有所不同,接下来就计算光照信息(包括点光源、聚光灯效果等的光照信息),最后一步是进行着色,得到最后的结果,当然刚才前面说到的计算光照和着色也可以放在一起来做。它相对于DL的最大优点是场景只需要画一遍,但是一般需要使用MRT,这样对帧缓冲区带宽的压力就比较大。它也跟场景复杂度没有太大关系,支持任意的点光源。主要缺点就是D3D9下面不支持MSAA,因为开启了MRT。
具体流程图如下所示:
4、延迟光照和延迟着色相结合 (Hybrid Deferred shading)
由于引擎需要支持各种好的效果,所以在单纯使用DL或者DS都不能很好的满足需要,所以就出了一种两种结合的方式,即同时支持两种着色方式,这个在CE的后期版本中已经应用起来,Crysis3也是这样做的。我们的引擎使用的也是这种方式,这样就兼备了两者的优点吧。当然还是要写多张RT,但是大多数的几何体就只需要画一遍就可以了。
具体流程如图所示:
5、前向渲染++ (Forward plus plus)
这个没有具体去研究,好像对D3D9支持不友好,在D3D10及以后的版本中比较好,而且也是在实验性阶段,不知道有没有在商业引擎中得到验证。各位看官如果有兴趣可以自行研究。[3]
目前应用最多的应该就是Hybrid Deferred shading这种方式了,加上现在又开发了Tile-based deferred shading技术,这样就可以减少很多带宽消耗,能提高不少效率。但是这种技术在不支持Compute shader的D3D9上实现并不是特别容易,我们引擎参考KlayGE的方式实现了下,但是效果并没有那么理想,可能实现方式有点问题。但是由于我们点光源数量并不会特别多(同屏几百个),所以也就没有再继续深究下去。
下面就简单介绍一下我们引擎使用的方式,出于保密,我们只讨论一个做法,并不会有特别详细的代码。我们也是参考国外的文章来写的,并没有太多创新性的东西,所以我会把我参考的文章一一列出来,这样大家在实现的时候也有一个比较好的参考。如果大家对上面说的这些没有太多的概念,可以看看Shishkovtsov 2005 Policarpo and Fonseca 2005以及Hargreaves and Harris 2004,但文章都比较老,建议实现的时候参考一些比较新的文章。下面开始说我们的实现。
由于我们的引擎需要对多光源进行很好的支持,以及需要支持比较多的光照模型,加上我们引擎支持超大视距,所以最后决定使用Hybrid deferred shading,这种方式,这样既可以满足多光源的需要,也能保证大部分物体只需要画一遍,只有在一些比较复杂的光照计算时才需要把几何体画两遍,这样可以明显减少DP(DrawPrimitive)的次数。
我们使用了3张RT,均为4字节具体格式如下所示。
Diffuse R |
Diffuse G |
Diffuse B |
Specular Level |
Normal.x |
Normal.y |
Gloss * sign(Normal.z) |
Material ID |
R16(Depth) |
G16(Reflection Level等) |
3张RT的格式分别为A8R8G8B8 A8R8G8B8 R16G16,Material id根据id的不同会采用不同的光照模型。法线采用压缩的方式,和Gloss一起压缩到RGB三个通道中,具体方法如下图所示:[4]
深度Dpeth存储的是一个线性值,这样可以直接在vs里面计算好到ps里面进行插值最后写到第三张RT上。第三张RT上的G16R的值会根据Material ID来进行不同的解释。在MRT里面的数据准备好以后,我们接下来就需要进行点光源和环境光的计算,这时为了简单会采用统一的光照模型来做。如果只是只画一遍的几何体,那么接下来就可以做一个全屏的后期处理来得到最后的结果,如果着色模型比较复杂,需要画两遍,那么我们就在第二遍的时候计算主方向的信息并得到最后结果。
这里有几个细节的地方需要说下,一个是通过世界深度信息来反算世界位置。这个我们利用了相似三角形来做的,需要在VS里面把世界空间中远裁剪面上四个点减去摄像机的位置传入,这样在ps中就可以直接利用相似三角形得到世界位置。
另外一个点光源照明范围的正确处理,我们是用了模板来标记它的处理范围的,这样做起来比较简单,效率也可以。当然也有其它的优化方法。
总结:
总起来说,其实这个东西还是比较简单的,当然要实现比较高的效率还是需要花费不少心思的,魔鬼都在细节中,要实现跟其它流程和效果的完美结合也需要花一些功夫,况且我们还要前向渲染也要很好的支持,主要是考虑在低端的笔记本上也能有比较流畅的帧率,我们引擎支持前向和延迟渲染的实时切换。而且还有透明物体的处理,这个也比较麻烦,我们就直接在前向里面来做的,当然延迟里面也是有方法可以做的[5][6]。除了上面提到的处理多光源的方法外,许多国外的牛人也对其进行了改造,比如Light Indexed Deferred Lighting[7]。
总之可能在未来的几年之内,Hybrid deferred shading可能会变得越来越流行,很值得大家去研究下。上面都是我个人参考资料并理解消化的,可能难免有错误之处,如果有还请指正,也欢迎大家讨论。
参考文献:
[1] A bit more deferred -CryEngine3
[2] Hybrid Deferred Rendering Marries van de Hoef
[3] Tiled Rendering Show down Forward Plus Plus Vs Deferred Rendering
[4] The Art and Technology behind Crysis 3
[5] http://www.john-chapman.net/content.php?id=13
[6] Transparency with Deferred Shading
[7] Light Indexed Deferred Lighting Damian Trebilco