4

我编写了这个“模型”类来加载 .obj 文件并在 VBO 中为它们分配数据。它的代码是这样的:(注意它是如何不使用 VAO 的)

class Model {...}

void Model::LoadOBJ(const char *file)
{
    //load vertices, normals, UVs, and put them all in _vec, which is a private data member of std::vector<glm::vec3>
    ...

    //if an .obj file is loaded for the first time, generate a buffer object and bind it
    if(glIsBuffer(_vbo) == GL_FALSE)
    {
        glGenBuffers(1, &_vbo);//_vbo is a private data member
        glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    }
    
    //load the data in the array buffer object
    glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * _vec.size(), &_vec[0][0], GL_STATIC_DRAW);
}

void Model::Draw()
{
    glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    glDrawArrays(GL_TRIANGLES, 0, _numVertices);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

我曾经认为下一个代码可以很好地渲染两个不同的对象:

void init()
{
    //vao dummy (?)
    GLuint VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    //load 3d models
    Model cube = load("cube.obj");
    Model cow = load("cow.obj");

    //the next two lines should be valid for both objects?
    glVertexAttribPointer(prog.GetAttribLocation("vertex"), 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(prog.GetAttribLocation("vertex"));
}

void render()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //draw cube:
    //some matrix transformations
    ...
    cube.Draw();

    //draw cow:
    //some matrix transformations
    ...
    cow.Draw()

    glutSwapBuffers();
}

但事实证明,OpenGL 只会绘制两只牛或两个立方体。(取决于我在 init() 中最后加载的模型) 两头牛 两个立方体

顺便说一句,我很确定在第一张图片中,opengl 确实尝试绘制了两只奶牛,但是调用 glDrawArrays() 函数时使用了立方体所需的顶点数量。

那么我错过了什么?我是否需要为每个缓冲区对象或类似的东西使用不同的 VAO?

4

2 回答 2

4

“当前”状态应该归咎于此,尤其是与glVertexAttribPointer.

建立与当前绑定到glVertexAttribPointer (...)的缓冲区对象相关的内存指针的所有调用。我们倾向于将绑定到该位置的缓冲区对象称为顶点缓冲区对象,但实际上单个缓冲区对象可以用于多种用途,并且它们实际上没有类型。GL_ARRAY_BUFFER

在现代 OpenGL 中可以这样想:

GLuint GL_ARRAY_BUFFER_BINDING = 0; // Only 1 or 2 commands in GL care about this state
GLuint GL_VERTEX_ARRAY_BINDING = 0; // 0 is an invalid VAO (if this is 0, most vertex commands will generate `GL_INVALID_OPERATION`).

// Generic GPU-side Memory Store
struct GLBufferObject {
  GLsizeiptr* gpu_base_addr;
} *gl_buffer_objects;

// Vertex Array State
struct GLVertexArrayObject {
  GLsizeiptr* attribute_pointers [GL_MAX_VERTEX_ATTRIBUTES];
  GLboolean   attribute_enabled  [GL_MAX_VERTEX_ATTRIBUTES];
  GLuint      GL_ELEMENT_ARRAY_BUFFER_BINDING;
} *gl_array_objects;

void glBindVertexArray (GLuint array)
{
  GL_VERTEX_ARRAY_BINDING = array;
}

void glBindBuffer (GLenum target, GLuint buffer)
{
  if (target == GL_ARRAY_BUFFER)
    GL_ARRAY_BUFFER_BINDING = buffer;
}

void glVertexAttribPointer (GLuint index, ..., const GLvoid* offset)
{
  GLBufferObject*      current_vbo = &gl_buffer_objects [GL_ARRAY_BUFFER_BINDING];
  GLVertexArrayObject* current_vao = &gl_array_objects  [GL_VERTEX_ARRAY_BINDING];

  current_vao->attribute_pointers [index] = current_vbo->gpu_base_addr + offset;
}

这个伪代码的重点是向您展示 OpenGL 中只有一个命令,您绑定的内容GL_ARRAY_BUFFER在整个代码中很重要:glVertexAttribPointer (...).

所有其他命令,例如glDrawArrays (...)实际使用存储在 " current_vao" 中的状态,如glVertexAttribPointer (...).


最后,您不使用 VAO 实际上是一个问题。您正在覆盖您的软件当前与最后加载的模型一起使用的唯一VAO 的属性指针。

查看GLVetexArrayObject伪代码中存储的状态,然后考虑重构您自己的代码以利用它。否则,您将不得不在glVertexAttribPointer每次调用时至少拨打一个电话void Model::Draw()

有关不太有趣的解释,请参阅此相关答案

于 2014-12-14T22:59:19.070 回答
2

绑定缓冲区后,您需要将顶点属性代码(设置和启用)移动到绘图函数。这些调用将作用于您当前绑定的缓冲区。

如果您为不同的模型使用不同的 vao,则属性数据将存储在 vao 中,您无需在每次绘制新对象时重新绑定属性(只需更改 vao)。

于 2014-12-14T21:21:56.270 回答