Category:Higher Mathematics & Computer Graphics Application
正文
8 图形管道
前面几章已经建立了数学脚手架,我们需要看看第二种主要的渲染方法:将对象一个一个地绘制到屏幕上,或者对象顺序渲染。与在光线追踪中依次考虑每个像素并找到影响其颜色的对象不同,我们现在将依次考虑每个几何对象并找到可能对其产生影响的像素。寻找图像中所有被几何图元占据的像素的过程称为光栅化,因此对象顺序渲染也可以称为光栅化渲染。所需的操作序列,从对象开始,到更新图像中的像素结束,称为图形管道。
任何图形系统都有一种或多种可以直接处理的“原始对象”类型,并且更复杂的对象被转换为这些“原始物质”。三角形是最常用的原始。
对象顺序渲染因其效率而获得了巨大的成功。
对于大型场景,数据访问模式的管理对性能至关重要,与重复搜索场景以检索着色每个像素所需的对象相比,一次遍历场景访问每个几何位具有显着优势。
本章的标题表明只有一种方法可以进行对象顺序渲染。当然这不是真的——两个完全不同的图形管道示例具有非常不同的目标,一个是用于通过 OpenGL 和 Direct3D 等 API 支持交互式渲染的硬件管道,另一个是用于电影制作的软件管道,支持诸如 RenderMan 之类的 API。硬件管道必须运行得足够快,才能对游戏、可视化和用户界面做出实时反应。生产流程必须尽可能渲染最高质量的动画和视觉效果,并扩展到巨大的场景,但这可能需要更多时间。尽管这些不同的目标导致了不同的设计决策,但大多数(如果不是全部)流水线共享了大量的流水线,本章试图关注这些共同的基础知识,错误地倾向于更紧密地遵循硬件流水线。
基于栅格化的系统也称为扫描线渲染器。
需要在对象订单渲染中完成的工作可以组织到栅格化本身的任务中,在栅格化之前完成的几何操作以及在栅格化后对像素进行的操作。
如前两章所述,最常见的几何操作是应用矩阵变换,以绘制定义从对象空间到屏幕空间的几何形状的点,以便对栅格的输入以像素坐标或屏幕空间表示。最常见的Pixelwise操作是隐藏的表面拆卸,它在靠近观众的表面上排列,以远离观众的表面出现。在每个阶段也可以包括许多其他操作,从而使用相同的一般过程实现了广泛的不同渲染效果。
为了本章,我们将以四个阶段的范围讨论图形管道(图8.1)。从交互式应用程序或场景描述文件中馈入管道中的几何对象,并且它们始终由一组顶点描述。这些顶点在顶点处理阶段进行操作,然后使用这些顶点的原语将发送到栅格化阶段。Rasterizer将每个原始性分解为多个片段,每个像素都被原始的片段覆盖。片段在片段处理阶段进行处理,然后将与每个像素相对应的各种片段合并在片段混合阶段。
我们将首先讨论栅格化,然后通过一系列示例来说明几何和像素阶段的目的。
8.1栅格化
光栅化是对象顺序图形的核心操作,光栅化器是任何图形管道的核心。对于传入的每个图元,光栅化器有两个工作:枚举图元覆盖的像素,并在图元中插入称为属性的值——这些属性的用途将在后面的示例中清楚。光栅器的输出是一组片段,一个片段对应于图元覆盖的每个像素。每个片段“生活”在一个特定的像素上,并带有自己的一组属性值。
在本章中,我们将介绍光栅化,以期使用它来渲染 3D 场景。相同的光栅化方法也用于在 2D 中绘制线条和形状——尽管使用“幕后”的 3D 图形系统来进行所有 2D 绘图变得越来越普遍。
8.1.1线图
大多数图形软件包都包含一个行绘图命令,该命令在屏幕固定中采用两个端点(请参见图3.10),并在它们之间绘制一条线。对于考试,终点(1,1)和(3,2)的呼吁将打开像素(1,1)和(3,2),并填充它们之间的一个像素。对于通用屏幕坐标端点(x0,y0)和(x1,y1),例程应绘制一些“合理”的像素集,以近似它们之间的界线。绘制此类行是基于行方程,我们有两种类型的方程式可供选择:隐式和参数。本节描述了使用隐式线的方法
即使我们经常使用整数值端点作为示例,正确支持任意端点也很重要。
使用隐式线方程画线
使用隐式方程画线的最常用方法是中点算法(Pitteway (1967);van Aken 和 Novak (1985))。中点算法最终绘制出与 Bresenham 算法相同的线条 (Bresenham, 1965),但它更简单一些。
首先要做的是找到第 2.5.2 节中讨论的直线的隐式方程:
即使我们经常使用整数值端点作为示例,正确支持任意端点也很重要。
我们假设x0≤x1。如果那不是真的,我们交换了这些要点,以便它是真的。
线的斜率m由
以下讨论假设m∈(0,1]。可以得出M∈(-∞,-1],m∈(-1,0]和m∈(1,∞),可以得出类似的讨论。四个情况覆盖所有可能性。
对于m∈(0,1]的情况,比“ rise”更多的“运行”,即,该线在x中的移动速度要比y中更快。如果我们有y轴向下指向的API,我们可能会关心这是否使过程变得更加困难,但实际上,我们可以忽略该细节。我们可以忽略“ UP”和“ Down”的几何概念,因为两种情况对于代数完全相同。谨慎。谨慎。读者可以确认所产生的算法适用于y轴向下情况。中点算法的关键假设是,我们绘制可能没有间隙的最细线。两个像素之间的对角线连接不被认为是差距。
随着线从左侧点向右发展,只有两种可能性:在与左侧绘制的像素相同的高度上绘制一个像素,或者将像素绘制一个像素更高。端点之间的每一像素列中总会有一个像素。零将暗示差距,两个线太厚了。
对于我们正在考虑的情况,同一行中可能有两个像素。该线比垂直方向更水平,因此有时会正确,有时会向上行驶。
该概念如图8.2所示,其中显示了三条“合理”线,每个线路都在水平方向上比垂直方向前进。
M∈(0,1]的中点算法首先建立最左侧像素的最左侧像素和列编号(x值),然后循环地建立每个像素的行(y-value)。算法是:
请注意,X和Y是整数。用文字说:“继续从左到右绘制像素,有时在y方向上向上绘制像素。”关键是建立有效的方法来在IF语句中做出决定。
做出选择的有效方法是查看两个潜在像素中心之间线的中点。更具体地说,刚刚绘制的像素是像素(x,y),其实际屏幕坐标为(x,y)。要在右侧吸引的候选像素是像素(x 1,y)和(x 1,y 1)。两个候选像素中心之间的中点为(x 1,y 0.5)。如果线通过此中点以下,我们绘制底部像素,否则我们绘制顶部像素(图8.3)。
为了确定该线是以上还是以下(x+1,y+0.5),我们在方程式(8.1)中评估f(x,y+0.5)。从第2.5.1节中召回f(x,y)= 0对于线上的点(x,y),f(x,y)> 0对于线的一侧点,f(x,y)<0对于线的另一侧。因为-f(x,y)= 0和f(x,y)= 0都是该行的完全好方程式,因此尚不清楚f(x,y)是否为正表示(x,y)是在线上,还是下面。但是,我们可以弄清楚。等式(8.1)中的关键项是y项(x1 -x0)y。请注意,(x1 -x0)绝对是正面的,因为x1> x0。这意味着随着y的增加,术语(x1 -x0)y变大(即更正或更低的负)。因此,情况 f(x, ∞) 肯定是正的,并且肯定在线上方,意味着在线上方的点都是正的。另一种看待它的方式是梯度向量的 y 分量是正的。所以在这条线上,y 可以任意增加,f(x, y) 必须是正数。这意味着我们可以通过填写 if 语句使我们的代码更具体:
以上代码将对适当的斜率线(即零和一个之间)的线路很好地工作。读者可以解决其他三种情况,这些情况仅在小细节上有所不同。
如果需要更高的效率,使用增量方法会有所帮助。增量方法试图通过重用上一步的计算来使循环更有效。在提出的中点算法中,主要计算是对 f(x 1, y 0.5) 的评估。请注意,在循环内部,在第一次迭代之后,我们已经评估了 f(x - 1, y 0.5) 或 f(x - 1, y - 0.5)(图 8.4)。还要注意这种关系:
这允许我们编写代码的增量版本:
此代码应该运行得更快,因为与非增量版本相比,它几乎没有额外的设置成本(增量算法并不总是如此),但它可能会累积更多的数值错误,因为 f(x, y 0.5) 的评估可能是由许多用于长线的添加组成。然而,鉴于线条很少超过几千像素,这样的错误不太可能是严重的。
通过将 (x1 -x0) (y0 -y1) 和 (y0 -y1) 存储为变量,可以实现稍长的设置成本,但更快的循环执行。我们可能希望一个好的编译器能为我们做到这一点,但如果代码很关键,检查编译结果以确保是明智的。