渲染管线
应用阶段
这一阶段由CPU处理,主要任务是为接下来GPU的渲染操作提供所需要的几何信息,即输出渲染图元(rending primitives)以供后续阶段的使用。渲染图元就是由若干个顶点构成的几何形状,点,线,三角形,多边形面都可以是一个图元。
-
数据的准备
-
设置渲染状态
渲染状态包括着色器(Shader),纹理,材质,灯光等等。
设置渲染状态实质上就是,告诉GPU该使用哪个Shader,纹理,材质等去渲染模型网格体,这个过程也就是SetPassCall。当使用不同的材质或者相同材质下不同的Pass时就需要设置切换多个渲染状态,就会增加SetPassCall 所以SetPassCall的次数也能反映性能的优劣。 -
发送DrawCall
当收到一个DrawCall时,GPU会按照命令,根据渲染状态和输入的顶点信息对指定的模(网格)进行计算渲染。
CPU通过调用图形API接口( glDrawElements (OpenGl中的图元渲染函数) 或者 DrawIndexedPrimitive (DirectX中的顶点绘制方法) )命令GPU对指定物体进行一次渲染的操作即为DrawCall。此过程实质上就是在告诉GPU该使用哪个模型的数据(图形API函数的功能就是将CPU计算出的顶点数据渲染出来)。
DrawCall
:CPU每次调用图形API接口命令GPU进行渲染的操作称为一次DrawCall。
SetPassCall
:设置/切换一次渲染状态。
Batch
:把数据加载到显存,设置渲染状态,CPU调用GPU渲染的过程称之为一个Batch。
注:一个Batch包含至少一个DrawCall
几何阶段
几何阶段由GPU进行处理,其几乎要处理所有和几何相关的绘制事情。如绘制的对象,位置,形状。几何阶段处理的对象时渲染图元,进行逐顶点和逐多边形的操作。主要任务是把顶点坐标变换到屏幕空间中,以供给接下来的光栅器进行处理。具体输出的信息有,变换后的屏幕二位顶点坐标,顶点的深度值,着色,法线等等信息。
-
顶点着色器(shader中的vertex代码段)
- 流水线的第一个阶段,其可以通过编程进行控制。输入来自CPU发送的顶点信息,每个顶点都会调用一次顶点着色器
- 它主要执行坐标转换和逐顶点光照的任务。坐标转换是将顶点坐标从模型空间转换到齐次裁剪空间中,它是通过MVP(Model、View、Projection)转换得到的。
- 逐顶点光照得到的光照结果比较不自然,所以一般是在片元着色器中进行光照计算。
-
图元装配
- 裁剪(Clipping): 例如,一些图元线段的两个顶点一个位于视椎体内而另一个位于视椎体外,那么位于外部的顶点将被裁剪掉,而且在视椎体与线段的交界处产生新的顶点来代替视野外部的顶点(在裁剪空间中进行)。
- 标准化设备坐标(Normalized Device Coordinates,NDC):在裁剪空间的基础上,进行透视除法(perspective division)后得到的坐标叫做NDC坐标,将坐标从裁剪空间的(-w,-w,w)变换为(-1,-1,1),即除 w,获得NDC坐标是为了实现屏幕坐标的转换与硬件无关。
- 背面剔除(Back-Face Culling): 背对摄像机的三角面剔除,上面我们讲到过模型数据中含有索引列表,列表中的三个点组成一个三角片,如果这三个点是顺时针排列的,认为是背面,否则认为是正面。
- 屏幕映射(ScreenMapping): 是把每个图元的x和y坐标转换到屏幕坐标系(Screen Coordinates)下。屏幕坐标系是一个二维坐标系,它和我们用于显示画面的分辨率有很大关系.由于我们输入的坐标范围在-1到1,因此可以想象到,这个过程实际是一个缩放的过程。屏幕坐标系和z坐标一起构成了一个坐标系,叫做窗口坐标系(WindowCoordinates)。这些值会一起被传递到光栅化阶段。
光栅化阶段
此阶段仍然由GPU进行处理。这一阶段将会使用上个阶段传递的数据(屏幕坐标系下的顶点位置以及和它们相关的额外信息,如深度值(z坐标)、法线方向、视角方向等。)来产生屏幕上的像素,并渲染出最终的图像。光栅化的主要任务是决定渲染图元中的哪些像素应该被绘制在屏幕上,然后对其颜色进行合并混合。
-
三角形设置(Triangle Setup)
我们从上一个阶段获得图元的顶点信息,也就是三角面每条边的两个端点,根据基本的顶点构成基本的三角形。但如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。这样一个计算三角网格表示数据的过程就叫做三角形设置。 -
三角形遍历(Triangle Traversal)
根据上一个阶段的计算结果来判断一个三角网格覆盖了哪些像素,并使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值。这一步的输出就是得到一个片元序列。(需要注意的是,一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了它的屏幕坐标、深度值Z、顶点颜色,以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等。) -
片元着色器(shader中的fragment代码段)
它最主要的任务就是着色,光栅化阶段实际上并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用来表述一个三角网格是怎样覆盖每个像素的。而每个片元就负责存储这样一系列数据。着色有两种最常见的技术,分别是纹理贴图和光照技术。
输出合并阶段(GPU)
在DirectX中,该阶段被称为输出合并阶段(OpenGL:逐片元操作(Per-Fragment Operations)),从称呼中就可以看出,这个阶段主要是对每一个片元进行一些输出合并操作,包括Alpha测试、模板测试、深度测试和混合,它有几个主要任务:
- 决定每个片元的可见性。这涉及了很多测试工作,例如深度测试、模板测试等。为什么要进行测试呢?因为屏幕上的一个像素可能存在多个片元进行竞争,通过测试等规则,可以决定哪个片元最终能够渲染到屏幕上
- 如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合。
纹理采样
模板测试
深度测试