我所理解的RTR4-第2章图形渲染管线

首先声明,这篇文章不是RTR4第2章的翻译,要找翻译的朋友,恐怕要让你们失望了。

文章仅仅是我学了第2章后,结合我的实践,使用引擎的经验总结出来的理解性的东西。如果你想详细的了解图形渲染管线,请阅读原书,或者相关资料。

RTR4所说的渲染管线,大概是进行一次绘制要经过的阶段,对应到具体的代码上,就是提交一次drawcall所经历的阶段,如下所示:


渲染管线

渲染管线最后输出的是2D纹理图,但这并不表示一定要显示出来,可以用这个图来进行其他的处理,但这就不归图形渲染管线管了。

阶段一:应用阶段

应用阶段就是普通的编程阶段,这是我们程序员最熟悉的阶段。这阶段的程序运行在CPU之上,程序员对这个阶段的操作有绝对的掌控力。

为了充分利用CPU的性能,这个阶段的工作通常是多线程并行处理的。

应用阶段的工作是将需要渲染的图元交给后面的几何处理阶段。所谓图元,是GPU可以处理的基本几何体,比如点、线、三角形等。

现代GPU能处理的图元并不只有这些,但是这些是最常用的。

这个阶段的工作包括:
碰撞检测
全局加速算法(例如特定的剔除算法)
动画
物理模拟等
当然你可以添加或者删除一些操作,随你喜欢。

因为太熟悉了,以至于没有任何理解的困难。

阶段二:几何处理阶段

几何处理阶段需要把在窗口中能看到的三角形全部枚举出来,这就包括了裁剪,要把无法看到的三角形都剔除在外。

注意,几何处理阶段输出的粒度是三角形,不是物体。

通常,几何处理可以分成以下几个步骤:



分别是:顶点着色、投影、裁剪和屏幕映射

1、顶点着色

顶点着色阶段的工作是计算顶点相关信息,主要是位置、法线等。顶点着色阶段最繁重的工作是坐标转换,任何一个模型在创造的时候都是以自身锚点为原点的,这是模型坐标。然后把它放到一个场景中,就得到了世界坐标。然后为了计算方便,所有的坐标都会转换成以观察点为原点的坐标,这是观察坐标。顶点着色阶段就是需要把顶点的坐标,法线的坐标等都算成观察空间的坐标。就想下图那样:


2、投影

投影,是将上图中的视椎体转换成一个标准的立方体。立方体的xy坐标范围是(-1,-1)到(1,1),z坐标范围根据实现可以是(0,1),也可以是(-1,1)。

通常有两种投影方式,正交投影和透视投影,效果如下图所示:


左:正交;右:透视

对一般人而言,我们所看到的东西都是透视投影的结果,所以一般我们都是用透视投影得到结果。

投影后所在的空间叫做裁剪空间,这么叫就是因为下一个阶段要做的是裁剪工作。

事实上,在顶点处理过后,GPU还有一些可选的阶段可以执行,分别是:细分、几何着色和流输出。
细分阶段主要是根据物体离相机的距离对物体模型的精细度进行调整,它会在距离近的时候增加物体的顶点和三角形数,避免靠近了之后看到的物体棱角分明的,看着很假。而在物体离远了之后,又没有必要那么精细了,反正看不见,顶点和三角形多了还浪费渲染资源,所以就减少顶点和三角形数量。
几何着色阶段跟细分阶段类似,它也可以产生顶点和三角形面片,但是限制比细分阶段要多。(具体这个限制是啥,我也不了解。)它的主要作用是产生粒子,做粒子系统的时候很有用。
流输出阶段可以把之前处理好的数据直接输出到内存中,可以给CPU访问,也可以再次给GPU访问。就是把GPU当成一个数据处理引擎来用。

3、裁剪

裁剪阶段的工作很简单,就是把部分在视椎体之外的三角形去掉,在边缘生成新的顶点。


裁剪工作是在裁剪空间中做的,这个空间中,点的坐标是四维的,因为经过投影后,物体的属性并不是标准的线性插值,它需要第四维的坐标来正确插值。(具体的方式比较复杂,这里有个概念就行。)

最后,执行透视除法,将三角形的坐标转换成3D标准设备坐标(normalize device coordinates,NDC)。

4、屏幕映射

将图元的x和y坐标转换成屏幕坐标,屏幕坐标连同z坐标一起被称为窗口坐标。这个过程被称为屏幕映射。


这个过程很简单,就是把x和y坐标拉伸,让它适配窗口坐标。z坐标为了一些其他的目的,可能保存的是真正的z坐标,也可能是保存了z的倒数。

阶段三:光栅化

光栅化就是确定前面这些阶段处理后的三角形到底在屏幕上占据了哪些像素。我们用数学模型表示三角形的时候,它肯定是连续的,而要把它显示到屏幕上,对屏幕来说就只有这个像素到底是在你三角形里面,还是不在你三角形里面这两种情况。哪怕三角形是占据了一个像素的部分区域,也必须要做这个决定:到底是在你这个三角形里,还是不在。
于是,在这个阶段里,像素是否被三角形占据、被占据的话它的法线数据、纹理数据等等一系列信息都要计算,然后保存。

光栅化有时也被称为扫描转换

这个过程通常被分为两个子阶段,三角形配置和三角形遍历。


  • 配置阶段是准备三角形的数据,比如边缘函数、之前计算的着色数据等,方便遍历阶段调用。
  • 遍历阶段就是确定像素是否在三角形里面的计算过程。生成的数据就是像素的法线、着色模型数据等等信息,用这些信息来标记是否在三角形内部。

其实我们并不关心像素是否在三角形内部,我们要获取的就是法线、着色模型信息等等,因为只有这些数据齐了,才能进行下面的像素着色过程。内部还是外部只不过是计算这些数据的方式不同罢了。

阶段四:像素着色

最后是像素着色。说白了就是对每个像素运行一次着色程序,计算出像素的最终颜色。


这里的融合阶段不是很懂,在我的理解里,像素着色之后就是最终颜色了,为什么还有融合阶段?希望读过RTR4的朋友能来讨论一下。

像素着色阶段计算量非常中,简单点来说,你的窗口占多少像素,就要计算多少次。像我们平时1920x1080的分辨率,就要计算1920x1080次。那为啥算的还这么快,感觉不到延迟呢?因为现代GPU高度并行化,它非常擅长进行这种重复很多次,但是每次操作都不复杂的计算。

像素着色需要的资源也不少,法线贴图、纹理贴图、还有一些特定的着色流程下会用到的贴图等。而且,着色模型的计算就是在像素着色器里,也就是说如果用BRDF的模型,那么是在这里计算的。PBR的计算量多大,也用不着我多说。

事实上,UE4里有很多对像素的处理,比如PostProcessing等。可以看到,像素着色并不像你想象的那么鸡肋,它的重要性很高,甚至要超出顶点着色器。

总结

核心要素就是这些。书中的东西当然不止这么多,想深入研究的读者可以去读RTR,如果有心得,欢迎回来讨论。