Chapter 2 – The Graphics Rendering Pipeline 图形渲染管线

2.3 几何处理

GPU端的几何处理阶段将会负责大多数基于三角形和基于顶点的操作。几何处理阶段将被进一步划分为以下几个功能性阶段:顶点着色(vertex shading),投影(projection),剪裁(clipping)以及屏幕映射(screen mapping),如下图所示。

2.3.1 顶点着色

顶点着色的主要任务有两个,计算每一个顶点的位置并且设置输出顶点的数据结果,例如,包含法线向量与贴图坐标等等。传统意义上,我们将光线运用于每个顶点的位置,法线与顶点本身存储的颜色以计算出物体的阴影。计算出的颜色信息之后被插值到三角形。基于这个原因,这个可编程的顶点处理模块被称为顶点着色器。随着现代GPU的出现,部分的阴影计算或者说全部的阴影计算都变成基于每一个像素,顶点着色阶段可能并不会计算阴影,不过这也取决于程序员的想法。现在顶点着色器更多地被用于设置每个顶点的数据。例如,顶点着色器可以让一个物体产生动画效果。

首先,我们将学习顶点位置是如何计算的,当然一组坐标是必不可少的。为了让物体出现在屏幕上,其会被转换至多个不同的空间或者说不同的坐标系。最初,模型位于其自己的模型空间(model space),这意味顶点未进行任何转换。每个模型都与一个模型转换矩阵(model transform)相关联,这样模型就能被放置到某个位置并且带有朝向。而一个模型可以与多个模型转换矩阵相关联。这让我们能够使同一个模型拥有多个拷贝(instance),且每个拷贝都拥有不同的位置,朝向与尺寸,这一操作也避免了我们去拷贝模型的几何体。

模型的顶点与法线通过模型转换矩阵被转换。而物体的坐标被称为模型坐标(model coordinates)。在模型转行矩阵运用到这些坐标之后,模型就位于世界坐标(world coordinate)或者说位于世界空间(world space)。世界空间是一个特殊的概念,在所有的模型都基于其对应的模型转行矩阵进行转行之后,那么所有的模型都位于同一空间。

正如我们之前提到的,只有camera看得见的模型才会被绘制。所以,camera在世界空间中也有位置与朝向。为了实现投影与剪裁,camera以及所有的模型都会运用观察者转换矩阵(view transform)。这一转换矩阵的目的是将camera置于原点并让其拥有朝向,其视角的方向为z轴的负方向,y轴指向上方,x轴指向右方。本书中,我们使用z轴的负方向;有些书会使用z轴的正方向。而运用观察者转换矩阵之后实际的位置与方向则取决于我们所使用的图形学API。因此这个矩阵所描述的空间被称为camera space,或者说view space / eye space。模型转换矩阵和观察者转换矩阵都是4✖4的矩阵。

之后,我们将学习顶点着色所输出的第二种信息。为了呈现更为真实的画面,我们不单单绘制物体的形状与位置,还需要绘制它们的外观表现。这就包含了每个物体的材质以及光源对于该物体的影响。材质与光线可以通过各种方法进行模拟,从最简单的颜色到复杂的物理外观表现。

模拟光线在材质上所生成的效果就是所谓的shading(阴影?着色?这到底肿么翻译)。其包含了计算物体表面不同点的着色等式。在几何处理阶段处理模型的顶点时,我们会进行这些计算,但还有一部分计算会放在像素处理阶段。不同的材质数据可以存储在每个顶点中,例如,顶点的位置,法线,颜色或者其他用于着色计算的信息。顶点着色的结果(其可以是颜色,向量,贴图坐标或者其他着色数据)会被传输至光栅化阶段与像素处理阶段以进行插值并用于计算物体表面的着色。

作为顶点着色的一部分,渲染系统会进行投影之后是剪裁,这一操作会将视野空间转换至一个单位立方体,其极点为(-1,-1,-1)与(1,1,1)。不同的API会定义不同的范围,但都代表同一个视野空间,例如,DX会将z的范围定义为0≤z≤1.这个单位立方体被称为标准视野空间(canonical view volume)。投影将被先行处理,其由GPU端的顶点着色器负责。一般我们会使用两种投影方式,一种为正交投影orthographic(平行投影),另一种为透视投影perspective。如下图所示。

事实上,正交投影只是平行投影的一种。在建筑领域还会使用其他的平行投影。

需要注意的是,我们一般使用一个矩阵来表示投影,因此我们会将其与剩下的几何转行进行串联。

正交投影的视野空间一般是一个长方体,而正交投影将会把这个视野空间转换至单位立方体中。正交投影的主要特点就是,平行线在投影之后仍然互相平行。这个转换由位移与缩放组成。

透视投影相较于正交投影更复杂一些。在这一类型的投影中,距离camera更远的物体在投影之后会变得更小。此外,平行线将会相交于某个尽头。因此透视投影转换模拟了我们的视觉感受。几何意义上来说,其视野空间被称为椎体,一个被切去顶部的以长方形作为基础的金字塔。这个椎体也会被转换至单位立方体。正交投影转换与透视投影转换都由4✖4的矩阵构成,在经过任一转换之后,我们便认为模型位于剪裁坐标。事实上,这是齐次坐标(我们将在第四章中学习)。GPU的顶点着色器必须输出这一类型的坐标,这样之后的剪裁才能正常工作。

虽然这些矩阵将一个空间转换至另一个,但他们仍然被称为投影,这是因为在出现在显示屏幕上时,z轴的坐标并不会存储在图像中,而是存储在z buffer中。这样,模型就由三维空间被投影到了二维空间。

2.3.2 可选顶点处理

每一个管线都拥有我们之前描述的顶点处理阶段。当这一处理完成时,GPU端还有一些可选择的阶段,其顺序如下:tessellation,geometry shading以及stream output。是否使用这些阶段则取决与硬件的性能——也就是说并不是所有的GPU都能使用这些阶段——也取决于程序员的意愿。这些阶段是互相独立的,一般我们并不会使用它们。在第三章中,我们会学习更多这一方面的知识。

第一个可选阶段是tessellation。假设你有一个球型物体。如果你只使用一组三角形来表示这个物体,那么你会碰到两个问题,渲染的质量或者性能的优化。如果我们使用的三角形太少,在5米之外,你的球型物体可能看上去还不错,如果靠近看,那么球型物体的三角形轮廓就会显而易见。如果你使用更多的三角形以改善模型的质量,那么当球距离camera很远只会覆盖屏幕中的一小部分像素时,我们就是在浪费时间与内存空间去处理这些几何体。有了tessellation,一个带有弧度的平面就可以由一定数量的三角形所构成。

到现在为止,我们已经谈论了一些关于三角形的话题,但是我们所说的管线只是在处理顶点。而顶点可以用于表示点,线,三角形或者其他物体。顶点也能被用于表示带有弧度的平面,例如球体。这一类型的平面可以由一组patch表示,而每一个patch都由一组顶点所组成。tessellation阶段自身也被分为几个阶段——hull shader,tessellator,domain shader——这几个阶段将会把patch中的顶点转换为更多的顶点(一般来说是更多)以形成新的三角形。而场景中的camera被用于决定生成多少三角形:离camera更近那么数量更多,更远那么数量更少。

下一个可选阶段为geometry shader。这个着色器早于tessellation着色器出现,所以更多的GPU可以支持这一着色器。它的作用类似于tessellation着色器,其接收不同的图元并生成新的顶点。相较于tessellation,这一阶段更为简单,因为它所生成的新顶点有着数量限制,同时输出的图元类型也有更多的限制。geometry着色器有着不同的用处,不过主要用于粒子的生成。例如,我们模拟一个烟火的爆炸。每一个火球都由一个点,或者说一个顶点表示。那么geometry着色器可以将每个顶点转换为一个正方形(由两个三角形组成),且该正方形朝向camera并覆盖一些像素点。

最后一个可选阶段被称为stream output。在这个阶段中,我们可以将GPU作为几何引擎。正常的渲染管线流程,是将顶点传输至之后的阶段并最后绘制到屏幕上。但是,在这个可选阶段,我们可以将顶点输出为一个数组用于其他处理。这些数据可以由CPU使用,也可以由GPU在之后的管线中使用。这一阶段一般也用于粒子模拟,例如我们上一段所举的烟火的粒子。

这三个阶段的运行顺序为——tessellation,geometry shading,stream output——每一个都是可以选的。无论这几个可选阶段是否被我们启用,如果我们继续管线的流程,那么我们的顶点必须位于齐次坐标,而这些坐标将被用于检测camera是否能看见这些顶点。

2.3.3 剪裁

只有完全或者部分位于视野空间的图元才会被传输至光栅化阶段(以及之后的像素处理阶段),之后被绘制到屏幕上。完全位于视野空间内的图元将被传输至下一个阶段。完全位于视野空间之外的图元并不会被传输至下一个阶段,因为他们不会被绘制到屏幕上。只有部分位于视野空间中的图元才需要剪裁。例如,线段的一个顶点位于视野内而另一个位于视野外,那么线段将为基于视野空间进行剪裁,因此位于视野之外的顶点将被位于线段与视野交点处的顶点所替代。对于投影矩阵的使用则意味着转换后的图元将给予单位立方体进行剪裁。而在剪裁之前进行视野空间的转换与投影的好处就是我们可以让剪裁“保持一致”;图元永远基于单位立方体进行剪裁。

剪裁的处理如下图所示。除了这6个视野空间的剪裁平面,我们还可以定义其他的剪裁平面用以分割物体。我们将在之后的章节再谈论这一话题。

剪裁阶段将使用投影之后的齐次坐标。在透视空间中坐标值并不会基于三角形进行线性插值。这时,我们就需要齐次坐标的第四个值w进行透视除法(perspective division),之后三角形三个顶点的位置才会位于三维的normalized device coordinates。正如我们在之前的小节所说的,这个坐标系中的坐标值的范围在(-1,-1,-1)到(1,1,1)之间。几何阶段的最后一个步骤就是将位于这个空间的点转换至窗口坐标。

2.3.4 屏幕映射

只有被剪裁后位于视野空间内的图元才会传输至屏幕映射阶段,当坐标点进入该阶段时仍是三维坐标。每个图元的x轴坐标和y轴坐标将被转换,之后形成所谓的屏幕坐标(screen coordinate)。而屏幕坐标与z轴坐标都被称为窗口坐标(window coordinate)。假设我们的场景需要被绘制到一个窗口中,窗口的最小角为(x1,y1),最大角为(x2,y2),其中x1<x2,y1<y2。而我们的屏幕映射其实是一个缩放的操作。新的x轴y轴的坐标就是所谓的屏幕坐标。z轴坐标(OpenGL中范围为[-1,1],DX中范围为[0,1])也会被映射至[z1,z2],z1与z2的默认值分别为0和1。之后窗口坐标与重新映射的z值将会被传输至光栅化阶段。屏幕映射的处理如下图所示。

之后就是将整型数或浮点数与像素(与贴图坐标)进行关联。假设有一组横向的像素且使用笛卡尔坐标系,如果使用浮点数坐标,那么最左侧的像素就是0.0。OpenGL以及DX10之后的DX版本都是使用该准则。该像素的中心则是0.5。所以如果像素的范围为[0,9],那么其跨度则为[0.0,10.0)。其表达式为:d=floor(c)c=d+0.5;其中,d表示像素的索引(整型数),而c表示像素内部的连续值。

所有的API中像素位置的值都是从左至右而增加,但是DX与OpenGL对于0代表顶部或是底部有着不同的定义。OpenGL遵循笛卡尔坐标系,将左下角作为最小值,然而DX有时将左上角作为最小值。例如,(0,0)在OpenGL中为最图像的左下角,而DX中则是左上角。在我们使用不同的API时,这一区别极为重要。

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据