Skip to main content

3.1.1 光栅化技术

将需要迭代计算的渲染方程改为能够使用一些材质参数一次性计算的着色方程(第3.1节式(2))之后,利用图形硬件提供的图形处理器接口(如OpenGL,DirectX等),场景的渲染过程就变得十分简单,整个渲染过程可以简述如下(如图(1)所示):

图(1):利用图形处理器接口提供的渲染管线渲染场景的过程,首先将顶点光栅化为片元,然后根据相关材质参数在片元着色器中计算像素的颜色,帧缓存中的深度缓存被用来剔除被遮挡的片元

  1. 放置一个虚拟摄像机在3D场景中的某个位置,并设置一个视锥体来表示3D世界中的可视区域,这片区域被映射到屏幕上一块2D的具有一定分辨率的窗口。
  2. 场景中每个物体所包含的顶点数据以及绘制该物体所需要的所有环境贴图,阴影等被提交到OpenGL开始绘制该物体,这些顶点所构成的图元被视锥体裁剪(物体在被绘制之前通常也会在CPU端执行一个基于物体包围盒的3D空间的裁剪。),处于视锥体外部的图元将被丢弃,与视锥体相交的图元将被裁剪,剩下的图元被光栅化到与窗口分辨率对应的像素位置,每个像素块称为一个片元。
  3. 片元着色器对每个片元使用第3.1节式(2)进行着色计算,它遍历场景中的每个光源分别计算其对该片元辐射照度的贡献,并将最终结果与帧缓存上对应像素位置上的深度值进行比较,如果通过深度测试则将该颜色值与帧缓存上的颜色值进行混合。
  4. 当所有物体被遍历完后帧缓存上的颜色缓存被送到显示设备进行显示,或者其结果被读回到宿主程序。

这个传统的渲染过程非常简单,它基本上就是直接利用图形处理器接口提供的经典渲染管线,因此它具有所有图形处理器接口提供的便利或优点:

  • 结合图形接口深度测试和颜色混合的机制,传统的渲染管线可以很容易地实现对半透明物体的绘制。
  • MSAA被集成到渲染管线,它可以对每个片元的深度进行多次采样,而使用一次着色计算以实现反锯齿。
  • 每个物体可以根据其图形特征使用独立的着色器或着色器组合。

当场景的结构比较简单(拥有较少的物体数量以及光源数量)时,上述的渲染方法非常简单且高效,然而随着场景结构复杂度的增加,该方法会变得非常低效。

在传统的渲染管线中,所有很影响性能的因素几乎都跟一个称为过度绘制(overdraw)的概念有关。一般来说,要求得一个摄像机所能看到的场景中的每一个像素点,必须沿从摄像机穿过每一个2D屏幕上像素点的方向上,与整个场景数据做一次相交计算,该方向与场景所有交点的最近点即为屏幕上该点的可视点,然而这样的相交计算非常复杂,所以光栅化技术的核心要点正是简化了这个相交计算:它对整个场景只遍历一次,并记录下屏幕上每个像素点方向上的表面点的深度值,然后让后续的同样落于该像素点的表面点的深度值与该值进行一个简单的深度比较,并保留深度值更小的表面点,这样当整个场景被遍历一遍之后,帧缓存上留下的就是整个场景中所有可视的表面点,这个过程非常高效。然而它的缺陷就是,对于每个表面点,我们必须计算出完整颜色值,即是对其执行第3.1节式(2)的计算,这样就导致了过度绘制,因为大量的被遮挡的表面点将被深度测试丢弃,从而导致计算资源的浪费,尤其当场景中有大量光源的时候,第3.1节式(2)中需要分别计算每个光源的贡献,这种资源浪费随着光源数量的增加而增加,例如本书后面讲述的即时辐射度方法(参见第(9)章)可能有上万个虚拟的点光源(VPL),它也随着场景复杂度的增加而增加,因为更多的表面点可能被遮挡。这种渲染性能和场景复杂度的耦合导致的结果是灾难性的,它使得应用程序可能具有极不稳定的帧率。

图(2):当以物体的几何尺寸进行光源剔除时: (a)大物体小光源的场景会使得每个片元可能计算大量无效的光源影响,而(b)小物体大光源的场景会使得大量小物体重复进行光源剔除计算

其次,即使是针对有效片元(那些最终没有被深度测试丢弃的片元)的着色计算,由于场景中可能分布成千上万的光源,因此为了提高渲染性能,光源剔除(light culling)十分关键,它要求我们在绘制一个物体之前,应该排除那些对其完全没有影响的光源。这里考虑场景中像直线光源这样对整个场景都有影响的光源还是少数(例如一个场景可能只有一个太阳光源),大部分光源都是点或者其他面积光源,这些光源的辐射强度都受一个距离递减函数的影响,如图(2)(b)所示,因此光源剔除基本上可以排除大量的无效光源(光照影响为0),从而大大提高渲染性能。光源剔除的目标是使每个片元计算的光源的数量达到最少,有时候我们也称其为光源分配(light assignment),这是着色管线基础架构的重要内容。

在目前的这种方法中,进行光照剔除的唯一方法是在绘制之前对物体与光源执行包围盒比较,然后只将那些影响该物体的光源信息上传至GPU以进行最节省的片元着色计算。因此为了最大限度降低每个物体受其影响的光源数量,我们应该减少一次绘制的顶点的数量,或者说尽可能每次绘制更少数量的物体,然而这却和每次尽可能绘制更多顶点以降低GPU状态切换的高昂性能代价相矛盾,这个矛盾使得我们很难权衡每次绘制应该选择的批绘制的大小。

当我们以物体的尺寸为依据来选择批绘制块的大小时,如果场景中包含少量大物体和大量小光源时,虽然每个光源可能仅影响大物体的一部分,但是我们不得不对该次绘制使用全部光源,因为以物体的几何尺寸为依据无法剔除这些光源,如图(2)(a)所示;同样,当场景中包含大量小物体以及少量大范围的光源时,每个小物体都要单独分别对每个光源进行剔除计算,尽管我们知道多个小物体同时都处于该光源的影响范围之内,如图(2)(b)所示。更糟糕的是,场景中可能同时存在这两种情况,因此不管我们怎样权衡,都不可能使每片元计算光源数量达到最低。所以本章后面讲述的着色架构的重要改进就是不以几何物体尺寸,而是以屏幕空间的2D分块(tile)为依据进行光源剔除。

最后,由于传统的渲染管线的着色计算必须在一个片元着色器中完成,因此这些不同的光源组合将导致非常复杂的着色器管理。这些着色器的组合数量是物体类型数量和每种类型不同光源数量(#lights/type)的一个排列组合。如果我们只使用一个巨大的着色器,在着色器内部使用根据条件进行各种分支切换,这种臃肿包含众多分支的着色器称为大型着色器(uber shader),GPU中的大量分支计算将导致非常低的性能,参见第(2)章的内容。

同时,也因为所有着色方法被混在一起,所以所有的输入数据不得不随时在内存中处于可用状态,这包括所有的光照图,反射图等等。

由于以上传统渲染管线的各种问题,现代实时渲染方法通常采用改进的方法进行物体表面执行着色计算,这其中最著名的是本章将要讨论的针对过度绘制改进的延迟着色技术,以及在延迟着色技术基础上,针对光源剔除改进的基于2D分块和3D分簇的延迟渲染技术。