GPU渲染管线

本文详细描述GPU是如何实现概念阶段的后两个阶段——几何阶段和光栅化阶段可以分成若干个更小的流水线阶段,这些流水线阶段由GPU来实现,每个阶段GPU提供了不同的可配置性或可编程性。

下面的知识点会涉及到一些基础的词汇概念,大家不明白的可以参考这篇文章: OpenGL中的顶点、 图元、片元、像素的含义

0. 顶点着色器(可编程)

顶点着色器是流水线的第一个阶段,它的输入来自于CPU。顶点着色器的处理单位是顶点(不是图元),也就是说输入进来的每个顶点都会调用一次顶点着色器。

顶点着色器需要完成的工作主要有:坐标变换和逐顶点光照。当然,除了这两个主要任务外,顶点着色器还可以输出后续阶段所需的数据。

坐标变换,顾名思义,就是对顶点的坐标进行某种变换。例如我们可以通过改变顶点位置来模拟水面,布料等。

一个最基本的顶点着色器必须完成的一个工作是:把顶点坐标从模型空间转换到齐次剪裁空间。

类似下面代码:

o.pos=mul(UNITY_MVP,v.position);

1. 图元装配,曲面细分着色器,以及几何着色器

这一部分就是根据CPU传入的顶点之间的关系为依据进行图元的生成,然后将图元输出到裁剪阶段。

这一部分首先就是图元装配,它们如何装配是由CPU提前设定好的,例如我们在程序中都会提前指定好图元类型。

曲面细分着色器以及几何着色器都是为了根据图元装配之后的图元生成更多的图元,他们的算法是如何实现的,这里并不关心。

必须顶点着色器遍历完所有顶点之后才能进入到下一阶段,对之后的每个阶段也是,必须对输入的每个单位进行处理后才能输出进入到下一阶段。

2. 裁剪(不可编程) 裁剪所针对的单元是图元。

由于我们的场景可能会很大,而摄像机的视野范围很有可能不会覆盖所有的场景物体,一个很自然的想法就是,那些不在摄像机视野范围内的物体不需要被处理,而裁剪就是为了完成这个目的而被提出来的。

一个图元和摄像机的关系有3种:
完全在视野内
部分在视野内
完全在视野外

部分在视野内的图元需要裁剪,例如一条线段的一个顶点在视野内,而另一个顶点在视野外,那么视野外部的顶点应该使用一个新的顶点来代替,这个新的顶点位于这条线段和视野边界的交点处。

注意:这一部分是不可编程的,它是由OpenGL自动完成的。

3. 屏幕映射

这一步输入的坐标仍然是三维坐标系。屏幕映射的任务是把每个图元的x和y坐标转换到屏幕坐标系下,屏幕坐标系是一个二维坐标系,它和我们用于显示画面的分辨率有很大关系。

屏幕映射得到的屏幕坐标决定了这个顶点对应屏幕上哪个像素以及距离这个像素有多远。

opengl的屏幕坐标原点是左下角,而directx是左上角,如果你发现你得到的图像是倒转的,那么很有可能就是这个原因造成的。

屏幕映射以后3D坐标就被转换成了2D坐标。

4. 三角形设置

由这一步就进入了光栅化阶段,从上一个阶段输出的信息是屏幕坐标下的顶点位置以及和它们相关的额外信息,如深度值、法线方向、视角方向等。
光栅化有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算他们的颜色。光栅化的第一个流水线阶段是三角形设置,这个阶段会计算光栅化一个三角网格所需的信息。

具体来说,上一个阶段输出的都是三角网格的顶点,即我们得到的是三角网格每条边的两个端点。但如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。这样一个计算三角形网格表示数据的过程就叫做三角形设置,它的输出是为了下一个阶段做准备。

5. 三角形遍历

三角形遍历阶段将会检查每个像素是否被一个三角形网格所覆盖。如果被覆盖的话,就会生成一个片元,而这样一个找到哪些像素被三角网格覆盖的过程就是三角形遍历,这个阶段也被称为扫描变换。

三角形遍历阶段会根据上一个阶段的计算结果来判断一个三角网格覆盖了哪些像素,并使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值。
这一步的输出就是一个片元序列。注意:一个片元并不是一个真正意义上的像素,而是包含了很多状态的集合。

6. 片元着色器(可编程)

片元着色器是另一个非常重要的可编程着色器阶段,前面的光栅化阶段并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息集合,用来表述一个三角网格是怎样覆盖像素的。而每个片元就是负责存储这样一系列数据。真正会对像素产生影响的是下一个流水线阶段--逐片元操作。
片元着色器的输入是上一个阶段对顶点信息插值得到的结果,所以片元着色器的颜色也是由对顶点数据的插值来完成的,如果没有使用纹理,它就会直接对三角网格的三个顶点颜色进行插值。

更具体来说,是根据那些从顶点着色器输出的数据插值得到的。而它的输出是一个或多个颜色值。

这一阶段可以完成很多重要的的渲染技术,其中最重要的技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然和经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到其覆盖的片元的纹理坐标了。

7. 逐片元操作  (最后一步)

逐片元操作是opengl中的说法,在directx中,这一阶段被称为输出合并阶段。

这一阶段有几个主要任务:
决定片元的可见性。涉及很多测试工作,如深度测试,模版测试等。
如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并

片元 - > 模板测试 - > 深度测试 - > 混合 - > 颜色缓冲区

模板测试:模板测试和模板缓冲相关,开启模板测试后,GPU会读取模板缓冲区中该片元位置的模板值,然后将该值和读取到的参考值进行比较,这个比较函数由开发者指定。不管有没有通过测试,我们都可以根据模板测试和深度测试的结果修改模板缓冲区,这个修改操作也是由开发者指定的。
模板测试通常用于限制渲染的区域,另外,还有一些高级用法,如渲染阴影、轮廓渲染等。

深度测试:深度测试和深度缓冲相关,开启深度测试后,GPU会读取深度缓冲区中该片元位置的深度值,然后将该值和读取到的参考值进行比较,这个比较函数由开发者指定。和模板测试不同的是,如果一个片元没有通过深度测试,它就不能修改深度缓冲中的值。而如果它通过了测试,开发者还可以指定是否要用这个片元的深度值覆盖掉原有的深度值,这是通过开启/关闭深度写入来做到的。

透明效果和深度测试以及深度写入的关系非常密切。

合并:为什么需要合并操作,这里所讨论的渲染过程是一个物体接着一个物体画到屏幕上,每个像素的颜色信息被存储在一个名为颜色缓冲的地方。因此当我们执行这次渲染时,颜色缓冲中往往已经有了上次渲染之后的颜色结果,所以两种颜色的混合是合并所需要解决的问题。

对于大多数GPU来说,它们会尽可能在执行片元着色器之前就进行这些测试。如果,GPU在片元着色器上画了很大力气计算出片元颜色后,却发现这些片元无法通过检验,那么会浪费大量成本。在unity中,深度测试就是在片元着色器前执行。

双缓冲技术:为了避免我们看到那些正在光栅化的图元,GPU会使用双重缓冲的策略。

即:对场景的渲染是在幕后发生的,在后置缓冲中。一旦场景已经被渲染到了后置缓冲中,GPU就会交换后置缓冲区和前置缓冲中的内容,而前置缓冲区是之前显示在屏幕上的图像,以此保证我们看到的图像总是连续的。

来源:CSDN,作者:莫之,转载此文目的在于传递更多信息,版权归原作者所有。
原文链接:https://blog.csdn.net/qq_36383623/article/details/81095411

推荐阅读