技术分析-提高显卡效能

下面的文章将探讨在现代PC游戏中所存在的与画质相关的一些问题——的确,即使是在最新的硬件中我们仍然会遇到这些问题。虽然我更愿意直接谈论我们今天的第二个话题(alpha transparency在材质中的应用),不过在此之前我们还是再来做一些基础知识的介绍,以便不同层次的读者都能读得懂这篇文章。下面我们就从分析为什么rotated grid antialiasing(旋转栅格抗锯齿)要优于odered grid antialiasing (正方栅格抗锯齿)技术开始吧。

什么是抗锯齿?


  我们知道,电脑画面是由一个一个小的象素所构成的。虽然这些象素非常的小,不过每一个象素都覆盖了这一画面上的某一个区域。我们可以将这些象素比作通向虚拟世界的一扇一扇窗户,而我们电脑的任务就是决定每一个象素的颜色以使得我们通过这些窗户能看到一幅最美的画面。不过渲染的速度也是一幅“实时”图片的所需要考虑的一个重要因素(我们称之为“实时”是因为我们在游戏中画面总是不断变化的),因此通常象素的色彩都通过对该象素内的某一点进行采样得到的颜色所决定的(即我们所谓的点采样point sampling)。不幸的是,为了提高速度而采取的这种做法可能使得所采样的单个点的色彩不能体现出整个象素所覆盖区域的色彩状况。当这种情况出现在物体的边缘的时候就非常明显了。

  有一些象素“跨”在物体的边缘,该象素内部的色彩是有一定比例的,而且在外部的显示我们也希望体现出这一比例。物体的边缘两边却会呈现出不同的颜色(否则我们就不称之为边缘了)。点采样技术将会使得整个象素呈现出边缘两边的某一种颜色。而这样对物体边缘的着色无论是着上前景色或是背景色中的哪一种色,由于象素间色彩的突然跳变,都自然而然的会呈现出锯齿状。这种情况就是我们所说的锯齿(aliasing)了。这是由于这一个象素的面积正好覆盖在了边缘上,两边都有它的存在。一个更好的办法就是将前景色和背景色进行混合从而造出第三种颜色来填充色一象素。这种方法能有效的改进图像边缘的表现效果,换一种说法就是实现了锯齿的作用。

                                   

  我们可以通过对象素所在的交叉点进行计算来达到这一目的,边界的效果体现了每个象素所能表现细节的一个方面。举个例子来说,一棵垂直生长树的边界可能其宽度只占有了一个象素,我们如何体现这棵树的边缘效果呢?我们所需要的是一个比较通用的方法来提升由象素所组成场景的效果,而要做到这一点,最直接的方法就是增加对每个象素所占有的小的场景采用的采样点数。这些独立的采样点通常被称为子采样(sub-samples),同时他们都是以象素为单位成组的产生的。对每个象素生成子采样的各个独立的采样点称为上采样过程(up-sampling),而所采用的子采样个数我们用“X”来表示(譬如2X4X6X等等)。通过平均运算子采样得到的颜色来对单个象素进行着色以使得该象素更加具有表现力的这一过程我们成为下采样过程(down-sampling)。虽然一般而言下采样过程中所运算的点数与上采样中生成的子采样点数相同,不过实际操作中这一点并非是一成不变的。NVIDIA的五点梅花排列采样法(Quincunx)抗锯齿在上采样时对每个象素只产生两个子采样点,而下采样的时候却对5个子采样点进行了运算(通过从临近象素那里去借用子采样点的方法来获得多出的3个子采样点)。

不同的抗锯齿方法

  现今的两种不同的抗锯齿技术分别是超级采样Super-Sampling (SSaa)和多点采样 Multi-Sampling (MSaa)

  超级采样简单的说就对每一个被采样的象素进行更细分的采样和计算。其方法和我们前面所介绍的类似。这一做法非常的占用资源因为说要计算的数据比起原来成倍的增加。可以理解为这种工作状态实际上相当于在进行更高分辨率的运算之后将画面用低分辨率来显示。

  多点采样意识到了对同一物体材质内部进行重复的子采样将可能得到非常相近的色彩,所以对每一个象素进行精确的色彩运算简直就是浪费时间。它在对于像素进行抗锯齿处理之前首先对于像素的位置进行检测,看其是否位于材质的边缘,对处于材质内部的部分不进行平均处理的方法。故多点采样抗锯齿对每组上采样只会读一次材质,这也是为什么MSAASSAA要快的原因。而对于那些处在物体边缘的象素仍然要由前景色和背景色进行运算后得到新的颜色来进行填充,这些过渡的颜色成功的使得边缘看起来更加平滑。MSAA实际上并没有改善一个使用了透明材质的物体内部的画质,在后面的讨论中我们会看到这一点。

  注意:在一个分组中混合使用MSSS仍然是可能的!

子采样点的排列方式

  似乎象素内部子采样的排列方式并不重要,不过实际上这些采样点的排列方式与我们对物体边缘部分着色的效果是有关系的。我们以下面的4X(每个象素4个子采样点)抗锯齿正交排列为例进行说明:

                 

  在上面的图中每一个象素(红色的方块)被8个其他的象素所包围(黑色的方块)。每一个象素包含了4个子采样点。下面我们看看当一条近水平的红色分界线穿过这样一行象素的候的情况:

   

  我们假设红线的上方都是黑色而下方都是呈白色。随着红色边缘的上升我们可以看到每个象素灰度的变化。在采样的前三个格子里,所有的4个子采样点都出于黑色区域,所有点采样得到的颜色全为黑色,所以平均运算后得到的色彩也是黑色。第4个象素有一个点出于白色区域中,所以其灰度为75%。以此类推,接下来的8个象素均为50%的灰度。而最后4个象素的子采样点色彩全为白色,故这几个象素也是全白的。

  通过上面的例子我们就可以很好的理解子采样是怎样的通过中间过渡色彩来平滑边缘两个色彩之间的跳变,其中有3种可能用到的过渡色彩,我们可以定义为75:25, 50:50 25:75。然而这一种采样的排列方式并没有优化的使用这3种颜色。而且可以看到其中25:75并没有用到!

下面我们看看另外一种4个子采样点的排列方式。可以看到这种排列将这4个子采样点做了一个1-in-2(以小网格作为计算单位)的角度变换。

              

  这种变换使得采样点是逐个越过红色边界的,而不会出现像前面那种有两个子采样点一起越过边界的情况,下面这张图很清楚的体现了这种排列方式的效果。

        

  很明显,我们看到这种排列方式将这3种过渡色充分的加以利用使得边界的平滑效果比前者要好很多。不过,问题并非就这么简单。在使用后面这种排列方式时,当边缘的倾斜角度与子采样点组的旋转角度相近的时候其效果又不如之前那么好。那么为什么我们说旋转的采样方式更好呢?

  拿一个有1-in-2倾斜度的边缘来看,需要进行平滑处理的每一级跳变只有两个象素的长度,所以并没有足够的空间来填充这3种过渡色!这就是为什么正交的排列效果不好的原因了。它可能完成平滑斜面边界的任务却没有足够的空间去让他完成这一任务,但是它在要求能很好分布过渡色的近水平和近垂直的情况下表现又非常差。而旋转栅格排列方式由于采用了另外一种方式就显得更加有效。

  之前,ATINVIDIA都使用了一些不够优化的子采样方式。ATI曾引入了2X&3X抗锯齿的功能将采样点列为垂直的队列来平滑近水平的边缘,不过垂直方向却无能为力,而即使是一个简单的倾斜排列也可以使二者都得到平滑处理。另一方面,NVIDIA抱着他们的4X正交模式不放,直到最后在6800家族中才引入了新的排列方式。很难想象是什么原因让他们采取这些做法。

下面我们用事实说话,来比较一下在实际游戏中4X正交排列和4X旋转排列的效果。下面的这幅动画是从FarCryMango River这一关中截图下来的。下落的瀑布为我们提供了一个非常好的证明。你可以看到图像在点采样(没有开抗锯齿功能),4X正交采样和4X旋转采样几种方式间切换。前面我们讨论的过渡色的作用在这里表现的非常明显,而你可以在右下方的放大图里面更清楚的看到这一点。站起来离显示器远一点观察这一个放大图,你会发现4X的正交采样的瀑布边缘仍然有一些边缘的跳变,而在旋转采样的方式下则完全没有了。

注意:旋转采样的截图来自于Geforce 6800 Ultra,因而

我们也证实了NVIDIA实际上真的采用了4X旋转采样模式。

  事实上,所谓的“旋转”的说法只是一种假象。使得这种子采样方式更加有效不是由于它做了旋转,从本质上是由于它使得水平或者垂直穿过单个象素的边界按照一定的间隔与单个的采样点相遇。下面的这幅图显示的这种8点子采样(8X抗锯齿)排列方式也满足了这样的要求,不过它却不能被归为旋转这一类方式。

                

  其他点数的子采样与4点子采样所遇到了同样的局限。上面的8点子采样可以运算得到7个过渡色。不过要想充分的利用到整个的7种过渡色,边缘的斜度水平不能超过1-in-7,垂直不能超过7-in-1。所以增加采样点个数只能明显的改善非常接近水平或垂直的边界的显示效果。

  随着分辨率的提高,增加子采样点的方法能带来的视觉改善效果会逐渐减少。不过子采样抗锯齿的目的并不单单是为了改善边界的显示效果,同时也是为了在对细微物体进行着色的时候使得这些物体能更加的协调和容易辨认。还记得那些垂直生长的小树吗?这里我们有一个人眼能感知的视觉效果的极限问题。与提高分辨率不同,子采样抗锯齿只能通过改变象素的颜色来提高细节的表现力。换句话说,假设如果有一棵小树在画面上的大小比一个象素还要小的时候我们应该怎样对它进行着色?事实上,子采样抗锯齿仅仅是试图保持物体外观的连续性,也就是被着色的树应该是处于一个象素的中央,或者是两个象素之间抑或是四个象素之间。对于其他所有子采样抗锯齿尝试要改善的那些细节来说都与上面的例子相似,而前面所说的通过增加子采样点的方法带来的视觉改善效果逐渐减少的原则仍然适用。

  我个人认为4子采样点旋转采样的模式是在性能和视觉效果之间的一个较好的平衡点。不过业界似乎并没有将这一模式作为一个标准。就像我们在下一部分中会看到的那样,也许他们只是更需要在超级采样和多点采样中作出一个选择。

Transparency & Translucency(透明和半透明)

  注:在这一部分我们所谓“透明transparent”是指一个可以完全透视的表面(看不见的),而半透明translucent”是指可以部分透视的表面(就像有色或者有雾的玻璃)

  一个表面的色彩我们通过红绿蓝(RGB)三种颜色的组合来体现,而半透明度则用alphaA)通道来表示。一个表面被不同格式和样式的材质所覆盖以体现这个表面的色彩变化。通常的材质是32bit 8888RGBA),可以为红绿蓝三种颜色中的每一种提供256个级别的色彩强度(混合之后就是一千六百八十万种色彩)。

  对于材质通常用一个单bitalpha通道值来定义它的透明度,即透明或不透明(注,所谓单bit,即只用一个bit来记录该alpha通道值,那么alpha通道值只能为01,即两种透明或者不透名两种状态了。这与用分别8bit表示红绿蓝三种颜色,或者后面所说的用多bitalpha通道值来记录alpha通道的半透明度相对应)。这在要使用材质来定义一个复杂结构的一维或者二维物体的时候处理起来会显得非常方便。譬如带刺铁丝网、植物或者一连串的栅栏等等都是这样的物体。所有这些物体有非常精致的细节使得要用多变形来建模会比较困难。如果使用一些少量的大的多边形建模然后通过定义细节的透明部分来实现就会好的多。

  下面的图像是从CryTek的游戏大作Farcry中截下来的。大的树叶的外形只是由少数几个多边形组成。每一张叶子材质都是绿色,不过对每一张叶子使用了一个bit来记录alpha通道值定义其透明度:透明或者不透明。

                          

  不幸的是使用单个bitalpha通道值来定义形状有一些不足之处。首先,采用多采样全屏抗锯齿(MSAA)不能改善同一材质内部的图像质量,而且不能对那些通过定义材质内部部分透明而产生的边界进行抗锯齿的操作(因为这些边界处于材质内部,在前一部分我们已经说过了MSAA的原理了)。不过对于超级采样抗锯齿(SSAA)这个问题就不复存在。

  注意:除开MSAA不谈,NVIDIA的五点梅花采样抗锯齿会使得由部分透明产生的边界显示变得有些模糊。这种模糊我们认为不能被看作是抗锯齿的作用而是一定程度上的混乱。这是由于五点梅花抗锯齿的算法本身所决定的。因为五点梅花抗锯齿是通过在附近的象素借一些子采样点的信息进行运算以实现抗锯齿。经过该算法运算后得到的边缘看起来显得比较模糊而没能有效的提高视觉效果。

下面的这些图片(gif动画)都是在单点采样到MSAA再到SSAA之间变化以便大家作出对比。

                        

  在第一个例子里面注意前面大的叶子的边缘,MSAA的效果有限,而SSAA则表现不错。

                       

  下一个例子,注意石头和天篷的竿子通过MSAA得到变得比较平滑,不过只有SSAA能改善天篷内部的表现。

                      

  在这一幅画面中,一些与天空交接的边缘是由岩石构成的,MSAA使得这些交界处岩石的边缘比较平滑,而其他部分譬如上面的树木的边界是内部材质构造而成的没有得到改善。只有SSAA使得二者都得到了改善。

                     

  最后我们看到树木的叶子在SSAA中改善明显,甚至你可以看到红色那朵小花都更具表现力。

  这里最后一幅图片为我们证明了被限制采用单bitalpha通道值存在的第二个问题;就是当物体变得越来越小(越来越远)的时候如何保持其细节。当边界象素只能为不透明或者透明(ON或者OFF)的时候,变得越来越少的可用象素可能不足以用来构成原本的外形。幸运的是材质可以根据距离的大小而进行替换(LOD mip-mapping细节水平性Mip贴图)。这使得我们可以为远距离制作相应的材质使得可用的象素能被最大限度的使用到。我个人相信Cry Tek在制造远景植物材质方面所做的工作是令人称道的,使得他们在Far cry中让前面所说的问题得到有效了的限制。不过,通过使用多bit alpha通道值(比如,透明度),这些问题都可以得到解决。

  注:虽然你可能会认为CryTek使用了自动生成mip-map的方法,不过我想提醒你,比起单独制作出来的远景材质来说,通过自动生成得到的材质看起来会粗糙得多。

远景材质可以通过与变化的半透明度相结合从而大大加强其表现效果,而在标准的材质过滤过程中加入多个bit的过滤,比如bilinear(双线性),trilinear(三线性)或者anisotropic(各向异性),能帮助平滑材质内部的边缘部分,而不需要用超级采样来解决我们前面提到的多点采样所遇到的问题。

  Farcry事实上也在少数地方的植物上引入了多bit alpha通道值(多半透明度)。注意下面这一系列图片中我们没有打开全屏抗锯齿功能,树叶边界的边界也仍然得到了平滑。同时你应该也注意到了在远处的植物没有变得破碎难以辨认,而这种情况在前面的例子当中却时有发生。

    

  看看下面这幅图中铁窗的上半部分。虽然材质象素很容易呈破裂状,不过其边界相对与背景还是很好的得到了平滑。等一下,铁窗的下半部分发生了什么事?显然这里有点不太对劲。

      

  这里的确遇到了一点问题。如果可以简单的通过使用半透明的方法来平滑边界和细节,为什么Farcry不将其使用到所有的植物上呢?

半透明的问题

  当进行3D绘图的时候,显然应该将近处的物体绘于远处物体之上。在以前,这就要求将物体(多边形)从前到后进行排列。不幸的事这一排列运算非常耗时间而且对于实时图像来说来说这也是行不通的。另外,物体的相互交叉也使得无法简单的将其作出一个正确的排序。这就引入了深度缓冲(Z -缓冲Z-buffer)的概念。在3D环境中每个像素中会利用一组数据资料来定义像素在显示时的纵深度(即Z轴座标值)。当要对一个新的物体进行绘图时,会将每一个新象素都的纵深度于当前所记录的深度缓冲相比较。如果新的象素处于原来已经存在的象素后面,将不会对这一象素进行绘图。否则,将会对新的象素进行绘图然后用新的值对深度缓冲进行更新来反映这一新的,更近的距离。深度缓冲使得物体可以以任何顺序进行绘图,而由于被隐藏的表面是以象素为单位进行处理的,物体相互交叉的情况就可以被正确的渲染。因此也不必进行排序了。

  上面所说的方法在处理所有物体都是不透明的情况下是非常有效的,不过如果他们是透明或者是半透明的呢?

  在透明的情况下也没问题。当对一个有透明部分的材质进行渲染的时候,不透明的区域可以用于深度缓冲的对比和更新。下面我们举个例子来说明一下。在一个以一串篱笆为前景的场景中,当前景和背景都已经被渲染了,仍然可以在他们之间加入另外一个物体。这是由于相比之下篱笆的不透明部分的Z轴座标值更小,从而防止在这些象素上对新的物体进行绘图。而透明的部分则保持了背景的Z轴座标值,这时新的物体相比之下该值更小,于是实现了这一部分的替换。所以虽然新的物体是在篱笆进行渲染之后才加入的(也就是无序的加入),我们仍然可以进行正确的渲染整个场景。因为那些可以透过篱笆的间隙看到的后面的物体同样进行了绘图。通过对透明材质部分不更新其Z缓冲值,使得这些透明的部分就变成了一个一个的空洞——这正是我们想要的效果。

  不幸的是上面所说的这些方法不能用到半透明的表面上。因为他们既不是实体,也不是一个个的空洞。半透明体现了背景材质和前景材质的一种合成效果。如果前面所说的例子中,将篱笆换成绿色的玻璃,透过绿色玻璃背景也带上了绿色。我们就不可能将另外一个物体放到前景的玻璃和背景之间了。因为这要求新的物体也要带上玻璃的绿色。而绿色的玻璃已经和背景混合之后进行了绘图。Z轴座标值被记录为着色后玻璃的值,这个值显然小于后来加入物体的Z轴座标值,这样将不会对新加入的物体进行绘图。唯一保证通过绿色玻璃看到的场景被正确渲染的办法就是对玻璃后面的所有物体先进行渲染。最后在顶部进行绿色玻璃的渲染。这样就意味着,所有的半透明表面都必须最后渲染。

  虽然这里会引入一定的表面分类,不过这种分类只是将不透明的表面和半透明的表面区分开来,而这一工作显然非常的简单。

  不过这还不够。这一技术又有一定的局限性!它只对没有重叠的半透明表面有效。一旦引入了多层次的半透明材质,上面的方法就失效了。有一种处理方法就是将这些材质重后到前进行排列。在像Farcry这样的游戏中,如果存在一些采用多层次半透明方法处理的植物(以提高植物的细节表现力和边缘的平滑效果),那么就要对上百个植物和上千个多边形进行排序。这样做显然行不通。

现在我们可以理解前面那张图片中所存在的问题了。在这里,那株植物是在铁窗之后进行绘图的。铁窗的材质是部分全透明的,透明部分的深度缓冲值保持了远处墙面的缓冲值。这样就允许对植物进行绘图。然而,保持墙面深度缓冲值的部分必须是铁窗完全透明的部分。由于为了实现铁窗边缘的平滑效果,对这边缘部分进行了半透明的处理,所以在绘图之后半透明部分混合了墙面的颜色。这时Z缓冲值记录的是前景铁窗的值。当再对植物进行绘图的时候,让我们通过半透明的铁窗看到呈现出了墙面的色彩的边缘!

  你也许会问,为什么游戏中不像我们之前所说的哪样,最后对铁窗进行渲染呢?嗯,它是应该这样做,不过在这里,植物也是采用了半透明的特性来定义它的形状,所以这二者应该留到决定出哪一个应该在前哪个在后才进行渲染。这就解释了为什么要对重叠的半透明层从后到前的排序了。不过大多数的游戏都不会进行这一排序,要想得到正确的排序完全要靠概率。而通常来说所得到的顺序都是不正确的。许多游戏在运行的时候允许植物的多边形相互交叉。这就造成了在交叉点两边的表面排列顺序颠倒的情况。所以这时有一边被按照正确的顺序进行了渲染,而另外一边就出现了错误。下面这幅图就是一个很好的例子:

     

  这幅图是从现在流行的游戏GTA Vice City中截下来的。这一游戏中和Farcry一样也是采用了透明的方法来定义植物和栅栏,不过该游戏通过允许alpha平滑,以一些明显的渲染错误为代价换取一个视觉效果的折中。下面就是一个渲染错误的例子:

      

  第一张图片和前面的铁窗例子相似。栏杆的边缘有天空的颜色,人物很明显在栏杆之后才进行了渲染(人物在游戏中通常都是最后才渲染的)。

       

  在第二个例子中绿色的半透明玻璃向我们展示了它后面的棕榈树的渲染顺序。明显,右边那棵小一点的棕榈树最先渲染,然后就是上面的绿色的玻璃与之进行了混合。之后才渲染的另外两棵棕榈树,因为绿色的玻璃用其整个表面的值已经更新了深度缓冲的值,远处的树没有与玻璃一起进行渲染(否则它们也可能看起来不正常)。如果我们不能看透这些玻璃就没有上面的问题了,就是因为玻璃的半透明状态,很明显的体现了半透明的缺点。

      

  最后一个例子看起来非常让人困惑,因为红色的小汽车在铁丝网前面,而铁丝网在人的前面,人呢?人在车的前面!看看大家能不能想明白这究竟是怎么一回事呢?:)

  通过上面的分析和例子我们看到,无序的透明材质渲染是可以正常工作的,而无序的半透明材质渲染确不行。这就是为什么Farcry中在这么多的植物中只有一小部分是使用单bit alpha通道值(透明),而放弃了alpha平滑和多点采样抗锯齿的缺点。

解决办法?

  我们能找到一种方法来解决顺序渲染的半透明材质所遇到的问题吗?这样我们就可以对多bitalpha通道值用alpha平滑的方法来增强材质内部的细节表现力。事实上,如果确实有这样一种简便的方法那么肯定它现在已经被广泛的采用了:)

  有一种技术叫做“depth peeling”(深度剥离),采用了多个深度缓冲来避免对材质进行分类,不过这样以来至少要消耗掉四个渲染通道。还有一种方法叫做Z3,过我想不在我们今天这篇文章的讨论之列。我想暂时我们还只有接受这样的事实,就是单层的半透明的表面必须在最后渲染,而多层的半透明表面必须引入局部表面排序这种做法,或者,干脆采用单bitalpha通道值吧。大家已经看到Farcry向我们展示了优秀的材质设计可以使得使用单bitalpha通道值同样可以做出精美的画面。相信加上显卡技术的不断进步,采用4X SSAA对单bitalpha通道值的材质进行抗锯齿的画面将会非常漂亮!

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值