为什么要为一个属性指定不同的顶点格式?
Why specify different vertex formats for one attribute?
OpenGL 函数glVertexAttribPointer
和glVertexAttribFormat
允许用户指定数据的格式,这些数据将在渲染时绑定到着色器程序中的给定属性变量。顶点属性格式是数据类型(int
、float
、byte
等)、属性变量中的维数(vec2
、vec3
, 等), 数据是否应该归一化, 以及数据数组起始位置的顶点数组中的偏移量。这些函数在构建顶点数组对象 (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_FLOAT
到glVertexAttribFormat
。那么,为什么 OpenGL 以这种方式设计,以至于我每次创建 VAO 时都可能不得不调用 glVertexAttribFormat
,并指定一种将保持不变的格式?在我看来,我应该在调用glGetAttribLocation
时指定格式,这样我就只调用一次。
那么,如果您拥有多个属性会怎样?或者,如果您想存储位置、颜色和法线怎么办?根据我的经验,这特别适用于 UV 坐标。
因为那样你的属性将只有大小 2 而不是 3。因为你的纹理将只有 2 个坐标(忽略 3D 纹理的可能性)。如果您在 2D 中工作怎么办? attrib 指针允许您指定该属性的输入大小和类型。它很有用,因为它允许您更改数据的格式。在任何情况下,您应该只创建一次 VAO,除非您需要将新数据重新绑定到缓冲区。
"t seems to me that I should have specified the format
嗯,不,你没有指定任何格式,你只是获取了一个属性位置。
那里的选择只是因为 GL 在
方面给了你很大的灵活性
- 正在为您将其他数据类型转换为浮点数
- 传递具有不同大小的向量
- 规范化非浮点类型
- 对属性使用交错的非零偏移缓冲区
这只能 "partially" 自动化。当然,您可以反省着色器并弄清楚您的属性确实是一个 vec3
,所以 3 个浮点数,但这仍然没有告诉 GL 顶点数组缓冲区对象中的数据是什么样子的。值得注意的是,它允许包含
- 其他兼容类型(注意
glVertexAttribPointer
/ glVertexAttribFormat
可以采用多种不同的类型,而不仅仅是 GL_FLOAT
)
- 维度较低的数据(谁告诉 GL 的?)
- 交错数据(谁告诉 GL 步幅是多少?)
- 非零偏移量的数据(谁告诉 GL 基本偏移量是多少?)
- 可能需要也可能不需要规范化的数据(谁告诉 GL 的?)
对 "who tells" 的回答:您对 glVertexAttribFormat
的呼叫。
问问自己当它们不匹配时会发生什么。
这允许指定不同于内部着色器使用本身的存储数据结构。所以说你存储打包的 rgba8,你仍然可能想用它做浮点数学。所以你在着色器中将它声明为 vec4,但使用 RGBA8 作为你的顶点数据格式。 GL会为您在两种格式之间进行转换。
好吧,每个 API 都会做出设计决策。大多数情况下,完成给定任务的方法不止一种,在某种程度上,这正是在这种情况下定义的方式。
我对这背后的决策没有任何直接的了解,但可以想到一些为这一决策提供有效动机的考虑因素。
没有着色器的操作 : OpenGL 已经发展了很长时间。着色器最初不是 API 的一部分,并且仍然有 API 的版本可以在没有着色器的情况下运行(兼容性配置文件)。如果没有着色器,你从着色器属性中导出属性类型的想法根本行不通。
支持与 GLSL 类型不对应的数据类型:在着色器中使用类似 vec4
和 GL_FLOAT
的数据类型因为属性规范是最常见的,它不是唯一的选择。例如,属性可以指定为 GL_HALF_FLOAT
或像 GL_INT_2_10_10_10_REV
这样疯狂的格式。如果您真的想为顶点数据节省内存,这些格式可能会有用处。或者,如果您已经拥有这种格式的顶点,例如因为您正在移植支持它们的 Direct3D 代码。这些格式不直接对应 GLSL 中的类型,因此如果不明确指定属性类型,则无法支持它们。
顶点设置状态和程序状态之间的依赖关系:这个比较概念化。它现在的工作方式,顶点设置状态和程序状态是正交的。避免 没有 依赖的概念之间的依赖始终是一个很好的设计目标。如果顶点属性格式依赖于当前绑定的程序,则破坏了这种独立性。例如,当绑定一个新程序时,顶点属性数据的解释可能会改变,这意味着顶点设置状态必须更新。由于由此产生的复杂性和低效率,这些类型的状态依赖性是非常不受欢迎的。
OpenGL 函数glVertexAttribPointer
和glVertexAttribFormat
允许用户指定数据的格式,这些数据将在渲染时绑定到着色器程序中的给定属性变量。顶点属性格式是数据类型(int
、float
、byte
等)、属性变量中的维数(vec2
、vec3
, 等), 数据是否应该归一化, 以及数据数组起始位置的顶点数组中的偏移量。这些函数在构建顶点数组对象 (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_FLOAT
到glVertexAttribFormat
。那么,为什么 OpenGL 以这种方式设计,以至于我每次创建 VAO 时都可能不得不调用 glVertexAttribFormat
,并指定一种将保持不变的格式?在我看来,我应该在调用glGetAttribLocation
时指定格式,这样我就只调用一次。
那么,如果您拥有多个属性会怎样?或者,如果您想存储位置、颜色和法线怎么办?根据我的经验,这特别适用于 UV 坐标。
因为那样你的属性将只有大小 2 而不是 3。因为你的纹理将只有 2 个坐标(忽略 3D 纹理的可能性)。如果您在 2D 中工作怎么办? attrib 指针允许您指定该属性的输入大小和类型。它很有用,因为它允许您更改数据的格式。在任何情况下,您应该只创建一次 VAO,除非您需要将新数据重新绑定到缓冲区。
"t seems to me that I should have specified the format
嗯,不,你没有指定任何格式,你只是获取了一个属性位置。
那里的选择只是因为 GL 在
方面给了你很大的灵活性- 正在为您将其他数据类型转换为浮点数
- 传递具有不同大小的向量
- 规范化非浮点类型
- 对属性使用交错的非零偏移缓冲区
这只能 "partially" 自动化。当然,您可以反省着色器并弄清楚您的属性确实是一个 vec3
,所以 3 个浮点数,但这仍然没有告诉 GL 顶点数组缓冲区对象中的数据是什么样子的。值得注意的是,它允许包含
- 其他兼容类型(注意
glVertexAttribPointer
/glVertexAttribFormat
可以采用多种不同的类型,而不仅仅是GL_FLOAT
) - 维度较低的数据(谁告诉 GL 的?)
- 交错数据(谁告诉 GL 步幅是多少?)
- 非零偏移量的数据(谁告诉 GL 基本偏移量是多少?)
- 可能需要也可能不需要规范化的数据(谁告诉 GL 的?)
对 "who tells" 的回答:您对 glVertexAttribFormat
的呼叫。
问问自己当它们不匹配时会发生什么。
这允许指定不同于内部着色器使用本身的存储数据结构。所以说你存储打包的 rgba8,你仍然可能想用它做浮点数学。所以你在着色器中将它声明为 vec4,但使用 RGBA8 作为你的顶点数据格式。 GL会为您在两种格式之间进行转换。
好吧,每个 API 都会做出设计决策。大多数情况下,完成给定任务的方法不止一种,在某种程度上,这正是在这种情况下定义的方式。
我对这背后的决策没有任何直接的了解,但可以想到一些为这一决策提供有效动机的考虑因素。
没有着色器的操作 : OpenGL 已经发展了很长时间。着色器最初不是 API 的一部分,并且仍然有 API 的版本可以在没有着色器的情况下运行(兼容性配置文件)。如果没有着色器,你从着色器属性中导出属性类型的想法根本行不通。
支持与 GLSL 类型不对应的数据类型:在着色器中使用类似
vec4
和GL_FLOAT
的数据类型因为属性规范是最常见的,它不是唯一的选择。例如,属性可以指定为GL_HALF_FLOAT
或像GL_INT_2_10_10_10_REV
这样疯狂的格式。如果您真的想为顶点数据节省内存,这些格式可能会有用处。或者,如果您已经拥有这种格式的顶点,例如因为您正在移植支持它们的 Direct3D 代码。这些格式不直接对应 GLSL 中的类型,因此如果不明确指定属性类型,则无法支持它们。顶点设置状态和程序状态之间的依赖关系:这个比较概念化。它现在的工作方式,顶点设置状态和程序状态是正交的。避免 没有 依赖的概念之间的依赖始终是一个很好的设计目标。如果顶点属性格式依赖于当前绑定的程序,则破坏了这种独立性。例如,当绑定一个新程序时,顶点属性数据的解释可能会改变,这意味着顶点设置状态必须更新。由于由此产生的复杂性和低效率,这些类型的状态依赖性是非常不受欢迎的。