概述
- 渲染管线 以 并行 的方式执行,每个 阶段 依赖于 前一个阶段 的结果。
- 渲染管线 的基础结构由 四个阶段 组成:应用阶段、几何处理阶段、光栅化阶段 和 像素处理阶段。
应用阶段 (Application Stage)
- 应用阶段 是 由应用程序驱动 的,因此通常在 通用 CPU 上以软件形式实现。
传统上, 在 CPU 上执行的任务包括 碰撞检测、全局加速算法、动画、物理模拟,以及依应用类型而定的其他多种任务。
一个 应用阶段算法 或 设置 可以有效减少要渲染的 三角形 数量。 -
一些 应用阶段 工作可以由 GPU 完成,使用一种称为 计算着色器 (compute shader) 的 单独模式。
- 在应用阶段结束时,要渲染的几何体 被传送到 几何处理阶段。
- 这些几何体一般为 点,线 和 三角形, 被为 渲染图元 (rendering primitive) 。
这是 应用阶段 的 最重要任务。
几何处理阶段 (Geometry Processing Stage)
-
几何处理阶段 处理 变换、投影 和所有其他类型的 几何处理 任务。
该阶段计算 要绘制什么,如何绘制,以及在哪里绘制。几何阶段通常在 图形处理单元 (GPU) 上执行。 -
几何处理 阶段负责大部分的 逐三角形 和 逐顶点 操作。
该阶段进一步分为以下功能阶段:
顶点着色、投影、裁剪 和 屏幕映射。 -
顶点着色阶段 (Vertex Shading)
-
两个任务
- 顶点着色 的两个主要任务是,计算顶点的 位置 和 对希望作为 顶点输出数据 的任何内容求值,例如 法线 和 纹理坐标。
-
顶点着色器 (Vertex Shader) 名字的起源
- 传统上,对象的 着色 主要是通过将 光源 应用于每个 顶点的位置 和 法线,并仅在 顶点 上 存储 生成的颜色 来计算的。这些颜色随后在 三角形 上进行 插值。
因此,这个 可编程的顶点处理单元 被命名为 顶点着色器。
随着现代 GPU 的出现,部分或全部的着色任务改为 逐像素 进行 (在 像素着色阶段 进行),而顶点着色阶段变得更加通用,可能根本不进行任何着色计算。
顶点着色器现在是一个更通用的单元,专用于 设置与 逐顶点 相关联的数据, 例如 动画数据 。
- 传统上,对象的 着色 主要是通过将 光源 应用于每个 顶点的位置 和 法线,并仅在 顶点 上 存储 生成的颜色 来计算的。这些颜色随后在 三角形 上进行 插值。
-
坐标系统(Coordinate System)
- 我们首先描述如何计算 顶点位置 (vertex position),这是一组始终需要的 坐标 数据。
一个模型在渲染到屏幕的过程中,会被 转换 (transform) 到几个不同的 坐标系统 中。
单一模型可以与多个 模型变换 (model transform) 关联。这表示同一模型的多个副本(称为实例 (instrance) )在同一[[场景]]中具有不同的 位置 (position)、方向 和 大小。
- 我们首先描述如何计算 顶点位置 (vertex position),这是一组始终需要的 坐标 数据。
-
模型变换 (Model Transform)和世界空间 (World Space)
- 模型 的 顶点 和 法线 会被 模型变换 所转换。对象的 坐标 被称为 模型坐标 (model coordinates) 。
- 在 模型变换 应用于这些坐标之后,模型被认为位于 世界空间 中。
-
世界空间 是 唯一的,在模型用各自的 模型变换 转换后,所有模型都存在于这个空间 中。
-
视图变换 (View Transform) 和视图空间 (View Space)
- 只有 相机 看到的 模型 才会被 渲染。所以为了便于 投影 和 裁剪,相机 和所有 模型 都用 视图变换 进行 转换。
视图变换 的目的是将 相机 放在 原点,并对准它使其朝着 负z轴 的方向看,y轴 向上,x轴 向右。这个 坐标系 的所描述的空间 被称为 视图空间 。
- 只有 相机 看到的 模型 才会被 渲染。所以为了便于 投影 和 裁剪,相机 和所有 模型 都用 视图变换 进行 转换。
-
顶点着色的第二种输出类型
- 对于构造一个逼真的场景来说,只绘制模型的 形状 和 位置 是不够的,还需要它们的外观。还必须对它们的 外观 进行建模。 这包括每个对象的 材质 ,以及任何照射在对象上的光源 的效果。
- 确定 光 对 材质 的效果的操作称为 着色。它涉及在对象的不同 点 上计算 着色方程 (shading equation)。通常,一些计算是在 几何处理 阶段对模型的 顶点 进行的,其他可能在 逐像素处理 阶段进行。
每个 顶点 上可以 存储 多种 材质数据,如点的 位置、法线、颜色,或任何其他需要评估 着色方程 的 数值信息。 - 顶点着色结果 会被发送到 光栅化 和 像素处理阶段,以进行 插值 并用于计算 表面的着色。
-
-
可选的顶点处理阶段
-
每个 管线 都有刚才描述的 顶点处理阶段 。一旦完成这个处理,还有一些 可选阶段 可以在 GPU 上进行,按 顺序 为:曲面细分、几何着色 和 流输出。
-
细分阶段(Tessellation Stage)
- 通过细分,可以用适当数量的 三角形 生成 曲面 。
- 顶点可以用于描述点、线、三角形 等 图元 ,也可以描述曲面,比如球体 。这些表面可以由一组 贴片(patch) 来指定,每个贴片 由一组顶点 组成。
细分阶段本身由一系列子阶段组成——外壳着色器(Hull Shader)、细分器(Tessellator)和域着色器(Domain Shader)——这些子阶段将这些 贴片顶点集 转换为(通常是更多的)顶点集,然后用于生成新的三角形集。
场景的摄像机可以用于确定生成多少个三角形:当相机接近时生成很多贴片,当相机远离时生成较少。
-
几何着色阶段(Geometry Shading Stage)
- 几何着色器 类似于 细分着色器,它接受各种类型的图元,并可以产生新的顶点。
但这是一个更简单的阶段,因为这种创建是有限的,并且输出图元的类型也受到更多限制。
几何着色器有几个用途,其中最流行的之一是粒子生成,例如模拟烟花爆炸。
- 几何着色器 类似于 细分着色器,它接受各种类型的图元,并可以产生新的顶点。
-
流输出阶段(Stream Output Stage)
- 流输出阶段 让我们能够使用 GPU 作为几何引擎。与其将处理过的顶点发送到管线的其余部分以呈现到屏幕上,此时我们可以选择性地将这些输出到数组以进行进一步处理。这些数据可以由 CPU 或 GPU 自身在 后续传递 中使用。这个阶段同样通常用于粒子模拟,比如烟花。
-
这些 可选的顶点处理阶段 的使用取决于 硬件 的功能(并非所有 GPU 都有这些阶段)以及程序的需求。它们是相互 独立 的,并且不常用。
-
无论使用哪个可选阶段,如果我们继续沿着 管线,我们会有一组带有 齐次坐标 的 顶点,这些顶点坐标会被检查是否能被相机看到。
-
-
投影阶段(Projection Stage)
- 作为 顶点着色 的一部分,渲染系统 执行 投影 然后进行 裁剪,将 视域 (view volume) 转换为一个 单位立方体,其 极点 在
(
−
1
,
−
1
,
−
1
)
(−1, −1, −1)
(−1,−1,−1) 和
(
1
,
1
,
1
)
(1, 1, 1)
(1,1,1)。
-
不同的 范围 定义相同的 体积 也是允许的,例如, 0 ≤ z ≤ 1 0 ≤ z ≤ 1 0≤z≤1。
-
- 这个 单位立方体 被称为 规范视图体。
投影阶段 在 GPU 上是由 顶点着色器 完成的。有两种常用的 投影方法,即 正交投影 和 透视投影。 -
正交投影(Orthographic Projection)
- 正交投影 是平行投影的一种。 在建筑领域等特定领域,还有其他几种平行投影方式,如斜投影(oblique projection) 和 轴测投影(axonometric projections)。
- 正交观察 的视域 通常是一个 长方体盒子 ,而 正交投影 将这个 视域 转换为 单位立方体。正交投影 的主要特点是 平行线 在 变换 后仍然保持 平行 。这种 变换 是 平移 和 缩放 的组合。
-
透视投影(Perspective Projection)
- 在这种 投影 中,物体离摄像机越远,在投影后显得越小。
此外,平行线可能在地平线处 汇聚 。因此,透视变换 模仿了我们对物体大小的感知。
从几何角度看,视域 被称为平截头体(frustum),其底部是长方形。平截头体也会被转换为单位立方体。
- 在这种 投影 中,物体离摄像机越远,在投影后显得越小。
- 在任一 变换 之后,模型 被认为处于 裁剪坐标 中。GPU 的顶点着色器 必须始终输出这种类型的 坐标,以便下一个功能阶段,即 裁剪,能够正确工作。
- 尽管这些 矩阵 将一个 volume 转换为另一个 volume,但它们被称为 投影,这是因为在显示后,z坐标 不会存储在生成的图像中,而是存储在一个 z缓冲区 中。
这样,模型 从 三维 被 投影 到 二维。
- 作为 顶点着色 的一部分,渲染系统 执行 投影 然后进行 裁剪,将 视域 (view volume) 转换为一个 单位立方体,其 极点 在
(
−
1
,
−
1
,
−
1
)
(−1, −1, −1)
(−1,−1,−1) 和
(
1
,
1
,
1
)
(1, 1, 1)
(1,1,1)。
-
裁剪阶段(Clipping Stage)
- 只有完全或部分在 视域 内的 图元 需要传递到 光栅化阶段 。
需要被裁剪 的 图元 是那些部分在视域内的。例如,一条线有一个顶点在视图体积外面和一个在里面,应当被剪裁,以便外面的顶点被一个新的顶点替代,该新顶点位于该线和视图体积的交点。
投影阶段 中 投影变换 的使用意味着图元 是沿着 单位立方体(unit cube) 进行裁剪。
- 除了视域 的
6
6
6 个裁剪平面(clipping planes)外,用户可以定义额外的裁剪平面 来切割物体。下图展示了这种称为*切割(sectioning)*的可视化类型。
- 裁剪 步骤使用 投影产生的4维齐次坐标系(4-value homogeneous coordinates)来进行裁剪。在透视空间(perspective space) 中,数值通常不是 线性插值(interpolate linearly) 的。第四维坐标 是必要的,以便在使用透视投影时正确地 插值 和 裁剪 数据。
最后,执行透视除法(perspective division),将生成的三角形的位置放置到 三维标准化设备坐标系(three-dimensional normalized device coordinates) 中。 - 几何阶段的最后一步是从这个 NDC 空间 转换到 窗口坐标(window coordinates)。
- 只有完全或部分在 视域 内的 图元 需要传递到 光栅化阶段 。
-
屏幕映射阶段(Screen Mapping Stage)
-
屏幕坐标系(Screen Coordinates)
- 只有裁剪后 留下的 图元 传递到 屏幕映射阶段,而且在进入这一阶段时,坐标仍然是 三维 的。
每个图元的 x x x坐标 和 y y y坐标 被 转换 以形成 屏幕坐标系。
- 只有裁剪后 留下的 图元 传递到 屏幕映射阶段,而且在进入这一阶段时,坐标仍然是 三维 的。
-
窗口坐标系(Window Coordinates)
- 屏幕坐标系 连同 z z z坐标 也称为窗口坐标系。
-
屏幕坐标系到窗口坐标系
- 假设场景应渲染到一个窗口,该窗口的最小角(minimum corner) 在 ( x 1 , y 1 ) (x_1, y_1) (x1,y1),最大角(maximum corner) 在 ( x 2 , y 2 ) (x_2, y_2) (x2,y2) ,其中 x 1 < x 2 x_1 < x_2 x1<x2 和 y 1 < y 2 y_1 < y_2 y1<y2 。屏幕映射 是对原本的 x x x坐标 和 y y y坐标 进行一个 平移 后跟一个 缩放 操作。新的 x x x坐标 和 y y y坐标 被称为屏幕坐标。
- z z z坐标(对于 OpenGL 是 [ − 1 , + 1 ] [−1, +1] [−1,+1],对于 DirectX 是 [ 0 , 1 ] [0, 1] [0,1])也映射到 [ z 1 , z 2 ] [z_1, z_2] [z1,z2],其中 z 1 = 0 z_1 = 0 z1=0和 z 2 = 1 z_2 = 1 z2=1作为默认值。这些可以通过API进行更改。
- 这些 窗口坐标 连同这个重新映射的 z z z 值被传递到 光栅化阶段 。
-
整数与浮点数值如何与像素关联
- 给定一个 水平像素数组 并使用 笛卡尔坐标 ,最左侧像素的左边缘在浮点
x
x
x 坐标中是
0.0
0.0
0.0。这个像素的中心位于
0.5
0.5
0.5 。因此,一个像素范围
[
0
,
9
]
[0, 9]
[0,9] 覆盖了从
[
0.0
,
10.0
)
[0.0, 10.0)
[0.0,10.0) 的跨度。转换很简单
d = floor ( c ) d=\operatorname{floor}(c) d=floor(c)
c = d + 0.5 c = d+0.5 c=d+0.5
其中 d d d 是像素的离散整数索引,而 c c c 是像素内的连续浮点值。
- 给定一个 水平像素数组 并使用 笛卡尔坐标 ,最左侧像素的左边缘在浮点
x
x
x 坐标中是
0.0
0.0
0.0。这个像素的中心位于
0.5
0.5
0.5 。因此,一个像素范围
[
0
,
9
]
[0, 9]
[0,9] 覆盖了从
[
0.0
,
10.0
)
[0.0, 10.0)
[0.0,10.0) 的跨度。转换很简单
-
OpenGL和DirectX中的窗口坐标
- 所有 API 都是像素坐标值从左到右增加。其中 OpenGL 始终是笛卡尔坐标系,将左下角视为最小值元素 ( 0 , 0 ) (0,0) (0,0),而 DirectX 有时根据上下文将左上角定义为最小值元素。
-
光栅化阶段(Rasterization Stage)
-
给定 变换 和 投影 后的 顶点 及其相关的着色数据(全部来自 几何处理 )情况下,光栅化阶段 的目标是找到在 图元 内部的所有像素。- 这一阶段通常以每 三个顶点 作为输入,形成一个三角形,并找到被认为在 三角形 内部的所有 像素 ,然后将它们转送到下一个阶段。我们称这个过程为光栅化,它完全在GPU上进行。它分为两个功能性的子阶段:三角形设置(Triangle Setup) 和 三角形遍历(Triangle Traversal)。
-
三角形装配(Triangle Setup)
- 在此阶段,三角形 的微分、边方程 以及其他数据被 计算 出来。这些数据可能用于 三角形遍历 ,以及各种 着色数据 的 插值 。
- 此任务使用的是固定功能硬件。
-
三角形遍历(Triangle Traversal)
- 光栅化就是从 屏幕空间 中的 二维顶点(每个顶点都有一个z值和与每个顶点相关联的各种着色信息)转换为屏幕上的像素。
- 光栅化 也可以被认为是 几何处理 和 像素处理 之间的一个同步点。
-
点采样(Point Sampling)
- 三角形 是否被认为与 像素 有 重叠 ,取决于如何设置 GPU管线 。
例如,你可能使用 点采样 来确定 “内部性” 。- 最简单的情况使用 像素 中心的 单点采样,即如果该中心点在三角形内部,那么相应的像素也被认为在三角形内部。
- 还可以使用*超采样(supersampling)或多重采样抗锯齿(multisampling antialiasing)*技术,让每个像素使用多个样本。
- 另一种方式是使用 保守光栅化(Conservative Rasterization),定义是如果像素的一部分与三角形重叠,则像素便判定为在三角形内。
- 三角形 是否被认为与 像素 有 重叠 ,取决于如何设置 GPU管线 。
像素处理阶段(Pixel Processing Stage)
- 像素处理阶段执行 逐像素 的程序以确定其颜色(color),并可能进行深度测试以查看它是否可见。该阶段完全在 GPU 上处理。
- 像素处理阶段分为 像素着色阶段 和 合并阶段 。
-
像素着色阶段(Pixel Shading Stage)
- 在这里执行所有与 逐像素 相关的着色计算,使用插值的着色数据作为输入。最终结果是一个或多个要传递到下一阶段的颜色值。
- 像素着色阶段 由可编程的 GPU 核 执行。为此,需向其提供一个用于像素着色器(pixel shader)的程序,该程序可以包含任何所需的计算。在这里可以采用各种各样的技术,其中最重要的之一是纹理映射。最简单的情况下,最终输出是每个片段的颜色值,这些将传递到下一个子阶段。
-
合并阶段(Merging Stage)
-
每个像素的信息存储在颜色缓冲区 (color buffer)中,该缓冲区是颜色值的矩形数组。
合并阶段 的责任是将 像素着色阶段 生成的片段颜色值与当前存储在缓冲区中的颜色值相结合。
与像素着色阶段不同,执行此阶段的GPU子单元通常不是完全可编程的。然而,它是高度可配置的,能够实现各种效果。 -
可见性和 z-buffer
- 合并阶段 还负责解决可见性。当整个场景都被渲染时,颜色缓冲区应该包含场景中从摄像机的视点可见的图元的颜色。对于大多数甚至所有图形硬件,这是通过 z-buufer 算法完成的。
-
模板缓冲(Stencil Buffer)
- 模板缓冲是一个离屏缓冲区,用于记录渲染图元的位置。它通常为每个像素的 8 8 8 位。可以使用多种函数把图元渲染到模板缓冲区中,并且缓冲区的内容之后可以用于控制将其渲染到颜色缓冲区和z-buffer。
-
举个例子,假设一个圆圈已经被绘制到模板缓冲区中。这可以与一个运算符结合使用,该运算符仅允许在圆圈存在的地方将后续图元渲染到颜色缓冲区中。所以模板缓冲区可用于生成一些特殊效果。
-
所有这些在管线末端的函数称为光栅操作或混合操作。
如前所述,混合(blend)通常是使用可配置的 API,而不是完全可编程的。然而,一些API支持 raster order views,也称为像素着色器排序(pixel shader ordering),这种操作启用了可编程混合功能。 -
帧缓冲(Frame Buffer)
-
参考
Real-Time-Rendering Forth Edition