什么是渲染管线
渲染管线(渲染流水线)是将三维场景模型转换到屏幕像素空间输出的过程。图形渲染管线主要包括两个功能:一是将物体3D坐标转变为屏幕空间2D坐标,二是为屏幕每个像素点进行着色。
渲染管线的流程
渲染管线的一般流程如下图所示。分别是:顶点数据的输入、顶点着色器、曲面细分过程、几何着色器、图元组装、裁剪剔除、光栅化、片段着色器以及混合测试。
此外还可以抽象为4个阶段
-
应用阶段:由CPU主要负责的阶段,且完全由开发人员掌控。CPU将决定递给GPU什么样的数据(目标场景的灯光、模型),有时候还会对这些数据进行处理(将摄像机不可见的元素被**剔除(culling)**出去),并且告诉GPU这些数据的渲染状态(譬如纹理、材质、着色器等)。
这一阶段最重要的输出是渲染所需的几何信息,即渲染图元(通俗来讲可以是点、线、面等)
-
几何阶段:负责大部分多边形操作和顶点操作,将三维空间的数据转换为二维空间的数据。
-
光栅化阶段:决定每个渲染图元中哪些像素应该被绘制在屏幕上,它需要对上一阶段得到的逐顶点数据进行插值
-
像素处理阶段:给每一个像素正确配色,最后绘制出整幅图像
应用程序阶段在CPU端完成,后面的所有阶段都是在GPU端完成(所以又称GPU渲染管线)
特点
渲染管线的一个特点就是每个阶段都会把前一个阶段的输出作为该阶段的输入。
例如,片段着色器会将光栅化后的片段(以及片段的数据块)作为输入进行光照计算。除了图元组装和光栅化几个阶段是由硬件自动完成之外,管线的其他阶段管线都是可编程/可配置的。
各个阶段的功能
应用阶段
渲染管线的起点是CPU,CPU与GPU的通信即上文的应用阶段
(1)把数据加载到显存:将数据加载到显存中能使GPU更快的访问这些数据,当把数据加载到显存后,便可以释放了数据,但一些数据仍需留在内存中,如CPU需要网格数据进行碰撞检测。
(2)设置渲染状态:渲染状态的一个通俗解释就是,定义了场景中的网格是怎样被渲染的。例如,使用哪个顶点着色器/片段着色器、光源属性、材质等。如果不设置渲染状态,那所有的网格将使用同一种渲染,显然这是不希望得到的结果。
(3)调用Draw Call:当所有的数据准备好后,CPU就需要调用一个渲染指令告诉GPU,按照上述设置进行渲染,这个渲染命令就是Draw Call。Draw Call命令仅仅指向被渲染的图元列表,而不包含任何材质信息
在实际的渲染中,GPU的渲染速度往往超过了CPU提交命令的速度,这导致渲染中大部分时间都消耗在了CPU提交Draw Call上。有一种解决这种问题的方法是使用批处理(Batching),即把要渲染的模型合并在一起提交给GPU
GPU渲染管线
绿色表示开发者可以完全编程控制的部分,虚线外框表示此阶段不是必需的,黄色表示开发者无法完全控制的部分(但可以进行一些配置),紫色表示开发者无法控制的阶段(已经由GPU固定实现)
几何阶段
顶点着色器
顶点着色器的处理单位是顶点,但是每次处理都是独立的,因此无法创建或销毁任何一个顶点,也不能得到与其他顶点的关系
顶点着色器主要功能是进行坐标变换。将输入的局部坐标变换到世界坐标、观察坐标和裁剪坐标。
虽然我们也会在顶点着色器进行光照计算(称作高洛德着色),然后经过光栅化插值得到各个片段的颜色,但这种得到的光照比较不自然,所以一般在片段着色器进行光照计算
曲面细分着色器
曲面细分是利用镶嵌化处理技术对三角面进行细分,以此来增加物体表面的三角面的数量,是渲染管线一个可选的阶段
几何着色器
在这个阶段,开发者可以控制GPU对顶点进行增删改操作
投影
GPU将顶点从摄像机观察空间转换到裁剪空间(又被称为齐次裁剪空间),为之后的剔除过程以及投射到二维平面做准备。
常见的投影方式有透视投影与正交投影。
在三维中原有的三个分量x、y、z上又额外增加了w=1分量,使得可以通过矩阵乘的方式为三维坐标实现平移的效果。
裁剪
只有当图元部分或全部位于视椎体内时,我们才会将它送到流水线的下个阶段,也就是光栅化阶段。对于部分位于视椎体的图元,位于外部的顶点将被裁剪掉,而且在视椎体与线段的交界处产生新的顶点。
在把不需要的顶点裁剪掉后,GPU需要把顶点映射到屏幕空间,这是一个从三维空间转换到二维空间的操作。
-
对于透视裁剪空间,GPU需要对裁剪空间中的顶点执行齐次除法(将齐次坐标系中的w分量除x、y、z分量),得到顶点的归一化的设备坐标(NDC)
通过透视除法后,我们得到了NDC坐标,获得NDC坐标是为了实现屏幕坐标的转换与硬件无关。
-
正交裁剪空间只需要把w分量去掉即可。
此时顶点的x、y坐标接近于在屏幕上所处的位置了,此时z分量不会被丢弃而是被写入了深度缓冲(z-buffer)
屏幕映射
屏幕映射的任务就是将每个图元的x、y值变换到屏幕坐标系,对于z坐标不做任何处理(实际上屏幕坐标系和z坐标一起构成窗口坐标系),这些值会被一起传递到光栅化阶段
一般来说,屏幕坐标是2D的概念,只用于表示屏幕XY坐标,而窗口坐标是2.5D的概念,它还带有深度信息,也就是经过变换后的Z轴的信息。
使用齐次坐标的意义,其实就是为了正确记录下投影变换前(观察空间)中物体的深度信息,也就是Z坐标的值。
光栅化阶段
光栅化会确定图元所覆盖的片段,利用顶点属性插值得到片段的属性信息,然后送到片段着色器进行颜色计算,我们这里需要注意到片段是像素的候选者,只有通过后续的测试,片段才会成为最终显示的像素点。
三角形设置(Triangle Setup)
这个过程做的工作就是把顶点数据收集并组装为简单的基本体(线、点或三角形)
三角形遍历(Triangle Traversal)
这个过程将检验屏幕上的某个像素是否被一个三角形网格所覆盖,被覆盖的区域将生成一个片元(Fragment)
片元不是真正意义上的像素,而是包含了很多种状态的集合(譬如屏幕坐标、深度、法线、纹理等)
而片元的划分依据(像素块被覆盖到何种程度才被划分)不管怎么样都会产生锯齿,这就有许多抗锯齿的采样方式(比如MSAA)
片段着色器
片元着色器的输入是上一个阶段对顶点信息进行插值的结果(是根据从顶点着色器输出的数据插值得到的),而它的输出是像素颜色值
这个阶段我们能进行很多渲染技术,比如根据顶点法线或者UV计算颜色,接受阴影或者是进行纹理采样(纹理坐标是通过前述阶段的顶点数据插值得到的)
逐片元计算
对每个片元进行操作,将它们的颜色以某种形式合并,得到最终在屏幕上像素显示的颜色。主要的工作有两个:对片元进行测试(Test)并进行合并(Merge)。
- 测试步骤决定了片元最终会不会被显示出来,这个阶段是高度可配置的
- 如果一个片元通过了所有测试,就需要把这些片元的颜色值和颜色缓冲中已有的颜色值进行混合。
裁切测试
在裁剪测试中,允许程序员开设一个裁剪框,只有在裁剪框内的片元才会被显示出来,在裁剪框外的片元皆被剔除。
模板测试
模板测试通常用来限制渲染的区域,渲染阴影,轮廓渲染等
在模板测试中,GPU将读取片元的模板值与模板缓冲区的模板值进行比较,比较函数由开发者指定,如果比较不通过,这个片元将被舍弃
深度测试
现实生活中,近的物体会遮挡住远处的物体,深度缓冲就是用来实现这种效果的。
GPU将读取片元的深度值(就是我们前面留下来的坐标z分量)与缓冲区的深度值进行比较,和模板测试一样,如何渲染物体之间的遮挡关系也是自定义的(可以让GPU把没有被遮挡的部分隐藏了,让被遮挡的部分显示出来的。)
大量的被遮挡片元直到深度测试阶段才会被剔除,而在此之前它们同样地被计算,这占用了GPU大量的资源。
因此有种优化技术是将深度测试提前(Early-Z)。但这带来了与透明度测试的冲突,例如某个片元甲虽然遮挡了另一个片元乙,但甲却是透明的,GPU应当渲染的是片元乙,这就产生了矛盾,这就是透明度测试会导致性能下降的原因
合并
一个片元通过了所有测试,片元颜色就会被送到颜色缓冲区,到达了合并环节
合并有两种主要的方式,一种是直接进行颜色的替换,另一种是根据不透明度进行混合(Blend)
如果开启了混合,GPU会取出片元着色器得到的颜色(源颜色)和颜色缓冲区存在的颜色(目标颜色),之后按照设定的函数进行混合,可以设定透明度通道进行相加,相减还是相乘。
最后GPU会使用双重缓冲(Double Buffering)的策略,即屏幕上显示前置缓冲(Front Buffer),而渲染好的颜色先被送入后置缓冲(Back Buffer),再替换前置缓冲,以此避免在屏幕上显示正在光栅化的图元
渲染管线中的坐标系变换
- 我们从建模工具得到的是物体的局部坐标(Local Coordinate),
- 局部坐标通过模型矩阵Model变换到世界坐标(World Coordinate),
光照计算一般都是在世界空间进行的,所以输入的顶点坐标需要通过乘以模型矩阵变换到世界空间。在模型变换时,通常还需要将法线变换到世界空间中,对于法线变换不能简单的使用模型变换矩阵来变换法线,对于包含非均匀缩放的变换,需要求解模型变换的逆转置矩阵
-
世界坐标通过观察矩阵View变换到观察坐标(View Coordinate),
虚拟摄像机定义了我们的观察空间。虚拟摄像机的位置是坐标的原点,观察方向沿着Z轴的负方向。我们可以通过摄像机的位置(EyePosition)、观察目标点(FocusPosition)和向上的方向向量(UpDirection)来构建观察矩阵。
-
观察坐标经过投影矩阵Projection变换到裁剪坐标(Clip Coordinate),
裁剪空间的目标是能够方便的对图元进行裁剪,这块空间是由视锥体决定的,视锥体外为看不到的区域,也就是被裁剪的部分。当时不同的视锥体有不同的处理过程,而且透视投影判断点是否在视锥体内比较麻烦,通过一个投影矩阵将顶点转换到裁剪空间中是一种通用、方便的方式。——常见的投影矩阵有透视投影和正交投影
投影矩阵并没有真正的进行投影工作,而是为投影做准备,真正的投影发生在齐次除法过程中,这发生在屏幕空间映射阶段
-
裁剪坐标经过透射除法(Perspective Division)得到标准设备空间(Normalized Device Coordinates,NDC),
-
NDC坐标通过视口变换(Viewport Transformation)变换到窗口坐标进行显示