何时将什么绑定到VAO?(这是一个关于IT技术的问题)

29

我已经学习OpenGL三天了,可以完成工作,但感觉只是在复制粘贴而不知道自己在做什么。我认为我严重缺乏关于何时将、属性等绑定到顶点数组对象(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 的行为相同,所以我希望相同的“规则”适用。

Uniform 应该像全局变量一样运作,因此它们不应受到 VAO 的影响。

现在,关于解绑:

如果我将 VBO 绑定到 VAO,然后取消绑定 VBO,VBO 是否会从 VAO 中分离?

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

关于释放资源:

当我删除一个 VBO 时会发生什么?它会从所有的 VAO 中删除吗?还是它们仍然有指向该 VBO 的“悬挂引用”?

关于程序:

如果我想在不同的程序之间重复使用 VBOs,是否可以这样做?然而,如果 VAOs 绑定属性和 VBOs,属性又需要程序参数,那么我能否在不同程序之间重复使用 VAOs?为什么属性需要程序参数?

关于调试:

是否有一种方法可以美观地打印 OpenGL 状态机?我想知道已链接的程序、使用的着色器、存在哪些 VAOs、哪些 VBOs 绑定到哪些 VAOs 上、哪些属性绑定到哪些 VAOs 和 VBOs 上,它们是否已设置并启用,有哪些 Uniforms 等信息。

关于绘制调用:

假设有人给我一个 VAO,并要求我将其绘制出来。有没有一种方法可以知道我应该调用 glDrawArrays 还是 glDrawElements?我能否从 VAO 中查询这些信息?也许还包括存储在其中的 VBO 的大小?


1
应该已经有很多相关的答案了。例如,请查看我在http://stackoverflow.com/questions/26228498/glvertexattribpointer-overwrite和http://stackoverflow.com/questions/25794454/opengl-vertex-array-object-usage上的回答。 - Reto Koradi
谢谢,这些答案确实帮了我很多!我仍然对释放资源、调试VAO和操作系统感到困惑。我会继续寻找答案。 - gnzlbg
如果你释放了绑定到VAO的VBO,那么这个VBO直到不再绑定到VAO上才真正被“释放”(但是你不能再将其绑定)。倾倒整个状态机也相当不切实际;OpenGL是一个非常庞大的状态机。 - Colonel Thirty Two
2个回答

56
不是。VAO不拥有任何数据,因此创建VBO时不能将其绑定到VAO上。要将VBO与VAO相关联,请在更改VAO状态之前先绑定VBO,然后调用glVertexAttribPointer()设置属性指针。不会。你可以在绑定VAO之前绑定VBO,并使用glBufferData()函数填充VBO中的数据。 VBO本质上只是一个简单的数据容器。但在VAO中跟踪的任何类型的顶点属性设置只能在绑定VAO之后进行。 glGetAttribLocation()仅获取属性位置,它不会改变任何状态。您将使用属性位置进行调用,例如glEnableVertexAttribArray()和glVertexAttribPointer()。将属性与给定的VBO关联的操作是在调用glVertexAttribPointer()时建立的,而给定的VBO处于绑定状态。虽然EBO的行为大多数情况下与VBO相同,但不完全相同。 GL_ELEMENT_ARRAY_BUFFER绑定是存储在VAO中的状态的一部分。这很有意义,因为在绘制调用期间仅使用一个元素数组缓冲区(而顶点属性可以来自多个不同的数组缓冲区),并且没有单独的调用指定“从当前绑定的元素数组缓冲区中使用索引数据”,就像glVertexAttribPointer()指定“从当前绑定的数组缓冲区中使用顶点数据”一样。相反,当您调用glDrawElements()时隐式发生这种情况。因此,在绘制调用时需要绑定元素数组缓冲区,并且该绑定是VAO状态的一部分。正确的。统一变量与着色器程序相关联,而不是VAOs。如果我将VBO“绑定”到VAO,然后解除绑定VBO,它是否会从VAO中分离? 不会。

不需要,以上的解释已经涵盖了此问题。

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

当只有一个VAO时什么都不会发生,当有多个VAO时还是一样什么都不会发生。

删除VBO时会发生什么?它会从所有的VAO中删除吗?或者它们仍然有对该VBO的"空引用"?

这是OpenGL中比较复杂的部分之一。如果你能够背诵所有对象类型的确切删除规则(它们并不都相同),那么你已经达到了高级水平... 在这种情况下,VBO将自动从当前绑定的VAO中解除绑定,但是不会从未绑定的其他VAO中解除绑定。如果其他VAO引用VBO,则VBO将保持存在,直到所有这些绑定被断开或删除VAO。

然而,如果VAOs绑定属性和VBOs,并且属性采用程序参数,我可以在多个程序之间重用VAOs吗?

是的,您可以为多个程序使用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并知道当前活动内容一样?这可以让我打印出正在发生的事情,并且我认为编写这样的代码可以帮助我更好地理解。 - gnzlbg
有一件事情我还不太清楚。当我禁用一个属性时会发生什么?调用 glDisableVertexAttribArray 会从当前绑定的 VAO 中移除一个属性吗?如果是,它只会从当前绑定的 VAO 中移除吗? - gnzlbg
是的,它修改了当前绑定的VAO中的状态。我编辑了我的答案并将其添加到列表中。这些规则与“glEnableVertexAttribArray()”完全等效。可以将每个属性想象为具有“启用”属性,该属性是VAO状态的一部分。glEnableVertexAttribArray()glDisableVertexAttribArray()都会修改此属性的值。一个调用将其设置为“true”,另一个调用将其设置为“false”。 - Reto Koradi
谢谢!我看了你的其他答案,也受益匪浅。但是我仍然觉得整个VAO的事情并不傻瓜化(希望随着时间的推移会变得更好)。例如,我喜欢VBO的工作方式(请求缓冲区,获取缓冲区,对其进行任何操作,并在需要时将其删除)。感觉很熟悉(就像malloc / new / make_unique)。然而,VAO /属性的设计由于滥用副作用而显得不必要地复杂。VAO /属性很轻量级,应该像创建和配置VAO /属性结构体一样简单,一次性传递给OpenGL即可。 - gnzlbg
谢谢你的回答。我有一个问题... "因此,在绘制调用时需要绑定元素数组缓冲区,而这个绑定是VAO状态的一部分。"。所以如果有多个对象具有自己的元素缓冲区,它们需要在绑定VAO之前绑定,对吗?像这样: ` // 绑定对象1的元素数组 // 绑定对象1的VAO // 对象1的drawelements调用// 绑定对象2的元素数组 // 绑定对象2的VAO // 对象2的drawelements调用...` VAO是否保持关于使用哪个元素数组的状态? - sonofrage
2
@sonofrage 不,绑定元素缓冲区到VAO之前是没有意义的,因为第一次绑定会被VAO状态中的元素缓冲区绑定覆盖。你可以在VAO之后绑定元素缓冲区。或者更好的方法是,在最初设置对象的VAO时绑定元素缓冲区。然后你只需要在绘制调用之前绑定VAO,并且绑定正确的元素缓冲区。 - Reto Koradi

18

VAO表可以在规范的“状态表”部分找到

伪代码如下:

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];
};

值得注意的是,程序状态(绑定哪一个、统一值等)明显缺失。


1
感谢提供规范的链接! - lmat - Reinstate Monica

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