Vertex Buffer Object

对于经历过fixed pipeline的我来讲,VBO的出现对于渲染性能提升让人记忆深刻。完了,暴露年龄了~

//immediate mode
glBegin(GL_TRIANGLES);
glNormal3f(...);
glVertex3f(...);
glEnd(); //display list
list = glGenLists(1);
glNewList(list, GL_COMPILE);
glBegin(GL_TRIANGLES);
glNormal3f(...);
glVertex3f(...);
glEnd();
glEndList(); glCallList(list); //vertex array
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(...);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(...);
glDrawArrays(...);

上面的代码是远古时期的OpenGL绘制图元的执行流程,不懂也不用追究了,因为实在太老了。

接下来我们进入正题。

VBO标识的是显卡中的一块存储区域,我们可以从内存中向它传送顶点数据(空间位置,纹理坐标,法线等等),然后在draw的时候作为vertex attribute进行使用。

void init()
{
GLfloat position[] = //空间位置
{
-0.8f, -0.8f, 0.0f,
0.8f, -0.8f, 0.0f,
0.0f, 0.8f, 0.0f
};
GLfloat color[] = //颜色
{
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f
}; GLuint vbo[2] = {0};
glCreateBuffers(2, vbo); //创建buffer对象
//把buffer object绑定到GL_ARRAY_BUFFER(binding target),表示这个buffer object用来存放vertex attributes,现在它就是一个VBO啦
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBindBuffer(GL_ARRAY_BUFFER, 0);//为GL_ARRAY_BUFFER绑定一个无效的对象,防止后续的手贱操作,同时提醒你4.5版本引入DSA之后,可以在不bind的情况下直接操纵object了
glNamedBufferData(vbo[0], 9 * sizeof(GLfloat), position, GL_STATIC_DRAW); //向vertex buffer上传数据
glNamedBufferData(vbo[1], 9 * sizeof(GLfloat), color, GL_STATIC_DRAW);
}

至此,我们创建好了两个VBO并分别存放了空间位置数据和颜色数据。

我们的shader仍然是最简单的shader:

//vertex shader
#version 460 core layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color; layout(location = 0) out vec3 vs_out_color; void main(void)
{
vs_out_color = color;
gl_Position = vec4(position, 1.0);
} //fragment shader
#version 460 core layout(location = 0) in vec3 fs_in_color; layout(location = 0) out vec4 frag_color; void main(void)
{
frag_color = vec4(fs_in_color, 1.0);
}

接下来的问题就是我们如何把VBO与vertex attribute (location) 关联起来,这时候VAO就闪亮登场了。

Vertex Array Object

GLuint vao = 0;

void init()
{
... //set up vbo glCreateVertexArrays(1, &vao); //创建vao
//启用vertex attribute 0 和 1,其中0和1分别与vertex shader中的position(location = 0)和color(location = 1)对应
glEnableVertexArrayAttrib(vao, 0);
glEnableVertexArrayAttrib(vao, 1); glVertexArrayVertexBuffer(vao, 3, vbo[0], 0, sizeof(GLfloat) * 3); //设置vbo的binding point
glVertexArrayVertexBuffer(vao, 5, vbo[1], 0, sizeof(GLfloat) * 3); glVertexArrayAttribBinding(vao, 0, 3); //设置vertex attribute的binding point,须与对应的vbo bind到同一个binding point上
glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, 0); //指定vertex attribute的顶点规范,相当于告诉OpenGL如何解析对应的vbo数据,之后vertex shader就能够拿到正确的vertex attribute
glVertexArrayAttribBinding(vao, 1, 5);
glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, 0);
}

可以看到,自从4.5版本增加了DSA,API的执行顺序不是那么的重要了,因为调用OpenGL的命令需要显式的指定handle,而不是把这个handle绑定到当前的OpenGL Context(如上述代码,每次我们都传入了vao)。另外对于状态的从属关系,也更加明确了。

我们把vertex attribute和对应的vbo绑定到了同一个binding point上,相当于告诉OpenGL,vertex attribute的数据来自哪个vbo。这里我故意把binding point的值分别设置为3和5(其实可以设置为0和1),是担心有同学会把vertex attribute binding point和vertex attribute index弄混淆了。

另外需要注意的是glVertexArrayVertexBuffer最后一个参数指定了vbo中元素之间的stride,与glVertexAttribPointer不同的是,就算vbo中的元素是紧挨着的,也必须设置正确的stride值,而不能设置为0。因为在调用glVertexArrayVertexBuffer的时候,OpenGL对于vbo中的数据该如何解析丝毫不知情。而之所以你之前用到的glVertexAttribPointer的stride可以设置为0,是因为这个命令同时指定了每个元素的类型(比如GL_FLOAT)以及size(比如由3个GL_FLOAT组成),相当于OpenGL会自动帮我们去算正确的stride的值。有的同学可能会说,glVertexArrayAttribFormat不是指定了如何解析vbo中的数据吗,但是你有没有想过:glVertexArrayAttribFormat不一定在glVertexArrayAttribBinding之前调用,所以在调用glVertexArrayAttribBinding的时候OpenGL可能还不知道vbo的数据信息。

我们已经vao设置好了所有必要的信息了,现在用它进行render

void render()
{
... glBindVertexArray(vao); //绑定vao
glDrawArrays(GL_TRIANGLES, 0, 3 ); //draw
glBindVertexArray(0); //draw完成,将当前context下的Vertex Array绑定到一个无效的handle上
}

VAO先讲到这里,下面我们看一下EBO。

Element Buffer Object

所谓EBO,就是把顶点索引数据保存到buffer中,然后用这些索引去vertex buffer中查找对应的顶点来绘制图元,以避免在vertex buffer中存放冗余的顶点信息。

//绘制一个矩形
void init()
{
GLfloat position[] =
{
-0.5f, 0.5f,
-0.5f, -0.5f,
0.5f, 0.5f,
0.5f, -0.5f,
};
GLfloat color[] =
{
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f
}; GLubyte index[] =
{
0, 1, 3,
2, 0, 3
}; glCreateBuffers(2, vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glNamedBufferData(vbo[0], 8 * sizeof(GLfloat), position, GL_STATIC_DRAW);
glNamedBufferData(vbo[1], 12 * sizeof(GLfloat), color, GL_STATIC_DRAW); glCreateBuffers(1, &ebo); //创建buffer对象
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); //将buffer对象当作GL_ELEMENT_ARRAY_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //为当前的OpenGL Context的EBO置为无效值
glNamedBufferData(ebo, 6 * sizeof(GLubyte), index, GL_STATIC_DRAW); //向element array buffer传输索引数据 glCreateVertexArrays( 1, &vao ); glEnableVertexArrayAttrib(vao, 0);
glEnableVertexArrayAttrib(vao, 1); glVertexArrayVertexBuffer(vao, 3, vbo[0], 0, sizeof(GLfloat) * 2);
glVertexArrayVertexBuffer(vao, 5, vbo[1], 0, sizeof(GLfloat) * 3); glVertexArrayAttribBinding(vao, 0, 3);
glVertexArrayAttribFormat(vao, 0, 2, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 1, 5);
}

同学们只需要关注我加注释的那一段代码。可以发现,其实EBO的建立过程与VBO极为类似。

不过很遗憾,本来我以为可以同vbo一样,通过binding point或者其它手段建立EBO和VAO的联系,可惜没找到。所以我们在render的时候,除了要绑定VAO外,还要把用到的EBO绑定至当前的OpenGL Context。

void render()
{
glBindVertexArray(vao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); //绑定ebo
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, (GLvoid*)(nullptr));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}

draw命令也不能用glDrawArrays,而是用glDrawElements。现在我确信不存在可以建立EBO和VAO之间联系的API了,因为glDrawElements的最后的两个参数分别表示EBO存放的数据类型和起始位置的字节偏移。如果存在这样的API,那么这两个参数的信息肯定是保存到了VAO中了(参照VBO和VAO)。

小结:

  1. VBO表示显卡中用于存放vertex attribute数据的一块缓存。
  2. VAO通过vertex attribute binding point建立vertex attribute index与VBO之间的联系,并且在render的时候,只需要绑定一个VAO即可进行draw,减少了状态切换,提升渲染性能。
  3. EBO以索引的形式对VBO中的顶点进行多次利用,但是无法建立EBO与VAO之间的联系,所以每次draw之前,需要显式的绑定EBO。

VBO、VAO和EBO的更多相关文章

  1. [转]OpenGL图形渲染管线、VBO、VAO、EBO概念及用例

    直接给出原文链接吧 1.OpenGL图形渲染管线.VBO.VAO.EBO概念及用例 2.OpenGL中glVertex.显示列表(glCallList).顶点数组(Vertex array).VBO及 ...

  2. LearnOpenGL学习笔记(三)——VBO,VAO,EBO理解

    在opengl中所有的数据都要放在显存中,我们通过一定的手段去管理它,既要提供地方存放它,还要提供方法去正确地提取它们,去使用它们,opengl通过VAO,VBO,EBO这些手段来解决这些问题. (一 ...

  3. OpenGL图形渲染管线、VBO、VAO、EBO概念及用例

    图形渲染管线(Pipeline) 图形渲染管线指的是对一些原始数据经过一系列的处理变换并最终把这些数据输出到屏幕上的整个过程. 图形渲染管线的整个处理流程可以被划分为几个阶段,上一个阶段的输出数据作为 ...

  4. VBO, VAO, Generic Vertex Attribute

    VBO - 用于存储顶点数据的Buffer Object. VAO - 用于组织VBO的对象. Generic Vertex Attribute - 通用顶点属性. For example, the ...

  5. Opengl ES之FBO

    FBO介绍 FBO帧缓冲对象,它的主要作用一般就是用作离屏渲染,例如做Camera相机图像采集进行后期处理时就可能会用到FBO.假如相机出图的是OES纹理,为了方便后期处理, 一般先将OES纹理通过F ...

  6. OpenGL渲染管道,Shader,VAO&VBO&EBO

    OpenGL渲染管线 (也就是)OpenGL渲染一帧图形的流程 以下列举最简单的,渲染一个三角形的流程,你可以将它视为 精简版OpenGL渲染管线 更复杂的流程也仅仅就是:在此基础上的各个流程中 添加 ...

  7. VAO VBO IBO大乱炖

    最近对程序中绘制卡顿的问题忍无可忍,终于决定下手处理了.程序涉及的绘制比较多,除了点.线.三角形.多边形.圆柱体之外,还有自组格式模型.开始想全部采用显示列表优化,毕竟效率最高,虽然显示列表存在编译之 ...

  8. GLES2学习VBO和VAO的使用

    在GLES2中使用VBO和VAO对象,已经简单vs,ps绘制一个三角形. 1. 初始化操作代码,创建VBO.VAO,编译和链接shader program. void DebugApplication ...

  9. Opengl ES之VBO和VAO

    前言 本文主要介绍了什么是VBO/VAO,为什么需要使用VBO/VAO以及如何使用VBO和VAO. VBO 什么是VBO VBO(vertex Buffer Object):顶点缓冲对象.是在显卡存储 ...

随机推荐

  1. dfs求连通块

    递归 递归是什么?绝大部分人都会说:自己调用自己,刚开始我也是这样理解递归的.确实没错,递归的确是自己调用自己.递归简单的应用:编写一个能计算斐波那契数列的函数,也就是这样: int fb(int n ...

  2. 精通模块化JavaScript

    近日读了一本名为<精通模块化JavaScript>的书,并记录了其中的精髓. 一.模块化思维 精通模块化开发并不是指要遵循一套定义明确的规则,而是指能够将自己置身于使用者的角度,为可能即将 ...

  3. 从I/O多路复用到Netty,还要跨过Java NIO包

    本文是Netty系列第4篇 上一篇文章我们深入了解了I/O多路复用的三种实现形式,select/poll/epoll. 那Netty是使用哪种实现的I/O多路复用呢?这个问题,得从Java NIO包说 ...

  4. Python基础之告警定义与告警抑制

    技术背景 在前面一篇博客中我们介绍了在python中自定义异常以及异常的捕获.这里我们要介绍另外一种形式的用户提醒:告警.我们这里就不给出一些过于官方或者技术的定义了,在实际项目中的使用场景主要有这么 ...

  5. idea报错Selected Java version 11 is not supported by SDK (maximum 8)

    解决方案

  6. STM32内存结构介绍和FreeRTOS内存分配技巧

    这是我第一次使用FreeRTOS构建STM32的项目,踩了好些坑,又发现了我缺乏对于操作系统的内存及其空间的分配的知识,故写下文档记录学习成果. 文章最后要解决的问题是,如何恰当地分配FreeRTOS ...

  7. mysql 批量操作,已存在则修改,不存在则insert,同时判断空选择性写入字段

    注:如果是批量插入需要在 Java 连接数据库的字串中设置 &allowMultiQueries=true 针对单行数据有则修改无则新增 本案例的建表语句是: -- auto-generate ...

  8. Dynamics CRM邮箱配置

    Dynamics CRM对邮箱有很好的支持,开通邮箱后方便用户通过邮件进行Dynamics CRM的业务处理,同时也可以作为一直消息流提醒的手段应用于审批.通知等场景,可以做一些更深入的功能拓展. 本 ...

  9. 搜狗高级架构师帮你系统掌握TypeScript开发

    JavaScript 是一门动态弱类型语言,对变量的类型非常宽容,而且不会在这些变量和它们的调用者之间建立结构化的契约. Angular 已经使用 TypeScript 重构了代码,另一大前端框架 V ...

  10. springboot项目整合druid数据库连接池

    Druid连接池是阿里巴巴开源的数据库连接池项目,后来贡献给Apache开源: Druid的作用是负责分配.管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个: D ...