渲染管线

GPU和CPU的区别

GPU采用了数量众多的计算单元和超长流水线,但每一个部分只有非常简单的控制逻辑,尽管计算能力不如CPU,但架不住人多力量大(GPU最开始就是以算术处理单元独立出CPU)。

渲染管线

在运算过程中,CPU一直将要处理的数据丢给GPU,GPU调用一个个计算单元对这些数据进行简单的处理,最后组装出图像。
应用阶段(输出渲染图元)->几何阶段(输出屏幕空间的顶点信息)->光栅化阶段

应用阶段(Application Stage)

由CPU主要负责的阶段,且完全由开发人员掌握。在这个阶段,CPU将决定传递给GPU什么样的数据(如渲染目标场景中的灯光、场景的模型、摄像机的位置),有时还会对这些数据进行处理(如只传递给GPU可以被摄像机看见的元素,其他不可见的元素剔除(culling)出去),并且告诉GPU这些数据的渲染状态(如纹理,材质,着色器)。

几何阶段

由GPU主导的阶段。几何阶段将吧CPU在应用阶段发来的数据进行进一步处理。

放入显存与Draw Call

在应用阶段,CPU从硬盘中把需要的数据拿出来放进内存中,经过一系列操作后,再打包发给GPU进行进一步处理。从渲染角度看,当CPU把数据传递到显存中,这些内存数据就可以移出了。但有些特殊的数据,如一面墙,不仅需要被渲染出来,还需要进行计算物体碰撞,那么CPU将它的网格丢给GPU后并不会立马把ta从内存中移除,因为CPU还需要用这个网格计算碰撞。
在应用阶段,尽管CPU已经将数据准备的很充分了,但完成任务后,CPU还需要向GPU下达一个渲染命令。由于之前我们已经把这个数据准备的十分完善,所以DrawCall仅仅是一个指向需要被渲染的图元列表,没有其他材质信息。
CPU向GPU发送的指令也像流水线一样——CPU往命令缓冲区中一个个放入命令,GPU则依次取出执行。在实际的渲染中,GPU的渲染速度往往超过了CPU提交命令的速度,这导致渲染中大部分时间都消耗在CPU提交DrawCall上。(可以使用批处理(Batching)解决,即把要渲染的模型合并在一起交给GPU)。

顶点着色器(Vertex Shader)

顶点着色器是GPU流水线的第一个阶段也是必需的阶段,这一块可以完全由开发者控制。在顶点着色器中,我们无法创建或销毁任何一个顶点,也无法得到当前处理的这个顶点和其他顶点的关系。因为每次处理顶点都是独立的,所以进行这一步速度会hen快。
GPU还需要进行模型转换和相机转换(Model- & Camera transformation),在3D渲染中,我们必须要设置一个摄像机来接受图像,这个摄像机的视野决定了GPU最终会让我们看到什么样的画面,为了方便之后的运算,这里还需要根据视椎体将坐标空间由世界空间映射到摄像机的观察空间。
在世界空间下进行计算时,是相当不方便的,因此相比起在复杂的世界空间进行坐标计算,把计算的空间换到摄像机的观察空间会大大方便之后的流水线处理速度。
此外,我们还可以进行的操作有:坐标变换(如动画),逐顶点色彩处理(如光照、纹理采样)。

坐标变换

开发者可以编写程序在这个阶段修改顶点的坐标,诸如流动、摇曳与顶点位置相关的动画操作都可以实现。

逐顶点色彩信息处理

开发者可以在这个阶段计算每个顶点的光照信息,计算光照、阴影等。这里的“信息处理”,还不是真正的着色,可以理解为“为接下来的着色器的着色计算提供一些信息”。

曲面细分着色器(Tessellation Stage)

在这一阶段,程序员可以进行曲面细分操作,看起来就像在原有图元内加入了更多的顶点。对于一些有大量曲面的模型,进行曲面细分会更加圆润,如果为这些细分的顶点再准备一些位置信息,那么这些细分的顶点将有助于我们展示一个细节更丰富的模型。这也是贴图置换(Displayment Mapping)的基本思路。
在进入Tessellation Stage之前,流水线还将经过Hull-Shader Stage,这是一个可编程阶段,开发者可以指挥GPU如何对顶点进行细分操作,但还不会真正进行细分。
Tessellation Stage是真正的细分阶段,尽管开发者无法在这个阶段进行编程,但GPU会根据Hull-Shader Stage中的标记进行细分。
在离开Tessellation Stage之后,流水线将进入Domain-Shader Stage,这是一个可编程阶段,开发者可以指挥GPU对这些细分的顶点进行坐标计算。

几何着色器(Geometry Shader)

在这个阶段,开发者可以控制GPU对顶点进行增删改查操作。几何着色器与顶点着色器都可以对顶点的坐标进行修改,但几何着色器并行调用硬件困难,并行程度低,效率和顶点着色器有很大的差距。如果不是要做顶点增删仅仅能用几何着色器实现的效果,那么还是用顶点着色器来完成。

投影(Projection)

尽管致此GPU已经在三维空间中做了很多工作,但我们最终要在一个二维屏幕上查看我们渲染出的图像,这就需要GPU把三维空间映射到二维空间上了。在这个阶段中,GPU将顶点从摄像机观察矩阵转换到裁剪空间(又被称为齐次裁剪空间),为之后的剔除过程以及投射到二维平面做准备。
常见的投影方式有:透视投影和正交投影。他们在计算时要考虑远裁剪平面(Far Clipping Plane)和近裁剪平面(Near Clipping Plane)。透视投影需要额外考虑视野(Field of VIew),即视椎体张开的角度,正交投影需要额外考虑尺寸(Size),这个值用于衡量视椎体底的大小。
在这里引入了齐次坐标的概念,在三维中原有的三个分量x、y、z又额外增加了w分量,使得可以通过矩阵乘的方式为三维空间实现各种效果。
对于任意一个顶点,如果他乘上的是透视投影矩阵,它的分量将不再是1,而如果它乘上的是正交投影矩阵的话,w分量仍然是1。发生这种现象的根本原因是透视空间并不是一个欧几里得空间——在透视算法中,平行线会在无穷远处相交,因此数学家们引入了一个额外的w分量来描述这种空间下的坐标。在经过透视投影矩阵变化后,顶点中的w分量变成了一个衡量顶点到摄像机之间距离的参数。正交矩阵直接把空间变化为了一个x、y、z三个坐都在[-1,1]区间内,w=1的立方体。

裁剪(Clipping)

在经过投影过程把顶点坐标转换到裁剪空间后,GPU就可以进行裁剪操作。裁剪操作目的就是把摄像机看不到的顶点剔除出去,使他们不被渲染到。判断顶点免受裁剪只需满足 x , y , z ∈ [ − w , w ] x,y,z\in [-w,w] x,y,z[w,w]
在把不需要的顶点裁剪后,GPU需要把顶点映射到屏幕空间,这是一个从三维空间转到二维空间的操作。对透视裁剪空间来说,GPU需要对裁剪空间中的顶点执行齐次除法(其实就是将齐次坐标系中的w分量除x、y、z分量),得到归一化的设备坐标(Normalized Device Coordinates, NDC),经过齐次除法后,透视裁剪空间会变成一个x、y、z三个坐标都在[-1, 1]区间内的立方体。对于正交矩阵就要简单得多,只需要把w分量去掉即可。
此时顶点的x、y坐标就已经很接近它们在屏幕上的位置,不过还有一个多出来的z分量,不过它们不会被白白丢弃,而是被写入了深度缓冲(z-buffer)中,可以做一些有关于顶点到摄像机距离的计算。

屏幕映射(Screen Mapping)

尽管GPU已经得到了顶点的x、y坐标,但它们处于[-1,1]区间中,GPU还需要进行一定的计算才能把他们映射到我们的屏幕。得到的新坐标系叫做窗口坐标系,虽然只需要两个顶点投射到屏幕上,但它仍是三维的。

光栅化阶段(Rasterization Stage)

至此,GPU也才完成了一半的工作,我们只是得到了一些顶点并不是能被显示在屏幕上的像素。

图元组装(Primitive Assembly)

有些资料会把这个过程称为三角形设置(Triangle Setup),这个过程做的工作就是把顶点数据收集并组装为简单的基本体(线、点或三角面)。

三角形遍历(Triangle Traversal)

检验屏幕上的某个像素是否被一个三角形网格所覆盖,被覆盖的区域将生成一个片元(Fragment)。并不是所有像素都会被三角形完整的覆盖,有相当多的情况一个像素块只有一部分被三角形覆盖,有三种解决方案:Standard Rasterization(中心点被覆盖即被划入片元)、Outer-conservative Rasterization(只要被覆盖了,哪怕只有一点也会被划入进片元)、Inner-conservative Rasterization(完全被覆盖才会划分进片元)。
片元不是真正意义上的像素,而是包含很多状态的集合(如屏幕坐标、深度、法线、纹理等)。这些状态用于最终计算出每个像素的颜色。
这一阶段牵扯到了抗锯齿(Anti-aliasing)操作,因为不管用什么划分片元的方法,三角形边缘总会显得很锐利,有很多方法可以解决这个问题,如多重采样抗锯齿(MultiSampling Anti-aliasing, MSAA),这种抗锯齿方法对中心点不在三角形内的边缘处采用不同程度的浓度进行计算。
除此之外,GPU还将对覆盖区域的每个像素的深度进行插值计算。因为对于屏幕上的一个像素来说,可能有多个三角形重叠,所以这一步对于后面的计算遮挡、半透明等效果有着重要的作用。
简单的说,这一步将告诉接下来的步骤,一个个三角形是怎么覆盖每个像素的。

片元着色器(Fragment Shader)

为每个片元计算颜色。这个阶段是完全可编程的,在收到GPU为这个阶段输入了大量的数据后,开发者可以决定这些片元该着上什么颜色。
程序员可以引入更多的信息计算颜色,包括法线贴图、高度图、糙度图等等。虽然片元着色器可以完成很多重要效果,但它仅可以影响单个片元。当执行片元着色器时,它不可以将自己的任何结果直接发送给它附近的片元的。

逐片元操作(Per-Fragment Operations)

在DirectX中,这一步又称作输出合并阶段(Output-Merger)。从两个名字中我们大致可以推断出GPU在这个阶段要做的事情:对每个片元进行操作,将它们的颜色以某种形式合并,得到最终在屏幕上像素显示的颜色。主要的工作有:对片元进行测试(Test)并进行合并(Depth Test)。
测试步骤决定了片元最终会不会显示出来。在OpenGL中,主要的测试有:裁剪测试(Scissor Test)、透明度测试(Alpha Test)、模板测试(Stencil Test)以及深度测试(Depth Test)。这个阶段是高度可配置的。
如果一个片元通过了上面所有的测试,那它终于可以来到合并环节了。合并有两种主要的方式,一种是直接进行颜色替换,另一种是根据不透明度进行混合(Blend),而混合操作同样是可配置的,程序员可以设定是把这两种颜色进行相加、相减还是相乘。
经过上面的层层测试后,片元着色器就会被送到颜色缓冲区。GPU会使用双重缓冲(Double Buffering)的策略,即屏幕上显示的前置缓冲(Front Buffer),而渲染好的颜色先被送入到后置缓冲(Back Buffer),再替换前置缓冲,以此避免在屏幕上显示正在光栅化的图元。

裁剪测试(Scissor Test)

在裁剪测试中,允许程序员开设一个裁剪框,只有在裁剪框内的片元才会被显示出来,在裁剪框外的片元均被剔除。

透明度测试(Alpha Test)

在透明度测试中,允许程序员对片元的透明度值进行检测,仅仅允许透明度值达到设置的阈值后才会绘制。

模板测试(Stencil Test)

模板测试是一个相对复杂的测试。在模板测试中,GPU将读取片元的模板值与模板缓冲区的模板值进行对比。如何比较由程序员决定,如果比较不通过,这个片元将被舍弃。

深度测试(Depth Test)

在深度测试中,GPU将读取片元的深度值(前年留下来的坐标z分量)与缓冲区的深度值进行比较,比较方式同样是可以配置的。深度测试允许程序员设置如何渲染物体之间的遮挡关系。
大量的遮挡片元直到深度测试阶段才会被剔除,而在此之前它们同样的被计算,这占用了GPU大量资源。因此有种优化技术是将深度测试提前(Early-Z)。但这带来了与透明度测试的冲突。例如某个片元甲虽然遮挡了另一个片元乙,但甲却是透明的,GPU应当渲染的是片元乙,这就产生了矛盾,这就是透明度测试会导致性能下降的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值