OpenGL日常-三角形(下)

大家好,欢迎来到听风的OpenGL日常。

写在前面

本文代码

上回说到,预期的三角形并没有,并没有,并没有渲染出来,今天我们来补充下,看看问题出在哪里。

本文重点

我们先来搞清楚VAO,VBO缓存到底做的是什么工作?

首先是VBO(vertex buffer object),为什么我们要用VBO?

不使用VBO时,我们每次绘制(glDrawArrays)图形时都是从本地内存处获取顶点数据然后传输给OpenGL来绘制,这样就会频繁的操作CPU->GPU增大开销,从而降低效率。
使用VBO,我们就能把顶点数据缓存到GPU开辟的一段内存中,然后使用时不必再从本地获取,而是直接从显存中获取,这样就能提升绘制的效率。

在讲清楚这个概念之前,我们还要补充一些概念;

glBegin/glEnd

以此文的例子解释,我们在传递顶点位置数据的时候,在OpenGL旧版本里,是通过glVertex逐个从CPU传递到GPU的,代码示例如下:

glBegin(GL_TRIANGLES);
    glVertex(0.0f, 0.0f);
    glVertex(1.0f, 0.0f);
    glVertex(0.0f, 1.0f);
glEnd();

这样每进行一次glVertex调用就会向GPU传递一次,由于传输是同步的,所以效率很低;

000.png

于是:

DL

Display List(显示列表)的出现可以使CPU在传输的过程中等待数据打包完成,待其结束一次性发送到GPU,代码如:

GLuint listName = glGenLists (1);
glNewList (listName, GL_COMPILE);
    glBegin (GL_TRIANGLES);
        glVertex2f (0.0, 0.0);
        glVertex2f (1.0, 0.0);
        glVertex2f (0.0, 1.0);
    glEnd ();
glEndList ();
...
// 绘制(不传输数据)
glCallList(listName);
001.png

显示列表加快了传输效率,但是绘制时是一次性的,那么如果列表中的某单个顶点发生变化时,那么就需要CPU重新生成新的顶点再发送到GPU,GPU收集完成后完成绘制,这样做是极其浪费资源的,当每一帧都有变化时,它就退化成了单个顶点传输的方式。

VA

Vertex Array,顶点数据要区别于我们开头提到的VAO,它跟缓存是没有关系的,它也是一种传输方案。VA也是通过收集顶点的方式来减少传输次数,但与显示列表不同的是,CPU端将会负责收集所有顶点,收集完成后一次性传输到GPU再进行绘制。

002.png

这样做导致的结果是,每次进行绘制时,都会进行一次传输,所以绘制速度会低于显示列表。

// 每次绘制都将 vertices 传输一次
GLfloat vertices[] = {
    0.0f, 0.0f,
    1.0f, 0.0f, 
    0.0f, 1.0f
}
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2,GL_FLOAT,0,vertices);
glDrawArray(GL_TRIANGLES, 0, 3); 

VBO

VBO是结合DL和VA的特点,既方便传输,又要兼顾修改。

由于VA在CPU收集的顶点是一个整体,所以在GPU向渲染流水线提交数据是由一个整体提交的,无法在渲染时做修改;而DL虽然可以单个修改,但是渲染时却需要等待CPU端修改完成等GPU端收集完成再进行。

为了既提高传输效率,又可以使渲染时数据在GPU端也可以修改,VBO应运而生。

003.png

这样一来VBO保存了一份顶点数据,修改操作可以直接在GPU上进行,修改完成直接绘制。

所以按照这样理解,它的传输与修改是分开的,体现在代码上:

//生成VBO,并传输保存到GPU上
GLuint vbo;
glGenBuffer(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
...

// 绘制时直接从VBO中取得顶点数据
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2, (void*)0);
glDrawArray(GL_TRIANGLES, 0, 3);

...

但是这里只用VBO进行渲染没有可以参考的代码,本人也是进行了实验但是最终没有画出来,希望有厉害的小伙伴可以一起来交流一下这个问题,这里就留作一个探索。

VAO

本文最终采用的是VAO结合VBO画出的三角形,上篇我们讨论过了,结果没有出来,今天我们把坑填上。

Vertex Array Object,顶点数据对象是为了简化VBO的流程,当所要传输的VBO有很多的时候,我们需要管理多个VBO,这样对每个VBO都进行记录会比较乱,我们首先想到的管理方式就是用一个数组将它们保存起来,没错,这就是VAO,很直观。

VAO.png

如图所示,VAO保存了不同VBO的指针,用户可以通过这些指针来对数据进行操作。

本文中,我们先分别生成VAO和VBO,将VBO绑定到VAO中,将VAO绑定到缓存里

//VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

//VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//设置对缓冲区访问的步长为3以及相位为0,告诉着色器,这个数据输入到着色器的第一个(索引为0)输入变量,数据的长度是3个float
GLuint uPos = glGetAttribLocation( shaderProgram, "aPos" );
glVertexAttribPointer(uPos, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(uPos);

//delete buffer and array
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

这里,glGetAttribLocation( shaderProgram, "aPos" );一句中,shaderProgram就是我们上节中编译好的OpenGL程序,"aPos"是顶点着色器代码中的顶点,还记得吗?

004.png

这里的location你可以修改下它的值,看看结果uPos的值,会有助于理解它的意义,当然着色器关键字这里先不进行讨论。

glVertexAttribPointer(uPos, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

重点讨论一下这个函数:

第一个参数:对应着色器代码中的location;
第二个参数:顶点属性的大小,在这里是vec3,所以是3;
第三个参数:数据类型,没什么好说的;
第四个参数:定义是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间;
第五个参数:第五个参数叫做步长(Stride),还是用图来解释一下吧,比较直观;
第六个参数:表示位置数据在缓冲中起始位置的偏移量(Offset),当有多个VBO里,可以通过偏移量进行位置锁定;

上面的第五个参数中的步长为3,即在VBO里每一个顶点占12个位置,每个位置所占字节由其保存数据类型决定。

005.png

函数glVertexAttribPointer给出了如何从VBO中取得顶点数据的方式,所谓的OpenGL位置学(我又开始胡说八道了)。

接下来,快结束战斗了,画三角形吧。

glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays( GL_TRIANGLES, 0, 3 );

那么我们就愉快的结束了。等下!!!

res.png

EBO

对于VAO的这种方式,我们有点小想法,现有一个问题,如果我们要画两个三角形,你觉得最少可以用几个顶点呢?答案肯定是4个。但是如果用前面的方法,恐怕我们需要至少6个来完成,那么EBO,索引缓冲对象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO)的出现就是为了重复利用顶点。

个人觉得索引这个概念特别好,将顶点用索引作标记,当使用顶点时用索引间接访问,如图:

EBO.png

我们来定义一下数据和索引;

float vertices2[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = { // 注意索引从0开始! 
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

建立索引缓冲对象:

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

接下来同VBO类似,将索引复制到缓冲里,

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

最终进行绘制:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

数据调用的过程是,绘制时先从EBO从找到索引,再通过VAO中找到对应的VBO中的顶点,glDrawElements的参数:

第一个:与glDrawArrays一样,设置显示模式;
第二个:总共要绘制的顶点的个数;
第三个:索引的类型;
第四个:指定EBO中的偏移量,类似于VBO中的偏移量;

好了,就到这吧,EBO的部分不多做解释了;

res2.png

总结

BE -> DL -> VA -> VBO -> VAO -> EBO;

如果你最终懂得了这个链条的来源,那么恭喜你已经理解了。

本文参考:

最后这篇复现没有成功,希望有复现的朋友可以给出点提示。

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