我正在学习arcsynthesis上的优秀教程,同时构建了一个图形引擎,并发现我并不像我认为的那样理解VAOS。
来自教程Chapter 5. Objects In Depth
缓冲区绑定和属性关联
您可能会注意到glBindBuffer(GL_ARRAY_BUFFER)不在该列表中,即使它是用于渲染的属性设置的一部分。绑定到GL_ARRAY_BUFFER不是VAO的一部分,因为调用glBindBuffer(GL_ARRAY_BUFFER)时不会发生缓冲区对象和顶点属性之间的关联。当您调用glVertex AttribPointer时,会发生这种关联。
调用glVertex AttribPointer时,OpenGL会使用绑定到GL_ARRAY_BUFFER的该调用时刻的任何缓冲区,并将其与给定的顶点属性关联。将GL_ARRAY_BUFFER绑定视为glVertex AttribPointer读取的全局指针。因此,在调用glVertex AttribPointer后,您可以自由地将所需的任何内容绑定到GL_ARRAY_BUFFER;这在最终渲染中不会产生任何影响。因此,VAO确实存储哪些缓冲区对象与哪些属性相关联;但它们不存储GL_ARRAY_BUFFER绑定本身。
我最初错过了最后一行“...但它们不存储GL_ARRAY_BUFFER绑定本身”。在我注意到这一行之前,我以为一旦调用glVertex AttribPointer,当前绑定的缓冲区就被保存了。由于缺少这些知识,我创建了一个网格类,并能够正确地获得一个具有多个网格渲染的场景。
下面列出了该代码的一部分。请注意,我没有在DRAW函数中调用glBindBuffer。
// MESH RENDERING
/* ... */
/* SETUP FUNCTION */
/* ... */
// Setup vertex array object
glGenVertexArrays(1, &_vertex_array_object_id);
glBindVertexArray(_vertex_array_object_id);
// Setup vertex buffers
glGenBuffers(1, &_vertex_buffer_object_id);
glBindBuffer(GL_ARRAY_BUFFER, _vertex_buffer_object_id);
glBufferData(GL_ARRAY_BUFFER, _vertices.size() * sizeof(vertex), &_vertices[0], GL_STATIC_DRAW);
// Setup vertex attributes
glEnableVertexAttribArray(e_aid_position);
glEnableVertexAttribArray(e_aid_normal);
glEnableVertexAttribArray(e_aid_color);
glEnableVertexAttribArray(e_aid_tex);
glVertexAttribPointer(e_aid_position, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid*)offsetof(vertex, pos));
glVertexAttribPointer(e_aid_normal, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid*)offsetof(vertex, norm));
glVertexAttribPointer(e_aid_color, 4, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid*)offsetof(vertex, col));
glVertexAttribPointer(e_aid_tex, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid*)offsetof(vertex, tex));
/* ... */
/* DRAW FUNCTION */
/* ... */
glBindVertexArray(_vertex_array_object_id);
glDrawArrays(GL_TRIANGLES, 0, _vertices.size());
现在我要开始照明了,我想做一些调试工作,这样我就可以验证我所有的法线都是正确的。目前,我只将要为一帧渲染的所有行存储在一个向量中。由于此数据可能会更改每一帧,因此我使用GL_DYNAMIC_DRAW并在渲染之前指定数据。
最初,当我这样做时,我会得到指向无穷远的垃圾线。有问题的代码如下:
// DEBUG DRAW LINE RENDERING
/* ... */
/* SETUP FUNCTION */
/* ... */
// Setup vertex array object
glGenVertexArrays(1, &_vertex_array_object_id);
glBindVertexArray(_vertex_array_object_id);
// Setup vertex buffers
glGenBuffers(1, &_vertex_buffer_object_id);
glBindBuffer(GL_ARRAY_BUFFER, _vertex_buffer_object_id);
// Note: no buffer data supplied here!!!
// Setup vertex attributes
glEnableVertexAttribArray(e_aid_position);
glEnableVertexAttribArray(e_aid_color);
glVertexAttribPointer(e_aid_position, 3, GL_FLOAT, GL_FALSE, sizeof(line_vertex), (GLvoid*)offsetof(line_vertex, pos));
glVertexAttribPointer(e_aid_color, 4, GL_FLOAT, GL_FALSE, sizeof(line_vertex), (GLvoid*)offsetof(line_vertex, col));
/* ... */
/* DRAW FUNCTION */
/* ... */
glBindVertexArray(_vertex_array_object_id);
// Specifying buffer data here instead!!!
glBufferData(GL_ARRAY_BUFFER, _line_vertices.size() * sizeof(line_vertex), &_line_vertices[0], GL_DYNAMIC_DRAW);
glDrawArrays(GL_LINES, 0, _line_vertices.size());
经过一段时间的搜索,并找到了上面遗漏的细节,我发现如果在DRAW函数中先调用glBindBuffer再调用glBufferData,一切都会正常进行。
考虑到这一点,我很困惑为什么我的网格渲染一开始就能正常工作。如果更改缓冲区中的数据,是否只需要再次调用glBindBuffer?或者,如果您不绑定缓冲区,而我只是运气不好,并让它工作,那么行为是否未定义?
请注意,我的目标是OpenGL 3.0版。
2条答案
按热度按时间uoifb46i1#
如果更改缓冲区中的数据,是否只需要再次调用glBindBuffer?
是的,每次您在绑定Vao时调用
glVertexAttribPointer
时,Vao对象都会记住绑定了哪些缓冲区,因此您通常不需要再次调用glBindBuffer
。但是,如果您想要更改缓冲区中的数据,OpenGL需要知道您正在更改哪个缓冲区,因此您需要在调用glBufferData
之前调用glBindBuffer
。此时绑定哪个VAO对象是无关紧要的。waxmsbnn2#
顶点数组对象保存由
glEnableVertexAttribArray
、glDisableVertexAttribArray
、glVertexAttribPointer
、glVertexAttribIPointer
、glVertexAttribDivisor
和gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER)
设置的数据集换句话说,您可以将Vao定义为
可以使用多少个属性进行查询
您可以认为GL的全局状态具有指向使用
glBindVertexArray
设置的Vertex ArrayObject的指针您可以想象上面列出的其他函数是这样工作的
可能更容易从视觉上看到它
从this diagram开始,虽然上图显示的是
offset
而不是pointer
,因为它来自不允许客户端数组的WebGL,只有顶点缓冲区,因此指针字段始终被解释为偏移量。在OpenGL中,如果该属性的
buffer
字段为0(间接设置,请参见上文),则pointer
是指向用户内存的指针。如果属性的buffer
非零,则它是缓冲区的偏移量。有一件事没有存储在VAO中,那就是属性被禁用时的常量值。如果某个属性被禁用(它们默认为禁用,或者您调用
gl.disableVertexAttribArray
),则该属性将获得常量值。常量值可以用glVertexAttrib???
设置。这些值是全球性的。他们不是Vao州的一部分。