逐片元操作

转载自: http://blog.sina.cn/dpool/blog/s/blog_17148af6d0102wzi2.html?from=timeline


这是渲染流水线的最后一步。逐片元操作(Per-Fragment Operation)是OpenGL中的说法,在DirectX中,这一阶段被称为输出合并阶段(Output-Merger)。Merger这个词可能更容易让读者明白这一步骤的目的:合并。而OpenGL中的名字可以让读者明白这个阶段的操作单位,即对每个片元进行一些操作。但要合并哪些数据,又要进行哪些操作呢?

这一阶段有几个主要任务.

1.决定每个片元的可见性。这涉及了很多测试工作,比如深度测试、模板测试等。

2.如果一个片元通过了所有测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并或者说是混合。

需要指明的是,逐片元操作阶段是高度可配置的,即我们可以设置每一步的操作细节。这个阶段首先需要解决每个片元的可见性问题。需要进行一系列测试。就好比考试,一个片元只有通过了所有考试,才能最终获得和GPU谈判的资格,这个资格指的是它可以和颜色缓冲区进行合并。如果它没有通过其中的某个测试,那么之前为了产生这个片元所做的所有工作都是白费的,因为这个片元会被舍弃掉。下图显示了简化后逐片元操作所做的操作:

逐片元操作 X

测试过程是个比较复杂的过程,而不同的图形接口实现细节也不尽相同。这里有两个基本的测试–深度测试和模板测试的实现过程。能够理解这些测试过程将关乎读者是否可以理解以后提到的渲染队列,尤其是处理透明效果时出现的问题。下图给出了深度测试和模板测试的简化流程:

逐片元操作

首先看模板测试(Stencil Test)

与之相关的是模板缓冲(Stencil
Buffer)。实际上,模板缓冲和颜色缓冲、深度缓冲是一类东西。GPU首先会读取(使用读取掩码)模板缓冲中该片元位置的模 板值,然后将该值和读取(使用读取掩码)到的参考值(reference
value)进行比较,这个比较函数可以是有开发者指定的,比如小于时舍弃该片元,或者大于等于时舍弃该片元。如果这个片元没有通过测试,该片元就会被舍弃。不管一个片元有没有通过模板测试。我们都可以通过模板测试和下面的深度测试结果来修改模板缓冲区,这个修改操作是由开发者指定的。开发者可以设置不同结果下的修改操作,比如,在失败时模板缓冲区保持不变,通过时将模板缓冲区中对应位置的值加1等。模板测试通常用于限制渲染的区域。另外,模板测试还有一些更高级的用法,比如渲染阴影、轮廓渲染等。

如果一个片元幸运的是通过了模板测试,那么它会进行下一个测试—深度测试(Depth Test)
。这个测试同样是可以高度配置的。如果开启了深度测试,GPU会把该片元的深度值和已经存在于深度缓冲区中的深度值进行比较。这个比较函数也由开发者设置,比如,如果小于时舍弃该片元,或者大于等于时舍弃该片元。通常这个比较函数是小于等于的关系。即如果这个片元的深度值大于等于当前深度缓冲区的值,那么就会舍弃它。这是因为我们总想值显示出离摄像机最近的物体,那么那些被其他物体遮挡的就不需要出现在屏幕上。如果该片元没有通过测试,该片元就会被舍弃。和模板测试有些不同的是,如果一个片元没有通过深度测试,它就没有权利更改深度缓冲区中的值。而如果它通过了测试,开发者还可以指定是否要用这个片元的深度值覆盖掉原有的深度值,这是通过开启/关闭深度写入来做到的。以后我们会发现,透明效果和深度测试以及深度写入的关系非常密切。

如果一个片元幸运的通过了上面的所有测试,它就可以自豪的来到合并功能的面前。

那么为什么需要合并呢?这里讨论的渲染过程是一个物体接着一个物体画到屏幕上的。而每个像素的颜色信息被存储到一个名为颜色缓冲的地方。因此,当我们执行这次渲染时,颜色缓冲区中往往已经有了上次渲染之后的颜色结果,那么,我们是使用这次渲染得到的颜色完全覆盖之前的结果,还是进行其他处理?这就是合并需要处理的问题。

对于不透明的物体,开发者可以关闭混合(Blend)操作。这时片元着色器得到的颜色值会直接覆盖掉颜色缓冲区中的像素值。但对于半透明物体,我们就可以使用混合操作来让这个物体看起来是透明的。如下图简化版的混合操作流程图:

逐片元操作

从上图我们发现,混合操作也是可以高度配置的。开发者可以选择开启/关闭混合功能。如果没有开启混合功能,就会直接使用片元的颜色覆盖掉颜色缓冲区的颜色,而这也是很多开发者无法得到透明效果的原因(没有开启混合功能)。如果开启了混合,GPU会取出源颜色和目标颜色,将这两种颜色进行混合。源颜色是指片元着色器中的颜色值,而目标颜色则是已经存在于颜色缓冲区中的颜色值。之后,会有一个混合函数来进行混合操作。这个混合函数通常和透明通道息息相关,比如根据透明通道的值进行相加、相减、相乘等。混合很像ps中对图层的操作,每一层图层可以选择混合模式,混合模式决定了该图层和下层图层的混合结果,而我们看到图片就是混合后的图片。

上面的测试顺序不是唯一的,而且虽然从逻辑上来说这些测试是在片元着色器之后进行的。但对于大多数GPU来说,它们尽可能在执行片元着色器之前就进行这些测试。为啥呢?想象下,当GPU在片元着色阶段花了很大力气终于计算出片元的颜色后,却发现这个片元根本没有通过这些检验,也就是说这个片元还是被舍弃了。
那之前的花费的计算成本全都浪费了。例如下图:

逐片元操作

作为一个想充分提高性能的GPU,它会希望尽可能的早知道哪些片元是会被舍弃的,对于这些片元就不要在使用片元说色器来计算它们的颜色。在unity给出的渲染流水中,我们也可以发现它给出的深度测试是在片元着色器之前,这些深度测试提前执行的技术通常也被称作Early-Z技术。以后再讨论这个技术。

但如果将这些测试提前执行的话,其检验结果可能会与片元着色器中的一些操作冲突。比如我们在片元着色器进行了透明测试,而这个片元没有通过,我们会在着色器中调用API来手动将其舍弃掉。这就导致GPU无法提前执行各种测试。因此,现代的GPU会判断片元着色器中的操作是否和提前测试发生冲突,如果有冲突,就会禁用提前测试。但,这样会造成性能上的下降,因为由更多片元需要被处理了。这也是透明测试会导致性能下降的原因。

当模型的图元经过了上面层层计算和测试后,就会显示到我们的屏幕上。我们屏幕行显示的是颜色缓冲区中的颜色值。但,为了避免我们看到那些正在进行光栅化的图元,GPU会使用双重缓冲(Double
Buffering)的策略。这意味着,对场景的渲染是在幕后发生的,即后置缓冲(Back
Buffer)中。一旦场景已经被渲染到了后置缓冲中,GPU就会交换后置缓冲区和前置缓冲区中的内容,而前置缓冲区是之前显示在屏幕上的图像。因此,保证了我们看到的图像是连续的。

上面讲了很多,但真正的实现过程远比上面讲的要复杂,需要注意的是,上面给出的流水线的名称、顺序、可能和其他资料有些不同,一个原因是由于图像编程接口(如OpenGL和DirectX)的实现不仅相同。另一个原因是GPU在底层可能做了很多优化,比如上面说的会在片元着色器之前就进行深度测试,以避免无谓计算。

虽然渲染流水线比较复杂,但Untiy这个平台为我们封装了很多功能。更多时候,我们只需要在一个unity Shader
中设置一些输入、编写顶点着色器和片元着色器、设置一些状态就可以达到大部分常见的屏幕效果。但这样的缺点在于,封装性会导致编程自由下降,导致初学者迷失方向,无法掌握其背后的原理,并在出现问题时,无法找到错误原因,这是在学习unity时普遍的遭遇。

猜你喜欢

转载自blog.csdn.net/cui6864520fei000/article/details/85889994
今日推荐