OpenGL系列教程之八:OpenGL顶点缓冲区对象(VBO)

来源:互联网 发布:软件管家 编辑:程序博客网 时间:2024/05/23 01:17

相关主题:顶点数组,显示列表,像素缓冲区对象

下载:vbo.zip, vboSimple.zip

  • 创建VBO
  • 绘制VBO
  • 更新VBO
  • 例子

GL_ARB_vertex_buffer_object扩展通过提供顶点数组和显示列表的优点并且避免它们的不足提高了OpenGL的性能。顶点缓冲区对象(vertex buffer object,VBO)允许顶点数组存储在位于服务器端的高性能的图形内存中,并且提升了数据传输的效率。如果缓冲区对象用来存储像素数据,那么它叫做像素缓冲区对象(PBO)。

使用顶点数据可以减少函数调用的次数和共享顶点的冗余使用,但是,顶点数组的缺点是顶点数组中的函数位于客户端并且数组中的数据在它每次被引用时都需要发送到服务器端一次。

相反,显示列表位于服务器端,因此它不需要忍受数据传输的开销。但是,当一个显示列表编译后,显示列表里的数据就不能够再修改了。

顶点缓冲区对象(VBO)在服务器端高性能的内存中为顶点属性创造了一个“缓冲区对象”,并且提供了一些函数来引用在顶点数组中使用的数组,如glVertexPointer(),glNormalPointer(),glTexCoordPointer()等。

顶点缓冲区对象(VBO)中的内存管理根据用户定义的"目标(target)"和“用法(usage)”模式,将缓冲区对象放置在内存中最合适的地方。因此,内存管理通过在3中不同的内存(系统内存,AGP,显卡内存)中协调能优化缓冲区。

不像显示列表,通过将缓冲区映射到客户端的内存区域中顶点缓冲区中的数据可以被读取或者更新。

顶点缓冲区对象(VBO)的另外一个优点是缓冲区对象可以被多个客户端共享,像显示列表和纹理。由于顶点缓冲区对象(VBO)位于服务器端,多个客户端使用相关联的标示符可以访问相同的缓冲区。



创建VBO

创建VBO需要3步:
    1. 使用glGenBuffersARB()生成一个缓冲区对象;
    2. 使用glBindBufferARB()绑定一个缓冲区对象;
    3. 使用glBufferDataARB()将顶点数据复制到缓冲区对象中。



glGenBuffersARB()
glGenBuffersARB()创建一个缓冲区对象并且返回这个缓冲区对象的标示。它需要两个参数:第一个参数指示要创建的缓冲区对象的个数,第二个参数指示存放返回一个或多个缓冲区标示的GLuint类型变量或数组的地址。

<span style="white-space:pre"></span>void glGenBuffersARB(GLsizei n, GLuint* ids)



glBindBufferARB()
当缓冲区对象创建以后,在使用缓冲区对象之前我们需要将缓冲区对象的标示绑定。glBindBufferARB()需要两个参数:target和ID。

<span style="white-space:pre"></span>void glBindBufferARB(GLenum target, GLuint id)

target是告诉VBO这个缓冲区对象是用来存储顶点数组数据还是用来存储索引数组数据的:GL_ARRAY_BUFFER_ARB或 GL_ELEMENT_ARRAY_BUFFER_ARB 。任何顶点属性,如顶点坐标,纹理坐标,法向量,和颜色信息需要使用GL_ARRAY_BUFFER_ARB作为target。而像glDraw[Range]Elements()函数使用的索引数组则需要与GL_ELEMENT_ARRAY_BUFFER_ARB绑定。注意这个target标示帮助VBO决定顶点缓冲区的最佳位置,例如,有些系统将会将索引数组放置在AGP或系统内存中,而将顶点数组放置在显卡内存中。

当glBindBufferARB()被首次调用的时候,VBO使用大小为0的内存区初始化这个缓冲区并且设置了这个VBO的初始状态,例如usage和访问属性。



glBufferDataARB()
当缓冲区初始化以后你可以使用glBufferDataARB()将数组复制到缓冲区对象中。

<span style="white-space:pre"></span>void glBufferDataARB(GLenum target, GLsizei size, const void* data, GLenum usage)

第一个参数target可能是GL_ARRAY_BUFFER_ARB或GL_ELEMENT_ARRAY_BUFFER_ARB。size是将要传输的数据的字节数。第3个参数是指向数据源的指针,如果这个指针是NULL,那么VBO将会只保留指定大小的存储空间。最后一个参数usage是另一个VBO中的性能参数,指示这个缓冲区会被怎么使用,static(静态的),dynamic(动态的),stream(流),read(读),copy(复制),draw(绘制)。

VBO为usage标示指定了9中枚举的值:

GL_STATIC_DRAW_ARBGL_STATIC_READ_ARBGL_STATIC_COPY_ARBGL_DYNAMIC_DRAW_ARBGL_DYNAMIC_READ_ARBGL_DYNAMIC_COPY_ARBGL_STREAM_DRAW_ARBGL_STREAM_READ_ARBGL_STREAM_COPY_ARB

“static”意味着VBO中的数据不能被改变(指定一次使用多次),“dynamic”意味着数据将会频繁地改变(指定多次使用多次),“stream”意味着数据在每一帧中都会被改变(指定一次使用一次)。“draw”意味着数据被传输至GPU渲染(从应用程序到OpenGL),“read”意味着数据被客户端的应用程序所读取(从OpenGL到应用程序),“copy0”意味着既可以用来“draw”也可以用来“read”。

注意只有"draw"标示可以被VBO使用,“copy”和“read”标示对像素缓冲区对象(PBO)和帧缓冲区对象(FBO)才有意义。

VBO内存管理会根据usage的值为缓冲区对象选择最合适的内存位置,例如,GL_STATIC_DRAW_ARB 和GL_STREAM_DRAW_ARB将会选择显卡内存,GL_DYNAMIC_DRAW_ARB将会选择AGP内存。任何与_READ_相关的缓冲区既可以使用系统内存也可以是用AGP内存,因为数据应该很容易被访问到。



glBufferSubDataARB()

<span style="white-space:pre"></span>void glBufferSubDataARB(GLenum target, GLint offset, GLsizei size, void* data)

和glBufferDataARB()比较类似,glBufferSubDataARB()用来将数据复制到VBO中,但是它只将部分数据复制到已经存在的缓冲区中,从给定的偏移量开始。(缓冲区的总大小必须在使用glBufferSubDataARB()之前使用glBufferDataARB()设置)



glDeleteBuffersARB()

<span style="white-space:pre"></span>void glDeleteBuffersARB(GLsizei n, const GLuint* ids)

你可以使用glDeleteBuffersARB()函数来删除一个或多个不再使用的VBO。缓冲区对象被删除掉后,它的内容会丢失掉。

下面的代码是一个为顶点坐标创建一个简单的顶点缓冲区对象(VBO)的例子。注意当你把所有的数据复制到VBO中之后你可以删除在你的应用程序中为顶点数组分配的内存。

GLuint vboId;                              // VBO的IDGLfloat* vertices = new GLfloat[vCount*3]; // 创建顶点数组...// 生成一个新的顶点缓冲区对象并得到相关联的IDglGenBuffersARB(1, &vboId);// 绑定顶点缓冲区对象glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);// 将数据复制到顶点缓冲区对象中glBufferDataARB(GL_ARRAY_BUFFER_ARB, dataSize, vertices, GL_STATIC_DRAW_ARB);// 将数据复制到顶点缓冲区对象中后就可以删除顶点数组了delete [] vertices;...// 程序终止时删除顶点缓冲区对象glDeleteBuffersARB(1, &vboId);



绘制VBO

因为VBO是基于顶点数组实现的,渲染VBO和使用顶点数组比较类似。唯一的不同点是指向顶点数据的指针现在变成了指向现在绑定的缓冲区对象的偏移量。因此,除了glBindBufferARB()之外绘制VBO不需要额外的API。

// 为顶点数组和索引数组绑定VBOglBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId1);         // 为顶点数组glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vboId2); // 为索引数组// 除了指针不同外其他和顶点数组操作一样glEnableClientState(GL_VERTEX_ARRAY);             // 激活顶点数组glVertexPointer(3, GL_FLOAT, 0, 0);               // 最后一个参数是偏移// 使用索引数组的偏移值绘制6个面glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, 0);glDisableClientState(GL_VERTEX_ARRAY);            // 禁用顶点数组// 绑定0,这样将返回到正常的指针操作glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);

最后一行将缓冲区对象绑定到0将关闭VBO操作。这是一个使用VBO之外关闭VBO的好方法,这样正常的顶点数组操作将会被再次激活。



更新VBO

VBO相比于显示列表的优点是客户端可以读取和更改缓冲区对象中的数据,而显示列表不能。最简单的更新VBO中的数据的方法是使用glBufferDataARB()或glBufferSubDataARB()函数将数据一遍又一遍地复制到你所绑定的VBO中。对这种情况,你的应用程序需要一直有一个有效的顶点数组。这意味着你需要有顶点数组的两份拷贝:一份位于你的应用程序中,另一份位于VBO中。

另一个修改缓冲区对象的方式是将缓冲区对象映射到客户端内存中,然后客户端可以使用指向映射到缓冲区的指针更新数据。下面显示了如何将一个VBO映射到客户端内存中并怎样访问被映射的数据。



glMapBufferARB()
VBO使用glMapBufferARB()来将缓冲区对象映射到客户端内存中。

<span style="white-space:pre"></span>void* glMapBufferARB(GLenum target, GLenum access)
如果OpenGL能将缓冲区映射到客户端的内存中,glMapBufferARB()将返回指向缓冲区的指针,否则返回NULL。

第一个参数,target和上面的glBindBufferARB()一样,第二个参数access指定了对那些映射的数据的操作:读,写或者读写都可以。

GL_READ_ONLY_ARBGL_WRITE_ONLY_ARBGL_READ_WRITE_ARB

注意glMapBufferARB()将会导致一个同步的问题。如果GPU依然在顶点缓冲区中工作,那么glMapBufferARB()函数将会在GPU结束在指定的缓冲区的工作之后才返回。

为了避免等待,你可以首先使用一个空指针调用glBufferDataARB()函数,然后调用glMapBufferARB()。在这种情况下,之前的数据将会被舍弃,glMapBufferARB()将立即返回一个新分配区域的指针,即使GPU依然在之前的数据上工作。

然而,这种方法只有在你需要更新整个数据集的时候才有效,因为这样将会舍弃之前的数据。如果你只是希望改变部分数据或者只是希望读取数据,你最好不要舍弃之前的数据。


glUnmapBufferARB()

<span style="white-space:pre"></span>GLboolean glUnmapBufferARB(GLenum target)

修改完VBO的数据之后,必须解除掉缓冲区对象和客户端内存的映射。glUnmapBufferARB()成功时会返回GL_TRUE。当它返回GL_FALSE时VBO的内容会被破坏。破坏的原因是窗口分辨率的改变或者系统时间发生了。在这种情况下,数据需要被再次提交。

下面是一个使用映射的方法修改VBO的例子:

// 绑定并映射VBOglBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);float* ptr = (float*)glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);// 如果映射成功,则更新VBOif(ptr){    updateMyVBO(ptr, ...);                 // 修改缓冲区数据    glUnmapBufferARB(GL_ARRAY_BUFFER_ARB); // 解除映射}// 使用新的VBO绘图...


例子

这个例子使VBO沿着法向量摆动,它将VBO映射到了客户端并在每一帧中改变。你可以将它与传统的顶点数组的方式比较性能。

它使用了两个顶点缓冲区:一个给顶点坐标和法向量使用,另一个只用来存储索引。

下载源文件和可执行文件: vbo.zip, vboSimple.zip

vboSimple是一个非常简单的使用VBO和顶点数组绘制立方体的例子。你可以非常简单地看出顶点数组和VBO的相同点和不同点。

我同样在src文件夹下也包含了在linux下运行的makefile文件(Makefile.linux),你也可以在linux下使用下面的方式生成可执行文件:

 <span style="white-space:pre"></span>make -f Makefile.linux

原创粉丝点击