【问题标题】:What is the role of glBindVertexArrays vs glBindBuffer and what is their relationship?glBindVertexArrays 与 glBindBuffer 的作用是什么,它们的关系是什么?
【发布时间】:2014-03-06 07:53:12
【问题描述】:

我是 OpenGL 和图形编程的新手。到目前为止,我一直在阅读一本非常透彻且写得很好的教科书。但是,我在代码中遇到了一个我不太理解的地方,我想在我理解这些行之前继续前进。

GLuint abuffer;

glGenVertexArrays(1, &abuffer);
glBindVertexArray(abuffer);

GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);

本书解释了前三行是创建一个顶点数组对象,用于将关联数据与顶点数组捆绑在一起。第二行找到一个未使用的名称(我猜是存储在abuffer 中的无符号整数标识符),第三行创建对象/使其处于活动状态。

这本书解释了第 4 到第 7 行创建了一个 缓冲区对象 来存储我们的数据,第 5 行给了我们一个未使用的标识符(类似于第 2 行的顶点数组对象?),第 6 行创建缓冲区,第 7 行在 CPU 上分配足够的内存并为 GL_STATIC_DRAW 创建指向我们的数据(点)的指针。

对象处于活动状态是什么意思?您随后何时使用abuffer?顶点数组捆绑关联数据是什么意思,数据是什么时候与这个顶点数组对象关联的?

我对@9​​87654326@ 和buffer 之间的关系感到困惑。我对顶点数组与缓冲区对象的关系是什么以及在什么时候形成这种关系感到困惑。我不确定它们实际上是否相关,但它们在教科书中一个接一个地出现。

任何帮助将不胜感激。谢谢。

【问题讨论】:

    标签: c++ opengl


    【解决方案1】:

    从低层次的角度来看,您可以将数组视为由两部分组成:

    • 有关数组大小、形状和类型的信息(例如,32 位浮点数,包含每行四个元素的向量)。

    • 数组数据,不过是一大块字节。

    尽管低级概念基本保持不变,但多年来指定数组的方式已经发生了数次变化。

    OpenGL 3.0 / ARB_vertex_array_object

    这就是你可能今天应该做的事情。很少有人无法运行 OpenGL 3.x,但仍然有钱可以花在您的软件上。

    OpenGL 中的缓冲区对象是一大块比特。将“活动”缓冲区视为一个全局变量,并且有许多函数使用活动缓冲区而不是使用参数。这些全局状态变量是 OpenGL 丑陋的一面(之前 直接状态访问,如下所述)。

    GLuint buffer;
    
    // Generate a name for a new buffer.
    // e.g. buffer = 2
    glGenBuffers(1, &buffer);
    
    // Make the new buffer active, creating it if necessary.
    // Kind of like:
    // if (opengl->buffers[buffer] == null)
    //     opengl->buffers[buffer] = new Buffer()
    // opengl->current_array_buffer = opengl->buffers[buffer]
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    
    // Upload a bunch of data into the active array buffer
    // Kind of like:
    // opengl->current_array_buffer->data = new byte[sizeof(points)]
    // memcpy(opengl->current_array_buffer->data, points, sizeof(points))
    glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
    

    现在,您的典型顶点着色器将 vertexes 作为输入,而不是一大块位。因此,您需要指定如何将位块(缓冲区)解码为顶点。这就是数组的工作。同样,有一个“活动”数组,您可以将其视为一个全局变量:

    GLuint array;
    // Generate a name for a new array.
    glGenVertexArrays(1, &array);
    // Make the new array active, creating it if necessary.
    glBindVertexArray(array);
    
    // Make the buffer the active array buffer.
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    // Attach the active buffer to the active array,
    // as an array of vectors with 4 floats each.
    // Kind of like:
    // opengl->current_vertex_array->attributes[attr] = {
    //     type = GL_FLOAT,
    //     size = 4,
    //     data = opengl->current_array_buffer
    // }
    glVertexAttribPointer(attr, 4, GL_FLOAT, GL_FALSE, 0, 0);
    // Enable the vertex attribute
    glEnableVertexAttribArray(attr);
    

    OpenGL 2.0(旧方式)

    在 OpenGL 2.x 中,没有顶点数组,数据只是全局的。您仍然必须调用glVertexAttribPointer()glEnableVertexAttribArray(),但您必须在每次使用缓冲区时调用它们。在 OpenGL 3.x 中,您只需设置一次数组。

    回到 OpenGL 1.5,您实际上可以使用缓冲区,但您使用单独的函数来绑定每种数据。例如,glVertexPointer() 用于顶点数据,glNormalPointer() 用于普通数据。在 OpenGL 1.5 之前,没有缓冲区,但您可以使用指向应用程序内存的指针。

    OpenGL 4.3 / ARB_vertex_attrib_binding

    在 4.3 中,或者如果您有 ARB_vertex_attrib_binding 扩展,您可以分别指定属性格式和属性数据。这很好,因为它可以让您轻松地在不同缓冲区之间切换一个顶点数组。

    GLuint array;
    // Generate a name for a new array array.
    glGenVertexArrays(1, &array);
    // Make the new array active, creating it if necessary.
    glBindVertexArray(array);
    
    // Enable my attributes
    glEnableVertexAttribArray(loc_attrib);
    glEnableVertexAttribArray(normal_attrib);
    glEnableVertexAttribArray(texcoord_attrib);
    // Set up the formats for my attributes
    glVertexAttribFormat(loc_attrib,      3, GL_FLOAT, GL_FALSE, 0);
    glVertexAttribFormat(normal_attrib,   3, GL_FLOAT, GL_FALSE, 12);
    glVertexAttribFormat(texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 24);
    // Make my attributes all use binding 0
    glVertexAttribBinding(loc_attrib,      0);
    glVertexAttribBinding(normal_attrib,   0);
    glVertexAttribBinding(texcoord_attrib, 0);
    
    // Quickly bind all attributes to use "buffer"
    // This replaces several calls to glVertexAttribPointer()
    // Note: you don't need to bind the buffer first!  Nice!
    glBindVertexBuffer(0, buffer, 0, 32);
    
    // Quickly bind all attributes to use "buffer2"
    glBindVertexBuffer(0, buffer2, 0, 32);
    

    OpenGL 4.5 / ARB_direct_state_access

    在 OpenGL 4.5 中,或者如果您有 ARB_direct_state_access 扩展,您不再需要调用 glBindBuffer()glBindVertexArray() 来进行设置……您可以直接指定数组和缓冲区。只需要在最后绑定数组即可绘制。

    GLuint array;
    // Generate a name for the array and create it.
    // Note that glGenVertexArrays() won't work here.
    glCreateVertexArrays(1, &array);
    // Instead of binding it, we pass it to the functions below.
    
    // Enable my attributes
    glEnableVertexArrayAttrib(array, loc_attrib);
    glEnableVertexArrayAttrib(array, normal_attrib);
    glEnableVertexArrayAttrib(array, texcoord_attrib);
    // Set up the formats for my attributes
    glVertexArrayAttribFormat(array, loc_attrib,      3, GL_FLOAT, GL_FALSE, 0);
    glVertexArrayAttribFormat(array, normal_attrib,   3, GL_FLOAT, GL_FALSE, 12);
    glVertexArrayAttribFormat(array, texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 24);
    // Make my attributes all use binding 0
    glVertexArrayAttribBinding(array, loc_attrib,      0);
    glVertexArrayAttribBinding(array, normal_attrib,   0);
    glVertexArrayAttribBinding(array, texcoord_attrib, 0);
    
    // Quickly bind all attributes to use "buffer"
    glVertexArrayVertexBuffer(array, 0, buffer, 0, 32);
    
    // Quickly bind all attributes to use "buffer2"
    glVertexArrayVertexBuffer(array, 0, buffer2, 0, 32);
    
    // You still have to bind the array to draw.
    glBindVertexArray(array);
    glDrawArrays(...);
    

    ARB_direct_state_access 很不错,原因有很多。您可以忘记绑定数组和缓冲区(绘制时除外),因此您不必考虑 OpenGL 正在为您跟踪的隐藏全局变量。您可以忘记“为对象生成名称”和“创建对象”之间的区别,因为 glCreateBuffer()glCreateArray() 两者同时进行。

    伏尔甘

    Vulkan 更进一步,您是否编写了类似于我上面写的伪代码的代码。所以你会看到类似的东西:

    // This defines part of a "vertex array", sort of
    VkVertexInputAttributeDescription attrib[3];
    attrib[0].location = 0; // Feed data into shader input #0
    attrib[0].binding = 0;  // Get data from buffer bound to slot #0
    attrib[0].format = VK_FORMAT_R32G32B32_SFLOAT;
    attrib[0].offset = 0;
    // repeat for attrib[1], attrib[2]
    

    【讨论】:

    • +1 用于描述 GL 的选择器/锁存器机制(eg 顶点指针应用于的缓冲区对象取自绑定对象,而不是比传递的参数)。这个 API 设计可以使用GL_EXT_direct_state_access 来解决,但不幸的是,这确实排除了在 Mesa、Intel 或 Apple 驱动程序上运行您的代码的可能性:-\ 但是,我认为指出顶点数组自 GL 1.1 以来就存在很重要。 GL 2.0 中唯一改变的是通用顶点属性槽的引入。
    • 说到OpenGL 1.1,这个版本还引入了纹理objects。与顶点数组对象一样,在引入纹理对象之前,GL 已经支持纹理,但数据/状态并未持久存储。大多数软件不是在 OpenGL 1.0 中更改绑定的纹理对象,而是在显示列表中执行纹理数据/状态设置。 GL 3.0 对顶点数组做了类似的事情,它引入了顶点数组objects
    • 喜欢这里的历史背景。学习 GL 最令人沮丧的事情之一就是 API 和最佳实践随着时间的推移发生了怎样的变化。
    • 对于您在 OpenGL4.5 上的示例,您不应该使用 glCreateVertexArrays(1, &array) 而不是 glGenVertexArrays(1, &array) 吗?据我了解,在您第一次使用glGenVertexArrays 绑定它之前不会创建 vao,所以您的 glEnableVertexArray* / glVertexArray* 调用都不起作用?
    • @Acorn:很好,我已经对其进行了修改,以明确生成名称和创建对象之间的区别。
    【解决方案2】:

    您对这本书的解释并不完全正确。顶点数组对象不存储数据。它们是一类称为容器的对象,如帧缓冲区对象。您可以将其他对象与它们附加/关联,但它们本身从不存储数据。因此,它们不是上下文可共享的资源。

    基本上,顶点数组对象封装了 OpenGL 3.0 中的顶点数组状态。从 OpenGL 3.1(代替 GL_ARB_compatibility)和 OpenGL 3.2+ 核心配置文件开始,您必须始终有一个非零 VAO 绑定才能使 glVertexAttribPointer (...)glDrawArrays (...) 等命令正常运行。绑定的 VAO 为这些命令形成必要的上下文,并永久存储状态。

    在旧版本的 GL(和兼容性)中,VAO 存储的状态是全局状态机的一部分。

    还值得一提的是,GL_ARRAY_BUFFER 的“当前”绑定不是 VAO 跟踪的状态之一。虽然glVertexAttribPointer (...) 等命令使用此绑定,但 VAO 不存储绑定,它们仅存储指针(与 GL 4.3 一起引入的 GL_ARB_vertex_attrib_binding 扩展使这有点复杂,所以为了简单起见,我们忽略它)。

    VAOs确实记住绑定到GL_ELEMENT_ARRAY_BUFFER 的内容,但是,像glDrawElements (...) 这样的索引绘图命令会像您期望的那样运行(例如 VAO 重新使用最后一个元素数组缓冲区绑定)。

    【讨论】:

    • 为什么glVertexAttribPointer需要绑定GL_ARRAY_BUFFER这样的缓冲区? glVertexAttribPointer 不只是设置 VAO 的状态吗?
    • @BradZeis: glVertexAttribPointer 建立一个指针,该指针与调用函数时绑定到 GL_ARRAY_BUFFER 的任何对象所拥有的内存相关。老实说,这是一种愚蠢的设计——在像 D3D 这样设计的 API 中,您只需将缓冲区对象的 ID 传递给VertexAttribPointer,而不必绑定任何东西。但在 OpenGL 中,glVertexPointer (...) 在引入 VBO 之前已经存在了 10 多年,他们希望尽可能多地重用 API 而无需修改。他们创建了这个首先绑定非零 VBO 业务以避免添加另一个参数。
    • 谢谢!将 VAO 视为指针更有意义。
    • 感谢您指出 GL_ARRAY_BUFFER 的绑定不是 VAO 跟踪的状态之一
    【解决方案3】:

    调用glVertexAttribPointer时创建关系。

    GL_VERTEX_ARRAY_BINDINGGL_ARRAY_BUFFER_BINDING 是常量,但它们可以指向绑定的全局状态。我指的是状态而不是图像中的常量(橙色)。使用glGet 查找不同的全局状态。

    VertexArray 是关于一个顶点或多个平行顶点的分组信息(包括数组缓冲区)。

    使用GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDINGglGetVertexAttrib 查找设置了哪个属性数组缓冲区。

    glBindBuffer(GL_ARRAY_BUFFER 设置全局状态GL_ARRAY_BUFFER_BINDING

    glBindVertexArray 设置全局状态GL_VERTEX_ARRAY_BINDING

    【讨论】:

    • 嘿,这是一张很棒的图表。你介意分享它的来源吗?
    • @dawid 我在 lucidchart lucidchart.com/invitations/accept/… 中做到了。我以后可能会删除它,因为免费帐户有文件限制。
    【解决方案4】:

    OpenGL 是一个有状态的接口。这很糟糕,过时和丑陋,但这是你的遗产。

    顶点数组对象是缓冲区绑定的集合,驱动程序可以使用它来获取绘制调用的数据,大多数教程只使用一个,而从不解释如何使用多个 VAO。

    缓冲区绑定告诉 opengl 将该缓冲区用于相关方法,尤其是 glVertexAttribPointer 方法。

    【讨论】:

    • StackOverflow 中一些令人印象深刻的启发式试图阻止我称赞你批评 OpenGL 的状态性。
    【解决方案5】:

    VertexArray 和 VBO 之间没有关系。

    顶点数组在 RAM 中分配内存并将指针发送到 API。 VBO 在显卡中分配内存——系统内存没有地址。如果您需要从系统中访问 vbo 数据,您需要先将其从 vbo 复制到系统中。

    此外,在较新版本的 OpenGL 中,顶点数组已被完全删除(在 3.0 中已弃用,在 3.1 中已删除)

    【讨论】:

    • 顶点数组在 3.x 中没有被移除。顶点数组对象在 3.0 中引入,在核心配置文件的更高版本中强制
    • 问题是关于顶点数组对象,而不是一般的顶点数组。即便如此,顶点数组也不会被弃用。用于设置它们的 API 发生了根本性的变化(不再有专门的 VetexPointer、NormalPointer 等...),但顶点数组仍然存在(glVertexAttrib{I|L}Pointer (...) 替代了一些东西就像现代 GL 中的 glVertexPointer (...))。
    猜你喜欢
    • 1970-01-01
    • 2011-03-28
    • 2019-07-22
    • 2012-03-12
    • 2012-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多