songho——OpenGL的顶点缓冲对象

http://www.songho.ca/opengl/gl_vbo.html
另外参考网址:http://dev.gameres.com/Program/Visual/3D/vbo.htm

GL_ARB_vertex_buffer_object扩展的目的是提高OpenGL的性能,通过利用顶点数组和显示列表。避免了实现的缺点。顶点缓冲对象VBO允许顶点数据存储在高性能的图形显卡的内存中,提高数据的传输。如果缓冲对象用来存储像素数据,它要调用 Pixel Buffer Object (PBO).

使用顶点数组,可以减少函数的调用,重复利用共享的顶点。但是,使用顶点数组的弊端是,顶点数组函数是在客户端状态,数组中的数据必须在每次需要使用的时候重新在服务端设置。

另外一个方面,显示列表是服务器端的函数,它不受数据传输的影响。但是,一旦显示列表被编译,显示列表中的数据就不能被修改。

顶点缓冲对象为顶点属性创建缓冲对象,它是在服务器端,在高性能的内存中。提供相同的访问函数来引用数组,这个被用于顶点数组中,如glVertexPointer,glNormalPointer,glTexCoordPointer等等。

在顶点缓冲对象中内存管理将会把缓冲对象到最合适的内存空间,这个基于用户的提示:目标和使用方式。因此,内存管理者可用优化缓冲,通过在3中内存中权衡:系统、AGP和视频内存。

和显示列表不同,在顶点缓冲中的数据,可用被读,可用通过映射用户的内存来更新。

VBO的另外一个重要的优点,多个用户共享缓冲对象,像显示列表和纹理。由于VBO是服务端的,多个客户端可用通过对应的标识符来访问同一个缓冲。

创建顶点缓冲对象:
创建顶点缓冲对象,需要三步:
1、产生新的缓冲对象glGenBuffers
2、绑定缓冲对象glBindBuffer
3、拷贝数据到缓冲对象glBufferData

glGenBuffers
glGenBuffers创建缓冲对象,返回的是缓冲对象的id。它需要两个参数:第一个是缓冲对象的个数,第二个参数是GLuint的数组,包含一个ID或者多个ID。

void glGenBuffers(GLsizei n, GLuint* ids)

glBindBuffer()
一旦缓冲对象被创建,我们需要缓冲对象的对应的ID,glBindBuffer()有两个参数:目标和ID:

void glBindBuffer(GLenum target, GLuint id)

目标是一个提示告诉VBO,这个缓冲对象将会用来存储顶点数组数据还是索引数组数据:GL_ARRAY_BUFFER 或者
GL_ELEMENT_ARRAY_BUFFER。任何一个顶点属性,比如顶点坐标,纹理坐标,法线或者颜色分量数组都应该使用GL_ARRAY_BUFFER。索引数组是用在这个函数glDraw[Range]Elements(),它将绑定的是GL_ELEMENT_ARRAY_BUFFER。注意这个目标标记帮助VBO来最有效率决定缓冲对象的位置,比如,有些系统更偏向于在AGP或者是系统内存中使用索引,而在视频内存中使用顶点数据。

一旦,glBindBuffer()第一次被调用,VBO会使用零来初始化缓冲,设置VBO最初的状态,比如使用和访问属性。

glBufferData()
你可以在缓冲被初始化之后,使用glBufferData()来拷贝数据到缓冲对象。

void glBufferData(GLenum target, GLsizei size, const void* data, GLenum usage)

第一个参数,为目标,其值可以为GL_ARRAY_BUFFER 或则GL_ELEMENT_ARRAY_BUFFER。第二个参数是将要传送的数据的字节个数。第三个参数是指向源数据的指针。如果数据是NULL指针,那么VBO将保留给定的数据大小内存空间。最后一个参数,使用标记参数,用来标记VBO的使用缓冲对象的方式,它可以是static、dynamic或者是istream、read、copy或者是draw。

VBO定义8个枚举值,用来标记如何使用缓冲数据。

GL_STATIC_DRAW
GL_STATIC_READ
GL_STATIC_COPY
GL_DYNAMIC_DRAW
GL_DYNAMIC_READ
GL_DYNAMIC_COPY
GL_STREAM_DRAW
GL_STREAM_READ
GL_STREAM_COPY

"static"意味着VBO中的数据不会被改变(定义一次使用多次),"dynamic"意味着数据将会改变的很频繁(指定一次,重复使用),"stream"意味着数据会在每帧改变(指定一次使用一次)。"draw"意味着数据将会被送到GPU以备绘制(应用到GL),"read"意味着数据将会被客户端应用读取(GL到应用),"copy"意味着数据将会被用来绘制和读取(GL
到GL)。

注意只有绘制对VBO有用,拷贝和读取只对每像素/每帧的缓冲对象有意义(PBO或者FBO)。

VBO内存管理将会根据这些应用标记,选择最好的内存来存放缓冲对象,比如GL_STATIC_DRAW 和GL_STREAM_DRAW 会用来视频内存,GL_DYNAMIC_DRAW 会用AGP内存,任何_READ_相关的内存会在系统或者AGP内存,因为这些数据将会很容易访问。

glBufferSubData()

void glBufferSubData(GLenum target, GLint offset, GLsizei size, void* data)

和glBufferData()类似,glBufferSubData()被用来拷贝数据到VBO,但是它只替换一部分数据到已存在的缓冲,从给定的开始位置。(缓冲的总大小需要在使用glBufferSubData之前使用glBufferData设置)。

glDeleteBuffers()

void glDeleteBuffers(GLsizei n, const GLuint* ids)

你可以使用void glDeleteBuffers来删除一个VBO或者VBOs。当一个缓冲对象被删除之后,它的内容将会被丢失。

下面代码是对于每个顶点坐标,来创建一个VBO的例子。注意你可以在你的应用拷贝数据到VBO之后,删除顶点数组的内存分配。


GLuint vboId;                              // ID of VBO
GLfloat* vertices = new GLfloat[vCount*3]; // create vertex array
...

// generate a new VBO and get the associated ID
glGenBuffers(1, &vboId);

// bind VBO in order to use
glBindBuffer(GL_ARRAY_BUFFER, vboId);

// upload data to VBO
glBufferData(GL_ARRAY_BUFFER, dataSize, vertices, GL_STATIC_DRAW);

// it is safe to delete after copying data to VBO
delete [] vertices;
...

// delete VBO when program terminated
glDeleteBuffers(1, &vboId);

绘制VBO
由于VBO在存在的顶点数组实现之上,渲染VBO和使用顶点数组一样。唯一的不同是,指向顶点数组的指针是当前绑定的缓冲对象的开始位置。因此除了glBindBuffer()之外,没有额外的函数需要用来绘制一个VBO。

绑定缓冲对象用0,就会关闭VBO的操作。这是个很好的主意用来关闭VBO,在使用之后。所以正常的顶点数组操作用决定的指针将会被激活。

// bind VBOs for vertex array and index array
glBindBuffer(GL_ARRAY_BUFFER, vboId1);            // for vertex attributes
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId2);    // for indices

glEnableClientState(GL_VERTEX_ARRAY);             // activate vertex position array
glEnableClientState(GL_NORMAL_ARRAY);             // activate vertex normal array
glEnableClientState(GL_TEXTURE_COORD_ARRAY);      // activate texture coord array

// do same as vertex array except pointer
glVertexPointer(3, GL_FLOAT, stride, offset1);    // last param is offset, not ptr
glNormalPointer(GL_FLOAT, stride, offset2);
glTexCoordPointer(2, GL_FLOAT, stride, offset3);

// draw 6 faces using offset of index array
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, 0);

glDisableClientState(GL_VERTEX_ARRAY);            // deactivate vertex position array
glDisableClientState(GL_NORMAL_ARRAY);            // deactivate vertex normal array
glDisableClientState(GL_TEXTURE_COORD_ARRAY);     // deactivate vertex tex coord array

// bind with 0, so, switch back to normal pointer operation
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

OpenGL 版本2.0将会增加glVertexAttribPointer()、glEnableVertexAttribArray()和glDisableVertexAttribArray()函数用来指定通用的顶点属性。比如,你可以指定所有的顶点属性:位置、法线、颜色、纹理坐标,使用单个API:

// bind VBOs for vertex array and index array
glBindBuffer(GL_ARRAY_BUFFER, vboId1);            // for vertex coordinates
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId2);    // for indices

glEnableVertexAttribArray(attribVertex);          // activate vertex position array
glEnableVertexAttribArray(attribNormal);          // activate vertex normal array
glEnableVertexAttribArray(attribTexCoord);        // activate texture coords array

// set vertex arrays with generic API
glVertexAttribPointer(attribVertex, 3, GL_FLOAT, false, stride, offset1);
glVertexAttribPointer(attribNormal, 3, GL_FLOAT, false, stride, offset2);
glVertexAttribPointer(attribTexCoord, 2, GL_FLOAT, false, stride, offset3);

// draw 6 faces using offset of index array
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, 0);

glDisableVertexAttribArray(attribVertex);         // deactivate vertex position
glDisableVertexAttribArray(attribNormal);         // deactivate vertex normal
glDisableVertexAttribArray(attribTexCoord);       // deactivate texture coords

// bind with 0, so, switch back to normal pointer operation
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

更新VBO
VBO比显示列表的优点是,客户端可以读取和修改缓冲对象数据,但是显示列表不能。更新VBO最简单的方法是,拷贝新的数据到指定的VBO,使用glBufferData()或者glBufferSubData()。对于这个情况,你的应用该有一个有用的顶点数组。这就意味着你需要有两份顶点数据,一个是应用,另外一个是VBO。

另外一个修改缓冲对象的方法是,应用缓冲对象到你的客户端内存,客户端可以更新数据,使用指针指向映射对象。下面描述如何映射VBO到客户端内存,如何访问映射数据。

glMapBuffer()
VBO提供glMapBuffer()为了映射缓冲对象到客户端内存。

void* glMapBuffer(GLenum target, GLenum access)

如果OpenGL能用来映射缓冲对象到客户端地址空间,glMapBuffer()返回指针到缓冲。否则返回NULL。
第一个参数,目标,早先提到过,在glBindBuffer()中。第二个参数,访问标记指定对映射数据做什么,读、写后者两者都有。

GL_READ_ONLY
GL_WRITE_ONLY
GL_READ_WRITE

注意glMapBuffer阴影同步问题。如果GPU还是工作在缓冲对象,glMapBuffer将不会返回直到GPU在对应的缓冲对象上完成工作。

为了避免等待,你可以首先调用glBufferData()使用NULL指针,然后调用glMapBuffer()。在这种情况下,之前的数据会被丢弃,glMapBuffer()返回新开辟的内存指针立刻,甚至是GPU依然和之前的数据在工作,也会及时返回。

但是,这个方法是仅仅是在我们想要更新整个数据集合时有用,因为你丢弃了之前的数据。如果你想要改变一部分数据或者是读取数据,你最好不要释放之前的数据。

glUnmapBuffer()

GLboolean glUnmapBuffer(GLenum target)

在修改了VBO的数据之后,需要从客户端内存中解映射缓冲对象。glUmapBuffer()返回GL_TRUE,如果成功。当返回的是GL_FALSE,VBO的内容在被映射的时候被破坏。崩溃的结果来源于屏幕分辨率的改变或者是窗口系统特定的事件。这种情况下,数据必须重新提交。

这里有一个段代码是使用映射方法修改VBO。


// bind then map the VBO
glBindBuffer(GL_ARRAY_BUFFER, vboId);
float* ptr = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

// if the pointer is valid(mapped), update VBO
if(ptr)
{
    updateMyVBO(ptr, ...);             // modify buffer data
    glUnmapBuffer(GL_ARRAY_BUFFER);    // unmap it after use
}

// you can draw the updated VBO
...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值