11.1 OpenGL可编程顶点处理:顶点着色器

顶点着色器 Vertex Shaders

顶点属性(Vertex Attributes)

在图形编程中,尤其是在OpenGL等图形API中,顶点属性(Vertex Attributes)是图形管线处理过程中用于描述每个顶点特征的数据。这些数据通过绘制命令从应用程序传递给图形处理器,并与顶点着色器中的命名属性变量绑定。

  1. 属性绑定

    • 顶点着色器可以通过location限定符(在GLSL源码中)或SPIR-V着色器的Location装饰指定属性变量到通用顶点属性索引的绑定。
    • 应用程序也可以在程序链接前使用BindAttribLocation函数明确指定绑定关系。
  2. 数据类型和组件布局

    • 根据表11.3中列举的标量或向量数据类型声明的属性变量,当其绑定到通用属性索引i时,它的值将来自通用属性i的相应组件。使用的通用属性组件取决于变量的类型以及声明中可能指定的组件布局限定符(参照表11.1)。
    • 当矩阵类型的属性变量绑定到通用属性索引i时,其值会来自从i开始连续的通用属性。这样的矩阵被视为一列列向量数组,其中向量的值按照表11.2所示的方式由相应的通用属性成分提供。
    • 数组类型的属性变量绑定到通用属性索引i时,活跃的数组元素会被分配到从i开始的连续通用属性上,分配的属性数量和每个元素的组件数根据数组元素的数据类型及其声明中可能指定的组件布局限定符决定。
  3. 64位双精度类型

    • 对于表11.3中列出的64位双精度类型,如果顶点属性变量的值指定的组件少于该变量所需的组件数量,则不会提供默认值。例如,若一个dvec4类型的变量第四分量通过VertexAttribL3dv或其他只指定三个分量的方法来指定,则该分量将是未定义的。
  4. 自动绑定

    • 如果在链接程序时,有活动的属性没有通过BindAttribLocation、着色器文本内显式设置或SPIR-V二进制代码直接指定绑定,则图形系统(如OpenGL)会自动为这些属性分配通用顶点属性。已分配的绑定可通过GetAttribLocation函数查询。
  5. 链接限制

    • 在链接程序时,如果分配的绑定会导致引用不存在的通用属性(即大于或等于MAX_VERTEX_ATTRIBS的索引),或者对于需要连续多个通用属性的矩阵属性或多维数组属性没有足够的空间进行分配,那么链接操作将会失败。
  6. SPIR-V 着色器和 BindAttribLocation

    • 对于SPIR-V着色器,由于SPIR-V规范要求位置必须在着色器文本内部完全指定,因此BindAttribLocation函数对其无效。在SPIR-V着色器中,属性的位置必须按照特定章节的规定明确设置。如果同时在SPIR-V着色器内部和通过BindAttribLocation设置了不同的绑定,将以SPIR-V着色器内部的设置为准。
Data type component layout qualifier Components used
scalar 0 or unspecified x
scalar 1 y
scalar 2 z
scalar 3 w
two-component vector 0 or unspecified (x, y)
two-component vector 1 (y, z)
two-component vector 2 (z, w)
three-component vector 0 or unspecified (x, y, z)
three-component vector 1 (y, z, w)
four-component vector 0 or unspecified (x, y, z, w)

table 11.1:属性变量访问的通用属性组件

Data type Column vector type layout qualifier Generic attributes used
mat2, dmat2 two-component vector i, i + 1
mat2x3, dmat2x3 three-component vector i, i + 1
mat2x4, dmat2x4 four-component vector i, i + 1
mat3x2, dmat3x2 two-component vector i, i + 1, i + 2
mat3, dmat3 three-component vector i, i + 1, i + 2
mat3x4, dmat3x4 four-component vector i, i + 1, i + 2
mat4x2, dmat4x2 two-component vector i, i + 1, i + 2, i + 3
mat4x3, dmat4x3 three-component vector i, i + 1, i + 2, i + 3
mat4, dmat4 four-component vector i, i + 1, i + 2, i + 3

table 11.2: 绑定到通用属性索引i的矩阵变量的列向量所使用的通用属性和向量类型

Data type Command
int VertexAttribI1i
ivec2 VertexAttribI2i
ivec3 VertexAttribI3i
ivec4 VertexAttribI4i
uint VertexAttribI1ui
uvec2 VertexAttribI2ui
uvec3 VertexAttribI3ui
uvec4 VertexAttribI4ui
float VertexAttrib1*
vec2 VertexAttrib2*
vec3 VertexAttrib3*
vec4 VertexAttrib4*
double VertexAttribL1d
dvec2 VertexAttribL2d
dvec3 VertexAttribL3d
dvec4 VertexAttribL4d

table 11.3: 标量和矢量顶点属性类型以及用于设置相应泛型属性值的VertexAttrib*命令

在编译和链接着色器程序之前预绑定顶点属性(Vertex Attribute)到特定的位置索引

void glBindAttribLocation( uint program, uint index, const char *name );
  • program: 无符号整数,表示目标着色器程序对象的ID,它是一个已经创建并且已经附加了顶点着色器的 OpenGL 程序对象。
  • index: 无符号整数,代表要绑定到的通用顶点属性索引位置。
  • name: 字符串指针,指向着色器源代码中定义的顶点属性变量名。

请注意,这个绑定只在后续调用glLinkProgram时生效,并且如果着色器内部已经使用location布局限定符显式指定了位置,则内部布局会优先于glBindAttribLocation设置的值。

获取指定程序对象中索引为index的活跃输入变量的属性

void glGetActiveAttrib( uint program, uint index, sizei bufSize, sizei *length, int *size, enum *type, char *name );

// 等价于

const enum props[] = { ARRAY_SIZE, TYPE };
glGetProgramResourceName(program, PROGRAM_INPUT, index, bufSize, length, name); // 获取变量名
glGetProgramResourceiv(program, PROGRAM_INPUT, index, 1, &props[0], 1, NULL, size); // 获取数组大小(元素个数)
glGetProgramResourceiv(program, PROGRAM_INPUT, index, 1, &props[1], 1, NULL, (int *)type); // 获取变量类型

对于 GetActiveAttrib,会枚举所有活动的顶点着色器输入变量,包括特殊的内建输入变量 gl_BaseInstance、gl_BaseVertex、gl_DrawID、gl_InstanceID 和 gl_VertexID。

获取在program对象中名为name的活跃输入变量所分配的位置索引(location)

int glGetAttribLocation( uint program, const char *name );

// 等价于

glGetProgramResourceLocation(program, GL_PROGRAM_INPUT, name);

需要注意的是,glGetAttribLocation 函数的调用是在程序对象已经链接之后进行的。

在OpenGL等图形编程API中,应用程序确实可以将多个顶点属性名(attribute name)绑定到同一个位置索引(location)。这种做法被称为“别名”(aliasing),但并不是所有情况下都支持这样做。

如果不同属性名代表的数据类型和组件数量相同,并且在实际运行时只有一个属性是活跃的,或者没有一个着色器路径同时使用了这些被别名绑定在同一位置的所有属性,则别名绑定通常是可行的。然而,如果两个或更多具有相同位置的顶点属性同时在着色器代码中有用到,那么可能会导致不可预知的结果,甚至链接错误。

顶点着色器变量 (Vertex Shader Variables)

在顶点着色器(Vertex Shader)中,可以定义和使用多种类型的变量来处理传入的顶点数据。这些变量主要分为以下几类:

  1. 顶点属性(Vertex Attributes):这些是每顶点都会有的输入数据,如位置坐标、纹理坐标、法线向量等。每个顶点属性必须与图形管线中的一个通用顶点属性索引关联,并通过glBindAttribLocation或在着色器代码中使用layout(location = ...)显式指定。在顶点着色器内部,可以通过声明相应的输入变量并使用它们进行计算。

  2. Uniforms:全局常量,在整个渲染调用期间对所有顶点保持不变。例如,变换矩阵(模型、视图、投影矩阵)、光照参数等。顶点着色器可以通过uniform变量访问当前程序对象所拥有的 uniforms。

  3. Varyings:在顶点着色器中声明并赋值的变量,其值会在光栅化阶段传递给片段着色器(Fragment Shader)。varying 变量主要用于将顶点级别的信息插值得到像素级别的结果,如颜色、纹理坐标等。

  4. Samplers:用于纹理采样的接口,允许顶点着色器读取纹理贴图数据。尽管顶点着色器中使用纹理的情况较少见,但在某些情况下,如动态生成几何体时,可以从纹理中提取额外的信息。

  5. 临时局部变量(Temporary and Local Variables):顶点着色器可以拥有用于临时存储和计算结果的局部变量。

  6. 内置变量(Built-in Variables):OpenGL还提供了一系列预定义的内置变量,比如gl_VertexIDgl_InstanceID等,用来获取特定的顶点或实例编号信息。

     Transform Feedback机制允许将顶点着色器(或其他支持阶段)的输出结果直接写回缓冲区,而不是仅仅为了渲染目的。这意味着可以通过GPU执行并行计算来生成新的顶点数据或者更新现有数据,而无需将其读回到CPU内存。这对于粒子系统、模拟物理效果、几何变形和其他需要在图形管线内部循环处理顶点数据的场景特别有用。
    
     当启用Transform Feedback时,程序员需要通过glTransformFeedbackVaryings函数指定要记录的顶点着色器输出变量列表。这些变量的数据将在渲染过程中被写入绑定到特定反馈目标的缓冲区中。使用布局限定符(如xfb_buffer、xfb_offset、xfb_stride)可以更精细地控制如何存储这些数据。
    
     Transform Feedback捕获的数据可以根据不同的缓冲模式(INTERLEAVED_ATTRIBS 或 SEPARATE_ATTRIBS)进行组织,并且遵循一系列限制,比如最大可捕获组件数量(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 和 MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS),以及对双精度浮点数的特殊处理要求(它们占用更多的存储空间)。通过查询函数glGetTransformFeedbackVarying可以获取变换反馈变量的具体属性,如名称、大小和类型等。
    

指定程序对象中的一组输出变量,在变换反馈模式下进行记录

void glTransformFeedbackVaryings( uint program, sizei count, const char * const *varyings, enum bufferMode );

函数用于在编程阶段指定哪些着色器输出变量将在变换反馈(Transform Feedback)模式下被捕获。参数说明如下:

  • program: 这是一个GLuint类型的值,代表你想要设置变换反馈变量的程序对象ID。这个程序对象必须已经包含了编译和链接完成的顶点、曲面细分控制、曲面细分评估或几何着色器。

  • count: 一个sizei类型的值,表示接下来要指定的输出变量数组varyings中的元素数量。

  • varyings: 一个指向字符串常量指针的指针,它是一个包含输出变量名的数组。每个元素都是一个C风格的字符串,对应着着色器代码中声明并希望被记录到缓冲区的输出变量名。

  • bufferMode: 一个枚举类型值,指定了变换反馈数据在缓冲区中的布局方式,它可以是:

    • GL_SEPARATE_ATTRIBS:每个输出变量存储在一个独立的缓冲区对象中。
    • GL_INTERLEAVED_ATTRIBS:所有输出变量按照它们在数组中的顺序连续存储在一个缓冲区对象内。

通过调用此函数,可以定义在启用变换反馈时,哪些输出变量将被写入绑定的缓冲区。但是,这些设置并不会立即生效,直到对程序对象执行LinkProgram操作后才真正关联到着色器输出,并且在满足上述提及的各种链接条件的情况下成功链接程序。

获取变换反馈模式下的输出变量的属性信息

void glGetTransformFeedbackVarying( uint program, uint index, sizei bufSize, sizei *length, sizei *size, enum *type, char *name );

// 等价于

const enum props[] = { ARRAY_SIZE, TYPE };
glGetProgramResourceName(program, TRANSFORM_FEEDBACK_VARYING, index, bufSize, length, name);
glGetProgramResourceiv(program, TRANSFORM_FEEDBACK_VARYING, index, 1, &props[0], 1, NULL, size);
glGetProgramResourceiv(program, TRANSFORM_FEEDBACK_VARYING, index, 1, &props[1], 1, NULL, (int *)type);

参数说明:

  • program: 一个无符号整数(GLuint),表示已链接的着色器程序对象ID。
  • index: 变换反馈变量的索引,也是一个无符号整数(GLuint)。从0开始计数,对应于通过TransformFeedbackVaryings函数指定的输出变量列表中的位置。
  • bufSize: 一个大小类型(sizei)值,指定了缓冲区name的大小,以字符为单位。这个大小必须足够大以便容纳要获取的变量名。
  • length: 指向大小类型(sizei)变量的指针,用于接收实际写入name缓冲区的字符数量,包括终止空字符。
  • size: 指向大小类型(sizei)变量的指针,用于接收变换反馈变量的组件数量。
  • type: 指向枚举类型(GLenum)变量的指针,用于接收变换反馈变量的数据类型。
  • name: 一个指向字符数组的指针,用于存储变换反馈变量的名称。

调用此函数后,它将返回在给定索引处的变换反馈变量的名称、大小和数据类型等信息。

验证(Validation)

验证指定程序对象的状态是否有效和可执行

void glValidateProgram( uint program );
  • 调用glValidateProgram后,OpenGL会检查当前程序对象的状态以及与之相关的上下文状态,例如绑定的着色器、着色器的Uniform变量是否正确设置等等。然后,OpenGL会对程序对象的状态进行验证,并根据验证结果更新程序对象的状态信息。

  • 验证后的结果可以通过查询程序对象的信息日志来获取,通常通过glGetProgramiv函数查询GL_VALIDATE_STATUS参数来获取验证的结果。如果验证成功,则GL_VALIDATE_STATUS的值为GL_TRUE,否则为GL_FALSE

验证指定程序管线对象的状态是否有效和可执行

void glValidateProgramPipeline( uint pipeline );
  • 调用glValidateProgramPipeline后,OpenGL会检查当前程序管线对象的状态以及与之相关的上下文状态,例如绑定的程序对象是否正确设置等等。然后,OpenGL会对程序管线对象的状态进行验证,并根据验证结果更新程序管线对象的状态信息。

  • 验证后的结果可以通过查询程序管线对象的信息日志来获取,通常通过glGetProgramPipelineiv函数查询GL_VALIDATE_STATUS参数来获取验证的结果。如果验证成功,则GL_VALIDATE_STATUS的值为GL_TRUE,否则为GL_FALSE

你可能感兴趣的:(OpenGL,图形渲染)