glBindVertexArrays vs glBindBuffer的作用是什么,它们之间的关系是什么?


75

我是OpenGL和Graphics Programming的新手。到目前为止,我一直在阅读一本非常详尽且编写得很好的教科书,但是我碰到了我不太了解的代码中的一个要点,在此之前我想先了解一下这些内容继续。

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?顶点数组捆绑关联的数据是什么意思,数据何时与该顶点数组对象关联?

我对abuffer和之间的关系感到困惑buffer。我对顶点数组与缓冲区对象的关系以及在什么时候形成该关系感到困惑。我不确定它们是否确实相关,但是它们在教科书中紧接着出现。

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

Answers:


145

从低层次的角度来看,您可以认为数组有两个部分:

  • 有关数组的大小,形状和类型的信息(例如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);

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

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]

6
+1用于描述GL的选择器/锁存器机制(例如,从绑定对象而不是传递的参数中获取应用于顶点指针的缓冲区对象)。可以使用解决该API设计问题GL_EXT_direct_state_access,但是不幸的是,这确实排除了在Mesa,Intel或Apple驱动程序上运行代码的可能:-\但是,我认为必须指出,自GL 1.1起就存在了顶点阵列。GL 2.0中唯一发生变化的是引入了通用顶点属性槽。
Andon M. Coleman 2014年

4
说到OpenGL 1.1,此版本还引入了纹理对象。像“顶点数组对象”一样,在引入“纹理对象”之前,GL已经支持纹理了,但是数据/状态并未持久存储。并不是在OpenGL 1.0中更改绑定的纹理对象,而是编写了大多数软件来在显示列表中执行纹理数据/状态设置。GL 3.0对顶点数组做了类似的事情,它引入了顶点数组对象
安东·科尔曼

7
喜欢这里的历史背景。关于学习GL的最令人沮丧的事情之一就是API和最佳实践随着时间的变化。
Cheezmeister

1
对于在OpenGL4.5上的示例,您不应该使用glCreateVertexArrays(1, &array)overglGenVertexArrays(1, &array)吗?据我了解,要先使用绑定vao,才能创建vao glGenVertexArrays,因此glEnableVertexArray * / glVertexArray *调用都不起作用?
橡子

1
@Acorn:很好,我已经对其进行了修改,以试图弄清楚生成名称和创建对象之间的区别。
Dietrich Epp

15

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

基本上,顶点数组对象在OpenGL 3.0中封装了顶点数组状态。从OpenGL 3.1(代替GL_ARB_compatibility)和OpenGL 3.2+ Core配置文件开始,您必须始终将非零VAO绑定为类似glVertexAttribPointer (...)glDrawArrays (...)功能的命令。绑定的VAO形成了这些命令的必要上下文,并持久存储状态。

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

另外,值得一提的是,“当前”的结合GL_ARRAY_BUFFER不是该规定,VAOs赛道之一。尽管此绑定由诸如之类的命令使用glVertexAttribPointer (...),但VAO并不存储绑定,而是仅存储指针(GL_ARB_vertex_attrib_bindingGL 4.3附带引入的扩展使它有些复杂,因此为简单起见,我们忽略它)。

VAO确实记住了绑定的内容GL_ELEMENT_ARRAY_BUFFER,因此索引的绘图命令glDrawElements (...)您期望的功能)(例如, VAO重用了最后一个元素数组缓冲区的绑定)。


为什么glVertexAttribPointer需要绑定GL_ARRAY_BUFFER之类的缓冲区?glVertexAttribPointer是否仅设置VAO的状态?
布拉德·蔡斯

1
@BradZeis:glVertexAttribPointer建立一个相对于GL_ARRAY_BUFFER调用该函数时绑定到的内存的指针。老实说,这是一种愚蠢的设计-在像D3D这样的API中,您只需将buffer对象的ID传递给,VertexAttribPointer而不必绑定任何东西。但是在OpenGL中,glVertexPointer (...)在引入VBO之前已经存在了10多年了,他们希望在不进行修改的情况下重用尽可能多的API。他们创建了此绑定的非零VBO优先业务,以避免添加另一个参数。
安东·科尔曼

谢谢!将VAO视为指针会更有意义。
布拉德·蔡斯

1
感谢您指出GL_ARRAY_BUFFER的绑定不是VAO跟踪的状态之一
neevek

6

调用glVertexAttribPointer时将创建关系。

VertexArrays概述

GL_VERTEX_ARRAY_BINDING并且GL_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

@dawid我在lucidchartlucidchart.com/invitations/accept/…中做到了。我将来可能会删除它,因为免费帐户存在文件限制。
约西(Jossi)

5

OpenGL是一个有状态的界面。这是不好的,过时的和丑陋的,但这对你们来说是遗产。

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

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


1
StackOverflow中一些令人印象深刻的启发式尝试试图阻止我因批评OpenGL的有状态性而称赞您。
ArchaeaSoftware 2015年

-2

VertexArray和VBO之间没有关系。

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

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


1
在3.x中不会删除顶点数组。顶点数组对象在3.0中引入,在核心配置文件的更高版本中是必需的
Dietrich Epp 2014年

2
问题是关于顶点数组对象,而不是一般的顶点数组。即使这样,也不反对使用顶点数组。用来设置它们的API已经从根本上改变了(没有更多的专业VetexPointer,NormalPointer,等...),但是顶点数组仍然存在(glVertexAttrib{I|L}Pointer (...)替代品之类的东西glVertexPointer (...)在现代GL)。
安东·科尔曼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.