3.3.1 分块着色
使用全局的光源列表是非常低效的,我们也不可能针对每个可视的表面点都执行光源分配计算,那样光源分配计算本身的开支都是很大的,并且光源分配的结果需要更大的缓存对象来存储。相反,我们找到一种折中的方法,即让一个区域内相邻的表面点共享一个光源列表,这样做的原因是相邻的表面点一般拥有相同的光源列表。
分块着色(tiled shading)将屏幕区分划分为由多个块(tile)组成的网格(grid),每个块覆盖的像素区域(当然也可以根据性能分析选择其他尺寸的块,本书以块大小为例讨论。)为,每个块拥有一个独立的光源列表,它表示该区域内所有像素点受影响的光源列表,块内的所有像素点共享整个光源列表,如图(2)所示。
图(1):分块着色块光源列表的数据结构,全局光源列表存储着所有光源的ID,块光源索引列表连续地存储这每个块对应的所有光源的索引值,这样每个块通过一个偏移值和尺寸即可从块光源索引列表中取出该块的光源ID列表
全屏块表格的数据结构如图(1)所示,所有块存储为一个2D纹理,如图(1)下面的正方形表格,每个像素点可以通过在屏幕区域的像素位置来计算其块索引,每个块存储两个数据:偏移和尺寸,所有块的光源列表存储在一个大的全局光源索引列表中,该索引列表中的每个值指向全局光源列表的一个光源,每个块通过在全局光源索引列表中的偏移值和尺寸来选择该块对应的光源列表。
对于块表格结构数据的填充,最简单的方法是将每个光源的包围盒矩形投射到屏幕上,然后在每个块中分别插入受其影响的光源索引到全局光源索引列表中,并修改块数据中的偏移和索引值,如图(2)所示,该场景包含两个光源,重叠部分的块的光源列表将包含两个光源,未被光源包 围盒投射的块则不包含任何光源(注意这些块虽然没有任何直接的光源照射,它仍然可能被环境光,天空盒等其他形式的光源照射。),其他受单个光源影响的区域包含一个光源。
图(2):使用将光源包围盒直接投射到屏幕区域来建立块光源列表的数据,这里的数字仅用于演示表示光源的数量,它的真实数据结构如图\ref{f:shade-tiled-grid}所示
这种方法虽然简单,但是它在一些块中插入了无效的光源,例如在一个点光源的四个角的位置,或者表面点在垂直于屏幕方向上距离光源位置很远,从而其根本不会受到该光源影响,但是它同样处于该光源投射的区域。一些方法使用模板测试,类似阴影体积(shadow volume)的方法来精确地关联光源与其影响的块,另一些方法给表面点设定一个最近和最远受光源影响的距离,来排除一些距离比较远的光源的影响。我们将在下一节分簇着色中讨论一个更详细的示例。
块数据的建立可以发生在GPU中,也可以在CPU中处理,例如Dice在Battlefield 3[a:SPU-basedDeferredShadingforBattlefield3onPlaystation3}中就是将分块的数据从GPU读回到CPU,然后在SPU中处理块的数据。不但如此,他们还在SPU中执行块的着色计算,每次处理一个完整块内的所有像素,这样还可以减少一个块内光源列表的重复读取。
光源分配的结果既可以适用于传统的前向分块着色[a:ForwardBringingDeferredLightingtotheNextLevel}(tiled forward shading),也可以适用于延迟分块着色(tiled deferred shading)中。然而不管使用什么样的着色管线,光源分配几乎都具有以下的三个阶段:
- 对场景(不透明的物体)执行一次无光照的传统的渲染管线,以确定哪些像素是可见的,这通常就是延迟着色的前向着色阶段。
- 利用某种方法对上述可视的表面点执行光源分配(例如基于分块的方案或基于分簇的方案,见本节后面的内容),它输出一个针对每个可视表面点的光源列表。
- 利用光源列表执行每个可视表面点的光照计算。
因为我们不可能对场景中所有像素或区域都执行光源分配,所以无论使用前向或者延迟着色方法,都必须要有一个前向的深度测试阶段来避免不必要的光源分配计算。因此如果你使用的是前向着色方法(例如[a:ForwardBringingDeferredLightingtotheNextLevel}中的Forward+),则至少需要包含两个前向渲染阶段,这时第一个阶段往往称为pre-z pass。
对于延迟分块着色,它仅仅是增加了一个光源剔除的阶段,所以它拥有和延迟着色几乎一样的优点和缺点。针对光源剔除来讲,分块着色还存在很大的优化空间,由于分块着色将3D空间投射到2D的屏幕区域,因此它的每个块占据了深度方向上非常大的范围,如图(3)所示,在分块1中,两个物体之间存在深度不连续(depth discontinuities),我们无法用简单的分块范围(一个最大最小深度范围值)来剔除包含在中间的光源,所以这导致无效的光源被计算在分块光源列表中;例如对于分块3,它的深度是连续的,但是同样由于深度范围太多,使得该分块必须包含该深度范围内的所有光源。
图(3):分块着色仅仅将3D空间投射到一个2D的屏幕区域,所有每个分块包含了过度的光源信息,影响了着色计算时的计算性能,数字1,2,3,4表示分块索引,分块4对应的小图表示一段深度连续的路面,这种情况在游戏中很常见
使用2D分块的另一个问题是这些分块信息是与视点相关的(view dependent),即一旦摄像机位置和方向发生移动,或者场景中的某些物体位置发生改变(也包括光源位置改变,但此处仅改变光源颜色不影响光源分配。),则整个分块表格的数据需要重新计算。分块着色的这些问题使得我们将目光转向了3D的分簇着色。