详解Opengl的VBO和VAO

  • 前言
    • 什么是VBO
    • 如何创建VBO
    • 什么VAO
    • 如何执行VAO
    • 总结

前言

对于Opengl中的VBO和VAO相信很多人都熟悉这2个名字,但是有时候缺容易混淆2个概念或者说不理解这2个概念的作用是什么。本人对Opengl的理解也很浅显,所以专门对这2个概念做了学习,这里记录下,便于以后查看。这里使用的Opengl版本为3.3版本,部分api可能在老版本的Opengl会报错。

什么是VBO

VBO全名顶点缓冲对象(Vertex Buffer Object),他主要的作用就是可以一次性的发送一大批顶点数据到显卡上,而不是每个顶点发送一次。我们知道CPU传送数据给GPU其实是比较耗费时间的,所以尽可能的一次性把需要的顶点数据全部传给GPU,这样顶点着色器几乎能立即访问到顶点,有助于加快顶点着色器效率。

如何创建VBO

就目前所有游戏引擎来说VBO的机制已经是基础了,我们看下VBO在Opengl中是如何创建的。
首先,我们需要在Opengl生成一个缓冲类型的ID。

unsigned int VBO;
glGenBuffers(1, &VBO);

这里我们创建了一个缓冲的ID,当然了你也可以通过数组来批量创建一系列的VBO的ID,

unsigned int VBO[3];
glGenBuffers(3,VBO);

接下来,我们要将这个ID绑定给指定类型来告诉Opengl这个缓冲是什么类型的;

glBindBuffer(GL_ARRAY_BUFFER, VBO);

这里我们将VBO赋予了GL_ARRAY_BUFFER的类型,告诉Opengl这个VBO变量是一个顶点缓冲对象。
注意从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)

至此对于VBO的声明就结束了,其实也是蛮简单的,我们回顾下:
1)生成一个缓冲类型的ID;
2)指定ID的缓冲类型为GL_ARRAY_BUFFER
接下来我们看下,如何给VBO赋值,我们假定有如下三角形的顶点数据:

float vertices[] = {// 位置              // 颜色0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,   // 右下-0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,   // 左下0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // 顶部};

Opengl通过glBufferData接口来复制顶点数据到缓冲中供Opengl使用,对于上面的三角形顶点数据,代码就可能是这样的:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

第一个参数表示目标的缓冲类型,这里指当前绑定到GL_ARRAY_BUFFER上的顶点缓冲对象,第二个参数表示数据大小(字节为单位),第三个参数表示我们实际发出的数据。第四个参数GL_STATIC_DRAW表示Opengl如何处理上传的数据,Opengl中一共有三种类型:

  • GL_STATIC_DRAW :数据不会或几乎不会改变。
  • GL_DYNAMIC_DRAW:数据会被改变很多。
  • GL_STREAM_DRAW :数据每次绘制时都会改变。
    到这一步,我们的数据就已经上传到GPU上去了。一个VBO过程也就结束了。
    那这里引申出一个问题,我们看到vertices中有注释,表明这个数组里面有两种数据,一种是顶点坐标,一种是顶点颜色,那都放在一个float的数组中,Opengl如何来区分他们呢?这里就要用到glVertexAttribPointer这个接口了,该接口就是帮助Opengl解释如何处理数据的。我们来看下对于vertices的数据处理的代码:
 // 位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// 颜色属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);

我们分析下这段代码,首先我们来解释下glVertexAttribPointer的各个参数的意义。

  • 第一个参数表示我们希望数据中哪部分数据放在对应的Location位置上,这个可能主要体现在shader部分,我们看下顶点着色器的代码:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

我们根据变量名大致能猜出来location为0的是顶点坐标,location为1的是颜色值,这里的location就是对应了上面代码种glVertexAttribPointer第一个参数,location没有准确的位置定义,并没有说location为0的一定要是顶点坐标属性,也可以是颜色或者uv坐标属性,只是常规来说,我们习惯将坐标放在第一个位置。

  • glVertexAttribPointer第二个参数表示属性大小,坐标和颜色的大小都是3,所以这里填3;
  • 第三个参数表示数据类型;
  • 第四个参数表示我们是否希望数据标准化。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE;
  • 第五个参数表示步长,数据之间的间隔,我们这里不管坐标还是颜色都是3个分量,所以坐标和坐标数据之间隔了6个float字节,颜色和颜色之间隔了6个float字节;
  • 第六个参数表示偏移量,即在一段数据中,指定的数据偏移多少位置开始。在这里,坐标数据都是每段数据的起始位置,所以偏移量是0,而颜色数据在坐标数据之后,坐标数据有3个分量,所以每个颜色数据偏移三个float字节开始算;
    每次设定好一个location的值之后,记得要开启对应的位置数据glEnableVertexAttribArray,因为Opengl默认是全关闭的。
    经过glVertexAttribPointer执行之后,shader中对应layout的位置数据就有对应的值了。

什么VAO

VAO全名顶点数组对象(Vertex Array Object),每当我们绘制一个物体的时候都必须重复这一过程。这看起来可能不多,但是如果有超过5个顶点属性,上百个不同物体呢(这其实并不罕见)。绑定正确的缓冲对象,为每个物体配置所有顶点属性很快就变成一件麻烦事。VAO的作用就是顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。简单来说有点类似复用的概念。

如何执行VAO

VAO的生成和绑定都和VBO很类似;

unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

其过程也是先从生成一个ID开始,然后将ID绑定到顶点数组对象上。任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。概括来说就是从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。
一个顶点数组对象会储存以下这些内容:

  • glEnableVertexAttribArrayglDisableVertexAttribArray的调用。
  • 通过glVertexAttribPointer设置的顶点属性配置。
  • 通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。

我们来举个例子,我们还是使用上面三角形数据:

float vertices[] = {// 位置              // 颜色0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,   // 右下-0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,   // 左下0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // 顶部};

借用GLM的数学库,我们生成2个位置向量:

glm::vec3 traPositions[] = {glm::vec3(0.0f,  0.0f,  0.0f),glm::vec3(0.5f,  0.5f, 0.5f),};

生成VBO和VAO的ID:

unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);

复制顶点数据到缓冲:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

分别处理位置和颜色的数据:

// 位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// 颜色属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);glBindBuffer(GL_ARRAY_BUFFER, 0);//解除VAO绑定glBindVertexArray(0);

生成一个model的矩阵变量(这个model矩阵做用就是model->world的转变矩阵)

glm::mat4 model = glm::mat4(1.0f);

创建model是方便做测试,在世界坐标中绘制不同位置的三角形,其在顶点着色器的作用如下代码:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;out vec3 ourColor;uniform mat4 model;void main()
{gl_Position = model*vec4(aPos, 1.0);ourColor = aColor;
}

我们的片段着色器很简单,仅仅是把传入的颜色直接输出:

#version 330 core
out vec4 FragColor;in vec3 ourColor;void main()
{FragColor = vec4(ourColor, 1.0f);
}

好,至此准备工作都完成了,我们开始做渲染:

while (!glfwWindowShouldClose(window)){...glBindVertexArray(VAO);for (int i = 0; i < 2; i++){glm::mat4 model = glm::mat4(1.0f);model = glm::translate(model, traPositions[i]);outShader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 3);}...}

运行结果就是:

我放出了关键代码,其他代码不是我们关注的重点。我们看到glBindVertexArray(VAO)这个方法之后,代码逻辑就是直接循环绘制了2个三角形,我们注意到这其中我们不需要重复创建VBO,重复设定顶点数据,只需要绑定我们需要绘制的VAO对象,我们就能绘制任意数量的图形,这就是VAO带来的重复绘制的高效性。

总结

对于VBO/VAO的讲解基本差不多了,基于我本人对opengl的认识程度也不深,如果有理解不到位或者有误的地方,还望各位看官不吝赐教!
[参考]:
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/

详解Opengl中VBO和VAO相关推荐

  1. 三维空间刚体运动5:详解SLAM中显示机器人运动轨迹及相机位姿(原理流程)

    三维空间刚体运动5:详解SLAM中显示机器人运动轨迹及相机位姿(原理流程) 一.显示运动轨迹原理讲解 二.前期准备 三.git管理子模块及克隆源代码 1.学习使用Git Submodule 2.克隆源 ...

  2. 详解python中GPU版本的opencv常用方法介绍

    更多编程教程请到:菜鸟教程 https://www.piaodoo.com/ 友情链接:好看站 http://www.nrso.net/ 高州阳光论坛https://www.hnthzk.com/ 引 ...

  3. 详解OpenCV中的Lucas Kanade稀疏光流单应追踪器

    详解OpenCV中的Lucas Kanade稀疏光流单应追踪器 1. 效果图 2. 源码 参考 这篇博客将详细介绍OpenCV中的Lucas Kanade稀疏光流单应追踪器. 光流是由物体或相机的运动 ...

  4. python操作目录_详解python中的文件与目录操作

    详解python中的文件与目录操作 一 获得当前路径 1.代码1 >>>import os >>>print('Current directory is ',os. ...

  5. python3中unicode怎么写_详解python3中ascii与Unicode使用

    这篇文章主要为大家详解python3中ascii与Unicode使用的相关资料,需要的朋友可以参考下# Auther: Aaron Fan ''' ASCII:不支持中文,1个英文占1个字节 Unic ...

  6. foreach php,详解PHP中foreach的用法和实例

    本篇文章介绍了详解PHP中foreach的用法和实例,详细介绍了foreach的用法,感兴趣的小伙伴们可以参考一下. 在PHP中经常会用到foreach的使用,而要用到foreach,就必须用到数组. ...

  7. python open 打开是什么类型的文件-详解Python中open()函数指定文件打开方式的用法...

    文件打开方式 当我们用open()函数去打开文件的时候,有好几种打开的模式. 'r'->只读 'w'->只写,文件已存在则清空,不存在则创建. 'a'->追加,写到文件末尾 'b'- ...

  8. NVIDIA Jetson TK1学习与开发(八):图文详解OpenGL在Jetson TK1上的安装和使用

    图文详解OpenGL在Jetson TK1上的安装和使用 1.入门介绍与资源推介 OpenGL(全写Open Graphics Library)是个定义了一个跨编程语言.跨平台的编程接口规格的专业的图 ...

  9. python中list[1啥意思_详解Python中list[::-1]的几种用法

    本文主要介绍了Python中list[::-1]的几种用法,分享给大家,具体如下: s = "abcde" list的[]中有三个参数,用冒号分割 list[param1:para ...

最新文章

  1. 使用Redis来实现LBS的应用
  2. 数据降维与可视化——t-SNE
  3. eclipse一直卡住,出现 “android sdk content loader 0%” 卡住的错误分析及解决方法...
  4. 自动化测试:Selenium webdriver 学习笔记-C#版(四)
  5. PyTorch随笔-0
  6. Android——SQLite实现面向对象CRUD
  7. VTK:可视化之AlphaFrequency
  8. BusyBox telnet配置
  9. Data Poisoning Attacks to Deep Learning Based Recommender Systems论文解读
  10. 在Excel中如何利用VBA实现(符合条件)指定(空)行列的批量删除
  11. 《Linux命令行与shell脚本编程大全 第3版》Shell脚本编程基础---05
  12. numpy功能快速查找
  13. 面试再问HashMap,求你把这篇文章发给他!
  14. java核心知识点学习----创建线程的第三种方式Callable和Future CompletionService
  15. linux命令psd,Linux 下查看 Photoshop PSD 文件
  16. android 播放直播流,安卓大部分浏览器播放HLS协议直播流会从头开始
  17. 布线问题分支界限法求解
  18. 现在转行学软件测试还有前景吗?最真实的数据告诉你答案
  19. 玩转电脑常用的140个技巧
  20. Good Bye 2022: 2023 is NEAR 题解

热门文章

  1. 第10课:底实战详解使用Java开发Spark程序学习笔记(二)
  2. jQuery实现一个简单的轮播图
  3. 用思维导图来制作淘宝详情页
  4. 单片机计算机实训总结,单片机实习心得体会范文
  5. 《OpenWrt智能路由系统开发》书摘
  6. 华为平板android最新版本号,MediaPad ICS正式版升级包发布 华为平板电脑率先进入Android 4.0时代...
  7. 中企出海,数智人力重构企智人效的人才供应体系
  8. 使用动态SQL完成多条件查询
  9. 风炫安全Web安全学习第三十九节课 反序列化漏洞基础知识
  10. 2022 年全国职业院校技能大赛 网络搭建与应用赛项正式赛卷