一、说明

几何着色器对于渲染管线设计是一个新生事物;目前对应于几何着色器的资料不多,并且说法不一,因此如何用几何着色器,依然需要参照当前管线的设计细节,因而该手册也就是参考性的,并非权威。

二、关于几何着色器

几何着色器是可选的,不必使用。

几何着色器调用采用单个 Primitive 作为输入,并且可以输出零个或多个 Primitive。对于可以从单个 GS 调用生成多少个基元,存在实现定义的限制。编写 GS 以接受特定的输入基元类型并输出特定的基元类型。

虽然 GS 可用于放大几何图形,从而实现粗略的曲面细分形式,但这通常不是 GS 的良好用途。使用 GS 的主要原因是:

  • 分层渲染:获取一个基元并将其渲染到多个图像,而无需更改绑定的渲染目标等。
  • 变换反馈:这通常用于在 GPU 上执行计算任务(显然是预计算着色器)。
  • 在 OpenGL 4.0 中,GS 获得了两个新功能。一个是能够写入多个输出流。这专门用于转换反馈,以便不同的反馈缓冲区集可以获取不同的转换反馈数据。

另一个功能是 GS 实例化,它允许对同一输入原语进行多次调用。这使得分层渲染更易于实现,并且可能执行得更快,因为每个层的基元都可以由单独的 GS 实例计算。

注意:虽然几何着色器以前有 GL_EXT_geometry_shader4 和 GL_ARB_geometry_shader4 等扩展,但这些扩展以与核心功能截然不同的方式公开 API 和 GLSL 功能。本页仅介绍核心功能。

三、原始输入/输出规范

每个几何着色器都设计为接受特定的 Primitive 类型作为输入,并输出特定的 Primitive 类型。接受的输入基元类型在着色器中定义:

layout(input_primitive​) in;

input_primitive类型必须与提供给 GS 的顶点流的基元类型匹配。如果启用了 Tessellation,则基元类型由 Tessellation Evaluation Shader 的输出限定符指定。如果未启用 Tesslation,则基元类型由使用此着色器程序渲染的绘图命令提供。input_primitive的有效值以及有效的 OpenGL 基元类型或曲面细分形式为:

顶点计数是 GS 接收的每个输入基元的顶点数。

输出基元类型定义如下:

layout(output_primitive​, max_vertices = vert_count​) out;

output_primitive必须是以下项之一:

  • point
  • line_strip
  • triangle_strip

它们的工作方式与它们的对应 OpenGL 渲染模式完全相同。要输出单个三角形或直线,只需在发出每组 3 或 2 个顶点后使用 EndPrimitive(见下文)。

输出必须有max_vertices声明。该数字必须是编译时常量,它定义了 GS 的单次调用将写入的最大顶点数。它不能大于实现定义的MAX_GEOMETRY_OUTPUT_VERTICES限制。此限制的最小值为 256。请参阅下面的限制。

3.1 实例

GS 实例化
核心版本 4.6
核心自版本起 4.0
核心 ARB 扩展 ARB_gpu_shader5
GS 也可以实例化(这与实例化渲染是分开的,因为它已本地化到 GS)。这会导致 GS 对同一输入基元执行多次。每次对特定输入原语的 GS 调用都会得到一个不同的gl_InvocationID值。这对于分层渲染和输出到多个流非常有用(见下文)。

若要使用实例化,必须有一个输入布局限定符:

layout(invocations = num_instances​) in;

num_instances 的值不得大于 MAX_GEOMETRY_SHADER_INVOCATIONS(至少为 32)。内置值 gl_InvocationID 指定此着色器的特定实例;它将处于半开范围 [0, num_instances)。

实例的输出基元按gl_InvocationID排序。因此,如果用户渲染两个基元,并将num_instances设置为 3,则 GS 将按以下顺序有效调用:(prim0, inst0), (prim0, inst1), (prim0, inst2), (prim1, inst0), …GS 的输出基元将根据该输入序列进行排序。因此,如果 (prim0, inst0) 输出两个三角形,则在渲染 (prim0, inst1) 中的任何三角形之前,它们都将被渲染。

四、输入

几何着色器采用基元作为输入;每个基元都由一定数量的顶点组成,这些顶点由着色器中的输入基元类型定义。

因此,顶点着色器(或 Tessellation Stage,视情况而定)的输出将作为变量数组馈送到 GS。这些变量可以组织为单个变量,也可以组织为接口块的一部分。每个单独的变量都是一个与基元顶点计数长度相同的数组;对于接口块,块本身将以此长度排列。输入数组中顶点的顺序对应于先前着色器阶段指定的顶点顺序。

几何着色器输入可能具有插值限定符。如果这样做,则前一阶段的输出必须使用相同的限定符。

五 ·E
几何着色器提供以下内置输入变量:

in gl_PerVertex
{
  vec4 gl_Position;
  float gl_PointSize;
  float gl_ClipDistance[];
} gl_in[];

这些变量仅具有通过它们的先前着色器阶段赋予它们的含义。

有些 GS 输入值基于基元,而不是顶点。这些不会聚合到数组中。这些是:

in int gl_PrimitiveIDIn;
in int gl_InvocationID; // Requires GLSL 4.0 or ARB_gpu_shader5
gl_PrimitiveIDIn

当前输入基元的 ID,基于自当前绘图命令启动以来 GS 处理的基元数。
gl_InvocationID
实例化几何着色器时定义的当前实例。

五、输出

几何着色器可以根据需要输出任意数量的顶点(最多为 max_vertices 布局规范指定的最大值)。为此,几何着色器中的输出值不是数组。相反,使用基于函数的接口。

GS 代码写入顶点的所有输出值,然后调用 EmitVertex()。这告诉系统将这些输出值写入输出顶点的写入位置。调用此函数后,所有输出变量都包含未定义的值。因此,在发出下一个顶点(如果有下一个顶点)之前,您需要再次写入它们。

注意:您必须在每次 EmitVertex() 调用之前写入每个输出变量(对于每个 EmitStreamVertex() 调用的流的所有输出)。
GS 定义了这些顶点输出所代表的基元类型。GS 还可以通过调用 EndPrimitive() 函数来结束基元并启动新基元。这不会发出顶点。

为了从 GS 写入两个独立的三角形,您必须使用前三个顶点的 EmitVertex() 编写三个单独的顶点,然后调用 EndPrimitive() 以结束条带并启动一个新条带。然后你用 EmitVertex() 再写三个顶点。

对于 GLSL,输出变量定义为正常变量。根据需要,它们可以分组为接口块或单个值。可以使用插值限定符定义输出变量。Fragment Shader 等效接口变量应使用相同的限定符定义相同的变量。

五 ·E
几何着色器具有以下内置输出。

out gl_PerVertex
{
  vec4 gl_Position;
  float gl_PointSize;
  float gl_ClipDistance[];
};

gl_PerVertex定义了输出的接口块。该块在没有实例名称的情况下定义,因此不需要在名称前加上前缀。

GS 是最后的顶点处理阶段。因此,除非关闭栅格化,否则必须写入其中一些值。这些输出始终与流 0 相关联。因此,如果要向其他流发出顶点,则不必写入它们。

gl_Position
当前顶点的剪辑空间输出位置。如果要向流 0 发出顶点,则必须写入此值,除非栅格化处于关闭状态。
gl_PointSize
被栅格化的点的像素宽度/高度。只有在输出点基元时才需要写入它。
gl_ClipDistance
允许着色器设置从顶点到每个用户定义的裁剪平面的距离。正距离表示顶点位于裁剪平面的内部/后面,负距离表示顶点位于裁剪平面的外部/前面。为了使用此变量,用户必须手动重新声明它(以及接口块),并具有显式大小。
某些预定义的输出具有特殊的含义和语义。

out int gl_PrimitiveID;

原始 ID 将传递给片段着色器。特定直线/三角形的原始 ID 将从该直线/三角形的挑衅顶点中获取,因此请确保为正确的挑衅顶点编写正确的值。

这个值的含义是你想要的。但是,如果要匹配标准的 OpenGL 含义(即:如果不使用 GS,Fragment Shader 会得到什么),则必须在发出之前对每个顶点执行此操作:

gl_PrimitiveID = gl_PrimitiveIDIn;

这自然假设 GS 输出的基元数等于 GS 接收的基元数。

5.1 分层渲染

分层渲染是让 GS 将特定基元发送到分层帧缓冲区的不同层的过程。这对于执行基于立方体的阴影贴图非常有用,甚至可以用于渲染立方体环境贴图,而无需多次渲染整个场景。

五 ·E
GS 中的分层渲染通过两个特殊的输出变量工作:

out int gl_Layer;
out int gl_ViewportIndex; // Requires GL 4.1 or ARB_viewport_array.

gl_Layer输出定义基元转到分层图像中的哪个层。基元中的每个顶点都必须获得相同的层索引。请注意,当渲染到立方体贴图数组时,gl_Layer值表示图层面(图层中的面),而不是立方体贴图的层。

gl_ViewportIndex需要 GL 4.1 或 ARB_viewport_array,它指定要与此基元一起使用的视口索引。

注意:ARB_viewport_array虽然在技术上是一项 4.1 功能,但在 NVIDIA 和 AMD 的 3.3 硬件上广泛可用。
使用 GS 实例化可以提高分层渲染的效率,因为不同的 GS 调用可以并行处理实例。但是,虽然 ARB_viewport_array 通常在 3.3 硬件中实现,但没有 3.3 硬件提供ARB_gpu_shader5支持。

警告:gl_Layer 和 gl_ViewportIndex 是 GS 输出变量。因此,每次调用 EmitVertex 时,它们的值都将变为未定义。因此,每次循环输出时都必须设置这些变量。
如果几何着色器从不写入 gl_ViewportIndex,则所有内容的行为就像写入 0 一样。

哪个顶点
gl_Layer 和 gl_ViewportIndex 是每个顶点的参数,但它们指定了适用于整个基元的属性。因此,出现了一个问题:特定基元中的哪个顶点定义了该基元的图层和视口索引?

答案是它依赖于实现。但是,OpenGL 确实有两个查询来确定当前实现使用哪一个:GL_LAYER_PROVOKING_VERTEX 和 GL_VIEWPORT_INDEX_PROVOKING_VERTEX。

从 glGetIntegerv 返回的值将是以下枚举器之一:

  • GL_PROVOKING_VERTEX:使用的顶点将跟踪当前激发的顶点约定。
  • GL_LAST_VERTEX_CONVENTION:使用的顶点将由最后一个顶点激发顶点约定定义的顶点。
  • GL_FIRST_VERTEX_CONVENTION:使用的顶点将由第一个顶点激发顶点约定定义的顶点。
  • GL_UNDEFINED_VERTEX:实现不是说。
    为了获得最大的可移植性,您必须为每个基元提供相同的图层和视口索引。因此,如果您想输出一个三角形条带,其中不同的三角形具有不同的索引,那就太糟糕了。您必须将其拆分为不同的基元。

输出流
输出流
核心版本 4.6
核心自版本起 4.0
核心 ARB 扩展 ARB_transform_feedback3
使用变换反馈计算值时,能够以不同的速率将不同的顶点集发送到不同的缓冲区通常很有用。例如,GS 可以将顶点数据发送到一个流,同时在另一个流中构建每个实例的数据。顶点数据和每个实例的数据将具有不同的长度,以不同的速度写入。

多流输出要求输出基元类型为点。您仍然可以接受您喜欢的任何输入。

为了提供这一点,可以为输出变量提供带有布局限定符的流索引:

 layout(stream = stream_index) out vec4 some_output;

stream_index范围从 0 到 GL_MAX_VERTEX_STREAMS - 1。

可以使用以下命令设置流的默认值:

layout(stream = 2) out;

所有以下变量都将使用流 2,除非它们指定了流。以后可以更改默认值。初始默认值为 0。

若要将顶点写入特定流,请使用函数 EmitStreamVertex。此函数采用流索引;仅写入这些输出变量。同样,EndStreamPrimitive 结束特定流的基元。但是,由于多流输出需要使用点基元,因此后一个函数不是很有用。

实际上,只有发送到流 0 的基元才会传递给 Vertex Post-Processing 并呈现;其余的流只有在使用转换反馈时才有意义。调用 EmitVertex 或 EndPrimitive 等同于使用流 0 调用它们的流对应项。

六、输出限制

几何着色器的输出存在两个相互竞争的限制:

  • GS 的单次调用可以输出的最大顶点数。
  • GS 的单次调用可以输出的最大输出组件总数。
    第一个限制(由 GL_MAX_GEOMETRY_OUTPUT_VERTICES 定义)是可以提供给max_vertices输出布局限定符的最大数量。单个几何着色器调用都不能超过此数字。

另一个限制,由 GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 定义,通俗地说,是单个 GS 调用可以写入的内容总量。它是输出值的总数(在 GLSL 术语中,分量是向量的分量。所以浮点数是一个组成部分;vec3 是单个 GS 调用可以写入的 3 个组件。这与 GL_MAX_GEOMETRY_OUTPUT_COMPONENTS(输出变量中允许的最大组件数)不同。总输出分量是可以写入的分量总数 + 顶点。

例如,如果总输出组件计数为 1024(GL 4.3 中的最小最大值),并且输出流写入 12 个组件,则可写入的顶点总数为
f l o o r ( 1024 12 ) = 85 {\displaystyle floor({\tfrac {1024}{12}})=85} floor121024=85
这是对可以写入的顶点数的绝对硬性限制。即使GL_MAX_GEOMETRY_OUTPUT_VERTICES大于 85,由于此顶点着色器为每个顶点写入 12 个分量,因此此几何着色器可以写入的真正最大值为 85 个顶点。如果几何着色器每个顶点只写入 8 个分量,那么它可以写入 128 个分量(当然,受输出顶点限制的约束)。

请注意,即使是像 gl_Layer 这样的内置输出也计入GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS。例如,总输出组件计数为 1024 的几何着色器,输出 vec4 gl_Position 和 int gl_Layer 最多支持
f l o o r ( 1024 4 + 1 ) = 204 {\displaystyle floor({\tfrac {1024}{4+1}})=204} floor4+11024=204

顶点。

03-24 04:33