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

Posted

技术标签:

【中文标题】为啥要为一个属性指定不同的顶点格式?【英文标题】:Why specify different vertex formats for one attribute?为什么要为一个属性指定不同的顶点格式? 【发布时间】:2015-03-19 11:17:40 【问题描述】:

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 时,我应该指定格式,所以我只做一次。

【问题讨论】:

介意详细说明吗? “属性状态”是什么意思? 也就是说,为什么数据的格式是关联VAO而不是属性?当您调用glVertexAttribFormat 来指定数据的格式时,您需要绑定 VAO,因为该格式与 VAO 相关联。我问为什么格式不与属性本身相关联,而是。这是因为我无法想象我会为不同的数据使用不同的属性格式的场景。该属性是在着色器中静态键入的。这更清楚了吗?我可能会将此信息添加到问题中。 我还编辑了一个示例,希望能说明我的意思。 如果我没看错,你会问为什么属性设置(大小、步幅等)存储在 VAO 而不是着色器程序中。这是正确的吗? 在某些情况下,您应该知道 OpenGL 是有意设计的,具有不匹配顶点属性格式的能力。例如,如果您在 GLSL 中将顶点属性声明为 vec4,然后使用 1D 顶点属性指针,则在 GLSL 中您将阅读以下每个顶点 (x, 0.0, 0.0, 1.0)。事实上,这就是为什么你应该总是将位置属性声明为vec4; GL 将自动为齐次 w 坐标分配 1.0 的值,并使您更容易编写任何矩阵变换(例如不再有像 matrix * vec4 (pos, 1.0) 这样的无意义的东西)。 【参考方案1】:

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

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

【讨论】:

【参考方案2】:

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

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

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

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

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

【讨论】:

【参考方案3】:

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

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

【讨论】:

【参考方案4】:

“在我看来,我应该指定格式

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

那里的选择仅仅是因为 GL 在

方面为您提供了很大的灵活性
    为您将其他数据类型转换为浮点数 传递不同大小的向量 规范化非浮点类型 为属性使用交错的非零偏移缓冲区

这只能“部分”自动化。当然,您可以自省着色器并确定您的属性确实是vec3,因此是 3 个浮点数,但这仍然不能告诉 GL 您的顶点数组缓冲区对象中的数据是什么样的。值得注意的是,它允许包含

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

回答“谁告诉”:您致电glVertexAttribFormat

【讨论】:

你不明白我的问题。我不是在问如何或为什么指定数据格式。我在问为什么 API 是这样设计的——为什么 API 允许每个 VAO 的数据格式规范,而不是每个属性。我很清楚对glGetAttribLocation 的调用仅获取属性的位置,这就是为什么我直接在该代码行上方这么说的原因。我在问为什么 API 不允许我指定属性的格式,因为属性的数据类型是在着色器程序中定义的,并且不会改变(AFAIK)。 你也不对。 glVertexAttribFormat 不允许指定缓冲区中的步幅。 glBindVertexBuffer 是在与属性格式分开指定缓冲区数据时允许这样做的函数。这是一个重要的区别。 为什么要在属性中指定数据应该以某种“形式”(类型/维度/偏移/步幅/规范化)从缓冲区中读取?你真的想重写你的顶点着色器以支持输入缓冲区中的不同布局吗?考虑对从文件加载的网格使用相同的着色器,作者可以选择任何属性交错。您可以轻松匹配 1 VAO = 1 网格(加载网格时),同时继续使用相同的着色器程序。 关于步幅,纯属学术。我提到了glVertexAttribPointer,你在哪里确实超越了。新的glBindVertexBuffer + glVertexAttribFormat 分解了它(一切都很好,现在可读性更强)。仍然没有改变大部分推理。 您在此处写的评论远比您的实际答案有用。尽管您似乎仍然不明白“为什么要在属性中指定应以某种“形式”(类型/维度/偏移/步幅/规范化)从缓冲区中读取数据?”基本上是我最初问的问题,我会给你+1,因为信息很有帮助:)

以上是关于为啥要为一个属性指定不同的顶点格式?的主要内容,如果未能解决你的问题,请参考以下文章

我的OpenGL学习进阶之旅顶点属性顶点数组

我的OpenGL学习进阶之旅顶点属性顶点数组

为啥 3D 模型具有不同 uv 坐标的重复顶点位置

为啥 GPU 外的顶点变换与 GPU 内的顶点变换不同?

为啥 glGetAttribLocation 为两个不同的属性返回相同的值?

为啥禁用顶点属性数组零时OpenGL绘图失败?