opengl 什么是顶点数组对象?

xuo3flqw  于 2022-11-04  发布在  其他
关注(0)|答案(5)|浏览(131)

我今天刚开始学习OpenGL,从这个教程:http://openglbook.com/the-book/
第二章,我画了一个三角形,除了VAOs(这个首字母缩写词可以吗?),我什么都懂了。

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

虽然我知道这段代码是必要的,但我不知道它的作用。虽然我从来没有使用过VaoId(除了破坏它),但没有它,代码就不能工作。我假设这是因为它需要绑定,但我不知道为什么。这段代码是否需要成为每个OpenGL程序的一部分?本教程将VAO解释为:
顶点数组对象( VAO )是一个描述顶点属性如何存储在顶点缓冲对象(VBO)中的对象。这意味着VAO不是存储顶点数据的实际对象,而是顶点数据的描述符。顶点属性可以通过glVertexAttribPointer函数和它的两个姐妹函数glVertexAttribIPointer和glVertexAttribLPointer来描述,我们将在下面介绍第一个。
我不明白 VAO 是如何描述顶点属性的,我没有以任何方式描述它们,它是从glVertexAttribPointer获取信息的吗?我猜应该是这样,VAO只是从glVertexAttribPointer获取信息的目的地吗?
顺便说一句,我正在遵循的教程可以接受吗?有没有什么我应该注意的或者更好的教程可以遵循?

roejwanj

roejwanj1#

“顶点数组对象”是由OpenGL ARB愚蠢名称小组委员会为您带来的。
把它想象成一个几何对象。(作为一个老SGI Performer程序员,我称它们为geosets。)对象的示例变量/成员是顶点指针、法线指针、颜色指针、属性N指针...
首次绑定 VAO 时,可以通过调用

glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer...;
glEnableClientState(GL_NORMAL_ARRAY); glNormalPointer...;

和您提供的指针都存储在 VAO 中。
之后,当你再次绑定 VAO 时,所有这些属性和指针也会变成当前的。因此,一个glBindVertexArray调用就相当于之前设置所有属性所需的所有代码。这对于在函数或方法之间传递几何图形非常方便,而不必创建自己的结构体或对象。
(One时间设置,多次使用是使用VAO最简单的方法,但您也可以通过绑定它并执行更多的enable/pointer调用来更改属性。VAO不是常量。)
更多信息,以回应帕特里克的问题:
新创建的 VAO 的默认设置是空的(AFAIK)。没有任何几何体,甚至没有顶点,所以如果你试图绘制它,你会得到一个OpenGL错误。这是合理的,就像“initialize everything to False/NULL/zero”一样。
当你设置的时候你只需要glEnableClientState。 VAO 记住每个指针的启用/禁用状态。
是的, VAO 将存储glEnableVertexAttribArrayglVertexAttrib。旧的顶点,法线,颜色,...数组与属性数组相同,顶点== #0等等。

xa9qqrwz

xa9qqrwz2#

我一直认为 VAO 是OpenGL使用的数据缓冲区阵列。使用现代OpenGL,你将创建一个VAO和顶点缓冲区对象。

//vaoB is a buffer
glGenVertexArrays(1, vaoB); //creates one VAO
glBindVertexArray(vao.get(0));
glGenBuffers(vbo.length, vbo, 0); //vbo is a buffer
glBindVertexArray(vao.get(1));
glGenBuffers(vbo1.length, vbo1, 0); //vbo1 is a buffer
glBindVertexArray(vao.get(2));
glGenBuffers(vbo2.length, vbo2, 0); //vbo2 is a buffer

下一步是将数据绑定到缓冲区:

glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER,vertBuf.limit()*4, vertBuf, GL_STATIC_DRAW); //vertf buf is a floatbuffer of vertices

此时,OpenGL可以看到:

现在,我们可以使用glVertexAttribPointer告诉OpenGL缓冲区中的数据代表什么:

glBindBuffer(GL_ARRAY_BUFFER, 0); //bind VBO at 0
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); //each vertex has 3 components of size GL_FLOAT with 0 stride (space) between them and the first component starts at 0 (start of data)

OpenGL现在在缓冲区中有数据,并且知道数据是如何组织成顶点的。同样的过程可以应用于纹理坐标等,但对于纹理坐标将有两个值。

glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER,coordBuf.limit()*4, coordBuf, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0);

接下来你可以绑定纹理和绘制数组,你会想创建一个垂直和碎片着色器,编译并将其附加到一个程序(这里不包括)。

glActiveTexture(textureID); //bind our texture
glBindTexture(GL_TEXTURE_2D, textureID);
glDrawArrays(GL_TRIANGLES,0,6); //in this case 6 indices are used for two triangles forming a square
fruv7luv

fruv7luv3#

顶点数组对象类似于字处理程序中的宏。here是一个很好的描述。
宏只是 * 记住 * 您所做的操作,例如激活此属性、绑定缓冲区等。当您调用glBindVertexArray( yourVAOId )时,它只是重放那些属性指针绑定和缓冲区绑定。
因此,下一个draw调用使用 VAO 绑定的任何内容。
VAO 不存储 * 顶点数据 *。不。顶点数据存储在顶点 * 缓冲区 * 或客户端内存数组中。

vfhzx4xs

vfhzx4xs4#

  • VAO 是一个对象,代表OpenGL管道的顶点提取阶段,用于为顶点着色器提供输入。*

您可以像这样创建顶点数组对象

GLuint vao;
glCreateVertexArrays(1, &vao);
glBindVertexArray(vao);

首先让我们做一个简单的例子。考虑着色器代码中的这样一个输入参数

layout (location = 0) in vec4 offset; // input vertex attribute

要填充此属性,我们可以使用

glVertexAttrib4fv(0, attrib); // updates the value of input attribute 0

虽然顶点数组对象为您存储这些静态属性值,但它可以做更多的事情。
创建顶点数组对象后,我们可以开始填充它的状态。我们将要求OpenGL使用我们提供的缓冲区对象中存储的数据自动填充它。每个顶点属性都可以从绑定到多个顶点缓冲区绑定之一的缓冲区中获取数据。为此,我们使用glVertexArrayAttribBinding(GLuint vao, GLuint attribindex, GLuint bindingindex)。我们还使用glVertexArrayVertexBuffer()函数将缓冲区绑定到顶点缓冲区绑定之一。我们使用glVertexArrayAttribFormat()函数来描述数据的布局和格式,最后通过调用glEnableVertexAttribArray()来启用属性的自动填充。
当顶点属性启用时,OpenGL会根据您通过glVertexArrayVertexBuffer()glVertexArrayAttribFormat()提供的格式和位置信息将数据提供给顶点着色器。当该属性禁用时,顶点着色器将使用您通过调用glVertexAttrib*()提供的静态信息。

// First, bind a vertex buffer to the VAO
glVertexArrayVertexBuffer(vao, 0, buffer, 0, sizeof(vmath::vec4));

// Now, describe the data to OpenGL, tell it where it is, and turn on automatic
// vertex fetching for the specified attribute
glVertexArrayAttribFormat(vao, 0, 4, GL_FLOAT, GL_FALSE, 0);

glEnableVertexArrayAttrib(vao, 0);

和着色器中的代码

layout (location = 0) in vec4 position;

毕竟你需要调用到glDeleteVertexArrays(1, &vao)
您可以阅读OpenGL SuperBible以更好地理解它。

svgewumm

svgewumm5#

我也试图理解这一点,现在我想我做到了,这将是谨慎的张贴一个代码示例,针对不太熟悉OpenGL架构的人,因为我发现以前的例子不是很有启发性,大多数教程只是告诉你复制粘贴代码,而不解释它。
(This是用C++编写的,但代码可以很容易地转换为C)
在这个例子中,我们将渲染一个矩形,它有4个顶点,每个顶点有一个位置(vec3,xyz),纹理坐标(vec2,uv)和颜色属性(vec4,rgba)。
我认为最简单的方法是将每个属性分别放入各自的数组中:

float positions[] = {
    +0.5, +0.5, 0,
    +0.5, -0.5, 0,
    -0.5, -0.5, 0,
    -0.5, +0.5, 0
};

float colors[] = {
    1, 1, 1, 1,
    1, 1, 1, 1,
    1, 1, 1, 1,
    1, 1, 1, 1
};

float tex_coords[] = {
    0, 0,
    0, 1,
    1, 1,
    1, 0
};

我们的顶点数组对象将用这些属性描述四个顶点。
首先,我们需要创建顶点数组:

GLuint vertex_array;
glGenVertexArrays(1, &vertex_array);

每个顶点数组都有一系列的缓冲区,这些都可以看作是数组的属性。每个顶点数组都有一个任意数量的缓冲区“插槽”。它保存了CPU端指向缓冲区数据的指针,以及CPU端数据的格式。我们需要让OpenGL知道要使用哪个插槽,数据在哪里,以及数据是如何格式化的。
缓冲区插槽已编入索引,因此第一个缓冲区的索引为0,第二个为1,依此类推。这些位置对应于顶点着色器中定义的layout

// vertex shader
std::string _noop_vertex_shader_source = R"(
    #version 420

    layout (location = 0) in vec3 _position_3d; // slot 0: xyz
    layout (location = 1) in vec4 _color_rgba;  // slot 1: rgba
    layout (location = 2) in vec2 _tex_coord;   // slot 2: uv

    out vec2 _vertex_tex_coord;
    out vec4 _vertex_color_rgba;

    void main()
    {
        gl_Position = vec4(_position_3d.xy, 1, 1);  // forward position to fragment shader
        _vertex_color_rgba = _color_rgba;   // forward color to fragment shader
        _vertex_tex_coord = _tex_coord;     // forward tex coord to fragment shader
    }
)";

我们可以看到位置属性位于位置0,颜色属性位于1,特克斯坐标位于2。为了清楚起见,我们将存储这些属性:

// property locations from our shader
const auto vertex_pos_location = 0;
const auto vertex_color_location = 1;
const auto vertex_tex_coord_location = 2;

现在我们需要告诉OpenGL有关上述每个缓冲区的信息:

// bind the array, this makes OpenGL aware that we are modifying it with future calls
glBindVertexArray(vertex_array);

// create the position buffer
glGenBuffers(1, &position_buffer);

// bind the buffer so opengl knows we are currently operating on it
glBindBuffer(GL_ARRAY_BUFFER, position_buffer);

// tell opengl where the data pointer is
glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

// tell opengl how the data is formatted
glVertexAttribPointer(vertex_pos_location, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);

// tell opengl that this slot should be used
glEnableVertexAttribArray(vertex_pos_location);

在这里,我们生成一个缓冲区来保存位置数据。对于glVertexAttribPointer,我们选择正确的位置,3个元素(因为位置是xyz坐标),没有偏移量或步幅。因为我们有一个单独的数组来存放所有属性,所以可以将两者都保留为0。
与位置类似,我们为颜色和纹理坐标属性生成并填充缓冲区:

// color
glGenBuffers(1, &color_buffer); // generate
glBindBuffer(GL_ARRAY_BUFFER, color_buffer); // bind
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW); // set pointer
glVertexAttribPointer(vertex_color_location, 4, GL_FLOAT, GL_FALSE, 0, (void*) 0); // set data format
glEnableVertexAttribArray(vertex_color_location); // enable slot

// tex coords
glGenBuffers(1, &tex_coord_buffer);
glBindBuffer(GL_ARRAY_BUFFER, tex_coord_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(tex_coords), tex_coords, GL_STATIC_DRAW);
glVertexAttribPointer(vertex_tex_coord_location, 2, GL_FLOAT, GL_FALSE, 0, (void*) 0);
glEnableVertexAttribArray(vertex_tex_coord_location);

其中我们选择了4个元素作为颜色,因为它们是RGBA格式,2个元素作为tex坐标,原因显而易见。
渲染顶点数组最后需要的是一个 * 元素缓冲区 *。这些可以被看作是一个索引列表,定义了顶点的渲染顺序。对于我们来说,我们希望将矩形渲染为三角扇中的两个三角形,因此我们选择了以下元素缓冲区:

// vertex order
static uint32_t indices[] = {
    0, 1, 2, 1, 2, 3
};

glGenBuffers(1, &element_buffer); // generate
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); // bind
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW) // set pointer

我们不需要启用元素缓冲槽,它是独立于顶点数组的。我们不需要在这里指定元素缓冲的格式,这将在渲染步骤的glDrawElements中完成。
那么为什么要这么做呢?所有这些函数都告诉OpenGL在哪里查找顶点的数据。如果我们现在在渲染步骤中绑定顶点数组,则需要指定指向正确缓冲区数据的指针及其布局:

glUseProgram(shader.get_program_id()); // shader program with our vertex shader

glBindVertexArray(vertex_array);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

其中6是元素缓冲区中的元素数。
这就是正确更新顶点着色器中in值所需的全部内容。OpenGL会将数据从CPU端positionscolorstex_coords分别移动到顶点着色器的正确位置0、1和2。我们不需要绑定任何其他内容,顶点数组会记住我们给它的内容,并为我们执行这些操作。这就是为什么它很方便,并且应该是现代OpenGL的首选。
总而言之:
每个顶点数组都有n个用于任意属性的缓冲区和1个元素缓冲区。
a)生成它(glGenBuffers
B)绑定它(glBindBuffer(GL_ARRAY_BUFFER
c)告诉OpenGL数据在RAM中的位置(glBufferData
d)告诉OpenGL数据的格式(glVertexAttribPointer
e)告诉OpenGL使用插槽(glEnableVertexAttribArray
对于元素缓冲区,我们只需要生成它,将它绑定到GL_ELEMENT_ARRAY_BUFFER,然后告诉opengl数据在哪里。
我几乎可以肯定这篇文章中会有事实错误,因为我也是OpenGL的新手,但这是我概念化它的方式,让我的代码工作。

相关问题