Modern OpenGL - 顶点数组、属性与绑定点(OpenGL 4.5+)

本文介绍如何在OpenGL 4.5+环境下用最现代的方式渲染顶点。本文持续更新中。

前言

上一节我们讲了如何在Modern OpenGL下渲染矩形体,但其中用到的主要是OpenGL 3.x中内容。OpenGL 4.x增添了很多新内容,并且一部分3.x的内容得到改进。本节中会详细讲解新版和与旧版的区别与联系,并给出应用最新技术的例子。

1. 直接状态访问 DSA (Direct State Access)

OpenGL 4.5中给了我们DSA,可以在调用方法时直接传入要操作的OpenGL对象,而不再需要绑定操作,更加高效。与之对应的,所有glGen*方法均不再使用,因为这些方法只生成了一个对象(实质上是分配了一个ID),并没有定义这个对象的类型。在4.5以前,一个对象的定义发生在首次绑定时。例如:

  • glGenTextures(1, &texture)生成一个纹理
  • 第一次调用glBindTexture(GL_TEXTURE_2D, texture)时,定义了该纹理是2D纹理。

从OpenGL 4.5起,由于不再需要绑定操作,所有创建OpenGL对象的操作都被glCreate*所替代,例如:

  • glCreateTextures(GL_TEXTURE_2D, 1, &texture),创建一个2D纹理。

如果依然使用glGen*而不进行首次绑定,则该OpenGL对象是无效对象,操作时会报出GL_INVALID_OPERATION错误。

2. 顶点数组对象 Vertex Array Object

Modern OpenGL渲染中必须使用VAO,它无处不在。VAO本身不存储任何顶点数据,它会保存我们要渲染时所需要的顶点的定义、规格与配置。一旦我们配置好了一个VAO,只要绑定它,就可以直接调用渲染函数,而不需要调用任何定义/配置类的函数。

创建一个VAO

GLuint vao_id;
glCreateVertexArrays(/* number */ 1, &vao_id);

3. 通用顶点属性 Generic Vertex Attribute

顶点属性是顶点着色器(Vertex Shader)的输入。在Modern OpenGL中,我们必须自己定义我们需要哪些顶点属性。每定义的一个属性,都可以叫做通用顶点属性。与之对应的,在传统固定管线中,顶点着色器存在内置顶点属性,但它们都已经废弃。所以我们说的顶点属性都是指通用顶点属性,它们的名字、类型都是完全自定义的。例如,我们的顶点着色器:

layout(location = 0) in vec2 a_Pos;
layout(location = 1) in vec4 a_Color;

smooth out ...
void main() {
...

顶点属性用in表示,location = 0显式指定了属性索引(attribute index)是0,location不一定连续。属性名在这种情况下仅在shader内使用。

4. 顶点缓冲绑定点 Vertex Buffer Binding Point

顶点缓冲绑定点是在一个VAO内共享的,我们可以将一种配置绑定到一个绑定点上,再将一个或多个顶点属性与之绑定,这样就可以随时切换顶点属性而不需要重新配置缓冲。也就是说,我们定义一个绑定点,和它如何从VBO中读取数据,这个定义信息会被存入VAO,关系图如下:

20210609183833.png

设置绑定点与VBO关系的函数为glVertexArrayVertexBuffer

glVertexArrayVertexBuffer(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride)
  • vaobj - 要操作的VAO
  • bindingindex - 绑定点的索引,0起始
  • buffer - 存储顶点数据的那个VBO
  • offset - 第一个顶点属性的起始偏移量
  • stride - 到下个顶点数据的跨度

比如:我们定义0号绑定点对应12个字节大小,然后我们的VBO创建为vbo_first,它的数据是每12个字节一组顶点属性,那么offset是0,stride为12。如果它第8到20字节,28到40字节以此类推,是我们需要的数据的话,那么offset是8(第一组数据是8开始),stride是20(8到28是20, 20到40是20)。剩下的一个个8字节(0到8,20到28...)也许是其他地方需要的数据。因为一个VBO可以存任何东西,VBO用一个还是多个,数据怎么存放,可自由设置。

5. 顶点属性格式 Vertex Attribute Format

重点来了,虽然我们在shader中定义了顶点属性,但OpenGL不知道我们是如何定义的,也就不知道怎么把数据传给shader的attribute,所以我们必须指定它们。

glVertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, 
                GLboolean normalized, GLuint relativeoffset);

第一个参数传入我们的VAO。attribindex传入属性索引,也就是顶点着色器中(location = 几)的几。

  • size - 指定了该顶点属性的大小,不是字节数,是分量数!该值只能是1,2,3,4。例如 vec2,分量数是 2。
  • type - 指定了VBO数据中对应此属性的类型。可选的值有 GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REVGL_UNSIGNED_INT_10F_11F_11F_REV
  • normalized - 指定了数据是否要被标准化。如果数据类型的符号型数值,则转换为[-1, 1]的浮点值,如果是无符号型数值,则转换为[0, 1]的浮点值。如果不标准化,则直接转换为浮点值。坐标不需要标准化,所以这里输入 GL_FALSE

注意:上面的函数最终会把数据转成浮点型。如果使用 glVertexArrayAttribIFormat(多了一个I, Integer),则会转成整数类型,并且 type 只能 GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INTGL_UNSIGNED_INT。这个方法不含 normalized 参数。

此外还有一个relativeoffset参数,相对偏移量,也就是在绑定点中的偏移量,而不是VBO中的偏移量。我们刚刚定义了绑定点0占据12个字节,这样的话我们可以把之前的vec2 a_Posvec4 a_Color都绑定到这个绑定点上,如果我们的VBO是前8个字节是坐标,后4个字节是颜色,那么就分别调用:

glVertexArrayAttribFormat(vao_id, 0, 2, GL_FLOAT, false, 0);
// location = 1, vec4 (4个分量), 相对偏移量是8
glVertexArrayAttribFormat(vao_id, 1, 4, GL_UNSIGNED_BYTE, true, 8);

在VBO中存储后4个字节为RGBA颜色值,标准化到浮点型,因为unsigned int是0~255,所以转换时会除255得到shader中使用的颜色值。8是因为坐标2两个浮点值8字节,一个绑定点是12字节。

别忘了,glVertexArrayAttribFormat只定义了属性格式,要绑定到绑定点上,还需要调用glVertexArrayAttribBinding

glVertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex);

第一个参数是VAO,第二个是属性索引,第三个是绑定点的索引。这里我们就需要把0和1号属性全都绑定到0号绑定点:

glVertexArrayAttribBinding(vao_id, 0, 0);
glVertexArrayAttribBinding(vao_id, 1, 0);

假设我们顶点着色器中还有2号和3号属性,也是vec2和vec4。那么只需最开始调用glVertexArrayAttribFormat,然后在需要切换的时候调用glVertexArrayAttribBinding切换绑定即可。你会发现,我们并没有重新配置绑定点,也就是怎么从VBO中读取。而且,我们从来没绑定过VBO,它只是作为参数传入。

6. 元素缓冲对象 Element Buffer Object

如果需要索引绘制(indexed drawing),我们还要将所使用的EBO配置进VAO:

glVertexArrayElementBuffer(vao_id, ebo_id);

这样一来,所有的配置都存进了VAO,由多个VBO存放顶点数据,只要绑定VAO即可渲染,渲染循环如下:

glBindVertexArray(vao_id);
glUseProgram(program);
glDrawElementsInstanced(...);

要更新顶点数据,只需在下一帧渲染开始前使用glNamedBufferSubData更新VBO即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容