OpenGL的缓冲区是如何工作的?

5

我不理解OpenGL缓冲区是如何工作的。我是通过阅读OpenGL Redbook第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个回答

11

OpenGL有所谓的“对象”概念。它们不是模型或几何对象,而是内部状态的封装。如果您熟悉面向对象编程以及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开发人员采用了一种不太正规的方法,对编译器进行了欺骗。我在回答“NULL + int的结果是什么?”这个问题时解释了详细内容。

请注意,OpenGL-4引入了一个新的、更强大和灵活的API,用于创建VAO属性和VBO绑定。


1
有点失望看到你在这个问题上没有更多的赞,你的回答非常扎实。 - Trevor Hart

3
每个目标都可以通过glBindBuffer(target,id)设置一个“当前缓冲区”,大多数缓冲区操作都是在其中运行的。
如果没有调用glEnableVertexAttribArray,则openGL不会使用数据。
glVertexAttribPointer告诉openGL在当前绑定的GL_ARRAY_BUFFER中必须找到属性,以便当前vertexArrays使用。在您的示例中:(假设vbo [0]仍绑定到GL_ARRAY_BUFFER)
1.索引0的属性在vbo [0]中找到,每个顶点有4个紧密压缩的floats,并从0开始。 2.索引1的属性在vbo [0]中找到,每个顶点有4个紧密压缩的floats,并从sizeof(strip_position)开始。
这些绑定持久存在于glBindBuffer调用中,因此如果您想要重新绑定,则需要绑定另一个缓冲区调用glVertexAttribPointer,然后您可以再次取消绑定。
我建议您始终使用0缓冲区调用glBindBuffer,这样openGL就知道您不再想使用当前缓冲区,并避免奇怪的行为。
要创建第二个对象,您可以每次切换对象时重新填充各种缓冲区。
或者,您也可以创建2组缓冲区:
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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接