VBO、VAO和EBO
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)。
小结:
- VBO表示显卡中用于存放vertex attribute数据的一块缓存。
- VAO通过vertex attribute binding point建立vertex attribute index与VBO之间的联系,并且在render的时候,只需要绑定一个VAO即可进行draw,减少了状态切换,提升渲染性能。
- EBO以索引的形式对VBO中的顶点进行多次利用,但是无法建立EBO与VAO之间的联系,所以每次draw之前,需要显式的绑定EBO。
VBO、VAO和EBO的更多相关文章
- [转]OpenGL图形渲染管线、VBO、VAO、EBO概念及用例
直接给出原文链接吧 1.OpenGL图形渲染管线.VBO.VAO.EBO概念及用例 2.OpenGL中glVertex.显示列表(glCallList).顶点数组(Vertex array).VBO及 ...
- LearnOpenGL学习笔记(三)——VBO,VAO,EBO理解
在opengl中所有的数据都要放在显存中,我们通过一定的手段去管理它,既要提供地方存放它,还要提供方法去正确地提取它们,去使用它们,opengl通过VAO,VBO,EBO这些手段来解决这些问题. (一 ...
- OpenGL图形渲染管线、VBO、VAO、EBO概念及用例
图形渲染管线(Pipeline) 图形渲染管线指的是对一些原始数据经过一系列的处理变换并最终把这些数据输出到屏幕上的整个过程. 图形渲染管线的整个处理流程可以被划分为几个阶段,上一个阶段的输出数据作为 ...
- VBO, VAO, Generic Vertex Attribute
VBO - 用于存储顶点数据的Buffer Object. VAO - 用于组织VBO的对象. Generic Vertex Attribute - 通用顶点属性. For example, the ...
- Opengl ES之FBO
FBO介绍 FBO帧缓冲对象,它的主要作用一般就是用作离屏渲染,例如做Camera相机图像采集进行后期处理时就可能会用到FBO.假如相机出图的是OES纹理,为了方便后期处理, 一般先将OES纹理通过F ...
- OpenGL渲染管道,Shader,VAO&VBO&EBO
OpenGL渲染管线 (也就是)OpenGL渲染一帧图形的流程 以下列举最简单的,渲染一个三角形的流程,你可以将它视为 精简版OpenGL渲染管线 更复杂的流程也仅仅就是:在此基础上的各个流程中 添加 ...
- VAO VBO IBO大乱炖
最近对程序中绘制卡顿的问题忍无可忍,终于决定下手处理了.程序涉及的绘制比较多,除了点.线.三角形.多边形.圆柱体之外,还有自组格式模型.开始想全部采用显示列表优化,毕竟效率最高,虽然显示列表存在编译之 ...
- GLES2学习VBO和VAO的使用
在GLES2中使用VBO和VAO对象,已经简单vs,ps绘制一个三角形. 1. 初始化操作代码,创建VBO.VAO,编译和链接shader program. void DebugApplication ...
- Opengl ES之VBO和VAO
前言 本文主要介绍了什么是VBO/VAO,为什么需要使用VBO/VAO以及如何使用VBO和VAO. VBO 什么是VBO VBO(vertex Buffer Object):顶点缓冲对象.是在显卡存储 ...
随机推荐
- 获得PyInstaller打包exe的py源码
参考链接:https://laucyun.com/33359ed9f725529ac9b606d054c8459d.html way1:pyi-archive_viewer 提取pyc,uncomp ...
- 4、MyBatis教程之配置解析
5.配置解析 核心配置文件 mybatis-config.xml 系统核心配置文件 MyBatis 的配置文件会深深影响 MyBatis 行为的设置和属性信息. 能配置的内容如下: configura ...
- java学习(更新中)
class Test { public static void main(String[] args) { System.out.println("Hello World!"); ...
- 《基于Kubernetes舵手集群的设计与实现》
前言 <基于Kubernetes舵手集群的设计与实现>是我的毕业设计项目.本系统采用Kubernetes容器编排.基于Jenkins\Gitlab的CICD技术.EFK日志收集.Prome ...
- windows环境下抓密码总结
在线抓密码 1.mimikatz privilege::debug token::whoami token::elevate lsadump::sam mimikatz.exe "privi ...
- 有关指针和C语言中的常量
常量类型(五种): 字面常量(2,3,6....) ; enum 定义的枚举常量; 字符常量('a','b'....) ; ...
- CPU 空闲时在干嘛?
人在空闲时会发呆会无聊,计算机呢? 假设你正在用计算机浏览网页,当网页加载完成后你开始阅读,此时你没有移动鼠标,没有敲击键盘,也没有网络通信,那么你的计算机此时在干嘛? 有的同学可能会觉得这个问题很简 ...
- 面试题:让你捉摸不透的 Go reslice
面试题: package main func a() []int { a1 := []int{3} a2 := a1[1:] return a2 } func main() { a() } 看到这个题 ...
- JMeter线程组编辑区揭秘
线程组编辑区如下: 有点复杂,但是慢慢看下来,还是比较容易理解. Name 带有业务含义的名字. Comments 线程组的备注说明. Action to be taken after a Sampl ...
- JAVAEE_Servlet_19_重定向可以解决页面刷新问题(sendRedirect)
重定向可以解决页面刷新问题(sendRedirect) 在向数据库中添加数据的时候,如果使用转发(getRequestDispatcher),数据插入成功后,转发到提示插入成功页面,在数据插入成功页面 ...