为什么要为一个属性指定不同的顶点格式?

Why specify different vertex formats for one attribute?

OpenGL 函数glVertexAttribPointerglVertexAttribFormat 允许用户指定数据的格式,这些数据将在渲染时绑定到着色器程序中的给定属性变量。顶点属性格式是数据类型(intfloatbyte等)、属性变量中的维数(vec2vec3, 等), 数据是否应该归一化, 以及数据数组起始位置的顶点数组中的偏移量。这些函数在构建顶点数组对象 (VAO) 时指定格式,指定的格式是 VAO 状态的一部分。所以这是我的问题:

为什么与属性关联的数据格式是 VAO 状态的一部分,而不是属性状态的一部分?换句话说,为什么与 VAO 关联的数据格式而不是属性?在什么情况下我会有对同一属性使用不同格式的 VAO?

为了更清楚起见,这里有一个例子可以说明我为什么感到困惑。想象一下,在我的顶点着色器中,我声明了变量:

in vec3 position;

现在我在我的 OpenGL 应用程序中获取属性位置:

GLint positionAttribute = glGetAttribLocation(myProgram, "position");

现在当我创建一个 VAO 时,我指定数据格式如下:

glVertexAttribFormat(positionAttribute​, 3, GL_FLOAT, GL_FALSE​, 0);

由于格式与 VAO 关联,每次创建 VAO 时都必须指定格式。 'position'属性是vec3,所以我总是指定3和GL_FLOATglVertexAttribFormat。那么,为什么 OpenGL 以这种方式设计,以至于我每次创建 VAO 时都可能不得不调用 glVertexAttribFormat,并指定一种将保持不变的格式?在我看来,我应该在调用glGetAttribLocation时指定格式,这样我就只调用一次。

那么,如果您拥有多个属性会怎样?或者,如果您想存储位置、颜色和法线怎么办?根据我的经验,这特别适用于 UV 坐标。

因为那样你的属性将只有大小 2 而不是 3。因为你的纹理将只有 2 个坐标(忽略 3D 纹理的可能性)。如果您在 2D 中工作怎么办? attrib 指针允许您指定该属性的输入大小和类型。它很有用,因为它允许您更改数据的格式。在任何情况下,您应该只创建一次 VAO,除非您需要将新数据重新绑定到缓冲区。

"t seems to me that I should have specified the format

嗯,不,你没有指定任何格式,你只是获取了一个属性位置。

那里的选择只是因为 GL 在

方面给了你很大的灵活性
  1. 正在为您将其他数据类型转换为浮点数
  2. 传递具有不同大小的向量
  3. 规范化非浮点类型
  4. 对属性使用交错的非零偏移缓冲区

这只能 "partially" 自动化。当然,您可以反省着色器并弄清楚您的属性确实是一个 vec3,所以 3 个浮点数,但这仍然没有告诉 GL 顶点数组缓冲区对象中的数据是什么样子的。值得注意的是,它允许包含

  • 其他兼容类型(注意 glVertexAttribPointer / glVertexAttribFormat 可以采用多种不同的类型,而不仅仅是 GL_FLOAT
  • 维度较低的数据(谁告诉 GL 的?)
  • 交错数据(谁告诉 GL 步幅是多少?)
  • 非零偏移量的数据(谁告诉 GL 基本偏移量是多少?)
  • 可能需要也可能不需要规范化的数据(谁告诉 GL 的?)

对 "who tells" 的回答:您对 glVertexAttribFormat 的呼叫。

问问自己当它们不匹配时会发生什么。

这允许指定不同于内部着色器使用本身的存储数据结构。所以说你存储打包的 rgba8,你仍然可能想用它做浮点数学。所以你在着色器中将它声明为 vec4,但使用 RGBA8 作为你的顶点数据格式。 GL会为您在两种格式之间进行转换。

好吧,每个 API 都会做出设计决策。大多数情况下,完成给定任务的方法不止一种,在某种程度上,这正是在这种情况下定义的方式。

我对这背后的决策没有任何直接的了解,但可以想到一些为这一决策提供有效动机的考虑因素。

  • 没有着色器的操作 : OpenGL 已经发展了很长时间。着色器最初不是 API 的一部分,并且仍然有 API 的版本可以在没有着色器的情况下运行(兼容性配置文件)。如果没有着色器,你从着色器属性中导出属性类型的想法根本行不通。

  • 支持与 GLSL 类型不对应的数据类型:在着色器中使用类似 vec4GL_FLOAT 的数据类型因为属性规范是最常见的,它不是唯一的选择。例如,属性可以指定为 GL_HALF_FLOAT 或像 GL_INT_2_10_10_10_REV 这样疯狂的格式。如果您真的想为顶点数据节省内存,这些格式可能会有用处。或者,如果您已经拥有这种格式的顶点,例如因为您正在移植支持它们的 Direct3D 代码。这些格式不直接对应 GLSL 中的类型,因此如果不明确指定属性类型,则无法支持它们。

  • 顶点设置状态和程序状态之间的依赖关系:这个比较概念化。它现在的工作方式,顶点设置状态和程序状态是正交的。避免 没有 依赖的概念之间的依赖始终是一个很好的设计目标。如果顶点属性格式依赖于当前绑定的程序,则破坏了这种独立性。例如,当绑定一个新程序时,顶点属性数据的解释可能会改变,这意味着顶点设置状态必须更新。由于由此产生的复杂性和低效率,这些类型的状态依赖性是非常不受欢迎的。