OpenGL的缓冲区如何工作?

时间:2013-12-11 11:31:52

标签: c++ opengl

我不明白OpenGL的缓冲区是如何工作的。我通过OpenGL红皮书第8版学习OpenGL。 例如,我有一个位置数组,一个颜色数组和一个索引数组:

static const GLfloat strip_position[] =
    {
        -4.0f,  0.0f, -1.0f, 1.0f,  //0
        -3.5f, -1.0f, -1.0f, 1.0f,  //1
        -3.0f,  0.0f, -1.0f, 1.0f,  //2
        -2.5f, -1.0f, -1.0f, 1.0f,  //3
        -2.0f,  0.0f, -1.0f, 1.0f,  //4
        -1.5f, -1.0f, -1.0f, 1.0f,  //5
        -1.0f,  0.0f, -1.0f, 1.0f,  //6
        -0.5f, -1.0f, -1.0f, 1.0f,  //7
         0.0f,  0.0f, -1.0f, 1.0f   //8
    };
static const GLfloat strip_colors[] =
    {
        1.0f, 1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
    };

static const GLushort strip_indices[] =
{
    0, 1, 2, 3, 4, 5, 6, 7, 8
};

好。然后我创建 顶点数组对象 如下:

    GLuint vao[1]; // vertex array object
    glGenVertexArrays(1, vao);
    glBindVertexArray(vao[0]);

根据我的理解,第一个参数(GLsizei n)是位置数组(或我的对象的顶点坐标)的数量。 然后我创建 元素数组缓冲区 如下:

GLuint ebo[1]; // element buffer object
glGenBuffers(1, ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glBufferData(
             GL_ELEMENT_ARRAY_BUFFER, 
             sizeof(strip_indices), 
             strip_indices, 
             GL_STATIC_DRAW
);

然后我创建 顶点缓冲区对象 如下:

GLuint vbo[1]; // vertex buffer object
glGenBuffers(1, vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(
                 GL_ARRAY_BUFFER, 
                 sizeof(strip_position) + sizeof(strip_colors), 
                 NULL, 
                 GL_STATIC_DRAW
    );
    glBufferSubData(
                    GL_ARRAY_BUFFER, 
                    0,                      //offset
                    sizeof(strip_position), //size date
                    strip_position          //data
    );
    glBufferSubData(
                    GL_ARRAY_BUFFER, 
                    sizeof(strip_position), //offset
                    sizeof(strip_colors),   //size data
                    strip_colors               //data
    );

接下来我打电话给glVertexAttribPointer()如下:

glVertexAttribPointer(
                      0,         //index
                      4,         //size
                      GL_FLOAT,  //type
                      GL_FALSE,  //normalized
                      0,         //stride
                      NULL       //pointer
);
glVertexAttribPointer(
                      1, 
                      4, 
                      GL_FLOAT, 
                      GL_FALSE, 
                      0, 
                      (const GLvoid*)sizeof(strip_position)
);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);

这是什么功能?(glVertexAttribPointer()glEnableVertexAttribArray()
好的。我完成了初始化我的数据。现在我可以画出如下:

glBindVertexArray(vao[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL);

OpenGL如何理解,需要使用哪个缓冲区以及它在哪里?单词“绑定”是指一种关系?即某事与某事有什么关系?如果我想显示两个对象,我该怎么办? 例如,我有两个位置数组,两个位置数组和两个索引数组?

static const GLfloat TWOstrip_colors[] =
{
    1.0f, 1.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f
};
static const GLfloat TWOstrip_colors[] =
    {
        1.0f, 1.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 0.0f, 1.0f,
        0.0f, 1.0f, 1.0f, 1.0f,
        0.0f, 1.0f, 0.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
    };

static const GLushort TWOstrip_indices[] =
{
    0, 1, 2, 3, 4, 5, 6, 7, 8
};

这是怎么做到的?

2 个答案:

答案 0 :(得分:3)

每个目标设置的“当前缓冲区”始终由glBindBuffer(target, id)设置,大多数缓冲区操作都知道该操作。

openGL使用glEnableVertexAttribArray知道它应该查找哪些属性,如果没有调用则openGL将不使用该数据。

glVertexAttribPointer告诉openGL在当前绑定的GL_ARRAY_BUFFER中必须找到当前vertexArrays的属性。在您的示例中:(假设vbo[0]仍绑定到GL_ARRAY_BUFFER

    索引0
  1. 属性位于vbo[0],每个顶点4 floats tightly packed0
  2. 索引1
  3. 属性位于vbo[0],每个顶点4 floats tightly packedsizeof(strip_position)
  4. 这些绑定在glBindBuffer调用上持续存在,所以如果你想重新绑定那么你需要绑定另一个缓冲区调用glVertexAttribPointer然后你可以再解除绑定

    我建议你总是用0缓冲区调用glBindBuffer,这样openGL就知道你不想再使用当前缓冲区了,避免出现奇怪的行为

    创建第二个对象,每次切换对象时都可以重新填充各种缓冲区

    或者您可以创建两组缓冲区:

    GLuint vao[2]; // vertex array object
    glGenVertexArrays(2, vao);
    glBindVertexArray(vao[0]);
    
    GLuint ebo[2]; // element buffer object
    glGenBuffers(2, ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
    glBufferData(
                 GL_ELEMENT_ARRAY_BUFFER, 
                 sizeof(strip_indices), 
                 strip_indices, 
                 GL_STATIC_DRAW
    );
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]);
    glBufferData(
                 GL_ELEMENT_ARRAY_BUFFER, 
                 sizeof(strip_indices), 
                 TWO_strip_indices, 
                 GL_STATIC_DRAW
    );
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    
    GLuint vbo[2]; // vertex buffer object
    glGenBuffers(2, vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(
                 GL_ARRAY_BUFFER, 
                 sizeof(strip_position) + sizeof(strip_colors), 
                 NULL, 
                 GL_STATIC_DRAW
    );
    glBufferSubData(
                    GL_ARRAY_BUFFER, 
                    0,                      //offset
                    sizeof(strip_position), //size date
                    strip_position          //data
    );
    glBufferSubData(
                    GL_ARRAY_BUFFER, 
                    sizeof(strip_position), //offset
                    sizeof(strip_colors),   //size data
                    strip_colors               //data
    );
    //fill other buffer (assuming the first TWOstrip_colors was actually TWOstrip_position
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(
                 GL_ARRAY_BUFFER, 
                 sizeof(TWOstrip_position) + sizeof(TWOstrip_colors), 
                 NULL, 
                 GL_STATIC_DRAW
    );
    glBufferSubData(
                    GL_ARRAY_BUFFER, 
                    0,                      //offset
                    sizeof(TWOstrip_position), //size date
                    strip_position          //data
    );
    glBufferSubData(
                    GL_ARRAY_BUFFER, 
                    sizeof(TWOstrip_position), //offset
                    sizeof(TWOstrip_colors),   //size data
                    strip_colors               //data
    );
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    
    glBindVertexArray(vao[0]);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0])
    glVertexAttribPointer(
                          0,         //index
                          4,         //size
                          GL_FLOAT,  //type
                          GL_FALSE,  //normalized
                          0,         //stride
                          NULL       //pointer
    );
    glVertexAttribPointer(
                          1, 
                          4, 
                          GL_FLOAT, 
                          GL_FALSE, 
                          0, 
                          (const GLvoid*)sizeof(strip_position)
    );
    
    glBindVertexArray(vao[1]);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glVertexAttribPointer(
                          0,         //index
                          4,         //size
                          GL_FLOAT,  //type
                          GL_FALSE,  //normalized
                          0,         //stride
                          NULL       //pointer
    );
    glVertexAttribPointer(
                          1, 
                          4, 
                          GL_FLOAT, 
                          GL_FALSE, 
                          0, 
                          (const GLvoid*)sizeof(TWOstrip_position)
    );
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    

    然后绘制:

    glEnableVertexAttribArray(0); 
    glEnableVertexAttribArray(1);
    
    glBindVertexArray(vao[0]);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
    glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL);
    
    glBindVertexArray(vao[1]);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]);
    glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL);
    

答案 1 :(得分:3)

OpenGL具有所谓的对象的概念。这些不是模型或几何对象,而是内部状态的封装。如果您熟悉面向 object 的编程,那么C ++ STL OpenGL对象可以被认为是一种类实例。

可以将调用glGenBuffers(count, out_names)解释为类似

std::map<GLuint, openglobject*> bufferobjects;

glGenBuffers(GLuint count, std::vector<GLuint> *out_names)
{
    out_names->resize(count);
    for(int i=0; i < count; i++) {
        GLuint name = get_next_free_handle_ID();
        bufferobjects[name] = NULL;
        out_names.set(i, name);
    }
}

它的作用是,它保留一个句柄ID(OpenGL称它们为名称),并在句柄和缓冲对象实例指针之间的内部映射中为它分配一个槽。

调用glBindBuffer实际上创建了缓冲区对象,类似

glBindBuffer(GLenum target, GLuint name)
{

    openglobject *objinstance = NULL;

    if( name != 0 ) {
        if( !bufferobjects.has_key(name) ) {
            push_openglerror( INVALID_NAME );
            return;
        }

        objinstance = bufferobjects[name];

        if( NULL == bufferobjects[name] ) {
            switch(target) {
            case GL_ARRAY_BUFFER:
                objinstance = new OpenGLArrayBuffer; break;

            case GL_ELEMENT_ARRAY_BUFFER:
                objinstance = new OpenGLElementArrayBuffer; break;

            /* ... and so on */    

            default:
                push_openglerror( INVALID_TARGET ); return;
            }

            bufferobjects[name] = objinstance;

            }
        }
    }

    if( objinstance != NULL && target_of(objinstance) != target ) {
         opengl_pusherror( INVALID_TARGET );
    }

    switch( target ) {
    case GL_ARRAY_BUFFER:
         /* this would be a static function of the subclass setting 
          * global singleton instance pointer
          */
         OpenGLArrayBuffer::make_current(objinstance);
         break;

         /* ... and so on */
    }
}

我认为你可以看到这种情况:缓冲区target指定了你正在使用的实例及其静态成员的子类的类型。

glBufferData然后实际分配特定对象的内存,并可以使用传递给它的缓冲区的内容对其进行初始化。 glBufferSubData只是将数据复制到内部存储空间。

缓冲对象(其中有几种)非常多。


另一部分是顶点阵列对象。这些是特殊的OpenGL对象,它们在顶点属性之间创建关联,它们是基于属性索引传递给着色器的每顶点数据和数组缓冲区对象。数据需要。

当你调用glGenVertexArray时会发生这样的事情:

std::map<GLuint, openglobject*> vertexarrayobjects;

glGenVertexArrays(GLuint count, std::vector<GLuint> *out_names)
{
    out_names->resize(count);
    for(int i=0; i < count; i++) {
        GLuint name = get_next_free_handle_ID();
        vertexarrayrobjects[name] = NULL;
        out_names.set(i, name);
    }
}

看起来很熟悉,不是吗?唯一的区别是,使用了不同的映射结构。 glBindVertexArray执行实例的分配等等。

现在可以将调用glEnableVertexAttributeglVertexAttribPointer视为以下内容:

glEnableVertexAttribute(GLuint idx)
{
    ((OpenGLVertexArrayObject*)currentvertexarray)->add_attribute(idx);
}

glVertexAttribPointer(GLuint idx, ..., void *ptr)
{
    ((OpenGLVertexArrayObject*)currentvertexarray)->bind_attribute(
          idx,
          OpenGLArrayBuffer::get_current(),
          (off_t)ptr );
}

好的,最后一点需要一些解释。您传递一个指向glVertexAttribPointer的指针是OpenGL-1.1的遗留问题,其中没有OpenGL缓冲区对象,而是您直接指向程序的内存。然后引入了缓冲对象,并且在绑定时不需要指针但是字节大小的偏移量。所以OpenGL开发人员走了肮脏的路线,只是骗了编译器。 I did explain the details in my answer to the question "What is the result of NULL + int?"

请注意,OpenGL-4引入了一个新的,功能更强大且更灵活的API来创建VAO属性←→VBO绑定。