目录
- 引言
- 第二章、图形渲染管线
- 2.1 图形渲染管线架构概述
- (1)渲染管线的主要功能
- (2)渲染结果是由输入对象相互作用产生的
- (3)图像渲染管线的三个阶段
- (4)其他讨论
- 2.2 应用程序阶段
- (1)实现方式
- (2)阶段任务
- 2.2 几何阶段
- (1)模型和视图变换
- 概述
- 实现
- (2)顶点着色
- (3)投影
- 概述
- 正交投影实现
- 透视投影实现
- (4)裁剪
- (5)屏幕映射
- 2.3 光栅化阶段
- (1)三角形设定
- (2)三角形遍历
- (3)像素着色
- (4)融合
引言
- 从事图形开发工作,至少要系统性学习一本关于图形渲染的书籍,本专栏记录我学习RTR3的心得。
- 由于我英语阅读能力差,因此学习的RTR3非原本,而是毛星云大神写的RTR3提炼总结。
第二章、图形渲染管线
2.1 图形渲染管线架构概述
(1)渲染管线的主要功能
- 渲染管线的主要功能:决定在给定虚拟相机、三维物体、光源、照明模式,以及材质等诸多条件的情况下,生成或绘制一幅二维图像的过程。
- 将 “虚拟相机、三维物体、光源、照明模式,以及其他条件” 比喻为流水线上的材料,那么渲染管线就是决定如何加工它们,以生成最终产品 “二维图像”。
- 注意渲染管线是决定生成或绘制图像的过程,而非生成或绘制图像的过程。即渲染管线像是导入生产流水线的配置文件,而具体去执行生产的是流水线上的机器设备。
(2)渲染结果是由输入对象相互作用产生的
-
下图展示了渲染出一系列物体例子:
-
可以看到,最终渲染出物体的几何形状是由:摄像机位置、物体位置、物体几何形状,共同决定的。而物体的外观是由:物体材质、光源、着色模型,共同决定的。即渲染的最终结果 “二维图像”,是由输入的对象相互作用产生的。
(3)图像渲染管线的三个阶段
- 图像渲染管线分为如下图所示的三个阶段。
- 何为管线,或者说何为流水线?按顺序执行一系列步骤就是流水线。上图中三个阶段是相互依赖的,前一个阶段提供输入给后一个阶段,因此这三个阶段需要从前往后按顺序执行,因此它们组成了一条流水线即管线。
- 渲染管线分为三个阶段,那么每个阶段是管线吗?
1.如上图所示,几何阶段可以划分为多个同步步骤,因此几何阶段是一条管线。
2.光栅化阶段整体可以作为一个管线,但对于其中某个步骤,可以将其划分为多个子步骤,这些子步骤间不存在同步关系,因此可以并行化运行。
3.应用程序阶段,它是程序员编码实现的,自由度非常高没有任何限制,因此在图中用一个黄色的块概述。它可以管线化也可以并行化,比如应用程序阶段可能划分为多个步骤,如碰撞检测、加速算法、输入检测等,它们之间是同步关系。而对于某个具体的步骤如碰撞检测,可以使用多线程并行等技术,同时并行多个子步骤。
(4)其他讨论
- 渲染管线的绘制速度是什么?渲染管线的绘制速度即图像的更新速度,一般用FPS来表示,即每秒钟绘制的图像数量。类似算法分析中,最高阶时间复杂度代表了整个算法的时间复杂度,而渲染管线中,最慢的管线阶段决定了整个管线的绘制速度。
- 渲染的时间怎样计算?整个渲染管线的时间,并非三个阶段的时间相加。因为三个阶段并非都运行在CPU上,只有应用程序阶段运行在CPU上。这代表应用程序阶段结束后,GPU执行几何和光栅化阶段时,CPU就可以执行下一帧的应用程序阶段了。当然CPU也不能自顾自运行,CPU和GPU需要协调工作。
2.2 应用程序阶段
(1)实现方式
- 应用程序阶段一般是图形渲染管线概念上的第一个阶段,它是通过软件方式来实现的,开发者能够对该阶段进行完全控制,并且能够通过改变实现方法来提升该阶段性能。其他阶段全部或部分建立在硬件基础上,因此要改变实现过程会非常困难。
- 应用程序阶段通过软件方式实现,开发者可以用任意方式编写,因此该阶段无法再细分。对于固定开发者而言,可以将该阶段自定义划分为多个同步阶段,并且对于某些阶段或子阶段可以通过编程技术实现并行。
(2)阶段任务
- 应用程序阶段通常实现的方法有:碰撞检测、加速算法、输入检测、动画,以及一些不在其他阶段执行的计算。相比于GPU,CPU能够执行更复杂的运算,因此只要不在GPU上执行的任务,都是丢给CPU执行的。应用程序阶段可以执行层次视锥裁剪等加速算法,即检测每个物体的包围体是否位于视锥之外,是则剔除物体,此方法显著的减少了后续阶段的输入,避免了不必要的渲染工作,大大提高了渲染效率。
- 应用程序阶段的主要(必须)任务:在应用程序阶段的末端,将需要在屏幕上显示出的几何体(即绘制图元,如点、线、三角形等)输入到渲染管线的几何阶段。为什么渲染任务位于应用程序的末端呢?易知应用程序应先处理输入等内部逻辑,最后再使用更新后数据信息去渲染,因此渲染任务位于应用程序末端。
- 应用程序阶段需要提交的输入有哪些?只要是后续阶段所需的信息,如虚拟相机、三维物体、光源等信息,都需要提交。
2.2 几何阶段
- 几何阶段主要负责大部分多边形操作和顶点操作。可以将其划分为如下几个功能阶段:
(1)模型和视图变换
概述
- 每个模型都定义于其自身的模型空间中,而最终所有模型都显示在唯一的世界空间中。从每个模型的模型空间到世界空间,需要进行模型变换。具体来说,对于模型的顶点和法线等数据,通过模型变换,将其转换到世界空间中。
- 相机在世界空间中有位置和方向信息,用来表示相机在世界空间的位置和朝向。
- 若将相机放置在原点,令其朝向z轴负方向、y轴指向上方、x轴指向右边,则后续投影和裁剪的计算将大大简化。我们将这个理想空间称为观察空间,为了简化计算,在投影和裁剪之前,我们将相机变换到观察空间,而其他物体也做相应变换。
- 不同API使用的坐标系空间是不同的,对于OpenGL而言观察空间中摄像机朝向z轴负半轴,而Direct3D中摄像机朝向z轴正半轴。
实现
- 对于模型文件而言,其内部节点间就存在层次结构关系,比如A节点代表场景,B节点代表网格,而B节点就是A节点的子节点,这时B节点的位置、法向等几何信息受到A节点的影响。模型文件还可能包含骨骼动画,此时骨架层次结构和关键帧数据决定了模型变换后的状态。以上都是模型在局部空间进行变换的例子,它表示模型内部的变换。可以通过学习Assimp和FBX SDK来更深入了解相关内容。
- 当模型局部空间的数据确定后,就可对其进行世界变换,世界变换矩阵的构造基于缩放、旋转、位移等参数,并且必须严格遵守先缩放、再旋转、最后位移的顺序,因此世界变换矩阵也只能应用一次。可以参考基本世界矩阵的计算。
- 对于旋转而言,虽然可以绕任意轴旋转,但一般使用绕x、y、z轴旋转进行描述。即使每个轴旋转的角度确定,如果旋转的顺序不同,最终旋转的结果也是不同的。一般可采取绕x、y、z轴的顺序进行旋转,这样对于任意[x,y,z],其最终旋转结果是唯一的。旋转可以使用欧拉角或四元数描述,欧拉角虽然简单,但它无法通过旋转矩阵还原旋转角度,也可能产生万向节死锁,因此如果会四元数就使用四元数。可参考旋转世界矩阵的计算。
- 观察变换矩阵的实现其实就是用到了世界变换矩阵构造的原理,构造将摄像机变换到指定位置和方向的矩阵即可,可参考观察矩阵的计算。‘
(2)顶点着色
- 确定材质上光照效果的操作被称为着色 (shading) ,着色过程涉及计算着色方程。
- 着色可以在几何阶段的顶点上执行,也可以在光栅化阶段的像素上执行。如果在几何阶段的顶点上进行着色,其着色结果计算完成后,将会被发送到光栅化阶段进行插值。
- 着色计算通常是在世界空间中进行的,在实践中,也可以在其它空间,如模型或观察空间进行着色计算,虽然坐标系空间不同,但着色过程中所有实体间的相对关系不会变,因此着色结果都是正确的。
- 总结:顶点着色阶段的目的在于确定模型上顶点处材质的光照效果。为什么顶点着色阶段在模型和观察矩阵变换之后?因为顶点着色不能在模型局部空间中进行,可以在世界空间和观察空间中进行。
(3)投影
概述
- 在顶点着色过后,需要进行投影操作,以将三维空间投影为二维图像。
- 投影方法主要有两种:
- 正交投影的可视体通常是一个矩形,正交投影可以把这个可视体变换为单位立方体。正交投影的主要特性是平行线在投影变换后仍保持平行,这种投影变换是平移与缩放的组合。
- 透视投影会导致越远的物体看起来越小,并且平行线将在远处汇聚,这种投影模拟了人类感知物体的方式。
- 正交投影和透视投影都可以通过4x4矩阵来实现,在任何一种投影变换后,都可以认为模型位于归一化处理之后的设备坐标系中。
正交投影实现
- 可参考投影矩阵的计算。
- 现在所有对象都位于观察空间中,但我们不可能看见观察空间中的所有物体吧?考虑我们的眼睛,我们只能看到我们眼前一定范围内的物体,这个范围就是所谓的可视体。投射和正交投影的示意图如下:
- 可以看到正交投影的可视体是一个矩形,矩形的两个平面垂直与摄像机朝向即-z轴,将这两个平面中靠近摄像机的平面称为近平面,另一个称为远平面。
- 投影是将三维空间投影为二维图像,正交投影则是将可视体矩形投影到近平面上,近平面上的二维图像就是投影结果。在正交投影中,只有可视体矩形内的物体是可见的,上图中的绿色球位于可视体外因此没有投影到近平面中。
- 如何定义正交投影的可视体呢?由于可视体矩形的远近平面均垂直于摄像机朝向,并且远近平面都在摄像机的前方,因此使用n和f分别表示近、远平面到摄像机的距离,即可描述可视体矩形的远近平面。
- 远近平面描述了摄像机能看到的最远和最近物体的距离,而矩形的左右平面描述了摄像机能看到的横向范围,上下平面描述了摄像机能看到的纵向范围。
- 正交投影可视体的完整参数如下图所示:
- 观察空间摄像机位于原点,右侧为x轴,观察方向为z轴,上侧为y轴。因此n和f代表近远平面到摄像机的距离,为正数。而l和r表示可视体左右两侧平面与x轴相交点的x坐标值,为实数。类似的t和b表示可视体上下两侧平面与y轴相交点的y坐标值,为实数。
- 当定义好矩形可视体后,我们要将可视体转换到[-1,1]^3的立方体中,对于的变换矩阵如下:
- 为了抛开设备显示器等硬件信息,我们将可视体转换到[-1,1]^3的立方体中。此时抛开z值,只剩下了x和y值,即我们完成了对三维空间的投影。
透视投影实现
- 可参考投影矩阵的计算。
- 如下图所示,投影矩阵的可视体是一个截头四棱锥,这也符合我们人眼的视觉,我们人眼对于横向和纵向也是有可视范围的,但对于越远的物体,横向和纵向的可视范围越大。
- 类似正交投影,远近平面使用f和n参数表示即可。可以看到透视投影的远近平面大小是不同的,并且随着距离加深,可视平面也逐渐扩大。可以看到摄像机在横向和纵向的张角大小是固定的,设摄像机的纵向张角为fovY,则摄像机和近平面构成的三角形示意图如下:
- 在n和f设定后,我们可以设置fovY参数,它表示摄像机的垂直视场角,其角度越大则摄像机纵向能看到的范围越大。上图中近平面到摄像机的距离为n,近平面高度的一半为t,则在已知n、f、fovY的情况下,t可根据下图关系式计算:
- 在已知n、f、fovY的情况下,我们能够计算出远近平面的高度,但还无法计算出远近平面的宽度,这是因为宽度相关纵向可见范围,我们还没有定义它。可以使用宽高比来描述纵向可见范围,其计算式如下。当然纵向的角度也是固定的,因此也可以使用fovX纵向可视角度,来描述纵向可见范围。
- 综上所述,透视投影的可视体是一个截头四棱锥,我们使用n、f、fovY、aspect ratio即可完成对它的描述。
- 透视投影需要先将其可视体压缩为矩形,然后再进行正交投影。将透视投影的可视体压缩为矩形的变换矩形如下图所示:
- 注意上图中矩阵第四行为0010,它将导致结果向量的w变为原z值。
- 通过上述的模型变换、观察变换、投影变换,顶点乘以MVP矩阵后就位于裁剪空间中。顶点着色器的输出是在裁剪空间,GPU需要手动进行透视除法将顶点转移到投影空间,此时顶点坐标为标准化设备坐标(NDC)。
- 虽然这些矩阵变换是从一个可视体变换到另一个,但它们仍被称为投影,因为在完成显示后,Z坐标将不会再保持于得到的投影图片中。通过这样的投影方法,就将模型从三维空间投影到了二维的空间中。
- 总结:投影阶段就是将模型从三维空间投射到二维空间的过程。
(4)裁剪
- 只有当图元完全或部分存在于视体(也就是上文正交投影后得到的单位立方体)内部时,才需要将其发送到光栅化阶段,而光栅化阶段则负责将这些图元在屏幕上绘制出来。
- 一个图元相对视体的位置存在三种情况:
- 经过MVP变换后,顶点就位于裁剪空间中,将在裁剪阶段完成以下操作:
- 总结:裁剪阶段会剔除完全在可视体之外的图元,并对部分在可视体之外的图元进行裁剪,产生新的图元,抛弃旧的图元。
(5)屏幕映射
- 进入屏幕映射阶段的图元都是位于可视体内部的。
- 从顶点着色器到屏幕坐标系再到像素着色器的过程如下图(来源知乎-PZB的图形学空间)所示:
- 通过视口变换即可从NDC空间转换到屏幕坐标系空间。变换方法如下图所示:
- 像素着色器的输入如下:
- 屏幕映射阶段的主要目的,是将之前步骤得到的坐标映射到对应的屏幕坐标系上。
2.3 光栅化阶段
- 光栅化阶段,会将处于屏幕空间的二维顶点(所有顶点都包含Z值即深度值,及各种相关的着色信息)转换为屏幕上的像素。
- 该阶段分为以下功能阶段:
(1)三角形设定
- 三角形设定阶段主要用来计算三角形表面的差异和三角形表面的其他相关数据。该数据主要由几何阶段处理的各种着色数据的插值操作所用,该过程在专门为其设计的硬件上执行。
(2)三角形遍历
- 在此阶段将进行逐像素检查操作,检查该像素处的像素中心是否由三角形覆盖,而对于有三角形部分重合的像素,将在其重合部分生成片段。
- 找到哪些采样点或像素在三角形中的过程通常叫三角形遍历,每个三角形片段的属性均由三个三角形顶点的数据插值而生成。这些属性包括片段的深度,以及来自几何阶段的着色数据。
(3)像素着色
- 所有逐像素的着色计算都在像素着色阶段进行,使用插值得来的着色数据作为输入,输出结
果为一种或多种将被传送到下一阶段的颜色信息。纹理贴图操作就是在这阶段进行的。 - 像素着色阶段是在可编程 GPU 内执行的,在这一阶段有大量的技术可以使用,其中最常见,
最重要的技术之一就是纹理贴图(Texturing)。 - 总结:像素着色阶段的主要目的是计算所有需逐像素操作的过程。
(4)融合
- 每个像素的信息都储存在颜色缓冲器中,而颜色缓冲器是一个颜色的矩阵列(每种颜色包含
红、绿、蓝三个分量)。融合阶段的主要任务是合成当前储存于缓冲器中的由之前的像素着
色阶段产生的片段颜色。不像其它着色阶段,通常运行该阶段的 GPU 子单元并非完全可编程
的,但其高度可配置,可支持多种特效。 - 当图元通过光栅化阶段之后,从相机视点处看到的东西就可以在荧幕上显示出来。为了避
免观察者体验到对图元进行处理并发送到屏幕的过程,图形系统一般使用了双缓冲(double
buffering)机制,这意味着屏幕绘制是在一个后置缓冲器(backbuffer)中以离屏的方式进行
的。一旦屏幕已在后置缓冲器中绘制,后置缓冲器中的内容就不断与已经在屏幕上显示过的
前置缓冲器中的内容进行交换。注意,只有当不影响显示的时候,才进行交换。