渲染管线概述
渲染管线(Rendering Pipeline) 是 OpenGL 渲染对象时所经历的一系列步骤。本文档将提供管线各阶段的高层次描述。
管线流程
OpenGL 渲染管线在执行渲染操作时启动。渲染操作需要以下前置条件:
- 一个正确定义的 顶点数组对象(Vertex Array Object, VAO)
- 一个已链接的 程序对象(Program Object) 或 程序管线对象(Program Pipeline Object),为可编程管线阶段提供着色器
管线按以下顺序执行:
顶点处理(Vertex Processing)
- 顶点着色器处理每个顶点
- 可选的曲面细分阶段
- 可选的几何着色器处理
顶点后处理(Vertex Post-Processing)
- 变换反馈
- 图元装配
- 裁剪、透视除法和视口变换
光栅化(Rasterization)
- 扫描转换和图元参数插值,生成片段
片段处理(Fragment Processing)
- 片段着色器处理每个片段
逐样本操作(Per-Sample Operations)
- 裁剪测试、模板测试、深度测试、混合等
顶点规格化
顶点规格化(Vertex Specification)是应用程序设置有序顶点列表并发送到管线的阶段。这些顶点定义了 图元(Primitive) 的边界。
图元是基本的绘制形状,如三角形、线和点。顶点列表如何被解释为图元由后续阶段处理。
此阶段涉及的主要对象:
- 顶点数组对象(Vertex Array Objects, VAO):定义每个顶点包含的数据
- 顶点缓冲对象(Vertex Buffer Objects, VBO):存储实际的顶点数据
顶点数据由一系列 属性(Attributes) 组成。每个属性是下一阶段将要计算的一小部分数据。属性数据完全是任意的,其含义仅在顶点处理阶段被赋予。
顶点渲染
顶点数据规格化完成后,通过绘制命令将其渲染为图元。
顶点处理
从顶点渲染阶段获取的顶点在此开始处理。顶点处理阶段几乎都是可编程操作,允许用户自定义顶点处理方式。
顶点着色器
顶点着色器(Vertex Shader) 对每个顶点执行基本处理。它接收来自顶点渲染的属性输入,根据用户定义的程序将每个输入顶点转换为单个输出顶点。
顶点着色器可以定义用户输出,还有一个特殊输出表示顶点的最终位置。如果没有后续顶点处理阶段,顶点着色器需要将此位置设置为顶点的 裁剪空间(Clip-space) 位置。
后变换缓存优化
每个输入顶点必须映射到一个特定输出顶点。由于顶点着色器调用之间不能共享状态,输入属性到输出顶点数据的映射是 1:1 的。如果实现检测到正在处理一个之前处理过的顶点,可以使用存储在 后变换缓存(Post Transform Cache) 中的数据,无需重新运行顶点处理。
顶点着色器是必需的,不可省略。
曲面细分
图元可以通过两个着色器阶段和其间的固定功能细分器进行曲面细分。
- 曲面细分控制着色器(Tessellation Control Shader, TCS):确定应用于图元的细分量,并确保相邻细分图元之间的连通性
- 曲面细分评估着色器(Tessellation Evaluation Shader, TES):对固定功能细分过程生成的图元应用插值或其他操作
| 属性 | 值 |
|---|---|
| 核心版本 | 4.6 |
| 引入版本 | 4.0 |
| ARB 扩展 | ARB_tessellation_shader |
可选性
曲面细分是可选的。当 TES 处于活动状态时,曲面细分才被视为活动状态。TCS 是可选的,但只能与 TES 一起使用。
几何着色器
几何着色器(Geometry Shader) 是用户定义的程序,处理每个输入图元,返回零个或多个输出图元。
几何着色器的输入图元来自图元装配过程的子集。例如,如果发送一个三角形带作为单个图元,几何着色器将看到一系列三角形。
几何着色器可以:
- 移除图元
- 通过为单个输入输出多个图元来细分
- 修改顶点值
- 将图元转换为不同类型(如点图元变为三角形)
几何着色器是可选的。
顶点后处理
基于着色器的顶点处理后,顶点经历一系列固定功能的处理步骤。
变换反馈
几何着色器或图元装配的输出可以写入一系列为此设置的 缓冲对象(Buffer Objects)。这称为 变换反馈(Transform Feedback) 模式,允许用户通过顶点和几何着色器变换数据,然后保留该数据供后续使用。
图元装配
图元装配(Primitive Assembly) 是收集前一阶段输出的顶点数据并将其组合成图元序列的过程。
输出是有序的简单图元(线、点或三角形)序列。例如,如果输入是包含 12 个顶点的三角形带,则输出为 10 个三角形。
如果曲面细分或几何着色器处于活动状态,则在这些顶点处理阶段之前执行有限形式的图元装配。
管线中止
渲染管线可以在此阶段中止。这允许使用变换反馈操作,而无需实际渲染任何内容。
裁剪
图元随后被 裁剪(Clipping)。裁剪意味着位于观察体积边界上的图元被分割成多个图元,使得整个图元都位于体积内。
顶点位置通过 透视除法(Perspective Divide) 和 视口变换(Viewport Transform) 从裁剪空间转换到窗口空间。
背面剔除
三角形图元可以根据其在窗口空间中的朝向被剔除(即不渲染而丢弃)。这允许避免渲染背向观察者的三角形。
优化建议
对于封闭表面,背向观察者的三角形自然会被朝向用户的三角形覆盖,因此无需渲染它们。背面剔除是一种避免渲染此类图元的有效优化方式。
光栅化
到达此阶段的图元按其给定的顺序进行光栅化。光栅化图元的结果是一系列 片段(Fragments)。
片段是一组状态,用于计算输出帧缓冲区中像素(或多重采样时的样本)的最终数据。片段的状态包括:
- 屏幕空间中的位置
- 多重采样启用时的样本覆盖率
- 从前一顶点或几何着色器输出的任意数据列表
最后一组数据通过在图元顶点的数据值之间进行插值来计算。插值样式由输出这些值的着色器定义。
片段处理
来自光栅化阶段的每个片段数据由 片段着色器(Fragment Shader) 处理。片段着色器的输出是:
- 每个被写入的颜色缓冲区的颜色列表
- 深度值
- 模板值
片段着色器无法设置片段的模板数据,但可以控制颜色和深度值。
无片段着色器渲染
片段着色器是可选的。如果没有片段着色器,片段的深度(和模板)值获得其常规值,但所有颜色值是未定义的。这在仅渲染图元的默认深度信息到深度缓冲区时很有用,例如执行 遮挡查询(Occlusion Query) 测试时。
逐样本操作
片段着色器输出的片段数据经过一系列步骤处理。
剔除测试
首先是一系列剔除测试。如果测试处于活动状态且片段未通过测试,底层像素/样本通常不会被更新。
测试包括:
| 测试 | 说明 |
|---|---|
| 像素所有权测试(Pixel Ownership Test) | 如果片段的像素不属于 OpenGL(另一个窗口与 GL 窗口重叠),则失败。使用帧缓冲对象时始终通过 |
| 裁剪测试(Scissor Test) | 启用时,如果片段的像素位于屏幕指定矩形之外,则失败 |
| 模板测试(Stencil Test) | 启用时,如果提供的模板值与模板缓冲区中的值比较不符合用户指定条件,则失败 |
| 深度测试(Depth Test) | 启用时,如果片段的深度与深度缓冲区中的值比较不符合用户指定条件,则失败 |
早期片段测试
虽然这些测试被指定为在片段着色器之后发生,但在某些条件下可以让它们在片段着色器之前发生。如果它们在 FS 之前发生,则任何片段剔除也将阻止片段着色器执行,从而节省性能。参见 早期片段测试(Early Fragment Test)。
混合与逻辑操作
测试通过后,发生 颜色混合(Blending)。对于每个片段颜色值,与帧缓冲区该位置已有的颜色执行特定的混合操作。
逻辑操作(Logical Operations) 也可以代替混合执行,在片段颜色和帧缓冲区颜色之间执行按位操作。
写入掩码
最后,片段数据被写入帧缓冲区。掩码操作(Masking Operations) 允许用户阻止写入特定值:
- 颜色、深度和模板写入可以独立掩码
- 单个颜色通道也可以独立掩码