Learn OpenGL Plus——VAO,VBO

在学习 OpenGL 的时候,可能第一次遇到的比较难以理解的概念就是 VBO,VAO 和 EBO 这三个 O。而之所以难以理解的原因,我认为有以下几个原因:

  • 缺少关键的历史背景
    OpenGL 在其发展过程中发生过的一个重大的改变就是从固定渲染管线(Fixed Function Pipeline)变为可编程渲染管线(Programmable Pipeline)。而这两种截然不同的渲染管线的转变也涉及到大量概念的改变。对于绝大部分刚开始接触 OpenGL 的新手来说,由于缺少了很多的历史背景,导致在理解很多概念的时候无法有一个正确。
  • 翻译
    在学习的过程中,我发现一个很重要的点,那就是很多固定的技术术语在翻译为中文的时候,由于没有一个统一的翻译或者没有术语表,导致同一个名词会有很多不同的翻译,最终到知道很多时候对着几个翻译不同的术语疑惑不解。

因此,我接下来写的内容将会有两个重要的前提:

  • 尽我所能补全对应的历史背景信息
  • 术语表,标清楚英文原文和我认为清晰的中文翻译

顶点属性

顶点属性(Vertex Attribute)又叫顶点数据,例如位置坐标,纹理坐标,颜色,法线等都属于纹理属性。顶点属性是有上限的,可以通过
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs) 来查询支持的最大顶点属性的个数,所有 OpenGL 实现必须支持至少 16 个顶点属性。注意,这里需要避免误解,不是说 16 个顶点,而是 16 个顶点属性,顶点个数不受 GL_MAX_VERTEX_ATTRIBS 限制,具体可以看一下下面的图会能够理解:

Vertex Array 和 Vertex Buffer Object

顶点属性的组织存储方式经历了很多——从最开始的 glVertex 到显示列表(Display List),顶点数组(Vertex Array),而在 VBO 出现了以后,之前的方法就陆续都被弃用了。所以花太多时间去理解一些已经被弃用的东西不是很有意义,而为了体现出 VBO 的优势,简单认识一下历史还是很有必要的,具体的内容可以看 这篇十分优秀的文章 ,这里简单总结一下:

  • glVertex 的问题在于低效,每次 glVertex 调用都需要与 GPU 做一次通信
  • 显示列表解决了 glVertex 的效率问题,可以将所有的顶点收集好一次性发送给 GPU。但是问题在于缺乏灵活性,顶点属性数据在发送给 GPU 以后,就不能再修改了
  • 顶点数组没有显示列表不能修改数据的问题,但是顶点数组的问题在于数据处理效率:顶点数组是在客户端设置的,所以执行 glDrawArrayglDrawElement 这类函数()后,客户端还得把得到的顶点数据向服务端传输一次(所谓的 “二次处理”),这样一来就有了不必要的动作了,降低了效率

而 VBO 则结合了之前各个方法的优点,直接把顶点数据交到流水线的第一步,与显示列表的效率还是有差距,但它这样就得到了操作数据的弹性 —— 渲染阶段,我们的 VBO 绘制函数持续把顶点数据交给流水线,在某一刻我们可以把该帧到达了流水线的顶点数据取回客户端修改(Vertex mapping),再提交回流水线(Vertex unmapping),或者用 glBufferData 或 glBufferSubData 重新全部或 buffer 提交修改了的顶点数据,这是 VBO 的另一个特性。[^2]

需要区分顶点属性和通用顶点属性的区别,在老版本的 OpenGL 里,顶点属性的解释是通过不同的函数来实现的。例如纹理坐标是通过 glTexCoordPointer(),法线是通过 glNormalPointer()。但是这样有一些局限性就是不灵活。因此在 OpenGL 2.0 的时候引入了新的方法——glVertexAttribPointer()。这个方法的引入带来了一个全新的概念——通用顶点属性(generic vertex attribute)。通过这个方法,可以对 VBO 里的所有顶点属性进行更加灵活的定义说明。^1

With GL 2.0, a new way to specify your vertex information became available: glVertexAttribPointer

You could of course continue to use glVertexPointer, glTexCoordPointer, glNormalPointer, glColorPointer.

If you create a GL 3.0 forward context, you won’t be able to use glVertexPointer, glTexCoordPointer, glNormalPointer, glColorPointer.

You must use the generic version in this case: glVertexAttribPointer

在理解了顶点属性的概念了以后,接下来的问题就是,这些顶点属性存在哪?

VBO VAO 傻傻分不清楚

VBO 是真正存储顶点属性数据的缓冲对象 (Buffer Object),但是 VBO 也就仅仅是一块在 GPU 内存储数据的内存空间而已,需要有人对这块内存空间存储的格式进行解释说明,而 VAO 就承担了这个角色。

VAO 记录的是一次绘制中所需要的信息,这包括:

  1. 数据在哪里: glBindBuffer
  2. 数据的格式是怎么样的 glVertexAttribPointer
  3. shader-attribute 的 location 的启用 glEnableVertexAttribArray。[^2]

VBO 与 Shader

一开始学习 Shader 的时候肯定对下面的 glsl 代码疑惑过:

layout (location=0) in vec3 aPos;

这个回答 可以看到,layout (location=X)一种类似语法糖的写法,是在新版(相对)的 OpenGL 版本才支持这种写法;而通过 glBindAttribLocation() 的写法是传统写法,可能兼容性会更好一些,但是两者最终的作用是一样的。

[^2]: OpenGL 中 glVertex、显示列表 (glCallList)、顶点数组 (Vertex array)、VBO 及 VAO 区别

Author: simowce

Permalink: https://blog.simowce.com/all-about-vao-vbo/

知识共享许可协议
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。