【问题标题】:When is What bound to a VAO?什么时候绑定到 VAO?
【发布时间】:2014-10-24 16:54:38
【问题描述】:

我已经学习 OpenGL 三天了,我可以完成一些事情,但我感觉就像在不知道自己在做什么的情况下复制粘贴。我认真地认为我缺乏关于什么时候what(VBO,属性,...)绑定到顶点数组对象(VAO)的基本理解,并且没有找到任何资源来澄清这些方面详细。

特别是,这些是我的一些问题。如果我创建一个 VAO:

GLuint vao;
glGenVertexArrays(1, &vao);

在我绑定 VAO 之前可以绑定任何东西吗? (如果我现在创建一个 VBO,它会绑定到 VAO 吗?)

glBindVertexArray(vao);

绑定VAO后,如果我创建一个VBO:

GLuint vbo;
glGenBuffers(1, &vbo);

它是否绑定到 VAO?还是我绑定的时候会出现这种情况?

glBindVertexArray(vbo);

或者当我向它复制一些东西时?

如果我得到一个属性位置:

att = glGetAttribLocation(program_id, "name");

它是否绑定到 VAO?还是启用后会发生:

glEnableVertexAttribArray(att);

...或设置后:

glVertexAttribPointer(att, ...);

?

我猜 EBO 的行为就像 VBO,所以我希望同样的“规则”适用。

Uniforms 的行为应该像全局变量,因此它们根本不受 VAO 的影响。

现在,关于解除绑定:

如果我将 VBO“绑定”到 VAO,然后解除绑定 VBO,它会与 VAO 分离吗?

如果我有一个绑定到多个 VAO 的 VBO,当我取消绑定该 VBO 时会发生什么?

关于释放资源:

当我删除 VBO 时会发生什么?它会从所有 VAO 中删除吗?还是他们仍然对那个 VBO 有“悬空引用”?

关于程序:

IIUC 我可以在程序之间重用 VBO。但是,如果 VAO 绑定了属性和 VBO,并且属性带有程序参数,我可以在程序之间重用 VAO 吗?为什么属性完全采用程序参数?

关于调试:

有没有办法漂亮地打印 OpenGL 状态机?我想知道已链接的程序,与哪些着色器,存在哪些 VAO,哪些 VBO 绑定到哪些 VAO,哪些属性绑定到哪些 VAO 和 VBO,是否已设置?他们启用了吗?那里有哪些制服……

关于绘图调用:

假设有人给了我一个 VAO,我必须画出来。有没有办法知道我应该调用 glDrawArrays 还是 glDrawElements?我可以从 VAO 以某种方式查询这些信息吗?也许连同存储在那里的我的 VBO 的大小?

【问题讨论】:

  • 应该已经有很多相关的答案了。例如,查看我在 stackoverflow.com/questions/26228498/…stackoverflow.com/questions/25794454/… 上的回答。
  • 谢谢,这些答案确实有很大帮助!我仍然对释放资源、调试 VAO 和操作系统感到困惑。我会继续寻找。
  • 如果释放绑定到 VAO 的 VBO,则 VBO 实际上并没有“释放”,直到它不再绑定到 VAO(但你不能再绑定它了)。转储整个状态机也是相当不切实际的; OpenGL 是一个相当大的状态机。

标签: c++ opengl


【解决方案1】:

这是很多子问题。但由于这是一个经常让新 OpenGL 爱好者感到困惑的领域,让我尝试提供一些内容,希望能帮助更多人。我会故意略读一些细节,比如不是来自缓冲区的顶点属性,以避免在这里写一本书。

要理解的关键是VAO 是状态 的集合。它不拥有任何数据。拥有顶点数据的是 VBO。另一方面,VAO 包含用于描述绘图调用从何处获取其顶点属性的所有状态。这包括,对于每个属性:

  • 如果已启用。
  • 属性存储在哪个缓冲区中。
  • 数据从缓冲区中的哪个偏移量开始。
  • 后续属性之间的间距(也称为步幅)。
  • 数据的类型。
  • 组件的数量。

另外,只有一次:

  • 绑定了哪个元素数组缓冲区。

将此映射到 API 调用,以下调用会更改当前绑定的 VAO 跟踪的状态:

  • glEnableVertexAttribArray(...)
  • glDisableVertexAttribArray(...)
  • glVertexAttribPointer(...)
  • glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ...)

请注意,这包含GL_ARRAY_BUFFER 的当前绑定。用于每个属性的缓冲区被间接跟踪,基于为特定属性调用glVertexAttribPointer() 时绑定的缓冲区。

这应该为具体的子问题奠定基础:

如果我创建了一个 VAO,在我绑定 VAO 之前可以绑定任何东西吗?

没有。需要先绑定 VAO,然后才能修改其中存储的任何状态。

(如果我现在创建一个 VBO,它是否绑定到 VAO?)

没有。您可以在绑定 VAO 之前绑定 VBO,并使用 glBufferData() 用数据填充 VBO。 VBO 本质上只是一个哑数据容器。但是在VAO中跟踪的任何一种顶点属性设置都只能在绑定VAO之后进行。

如果我得到一个属性位置,它会绑定到 VAO 吗?

不,glGetAttribLocation() 只做顾名思义,即获取属性位置。它不会改变任何状态。您将使用属性位置来调用 glEnableVertexAttribArray()glVertexAttribPointer()

或者是在启用它之后发生...还是在设置它之后发生

当您在绑定给定 VBO 时调用 glVertexAttribPointer() 时,将建立属性与给定 VBO 的关联。

我猜 EBO 的行为就像 VBO,所以我希望同样的“规则”适用。

大部分,但不完全。 GL_ELEMENT_ARRAY_BUFFER 绑定存储在 VAO 中的状态的一部分。这是有道理的,因为只有一个元素数组缓冲区用于绘制调用(而顶点属性可能来自多个不同的数组缓冲区),并且没有单独的调用指定“使用当前绑定的元素数组缓冲区中的索引数据”,如glVertexAttribPointer() 指定“使用当前绑定的数组缓冲区中的顶点数据”。相反,当您调用glDrawElements() 时,这会隐式发生。所以在draw call的时候需要绑定元素数组buffer,这个绑定是VAO状态的一部分。

Uniforms 的行为应该像全局变量,因此它们根本不受 VAO 的影响。

正确。 Uniform 与着色器程序相关联,而不是 VAO。

如果我将 VBO“绑定”到 VAO,然后解除绑定 VBO,它会与 VAO 分离吗?

没有。我相信上面的解释已经涵盖了这一点。

如果我有一个绑定到多个 VAO 的 VBO,当我取消绑定该 VBO 时会发生什么?

由于一个 VAO 没有任何反应,因此多个 VAO 仍然没有任何反应。

当我删除 VBO 时会发生什么?它会从所有 VAO 中删除吗?还是他们仍然对那个 VBO 有“悬空引用”?

这是 OpenGL 的黑暗角落之一。如果你能背诵所有对象类型的准确删除规则(它们并不完全相同),你已经达到了高级水平......在这种情况下,VBO会自动与当前绑定的VAO绑定>,但不是来自当前未绑定的其他 VAO。如果其他 VAO 引用了 VBO,则 VBO 将保持活动状态,直到所有这些绑定都被破坏或 VAO 被删除。

但是,如果 VAO 绑定了属性和 VBO,并且属性带有程序参数,我可以在程序之间重用 VAO 吗?

是的,您可以将 VAO 用于多个程序。程序状态和 VAO 状态是独立的。程序中的顶点属性位置指定使用哪个顶点属性来获取顶点着色器中每个attribute/in 变量的值。

只要多个程序对相同的属性使用相同的位置,就可以使用相同的 VAO。为此,您可能希望通过使用顶点着色器的layout (location=...) 指令或在链接程序之前调用glBindAttribLocation() 来指定每个程序的属性位置。

有没有办法漂亮地打印 OpenGL 状态机?

glGet*() 调用可以让您检索几乎所有当前的 OpenGL 状态。不方便,但都可以。许多平台/供应商还提供开发人员工具,允许您查看程序执行中给定点的 OpenGL 状态。

假设有人给了我一个 VAO,我必须画出来。有没有办法知道我应该调用 glDrawArrays 还是 glDrawElements?

这是一个不寻常的场景。大多数时候,您创建了 VAO,因此您知道如何绘制它。或者,如果其他人创建了它,您会要求他们绘制它。但是如果真的需要,可以用glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, ...)获取当前绑定的元素数组缓冲区。

【讨论】:

  • 感谢您提供如此明确的答案。您认为在应用程序中拥有“vao 管理器”是否值得?就像一个包含创建/销毁 vaos/vbos/shaders/attributes/programs 并知道当前处于活动状态的小类?它可以让我漂亮地打印正在发生的事情,我认为写这样的东西可以帮助我更好地理解这一点。
  • 有一件事我还不清楚。当我禁用某个属性时会发生什么?对glDisableVertexAttribArray 的调用是否会从当前绑定的 VAO 中删除一个属性?如果是,它是否仅将其从当前绑定的 VAO 中删除?
  • 是的,这会修改当前绑定的 VAO 中的状态。我编辑了答案以将其添加到列表中。规则完全等同于glEnableVertexAttribArray()。想象每个属性都有一个“启用”属性,这是 VAO 状态的一部分。 glEnableVertexAttribArray()glDisableVertexAttribArray() 都修改了这个属性的值。一次调用将其设置为“true”,另一次调用将其设置为“false”。
  • 谢谢!我已经阅读了您的其他一些答案,它们也提供了很多帮助。不知何故,我仍然觉得整个 VAO 的事情都不是白痴证明(我希望随着时间的推移会变得更好)。例如。我喜欢 VBO 的工作方式(请求缓冲区,获取缓冲区,随心所欲地使用它,并在需要时删除它)。感觉很熟悉(比如 malloc/new/make_unique)。由于滥用副作用,VAO/属性 OTOH 的设计不必要地复杂。 VAO/属性是轻量级的,它应该像拥有​​可以创建和配置并立即传递给 OpenGL 的 VAO/属性结构一样简单。
  • @sonofrage 不,在 VAO 之前绑定元素缓冲区没有意义,因为第一个绑定将被作为 VAO 状态一部分的元素缓冲区绑定覆盖。您可以在 VAO 之后绑定元素缓冲区。或者更好的是,在最初为对象设置 VAO 时绑定元素缓冲区。那么你只需要在draw call之前绑定VAO,并绑定正确的元素缓冲区。
【解决方案2】:

VAO 表可以在State Tables section of the spec中找到

在伪代码中它看起来像:

struct VAO{
    GL_INT element_array_binding; //IBO used for glDrawElements and friends
    char* label;//for debugging

    struct{//per attribute values
        bool enabled; //whether to use a VBO for it

        //corresponding to the values passed into glVertexAttribPointer call
        int size;
        unsigned int stride; 
        GL_ENUM type; 
        bool normalized;
        bool integer; //unconverted integers
        bool long; //double precision
        void* offset;
        int bufferBinding;//GL_ARRAY_BUFFER bound at time of glVertexAttribPointer call

        int attributeDiviser; //as used for instancing 

    } attributes[MAX_VERTEX_ATTRIBS];
};

明显不存在的是程序状态(哪个是绑定的、统一的值等)

【讨论】:

  • 感谢规范的链接!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
  • 2018-08-27
  • 1970-01-01
  • 2014-12-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多