[OpenGL] 几何着色器

Posted ZJU_fish1996

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[OpenGL] 几何着色器相关的知识,希望对你有一定的参考价值。

reference:https://www.khronos.org/opengl/wiki/Geometry_Shader

       几何着色器(GS)是一个使用GLSL编写的处理图元生成的shader程序,它位于顶点着色器(或者可选的细分阶段)和固定的顶点后处理阶段之间。

       几何着色器是可选的,并非必须的。

       几何着色器调用将单个图元作为输入,可能会输出零个或多个图元。可以通过代码实现来限制单个GS调用生成的图元数量。我们编写GS以接受特定的图元输入和特定的图元输出。

       尽管GS可以用于放大几何形状,从而实现粗略的曲面细分,但这通常不是GS的一个好用法。使用GS的主要原因是:

  • 分层渲染:利用一个图元并将其渲染到多张图像,而不必更改绑定的渲染目标。
  • Transform feedback:通常用于在GPU上(计算着色器前)执行计算任务。

       在OpenGL 4.0中,GS新增了两个新特性。一个是写入多个输出流的能力,这一特性专用于transform feedback,这样不同的feedback缓冲区就能够得到不同的feedback数据。

       另外一个特性是GS实例化,它允许在同一个图元上执行多个调用。这使得分层渲染更容易实现并且效率可能会更高,因为每个分层的图元可由单一的GS实例计算。

       注意:虽然之前GL_EXT_geometry_shader4和GL_ARB_geometry_shader4扩展包含了几何着色器,但这些扩展是以与标准较为不同的方式实现的,而本文仅仅介绍了标准特性。

图元输入/输出规范

       每个几何着色器均设计为可以接受特定类型的输入和输出图元类型。可接受的输入图元类型在shader中的定义如下:

layout(input_primitive) in;

       input_primitive必须与提供给GS的顶点流相匹配。如果支持曲面细分,那么图元类型取决于细分评估着色器的输出;如果禁用了曲面细分,则图元类型由着色器程序渲染的绘制指令提供。Input_primitive的有效值,以及有效的OpenGL原始类型或细分的形式如下:

    顶点数量是GS每个输入图元接受的顶点个数。

    输出的图元类型如下定义:

layout(output_primitive, max_vertices=vert_count) out;

out_primitive必须是如下之一:

  • points
  • line_strip
  • triangle_strip

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

       输出必须要有max_vertices的定义。该数字应该为编译时期的常量,它定义了一次GS调用后可写入的最大顶点数量。该数字不得大于实现定义的限制MAX_GEOMETRY_OUTPUT_VERTICES。该限制的最小值为256,请参见如下的限制。

实例化

       GS也可以被实例化(这和实例化渲染是分离的,由于是GS本地化的)。这使得GS对同一输入图元执行多次调用。针对特定图元的每个GS调用有不同的gl_InvocationID值,这对于分层渲染或和输出到多个流很有用(参见下文)。

       为了使用实例化,必须有如下layout限定符:

layout(invocations=num_instances) in;

       Num_instances的值不应大于MAX_GEOMETRY_SHADER_INVOCATIONS((至少为32)。内建变量gl_InvocationID指定了该着色器的特定实例,它的取值位于半开区间[0, num_instances]

       实例化输出的图元根据gl_InvocationID排序。因此如果用户渲染了两个图元,并设置num_insances为3,那么GS的有效调用顺序如下:(prim0,inst0),(prim0,inst1),(prim0,inst2),(prim1,inst0)…,GS的输出图元根据输入序列进行排序。因此如果(prim0,inst0)输出两个三角形,它们都会在渲染(prim0,inst1)的任何三角形之前被渲染。

输入      

       几何着色器将图元作为输入,每个图元由着色器中定义的图片类型的多个顶点组成。

       顶点着色器的输出(或者细分阶段)作为变量数组传递给GS。这些可以设计为单个变量或者作为接口结构块的一部分。每个独立变量将是图元顶点数量长度的数组,对于接口块而言,块本身将按此长度排列。输入数组中的顶点数据和前一个着色阶段指定的顶点顺序相对应。

       几何着色器的输入可能有插值限定符。如果有的话,前一个阶段的输出必须有相同的限定符。

       几何着色器具有如下内建输入变量:

in gl_PerVertex

  vec4 gl_Position;
  float gl_PointSize;
  float gl_ClipDistance[];
 gl_in[];

       这些变量的含义由前一着色器阶段传递赋值得到。

       有一些GS的输入值是基于图元而非顶点的。它们并没有被包含在数组中,如下:

int gl_PrimitiveIDIn;
int gl_InvocationID; // 需要GLSL4.0或ARB_gpu_shader5

gl_PrimitiveIDIn

       当前输入图元的ID,基于GS当前绘制指令下处理的图元数量

gl_InvocationID

       当前实例,在实例化几何着色器时定义。

输出

       几何着色器可以根据需求输出任意数量的顶点(最大为max_vertices layout限定符指定的顶点)。为此,几何着色器中的输出值不是数组,而是一个基于函数的接口。

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

       注意:你必须在每次EmitVertex()调用前写入每个输出变量(对于多个流输出每个进行EmitStreamVertex()调用)

       GS定义了每个顶点输出的图元类型。GS也可以通过调用EndPrimitive()函数来结束当前图元并开始新的图元。该函数并不会产生一个顶点。

       为了在GS中写入两个独立的三角形,你需要利用EmitVertex()调用三个独立的顶点,然后调用EndPrimitive()结束当阶段并开启新的。然后再利用EmitVertex()写入额外三个顶点。

       GLSL也定义了输出变量。它们要么被分组到接口块,要么作为独立变量。输出变量可以包含插值限定符。片元着色器对应的接口变量应该具备相同的限定符。

       几何着色器包含了如下内置输出:

out gl_PerVertex

  vec4 gl_Position;
  float gl_PointSize;
  float gl_ClipDistance[];
;

       gl_PerVertex定义了输出的接口块。该块并没有实例名字,因此不需要前缀名字。

       GS是顶点处理的最后阶段。因此,除非光栅化被关闭,你必须写入一些值。这些输出和流0有关联。所以如果要向别的流发送顶点,则不必写入它们。

gl_Position

       当前顶点裁剪空间的输出位置。如果需要向流0发送顶点,则必须写入该值,除非光栅化被关闭。

gl_pointSize

       正在被光栅化的像素的宽度/高度。仅在输出点图元时才需要写入该内容。

gl_clipdistance

       允许着色器设置从顶点到每个用户定义的裁剪平面的距离。距离为正代表顶点在裁剪平面之后,距离为负表示顶点在裁剪平面之前。为了使用该变量,用户必须使用显示大小重新声明它(因此重新声明接口块)

       某些预定义的输出有特殊的含义和语义:

       out gl_PrimitiveID;

       图元id将被发送给片元着色器。特定线/三角形的图元id将从该线/三角形的provoking vertex中获取。

       该值是你想要的任何值。但是,如果你想要和标准OpenGL含义相匹配(ie:如果不使用gs,片元着色器能获取的),你必须在发送每个顶点前执行这一操作:

       gl_PrimitiveID = gl_PrimitiveIDIn;

       这将自然地假定GS输出的图元数量等于GS接收的图元数量。

分层渲染

       分层渲染是使用GS给不同层的分层帧缓冲传递特定的图元。这在制作立方体阴影映射,或者渲染立方体环境映射而无需多次渲染整个场景时非常有帮助。

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

out int gl_Layer;
out int gl_viewportindex; // 需要GL4.1或者ARB_viewport_array

       gl_Layer输出定义了图元将输出到分层图像的哪一层。图元的每个顶点必须有相同的层级下标。注意在渲染立方体数组时,g_layer值代表面的层级(在一层中的所有面),而非立方体的层级。

       gl_ViewportIndex,需要gl 4.1或ARB_viewport_array,它指明了哪个视口下标使用了这一图元。

       注意:ARB_viewport_array,虽然是4.1的特性,但在NVIDIA和AMD的3.3硬件上广泛使用。

        使用GS实例化能使得分层渲染更高效,因为不同的GS调用可以在实例化过程中并行执行。此外,尽管大部分3.3硬件厂商支持了ARB_viewport_array,但它们并没有同时支持ARB_gpu_shader5。

        警告:gl_Layer和gl_ViewportIndex是GS的输出变量。因此,每次调用EmitVertex时,它们的值都会变为未定义。因此,每次循环输出时都必须设置这些变量。

       如果几何着色器中并没有写入gl_ViewportIndex,那么就等价于赋值为0。

确定顶点

       gl_Layer和gl_ViewportIndex是每顶点属性,但它们实际指定了整个图元的属性。因此,出现一个问题,特定图元中哪个顶点定义了该图元的layer和viewportIndex?

       答案是与它的实现有关。OpenGL中有两个查询可以确定当前实现使用了哪个顶点:GL_LAYER_PROVOKING_VERTEX和GL_VIEWPORT_INDEX_PROVOKING_VERTEX。

       glGetIntegerv的返回值将是如下枚举值之一:

       GL_PROVOKING_VERTEX:使用的顶点将跟踪为当前设定的激发顶点(provoking index)。

       GL_LAST_VERTEX_CONVENTION:使用的顶点将为最后一个激发顶点。

       GL_FIRST_VERTEX_CONVENTION:使用的顶点将为第一个激发顶点。

       GL_UNDEFINED_VERTEX:实现未知。

       为了获得最大的可移植性,必须为每个图元提供相同的layer和viewportIndex。因此,如果要输出三角形带,其中不同的三角形有不同的索引,那就太糟了。你必须将它切分割为不同的图元。

输出流

        当使用transform feedback来计算值时,通常需要以不同的速率发送不同的顶点集合到不同的缓冲区。例如,GS可以在一个流里构建每实例数据的同时,将顶点数据发往另一个流。顶点数据和每实例数据可以有着不同长度,以不同速率写入。

        多个输出流需要输出的图元类型为点(points)。你仍然可以选择任何喜欢的输入。

        为了实现这一点,可以给一个流的输出变量添加layout限定符:

layout(stream=param|stream_index) out vec4 some_output;

        设定一个流的默认值如下:

layout(stream=2) out;

        所有接下来的输出变量都将使用流2除非它们有指定的流。默认值可在之后修改。初始的默认值为0。

        为了向指定流写入顶点,需要使用函数EmitStreamVertex。这一函数需要给定流索引,仅有输出变量会被写入。类似的,EndStreamPrimitive将结束一个特定图元流。但是,由于多个流输出需要使用点图元,后一个函数实际用处不大。

        只有传递给流0的图元才会实际被传递到顶点后处理阶段渲染。剩余的流仅在使用transform feedback时会用到。调用EmitVertex或EndPrimitive等效于它们相对于流0的副本。

以上是关于[OpenGL] 几何着色器的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL渲染管线(rendering pipeline)

✠OpenGL-2-图像管线

OpenGL中着色器,渲染管线,光栅化

opengl ---几何着色器

OpenGL 着色器加载失败

OpenGL 之 Compute Shader(通用计算并行加速)