上一回我们说到Cluster Based Lighting,将摄像机空间切成块,使每一块棱台都保持有一个自己的光照队列,这样的光照剔除方法有非常多的好处,其中最主要的好处在于其鲁棒性高,比如既能支持Forward Pipeline,也可以支持Deferred Pipeline,无论是气体渲染,液体渲染,透明固体渲染,粒子渲染等,都可以通过一个世界坐标获取到当前的受光信息。但是仅仅如此是不够的,因为Cluster Based Lighting也有其局限性,其中最致命的局限在于其光照剔除精度的不足。因为不依赖深度,所以在切割的时候必须将空间的Z轴切割成多段,譬如在MPipeline中,我们按照非线性近小远大的规则将空间的Z轴切割成64份,因此在同样的切割解析度下,其剔除消耗将会是TBDR的64倍。最终的结果就是光照的剔除精度必须做妥协,因此我们的XY轴切割解析度就大大的降低了,降低到了16 * 8。很显然,将屏幕横竖分为16 * 8这样的剔除精度是远远不够的,因此我们需要专门为光照运算的主要部分,也就是延迟管线渲染的物体做一个单独的TBDR。

光照的剔除方法一般要考虑需求场景的目标光源的大致数量,而我们这里场景管理考虑的是,在到摄像机裁面的一定距离内,如128米,192米或256米等,使用实时的灯光,即支持阴影,高光,物理衰减等高级光照运算。而超出一定距离后则使用LOD的切换,换成Light Volume直接使用GBuffer进行叠色,据悉GTAV使用的光照计算方法就是这样的:

在这种情况下,Tile/Cluster负责的光照一般不会特别多,我们的设计预估量是在100个实时光源以下,所以在实现TBDR时并没有使用四叉树分割演算法,而是直接暴力的遍历所有光照顶点。

在剔除精度上,我们使用屏幕的1/16解析度作为Tile Resolution,如笔者当前使用的平台为2560 * 1440解析度,则最终会分割出一个160 * 90个Tile。

理论知识讲到这里,我们开始实践,首先第一步是生成一个降采样过的Depth Bounding Map,通俗的来讲就是这16 * 16个像素的最大的深度和最小的深度,并将这两个值保存到一个RGHalf格式的RenderTexture中,而这里将最小值储存在R通道,最大值储存在G通道,最终得到的结果如下:

这张图表达的结果一目了然,可以看到总体色彩是黄色,这说明深度相对连续,而绿色部分则说明深度比较不连续,即R值较小而G值较大。有了这张深度图,我们就可以开始光照的剔除运算部分了。

光照的剔除运算部分和之前的Cluster Rendering并无太大区别,都是算出Bounding Box在世界坐标的6个平面,并使用这6个平面与椎体,球体进行碰撞检测,下方是获取6个面的代码:

在剔除部分,我们在脚本里创建了两个RInt格式的Texture3D,一个负责点光源一个负责锥光源,XY轴对应每个Tile的坐标,而Z轴则给了64个灯光索引的位置以及头上的数量标签,也就是65个位置,这样剔除后直接将结果塞进图中:

接下来在Shader中,按照屏幕UV直接读取光照信息:

做到这里,整条TBDR基本实现,这个是最简单最粗暴的实现方法,本人也只用了一小时多不到两小时的时间就完成了,在场景中摆两个光源,一个点光一个聚光,先输出一下剔除结果,R通道表示点光覆盖,G通道表示聚光覆盖:

光照渲染的结果:

结果看起来非常正确,而性能也是非常高的,同屏70+盏灯:

本篇文章就到这里,最后祝大家身体健康,再见。

推荐阅读:

相关文章