顶点缓存对象(Vertex Buffer Object,简称 VBO),允许开发者根据情况把顶点数据放到显存中。

如果不用 VBO,用 glVertexPointer / glNormalPointer 来指定顶点数据,这时顶点数据是放在系统内存中的,每次渲染时,都要把数据从系统内存拷贝到显存,消耗不少时间。

实际上很多拷贝都是不必要的,比如静态对象的顶点数据是不变的,如果能把它们放到显存里面,那么每次渲染时都不需要拷贝操作,可以节约不少时间。

1. 检查扩展

GL_ARB_vertex_buffer_object,是一个 OpenGL 的扩展,也就是 VBO。

为了使用此扩展,首先要检测当前的 OpenGL 版本是否支持此扩展。写一个函数如下,可用于检查任意扩展,参数是扩展名的字符串。

int CheckExtension(char *extName)
{
char *p = (char *)glGetString(GL_EXTENSIONS);
char *end;
int extNameLen = (int)strlen(extName); end = p + strlen(p);
while (p < end) {
int n = (int)strcspn(p, " ");
if ((extNameLen == n) && (strncmp(extName, p, n) == 0)) {
return TRUE;
}
p += (n + 1);
}
return FALSE;
}

然后,调用 CheckExtension 检测扩展是否存在,扩展名为 GL_ARB_vertex_buffer_object。如果扩展存在,再调用 wglGetProcAddress 分别获取所需的扩展函数的指针。例如: glGenBuffers、 glBindBuffer。

有时候,也会使用 glGenBuffersARB 这样的名称,以表示此函数是扩展库中的。在旧版本的 OpenGL 中常见到,新版本的 OpenGL 使用 glGenBuffers 这样去掉 ARB 的即可。

if(CheckExtension("GL_ARB_vertex_buffer_object"))
{
glGenBuffers = (PFNGLGENBUFFERSARBPROC) wglGetProcAddress("glGenBuffersARB");
glBindBuffer = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB");
glBufferData = (PFNGLBUFFERDATAARBPROC) wglGetProcAddress("glBufferDataARB");
glBufferSubData = (PFNGLBUFFERSUBDATAARBPROC) wglGetProcAddress("glBufferSubDataARB");
glDeleteBuffers = (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress("glDeleteBuffersARB");
}
else
{
cout<<"ERROR: GL_ARB_vertex_buffer_object extension was not found"<<endl;
return FALSE;
}

2. 创建顶点缓存对象

建立 VBO 需要三个步骤:

  1. 创建缓存对象,使用 glGenBuffers()
  2. 绑定缓存对象(指定使用哪一个缓存对象),使用 glBindBuffer()
  3. 拷贝顶点数据到缓存对象中,使用 glBufferData()

三个函数的简介如下。

void glGenBuffers(GLsizei n, GLuint* ids)

创建缓存对象,并返回缓存对象的标识符。

  • n - 创建缓存对象的数量。
  • ids - 是一个 GLuint 型的变量或数组,用于储存缓存对象的单个 ID 或多个 ID。

void glBindBuffer(GLenum target, GLuint id)

一旦创建了缓存对象后,我们需要绑定缓存对象,以便使用。绑定,也就是指定当前要使用哪一个缓存对象。

target - 缓存对象要存储的数据类型,只有两个值: GL_ARRAY_BUFFER, 和 GL_ELEMENT_ARRAY_BUFFER。如果是顶点的相关属性,例如: 顶点坐标、纹理坐标、法线向量、颜色数组等,要使用 GL_ARRAY_BUFFER;索引数组,要使用 GL_ELEMENT_ARRAY_BUFFER,以便 glDrawElements() 使用。

id - 缓存对象的 ID。

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

拷贝数据到缓存对象。

  • target - 缓存对象的类型,只有两个值: GL_ARRAY_BUFFER 和 GL_ELEMENT_ARRAY_BUFFER。
  • size - 数组 data 的大小,单位是字节(bytes)。
  • data - 数组 data 的指针,如果指定为 NULL,则 VBO 只创建一个相应大小的缓存对象。
  • usage - 缓存对象如何被使用,有三中: 静态的(static)、动态的(dynamic)和流(stream)。共有 9 个值:
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

老版本的 OpenGL 会加 ARB,变成形如 GL_STATIC_DRAW_ARB 的形式。

  • Static 指在缓存对象中的数据不能够更改(设定一次,使用很多次)。
  • Dynamic 指数据将会频繁地更改(反复设定和使用)。
  • Stream 指的是每一帧数据都会更改(设定一次,使用一次)。
  • Draw 指数据将会被送到 GPU 被用于绘制(application to GL)。
  • Read 指数据将被读取到客户端应用程序(GL to application)。
  • Copy 指数据将被用于绘制和读取(GL to GL)。

要注意,Draw 只对 VBO 有用; Copy 和 Read 只对 PBO(像素缓存对象) 和 FBO(帧缓存对象) 有意义。

VBO 内存管理器会根据标记选取适当的存储位置。

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

与 glBufferData() 一样,都是用于拷贝数据到缓存对象的。它能拷贝一段数据到一个已经存在的缓存,偏移量为 offset。

void glDeleteBuffers(GLsizei n, const GLuint* ids)

删除一个或多个缓存对象。

3. 顶点缓存和索引缓存的使用

有数据如下:

GLfloat vertexs[] = { 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f,
-0.2f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f,
0.0f, -0.2f, 0.0f, 0.0f, 0.0f, 0.2f,
0.0f, 0.0f, -0.2f}; GLubyte indexs[] = {0,1,2,3,4,5,6};

生成缓存对象,并拷贝数据。

GLuint vboVertexId;
GLuint vboIndexId; glGenBuffers(1, &vboVertexId);
glBindBuffer(GL_ARRAY_BUFFER, vboVertexId);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexs), vertexs, GL_STATIC_DRAW); glGenBuffers(1, &vboIndexId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndexId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexs), indexs, GL_STATIC_DRAW);

然后,在使用的时候,加入如下代码。

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_INDEX_ARRAY); glBindBuffer(GL_ARRAY_BUFFER, vboVertexId);
glVertexPointer(3, GL_FLOAT, 0, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndexId);
glIndexPointer(GL_UNSIGNED_BYTE, 0, 0); ... //绘制图形 glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_INDEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);

使用缓存对象的方法有三种:

//1. 第一种
glBegin(GL_POINTS);
glArrayElement(0);
glArrayElement(1);
glArrayElement(2);
glArrayElement(5);
glEnd(); //2. 第二种
glDrawElements(GL_POINTS, 7, GL_UNSIGNED_BYTE, 0); //3. 第三种
glDrawArrays(GL_POINTS,0,7);

4. 将不同类型的数据拷贝到一个缓存对象

用 glBufferSubData() 可以将几个数据拷贝到一个缓存对象中。例如,有以下数据:

GLfloat vertexs[] = {0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f,
-0.2f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f,
0.0f, -0.2f, 0.0f, 0.0f, 0.0f, 0.2f,
0.0f, 0.0f, -0.2f}; GLfloat colors[] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f};

现在,要将两个数组存在同一个缓存对象中,顶点数组在前,颜色数组在后。代码如下:

glGenBuffers(1, &vboVertexId);
glBindBuffer(GL_ARRAY_BUFFER, vboVertexId);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexs)+sizeof(colors), 0, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertexs) , vertexs); //注意第三个参数,偏移量
glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertexs), sizeof(colors), colors);

创建好缓存对象后,要用 glVertexPointer 和 glColorPointer 指定相应的指针位置。但是,由于 glColorPointer 的最后一个参数,必须是指针类型。请看下面的代码,glColorPointer 的最后一个参数用偏移量指示了颜色数组的位置。

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_INDEX_ARRAY); glBindBuffer(GL_ARRAY_BUFFER, vboVertexId);
glVertexPointer(3, GL_FLOAT, 0, 0);
glColorPointer(3,GL_FLOAT,0,(void*)sizeof(vertexs)); //注意最后一个参数 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndexId);
glIndexPointer(GL_UNSIGNED_BYTE, 0, 0); glDrawArrays(GL_POINTS,0,7); glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_INDEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);

5. 修改缓存对象

比起显示列表,VBO 一个很大的优点是能够读取和修改缓存对象的数据。最简单的方法是重新拷贝虽有数据到 VBO,利用 glBufferData() 和 glBufferSubData(),这种情况下,你的程序必须要保存有两份数据:一份在客户端(CPU),一份在设备端(GPU)。

另一种方法,是将缓存对象映射到客户端,再通过指针修改数据。

void* glMapBuffer(GLenum target, GLenum access)

映射当前绑定的缓存对象到客户端,glMapBuffer 返回一个指针,指向缓存对象。如果 OpenGL 不支持,则返回 NULL。

  • target - GL_ARRAY_BUFFER 或 GL_ELEMENT_ARRAY_BUFFER。
  • access - 的值有三个 GL_READ_ONLY、 GL_WRITE_ONLY、 GL_READ_WRITE,分别表示只读、只写、可读可写。

如果 OpenGL 正在操作缓存对象,此函数不会成功,直到 OpenGL 处理完毕为止。为了避免等待,可以先用 glBindBuffer(GL_ARRAY_BUFFER, 0) 停止缓存对象的应用,再调用 glMapBuffer。

GLboolean glUnmapBuffer(GLenum target)

修改完数据后,将数据反映射到设备端。

使用方法见如下代码。

glBindBuffer(GL_ARRAY_BUFFER, vboVertexId);
GLfloat* ptr = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); if(ptr)
{
ptr[0] = 0.2f; ptr[1] = 0.2f; ptr[2] = 0.2f;
glUnmapBuffer(GL_ARRAY_BUFFER);
} glBindBuffer(GL_ARRAY_BUFFER, 0);

参考资料

[1] 关于GL_ARB_vertex_buffer_object扩展

[2] OpenGL Vertex Buffer Object (VBO)

OpenGL 顶点缓存对象的更多相关文章

  1. 顶点缓存对象(VBO)

    创建VBO 绘制VBO 更新VBO 实例 GL_ARB_vertex_buffer_object扩展致力于提供顶点数组与显示列表的优势来提升OpenGL效率,同时避免它们实现上的不足.顶点缓存对象(V ...

  2. 顶点缓存对象(VBO)【转】

    http://www.cnblogs.com/hefee/p/3824300.html 顶点缓存对象(VBO) 创建VBO 绘制VBO 更新VBO 实例 GL_ARB_vertex_buffer_ob ...

  3. OpenGL顶点缓冲区对象

    [OpenGL顶点缓冲区对象] 显示列表可以快速简单地优化立即模式(glBegin/glEnd)的代码.在最坏的情况下,显示列表的命令被预编译存到命令缓冲区中,然后发送给图形硬件.在最好的情况下,是编 ...

  4. OpenGL帧缓存对象(FBO:Frame Buffer Object)(转载)

    原文地址http://www.songho.ca/opengl/gl_fbo.html 但有改动. OpenGL Frame BufferObject(FBO) Overview: 在OpenGL渲染 ...

  5. OpenGL顶点缓冲区对象(VBO)

    转载 http://blog.csdn.net/dreamcs/article/details/7702701 创建VBO        GL_ARB_vertex_buffer_object 扩展可 ...

  6. OpenGL帧缓存对象(FBO:Frame Buffer Object)

    http://blog.csdn.net/dreamcs/article/details/7691690 转http://blog.csdn.net/xiajun07061225/article/de ...

  7. OpenGL帧缓存对象(FBO:Frame Buffer Object) 【转】

    http://blog.csdn.net/dreamcs/article/details/7691690 原文地址http://www.songho.ca/opengl/gl_fbo.html 但有改 ...

  8. 【OpenGL】OpenGL帧缓存对象(FBO:Frame Buffer Object) 【转】

    http://blog.csdn.net/xiajun07061225/article/details/7283929/ OpenGL Frame BufferObject(FBO) Overview ...

  9. NeHe OpenGL教程 第四十五课:顶点缓存

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

随机推荐

  1. JS中删除字符串中的空格

    问题描述:         在进行字符串操作时,由于字符串中存在较多的空格,因此需要考虑取消字符串中的空格 问题解决:       (1)删除字符串中的前导空格(字符串的前面的空格): 注意:这里使用 ...

  2. Matlab中find函数的使用

    一.问题来源 看到了 min_score_pos = find(A0_scores==min(A0_scores), 1); [r,c] = find(X,k),返回X中第k个非零元素的行列位置. 二 ...

  3. PAT-乙级-1052. 卖个萌 (20)

    1052. 卖个萌 (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 萌萌哒表情符号通常由“手”.“眼”. ...

  4. Socket 阻塞模式和非阻塞模式

    阻塞I/O模型: 简介:进程会一直阻塞,直到数据拷贝 完成 应用程序调用一个IO函数,导致应用程序阻塞,等待数据准备好. 如果数据没有准备好,一直等待….数据准备好了,从内核拷贝到用户空间,IO函数返 ...

  5. httpsClient抓取证书

    在执行webservice的过程中,出现如下异常: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorExcep ...

  6. HDU 1598 find the most comfortable road(枚举+并查集,类似于最小生成树)

    一开始想到用BFS,写了之后,发现有点不太行.网上查了一下别人的解法. 首先将边从小到大排序,然后从最小边开始枚举,每次取比它大的边,直到start.end属于同一个集合,即可以连通时停止.过程类似于 ...

  7. 桥接模式(Bridge Pattern)

    1,定义           桥接模式(Bridge Pattern),也称为桥梁模式,其用意是将抽象化与实现化脱耦,使得两者可以独立的变化,它可以使软件系统沿着多个方向进行变化,而又不引入额外的复杂 ...

  8. DJANGO变动库的一次真实手动经历

    在变更库时,由于对字段规划和约束性没考虑完全,需要手工操作数据库,以便可以重复执行. 有以下三点要注意. 1,先迎合错误输出,增删对应的表或字段. 2,必要时,修改migrations文件,以去除唯一 ...

  9. 【Linux高频命令专题(13)】cat

    概述 常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用. cat主要有三大功能: 1.一次显示整个文件:cat filename 2.从键盘创建一 ...

  10. 进程内核栈、用户栈及 Linux 进程栈和线程栈的区别

    Linux 进程栈和线程栈的区别 http://www.cnblogs.com/luosongchao/p/3680312.html 总结:线程栈的空间开辟在所属进程的堆区,线程与其所属的进程共享进程 ...