OpenGL有多少个VAO?

22

我正在编写一个OpenGL3+应用程序,对VAOs的使用有些困惑。目前我只有一个VAO,一个规范化的四边形设置在原点周围。这个单独的VAO包含3个VBO; 一个用于位置,一个用于表面法线,以及一个GL_ELEMENT_ARRAY_BUFFER用于索引(可以仅存储4个顶点,而不是6个)。

我设置了一些帮助方法来绘制场景中的物体,比如drawCube(),它需要位置和旋转值,并遵循以下步骤;

  • 绑定四边形VAO。
  • 每个立方体面:
    • 创建表示该面的模型矩阵。
    • 将模型矩阵上传到uniform mat4 model顶点着色器变量中。
    • 调用glDrawElements()将四边形绘制到此面的位置。

我刚刚开始添加每个立方体的颜色任务,并意识到我不能将我的颜色VBO添加到单个VAO中,因为它会随着每个立方体的变化而变化,这不太对。

我刚刚阅读了这个问题:OpenGL VAO最佳实践,它告诉我我的方法是错误的,并且我应该使用更多的VAO来保存设置整个场景的工作。

应该使用多少个VAOs? 显然我的方法只有1个不是最佳选择,对于场景中的每个静态表面都应该有一个VAO吗?移动的表面呢?

我正在为每个顶点编写一个统一变量,这是正确的吗? 我读到uniform着色器变量不应在帧中间更改,如果我能够向我的uniform变量写入不同的值,那么统一变量与顶点着色器中的简单in变量有什么不同?


一个VAO不包含任何VBO。这是关于VAO如何工作以及它们的状态向量实际包含什么的一个常见误解。你可以完全初始化一个VAO而不必绑定任何VBO。这样做不能让你将单个顶点推送到管道中,但是这是可能的。话虽如此,在你的情况下,“VBO”这个术语是有歧义的。通常,你会将绑定到GL_ARRAY_BUFFER的缓冲对象称为VBO。这种缓冲对象与绑定到GL_ELEMENT_ARRAY_BUFFER的缓冲对象具有不同的语义。后者的绑定实际上是VAO状态。然而,它们仍然只是缓冲对象。 - thokra
4
“我正在为每个顶点编写一个统一变量,这样做正确吗?” 那不仅是不正确的,而且不可能。你可能混淆了术语或者在做某些你自己没有意识到的事情。 - Nicol Bolas
@NicolBolas 我正在交错调用glDrawElementsglUniformMatrix4来通过更新被声明为uniform mat4 model的模型矩阵在不同的位置绘制相同的VAO。我只能假设这是有效的,因为我的立方体出现在了预期的位置,而这些位置没有被用于其他任何用途。 - lynks
@thokra 谢谢,我感觉 ELEMENT 缓冲区不同,但没有意识到将其称为 VBO 是不正确的。我应该为世界中的每个多边形使用一个 VAO 吗?还是为所有具有相同纹理的表面使用一个 VAO 等等...我正在尝试弄清楚 VAO 应该在什么粒度下使用。 - lynks
2
@lynks:Nicol Bolas 的意思是说,你不能在单个绘制调用中为每个提交的顶点更改统一变量的值。你只能在绘制调用之间更改统一变量,但永远不能为每个顶点更改,除非你为每个顶点发出一个绘制调用,这通常是愚蠢的,并且甚至对于除 POINTS 以外的任何基元类型都不起作用。 - thokra
@lynks:Uniform(统一变量)字面上是统一的,您只需在每个程序中设置一次,它就会在特定着色器阶段处理的每个顶点/基元/片段中保持统一。 - Andon M. Coleman
1个回答

22
显然,我的方法只使用一个VAO是不优化的。在场景中为每个静态表面分配一个VAO是不必要的,因为切换VAO会消耗大量资源。如果你的场景中有几百或几千个对象同时可见,并且每个对象都需要单独的VAO,则需要频繁切换VAO才能渲染这些对象。如果有多个对象共享相同的内存布局(即元素的大小/类型/归一化和步幅都相同),为什么要定义多个存储相同信息的VAO呢?您可以通过对应的绘制调用直接控制从哪里开始提取顶点属性。
对于非索引几何体来说,这很简单,因为您在gl[Multi]DrawArrays*()中提供一个first参数(或者在多绘制情况下提供偏移量数组),该参数定义了关联ARRAY_BUFFER的数据存储器中的偏移量。
对于索引几何体,如果在单个ELEMENT_ARRAY_BUFFER中存储了多个对象的索引,则可以使用gl[Multi]DrawElementsBaseVertex提供常量偏移量以供索引使用,或者在上传到缓冲对象之前手动添加常量偏移量来偏移索引。
提供偏移量可以将多个不同的对象存储在单个ARRAY_BUFFER中,将相应的索引存储在单个ELEMENT_ARRAY_BUFFER中。但是,缓冲区对象的大小应根据您的硬件而定,因为各个供应商的建议不同。
我为每个顶点编写一个uniform变量,这样做正确吗?我读到过 uniform shader 变量不应在帧中间更改,如果我可以向我的uniform变量写入不同的值,那么uniforms和vertex shader中的普通in变量有什么区别?
首先,声明为in/out的uniforms和shader输入/输出变量在各种情况下都有所不同:
• 输入/输出变量定义了着色器阶段之间的接口,即一个着色器阶段的输出变量由后续阶段中相应且同名的输入变量支持。如果使用相同名称声明,则uniform在所有阶段都可用,并且在应用程序更改之前保持不变。
• 顶点着色器内部的输入变量来自ARRAY_BUFFER。uniform块中的uniform由UNIFORM_BUFFER支持。
• 输入变量也可以使用glVertexAttrib*()系列函数直接编写。使用glUniform*()系列函数编写单个uniforms。
• uniforms的值是程序状态。输入变量的值不是。
语义上的差异也很明显:uniforms通常在一组基元中保持不变,而输入变量通常由于插值而针对每个顶点或片元而变化。 编辑:为了澄清并考虑到Nicol Bolas的意见:应用程序不能更改单个绘制调用提交的一组顶点的uniforms,也不能通过调用glVertexAttrib*()更改顶点属性。由缓冲区对象支持的顶点着色器输入将每个顶点更改一次或以某种指定的速率更改,该速率由glVertexAttribDivisor设置。 编辑2:为了澄清VAO理论上如何存储多个布局,您可以简单地使用具有相同语义但不同索引的多个数组来定义。例如,
glVertexAttribPointer(0, 4, ....);

glVertexAttribPointer(1, 3, ....);

你可以定义两个数组,索引为0和1,分别大小为3和4,均指向顶点的位置属性。但是,根据你想要渲染的内容,你可以绑定一个假设的顶点着色器输入。

// if you have GL_ARB_explicit_attrib_location or GL3.3 available, use explicit
// locations
/*layout(location = 0)*/ in vec4 Position; 
或者
/*layout(location = 1)*/ in vec3 Position;

可以直接将数据绑定到索引0或1处,也可以使用glBindAttribLocation()函数来绑定,但仍然使用同一个VAO。据我所知,规范没有说明如果一个属性被启用但当前着色器没有源数据会发生什么,但我猜测在这种情况下实现可能会简单地忽略该属性。

无论您是否从同一缓冲对象中获取上述属性的数据都是另一个问题,但当然是可能的。

个人而言,我倾向于每个布局使用一个VBO和VAO,即如果我的数据由具有相同属性的相等数量的属性组成,则将它们放入单个VBO和单个VAO中。

总体而言:您可以对此进行大量实验。去尝试吧!


那么VAO与任何一个特定的对象都没有关联吗?这可能是我的困惑,因为在阅读我的代码时,我看到我通过将VBO绑定到“in”变量并调用“glEnableVertexAttribArray()”后跟随“glBindBuffer()”来将位置值写入VAO。这两个调用应该是渲染循环的一部分吗?为每个表面交换不同的“position”VBO?现在它们是我的设置的一部分。 - lynks
2
VAO 存储了顶点属性索引和相应缓冲绑定之间的映射列表。当您调用 glVertexAttribPointer 时,当前绑定到 ARRAY_BUFFER 的缓冲对象的名称将记录在指定索引处,并存储在 VAO 中。这就是 VAO 关联索引和缓冲对象的方式。相应的值可以使用 glGetVertexAttribiv() 和 VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 进行查询。请参见 GL 3.3 核心规范第 6.2 节(状态表)中的表 6.5。 - thokra
感谢您的解释。我想我一定对事物的工作方式有一个非常错误的想法。如果您有两个不同位置和纹理的四边形,您会为所有内容使用一个大的VBO和仅一个VAO吗?还是每个四边形都有4个用于位置和纹理的VBO?然后是一个或两个VAO?我猜测是一个VAO,在渲染周期中使用glBindBuffer()glVertexAttribPointer()重新绑定到位置和纹理VBO上? - lynks
1
我建议使用单个VBO/VAO。绝对没有理由需要将四边形放入单独的存储中,属性的内存布局显然是相同的,因此您不需要将内容放入单独的VAO中。此外,请查看我的第二次编辑以获取更多信息。 - thokra
1
如果您为一个模型只使用一个VBO/VAO,那么如果对象的数量发生更改,您如何保留缓冲区的大小?或者如果有一个新对象,您会创建一个新的VBO并将其附加到现有的VAO吗?@thokra - Minee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接