赞
踩
目录
三. VBO(Vertex Buffer Object)顶点缓冲区对象
四. VAO(Vertex Array Object)顶点数组对象
这里的object指的都是GPU中的一块内存,每个内存对象都有不同的作用,但创建、绑定、数据传送等方式都比较类似,通过不同的类型加以区分,掌握了一种,其他的就很好理解。
最原始的设置顶点方法,在glBegin和glEnd之间使用。OpenGL3.0已经废弃此方法。每个glVertex与GPU进行一次通信,十分低效。
- glBegin(GL_TRIANGLES);
- glVertex(0, 0);
- glVertex(1, 1);
- glVertex(2, 2);
- glEnd();
顶点数组也是收集好所有的顶点,一次性发送给GPU。不过数据不是存储于GPU中的,绘制速度上没有显示列表快,优点是可以修改数据。
显示列表和顶点数组都是过时的东西了,下面的VBO和VAO才是重点!
- #define MEDIUM_STARS 40
- M3DVector2f vMediumStars[MEDIUM_STARS];
- //在这做点vMediumStars的设置//
- glVertexPointer(2, GL_FLOAT, 0, vMediumStars);
- glDrawArrays(GL_POINTS, 0, MEDIUM_STARS);
VBO,全称为Vertex Buffer Object,与FBO,PBO并称,但它实际上老不少。就某种意义来说,他就是VA(Vertex Array)的升级版。VBO出现的背景是人们发现VA和显示列表还有让人不满足的地方。一般,在OpenGL里,提高顶点绘制的办法:
(1)显示列表:把常规的glBegin()-glEnd()中的代码放到一个显示列表中(通常在初始化阶段完成),然后每遍渲染都调用这个显示列表。
(2)VA:使用顶点数组,把顶点以及顶点属性数据作为数组,渲染的时候直接用一个或几个函数调动这些数组里的数据进行绘制,形式上是减少函数调用的次数(告别glVertex),提高绘制效率。
但是,这两种方法都有缺点。VA是在客户端设置的,所以执行这类函数(glDrawArray或glDrawElement)后,客户端还得把得到的顶点数据向服务端传输一次(所谓的“二次处理”),这样一来就有了不必要的动作了,降低了效率——如果我们写的函数能直接把顶点数据发送给服务端就好了——这正是VBO的特性之一。显示列表的缺点在于它的古板,一旦设定就不容许修改,所以它只适合对一些“固定”的东西的绘制进行包装。(我们无办法直接在硬件层改顶点数据,因为这是脱离了流水线的事物)。而VBO直接把顶点数据交到流水线的第一步,与显示列表的效率还是有差距,但它这样就得到了操作数据的弹性——渲染阶段,我们的VBO绘制函数持续把顶点数据交给流水线,在某一刻我们可以把该帧到达了流水线的顶点数据取回客户端修改Vertexmapping),再提交回流水线(Vertex unmapping),或者用glBufferData或glBufferSubData重新全部或buffer提交修改了的顶点数据,这是VBO的另一个特性。
VBO结合了VA和显示列表这个说法不太妥当,应该说它结合了两者的一些特性,绘制效率在两者之间,且拥有良好的数据更改弹性。这种折衷造就了它一直为目前最高的地位。
通俗的解释:当我们在顶点着色器中把顶点传送到GPU中的时候,不能每个顶点数据传送一次,因为太耗时而且造成资源浪费,所以就要用到缓冲对象,我们把大量的数据存储在GPU内存上,然后一次传输大量数据到显卡上,顶点缓冲对象就是帮助我们来管理GPU内存的。
首先我们需要使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据,一定要使用该函数来配置各个属性的数据,因为顶点数据不只包含位置,还可能会包含顶点颜色、顶点法线等等,那一个顶点数据是如何被OpenGL解析然后放入到顶点着色器的各个属性中,就需要通过该函数进行准确的配置。
每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVetexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的,因为同一个类型的缓冲区同时最多绑定一个目标缓冲。
下面一个简单的例子:
- //创建VBO及VBO赋值
- glGenBuffers(1, &m_nPositionVBO);
- glBufferData(GL_ARRAY_BUFFER,
- sizeof(posData), posData, GL_STREAM_DRAW);
-
- glGenBuffers(1, &m_nTexcoordVBO);
- glBufferData(GL_ARRAY_BUFFER,
- sizeof(texData), texData, GL_STREAM_DRAW);
-
- glGenBuffers(1, &m_nIndexVBO);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER,
- sizeof(indexData), indexData, GL_STATIC_DRAW);
-
- //代码一,不使用shader VBO已经创建好了
- glBindBuffer(GL_ARRAY_BUFFER, m_nPositionVBO);
- glEnableClientState(GL_VERTEX_ARRAY);
- glVertexPointer(2, GL_FLOAT, 0, NULL);
-
- glBindBuffer(GL_ARRAY_BUFFER, m_nTexcoordVBO);
- glEnableClientState(GL_TEXTURE_COORD_ARRAY);
- glTexCoordPointer(2, GL_FLOAT, 0, NULL);
-
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nIndexVBO);
- glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
-
- glDisableClientState(GL_TEXTURE_COORD_ARRAY);
- glDisableClientState(GL_VERTEX_ARRAY);
-
- glBindBuffer(GL_ARRAY_BUFFER, NULL);
-
- //代码二,使用shader
- glBindBuffer(GL_ARRAY_BUFFER, m_nPositionVBO);
- glEnableVertexAttribArray(VAT_POSITION);
- glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);
-
- glBindBuffer(GL_ARRAY_BUFFER, m_nTexcoordVBO);
- glEnableVertexAttribArray(VAT_TEXCOORD);
- glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);
-
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nIndexVBO);
- glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
-
- glDisableVertexAttribArray(VAT_POSITION);
- glDisableVertexAttribArray(VAT_TEXCOORD);
-
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
- glBindBuffer(GL_ARRAY_BUFFER, NULL);

使用VBO之前必须调用create()创建。使用时,调用bind()。以告知OPenGL我们在使用的VBO。调用allocate()为VBO对象分配内存传递数据。有了数据还要告知OPenGL数据格式。VBO使用结束解除绑(养成一个好习惯);程序结束VBO自然没有用了,调用destroy()释放它。
数据存放在连续的内存中,要告知OPenGL如何解析数据。方法是调用:voidQOpenGLShaderProgram::setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)参数一:属性位置;参数三:偏移量。一个顶点的所有属性数据视为一组,offset指定某属性距该组最开始的字节数。最后一个参数:一个顶底的所有字节数。
数据准备好,着色器程序编译链接好,moel view projection 矩阵设定好,接着就可以绑定VBO进行绘制了。VBO用完就解绑了。
- /头文件
-
- #ifndef OPENGL_WIDGET_H
- #define OPENGL_WIDGET_H
-
- #include <QOpenGLFunctions>
- #include <QOpenGLWidget>
- #include <QOpenGLShader>
- #include <QOpenGLShaderProgram>
- #include <QOpenGLBuffer>
-
- class OpenGLWidget : public QOpenGLWidget,
- protected QOpenGLFunctions
- {
- Q_OBJECT
-
- public:
- explicit OpenGLWidget(QWidget *parent = nullptr);
- ~OpenGLWidget();
-
- protected:
- virtual void initializeGL() override;
- virtual void resizeGL(int w, int h) override;
- virtual void paintGL() override;
-
- private:
- void makeObject();
-
- private:
- QOpenGLShaderProgram *m_program;
- QOpenGLBuffer m_vbo;
- int m_matrixUniform;
- QMatrix4x4 m_pMat;
- };
-
- #endif // OPENGL_WIDGET_H
-
-
-
-
- 源文件
- #include "opengl_widget.h"
-
- OpenGLWidget::OpenGLWidget(QWidget *parent)
- : QOpenGLWidget(parent),
- m_program(nullptr),
- m_vbo(QOpenGLBuffer::VertexBuffer),
- m_matrixUniform(0),
- m_pMat()
- {
- }
-
- OpenGLWidget::~OpenGLWidget()
- {
- m_vbo.destroy();
- }
-
- void OpenGLWidget::initializeGL()
- {
- initializeOpenGLFunctions();
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-
- m_vbo.create();
-
- m_program = new QOpenGLShaderProgram(this);
- m_program->addShaderFromSourceFile(QOpenGLShader::Vertex,
- ":/vertex_shader.glsl");
- m_program->addShaderFromSourceFile(QOpenGLShader::Fragment,
- ":/fragment_shader.glsl");
-
- if (!m_program->link())
- close();
-
- m_matrixUniform = m_program->uniformLocation("matrix");
-
- makeObject();
- }
-
- void OpenGLWidget::resizeGL(int w, int h)
- {
- float aspect = float(w)/float(h?h:1);
- float fov = 45.0f, zNear = 0.1f, zFar = 100.f;
- m_pMat.setToIdentity();
- m_pMat.perspective(fov, aspect, zNear, zFar);
- }
-
- void OpenGLWidget::paintGL()
- {
- glClear(GL_COLOR_BUFFER_BIT);
-
- m_program->bind();
-
- QMatrix4x4 mvMat;
- mvMat.translate(0.0f, 0.0f, -3.0f);
- m_program->setUniformValue(m_matrixUniform, m_pMat*mvMat);
- m_vbo.bind();
- glDrawArrays(GL_TRIANGLES, 0, 3);
- m_vbo.release();
-
- m_program->release();
- }
-
- void OpenGLWidget::makeObject()
- {
- float arrVertex[] = {
- // position color
- 0.0f, 0.707f, 0.0f, 1.0f, 0.0f, 0.0f,
- -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
- 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
- };
-
- m_vbo.bind();
- m_vbo.allocate(arrVertex, sizeof(arrVertex));
-
- int attr = -1;
- attr = m_program->attributeLocation("posAttr");
- m_program->setAttributeBuffer(attr, GL_FLOAT, 0,
- 3, sizeof(float) * 6);
- m_program->enableAttributeArray(attr);
-
- attr = m_program->attributeLocation("colAttr");
- m_program->setAttributeBuffer(attr, GL_FLOAT, 3 * sizeof(float),
- 3, sizeof(float) * 6);
- m_program->enableAttributeArray(attr);
-
- m_vbo.release();
- }

vertex 和frag
- /v s//
- #ifdef GL_ES
- // Set default precision to medium
- precision mediump int;
- precision mediump float;
- #endif
-
- attribute vec4 posAttr;
- attribute vec4 colAttr;
- varying vec4 col;
- uniform mat4 matrix;
-
- void main()
- {
- col = colAttr;
- gl_Position = matrix * posAttr;
- }
-
-
-
- /f s//
- #ifdef GL_ES
- // Set default precision to medium
- precision mediump int;
- precision mediump float;
- #endif
-
- varying vec4 col;
-
- void main()
- {
- gl_FragColor = col;
- }

VAO并不是必须的,VBO可以独立使用,VBO缓存了数据,而数据的使用 方式(glVertexAttribPointer 指定的数据宽度等信息)并没有缓存,VBO将顶点信息放到GPU中,GPU在渲染时去缓存中取数据,二者中间的桥梁是GL-Context。GL-Context整个程序一般只有一个,所以如果一个渲染流程里有两份不同的绘制代码,当切换VBO时(有多个VBO时,通过glBindBuffer切换 ),数据使用方式信息就丢失了。而GL-context就负责在他们之间进行切换。这也是为什么要在渲染过程中,在每份绘制代码之中会有glBindbuffer、glEnableVertexAttribArray、glVertexAttribPointer。VAO记录该次绘制所需要的所有VBO所需信息,把它保存到VBO特定位置,绘制的时候直接在这个位置取信息绘制。
VAO的全名是Vertex Array Object,首先,它不是Buffer-Object,所以不用作存储数据;其次,它针对“顶点”而言,也就是说它跟“顶点的绘制”息息相关。我们每一次绘制的时候,都需要绑定缓冲对象以此来拿到顶点数据,都需要去配置顶点属性指针以便OpenGL知道如何来解析顶点数据,这是相当麻烦的,对一个多边形而言,它每次的配置都是相同的,如何来存储这个相同的配置呢。
VAO为我们解决了这个大麻烦,当配置顶点属性数据的时候,只需要将配置函数调用执行一次,随后再绘制该物体的时候就只需要绑定相应的VAO即可,这样,我们就可以通过绑定不同的VAO(提醒,与VBO一样,同一时刻只能绑定一个VAO),使得在不同的顶点数据和属性配置切换变得非常简单。VAO记录的是一次绘制中所需要的信息,这包括“数据在哪里glBindBuffer”、“数据的格式是怎么样的glVertexAttribPointer”、shader-attribute的location的启用glEnableVertexAttribArray。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
VAO的使用非常简单,要想使用VAO,要做的只是使用glBindVertexArray绑定VAO。从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,配置完以后,VAO中就存储了我们想要的各种数据,之后解绑VAO供之后使用,再次使用需要我们再次绑定。
这里分析上面这个代码片段,并解释一下VAO和VBO的联系。
注意:glVertexAttribPointer()这个函数会自动从当前绑定的VBO里获取顶点数据,所以在第一节VBO里如果想要使用正确的顶点数据,每次都要绑定相应的VBO,但是现在,我们在绑定VBO之前绑定了VAO,那么glEnableVertexAttribPointer()所进行的配置就会保存在VAO中。我们可以通过不同的配置从一个VBO内拿到不同的数据放入不同的VAO中,这样VAO中就有了我们所需要的数据,它根据顶点配置到VBO中索取到数据,之后直接绑定相应的VAO即可,glDrawArrays()函数就是需要从VAO中拿到数据进行绘制。但是要明白的是,我们是通过VAO来间接绑定VBO的,实际上数据还是要存储在VBO中的,VAO内并没有存储顶点的数据,如果我们要绘制两个不同的三角形,我们可能需要定义两个VBO,每个VBO内有三个顶点数据。
例子:
- glGenBuffers(1, &m_nQuadPositionVBO);
- glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);
- glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadPos), fQuadPos, GL_STREAM_DRAW);
-
- glGenBuffers(1, &m_nQuadTexcoordVBO);
- glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);
- glBufferData(GL_ARRAY_BUFFER, sizeof(fQuadTexcoord), fQuadTexcoord, GL_STREAM_DRAW);
-
- glGenBuffers(1, &m_nQuadIndexVBO);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(nQuadIndex), nQuadIndex, GL_STREAM_DRAW);
-
- //VAO 初始化部分
- glGenVertexArrays(1, &m_nQuadVAO);
- glBindVertexArray(m_nQuadVAO);
-
- //开始保存状态
- glBindBuffer(GL_ARRAY_BUFFER, m_nQuadPositionVBO);
- glEnableVertexAttribArray(VAT_POSITION);
- glVertexAttribPointer(VAT_POSITION, 2, GL_INT, GL_FALSE, 0, NULL);
-
- glBindBuffer(GL_ARRAY_BUFFER, m_nQuadTexcoordVBO);
- glEnableVertexAttribArray(VAT_TEXCOORD);
- glVertexAttribPointer(VAT_TEXCOORD, 2, GL_INT, GL_FALSE, 0, NULL);
-
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_nQuadIndexVBO);
- //保存结束
- glBindVertexArray(NULL);
-
- glBindBuffer(GL_ARRAY_BUFFER, NULL);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);

标准OPenGLes2.0还没有引进VAO,应该是OpenGL3.0以后才有。不过Qt封装的OPenGLes2.0可以使用VAO,它就是QOpenGLVertexArrayObject。其用法与OpenGL3.0中的VAO用法一样。接下来通过一个列子熟悉这个类的用法。
首先要调用QOpenGLVertexArrayObject::create()创建该对象。创建成功您就可以对它绑定某个VBO的状态,需要绘制时将其绑定到OPenGL上下文,最后要销毁掉。做法如下:
1.初始化:
Bind the VAO ----> Set vertex data state for this visual object ---> Unbind (release()) the VAO
2.渲染:
Bind the VAO ----> Call a glDraw*() function ----> Unbind (release()) the VAO
- #ifndef OPENGL_WIDGET_H
- #define OPENGL_WIDGET_H
-
- #include <QOpenGLFunctions>
- #include <QOpenGLWidget>
- #include <QOpenGLShader>
- #include <QOpenGLShaderProgram>
- #include <QOpenGLBuffer>
- #include <QOpenGLVertexArrayObject>
-
- class OpenGLWidget : public QOpenGLWidget,
- protected QOpenGLFunctions
- {
- Q_OBJECT
-
- public:
- explicit OpenGLWidget(QWidget *parent = nullptr);
- ~OpenGLWidget();
-
- protected:
- virtual void initializeGL() override;
- virtual void resizeGL(int w, int h) override;
- virtual void paintGL() override;
-
- private:
- void makeObject();
- void makeObject_2();
- void setVertexAttribute();
- private:
- QOpenGLShaderProgram *m_program;
- QOpenGLBuffer m_vbo;
- int m_matrixUniform;
- QMatrix4x4 m_pMat;
- QOpenGLVertexArrayObject m_vao;
- QOpenGLBuffer m_vbo_2;
- QOpenGLVertexArrayObject m_vao_2;
- };
-
- #endif // OPENGL_WIDGET_
-
- /
-
- #include "opengl_widget.h"
- OpenGLWidget::OpenGLWidget(QWidget *parent)
- : QOpenGLWidget(parent),
- m_program(nullptr),
- m_vbo(QOpenGLBuffer::VertexBuffer),
- m_matrixUniform(0),
- m_pMat(),
- m_vao(),
- m_vbo_2(QOpenGLBuffer::VertexBuffer),
- m_vao_2()
- {
- }
-
- OpenGLWidget::~OpenGLWidget()
- {
- m_vbo.destroy();
- m_vbo_2.destroy();
-
- m_vao.destroy();
- m_vao_2.destroy();
- }
-
- void OpenGLWidget::initializeGL()
- {
- initializeOpenGLFunctions();
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- makeCurrent();
-
- m_vbo.create();
- m_vbo_2.create();
-
- m_program = new QOpenGLShaderProgram(this);
- m_program->addShaderFromSourceFile(QOpenGLShader::Vertex,
- ":/vertex_shader.glsl");
- m_program->addShaderFromSourceFile(QOpenGLShader::Fragment,
- ":/fragment_shader.glsl");
-
- if (!m_program->link())
- close();
-
- m_matrixUniform = m_program->uniformLocation("matrix");
-
- makeObject();
- makeObject_2();
- }
-
- void OpenGLWidget::resizeGL(int w, int h)
- {
- float aspect = float(w)/float(h?h:1);
- float fov = 45.0f, zNear = 0.1f, zFar = 100.f;
- m_pMat.setToIdentity();
- m_pMat.perspective(fov, aspect, zNear, zFar);
- }
-
- void OpenGLWidget::paintGL()
- {
- glClear(GL_COLOR_BUFFER_BIT);
-
- m_program->bind();
-
- QMatrix4x4 mvMat;
- mvMat.translate(0.0f, 0.0f, -3.0f);
- m_program->setUniformValue(m_matrixUniform, m_pMat*mvMat);
- {
- QOpenGLVertexArrayObject::Binder vaoBind(&m_vao_2);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
- }
- {
- QOpenGLVertexArrayObject::Binder vaoBind(&m_vao);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
- }
- m_program->release();
- }
-
- void OpenGLWidget::makeObject()
- {
- makeCurrent();
-
- float arrVertex[] {
- // position color
- 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
- 0.0f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
- -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
- };
-
- QOpenGLVertexArrayObject::Binder vaoBind(&m_vao);
- m_vbo.bind();
- m_vbo.allocate(arrVertex, sizeof(arrVertex));
-
- int attr = -1;
- attr = m_program->attributeLocation("posAttr");
- m_program->setAttributeBuffer(attr, GL_FLOAT, 0,
- 3, sizeof(float) * 6);
- m_program->enableAttributeArray(attr);
-
- attr = m_program->attributeLocation("colAttr");
- m_program->setAttributeBuffer(attr, GL_FLOAT, 3 * sizeof(float),
- 3, sizeof(float) * 6);
- m_program->enableAttributeArray(attr);
- m_vbo.release();
- }
- void OpenGLWidget::makeObject_2()
- {
- makeCurrent();
-
- float arrVertex[] {
- // position color
- 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
- 0.0f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
- 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
- };
-
- QOpenGLVertexArrayObject::Binder vaoBind(&m_vao_2);
- m_vbo_2.bind();
- m_vbo_2.allocate(arrVertex, sizeof(arrVertex));
-
- int attr = -1;
- attr = m_program->attributeLocation("posAttr");
- m_program->setAttributeBuffer(attr, GL_FLOAT, 0,
- 3, sizeof(float) * 6);
- m_program->enableAttributeArray(attr);
-
- attr = m_program->attributeLocation("colAttr");
- m_program->setAttributeBuffer(attr, GL_FLOAT, 3 * sizeof(float),
- 3, sizeof(float) * 6);
- m_program->enableAttributeArray(attr);
- m_vbo_2.release();
- }
-
- /v s///
-
- #ifdef GL_ES
- // Set default precision to medium
- precision mediump int;
- precision mediump float;
- #endif
-
- attribute vec4 posAttr;
- attribute vec4 colAttr;
- varying vec4 col;
- uniform mat4 matrix;
-
- void main()
- {
- col = colAttr;
- gl_Position = matrix * posAttr;
- }
-
- /f s/
-
-
- #ifdef GL_ES
- // Set default precision to medium
- precision mediump int;
- precision mediump float;
- #endif
-
- varying vec4 col;
-
- void main()
- {
- gl_FragColor = col;
- }

运行结果:
这个很容易理解,就是该对象里存储的都是索引,索引的意义在于减少重复数据。
使用EBO绘图是使用GLDrawElements()函数,这个函数是要通过索引到相应的顶点缓冲区内去拿数据,如果绑定了VAO就到VAO里拿数据。那EBO要不要每次绘制的时候都重新绑定了呢,这个就要看顶点数据部分如何处理的了。如果你没有使用VAO,那么就要每次都重新绑定相应的EBO,然后先读取索引,再根据索引去到绑定的VBO里寻找数据。如果已经绑定了VAO,要注意一点:VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象,绑定VAO的同时也会自动绑定EBO。看下面这张图,索引缓冲对象会成为VAO的一个元素。
EBO也不是必须的,如果使用EBO,绘制过程将更清晰简单,EBO需配合VBO使用,索引必须指定索引的对象。
直接通过一个例子来说明ebo的用途和用法,下面的例子是绘制2个田字形的格子,分别使用线和四边形的方式绘制,考虑一 下,如果不使用ebo,设计坐标的时候就需要考虑绘制方式,用线的方式绘制时,坐标传送应该用线的方式,如例子中的田字格,使用GL_LINES方式绘制,需设计48个float型的坐标(共12条短线,每条线2个坐标,每个坐标2个float 数据),而使用GL_QUADS方式绘制,需要设计32个float型坐标,很麻烦,可以看出来,中间的很多坐标都是相同的。
这个时候使用ebo就非常简单了,先设计田字格的9个顶点,然后再设计绘制方式的索引,顶点设计和绘制方法设计完全分开,使程序更明了。
EBO与VBO使用方式类似,先使用glGenBuffers创建EBO,再使用glBindBuffer绑定EBO,然后使用glBindBuffer传送数据,注意类型为GL_ELEMENT_ARRAY_BUFFER,绘图的时候使用glDrawElements函数。
例子源码如下:
- #include <stdlib.h>
- #include <stdio.h>
- #include <GL/glew.h>
- #include <GL/glut.h>
- static const GLchar * vertex_source =
- "#version 330 core\n"
- "uniform float translate;\n"
- "layout (location = 0) in vec2 position;\n"
- "layout (location = 1) in vec3 color;\n"
- "flat out vec3 vertex_color;\n"
- "void main()\n"
- "{\n"
- "gl_Position = vec4(position.x+translate,position.y,0.0,1.0);\n"
- "vertex_color = color;\n"
- "}\0";
- static const GLchar * frag_source =
- "#version 330 core\n"
- "flat in vec3 vertex_color;\n"
- "out vec4 color;\n"
- "void main()\n"
- "{\n"
- "color = vec4(vertex_color,1.0);\n"
- "}\n\0";
- void loadShader(GLuint program, GLuint type, const GLchar * source)
- {
- const GLchar * shaderSource[] = {source};
- GLuint shader = glCreateShader(type);
- glShaderSource(shader, 1, shaderSource, 0);
- glCompileShader(shader);
- glAttachShader(program, shader);
- }
- GLint translate_loc;
- GLuint vao, vbo, ebo[2];
- void init()
- {
- GLuint program = glCreateProgram();
- /* 同时加载了顶点着色器和片元着色器*/
- loadShader(program, GL_VERTEX_SHADER, vertex_source);
- loadShader(program, GL_FRAGMENT_SHADER, frag_source);
- glLinkProgram(program);
- glUseProgram(program);
- translate_loc = glGetUniformLocation(program, "translate");
- /* 田字格的顶点坐标*/
- GLfloat vertices[][2] =
- {
- { -0.9f, 0.5f}, { -0.5f, 0.5f}, { -0.1f, 0.5f}, /* 第一行*/
- { -0.9f, 0.0f}, { -0.5f, 0.0f}, { -0.1f, 0.0f}, /* 第二行*/
- { -0.9f, -0.5f}, { -0.5f, -0.5f}, { -0.1f, -0.5f}, /* 第三行*/
- };
- /* 每个点给一种颜色*/
- GLfloat colors[][3] =
- {
- {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f},
- {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f},
- {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f},
- };
- /* 线的方式画田字格,每条线个点*/
- GLint index_line[][2] =
- {
- {0, 1}, {1, 2}, /* 第一行线索引*/
- {3, 4}, {4, 5},
- {6, 7}, {7, 8},
- {0, 3}, {3, 6}, /* 第一列线索引*/
- {1, 4}, {4, 7},
- {2, 5}, {5, 8},
- };
- /* 四边形的方式画田字格,每条四边形个点*/
- GLint index_quad[][4] =
- {
- {0, 1, 4, 3}, /* 第一个格子*/
- {1, 2, 5, 4},
- {3, 4, 7, 6},
- {4, 5, 8, 7},
- };
- glGenBuffers(1, &vao);
- glBindBuffer(GL_ARRAY_BUFFER, vao);
- /* 顶点数组和颜色数据放同一个vbo中,设置时候注意数据宽度和偏移*/
- glGenBuffers(1, &vbo);
- glBindBuffer(GL_ARRAY_BUFFER, vbo);
- glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);
- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
- glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);
- glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid *)0);
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(vertices)));
- glEnableVertexAttribArray(1);
- /* 创建画格子方式的索引,也可以用一个ebo,通过偏移量来存放索引*/
- glGenBuffers(2, ebo);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]); /* 存放用线画田字格的索引*/
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_line), index_line, GL_STATIC_DRAW);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]); /* 存放用线画田字格的索引*/
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_quad), index_quad, GL_STATIC_DRAW);
- glLineWidth(2.0);
- glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
- glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
- }
- void display()
- {
- glClear(GL_COLOR_BUFFER_BIT);
- glBindBuffer(GL_ARRAY_BUFFER, vao);
- /* 12根线,每根个索引*/
- glUniform1f(translate_loc,0.0f);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
- glDrawElements(GL_LINES,2*12, GL_UNSIGNED_INT, (GLvoid *)(0));
- /* 用偏移的方式画第二个田字格*/
- glUniform1f(translate_loc,1.0f);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]);
- glDrawElements(GL_QUADS,4*4, GL_UNSIGNED_INT, (GLvoid *)(0));
- glFlush();
- }
- int main(int argc, char * argv[])
- {
- glutInit(&argc, argv);
- glutInitDisplayMode(GLUT_RGBA);
- glutInitWindowPosition(200, 200);
- glutInitWindowSize(600, 400);
- glutCreateWindow("ebo");
- glewInit();
- init();
- glutDisplayFunc(display);
- glutMainLoop();
- return 0;
- }

效果图:
可以说从程序传入的数据都是要放入缓冲对象的,也就是Buffer Object,像VBO和EBO,而VAO则可以看作是把这些数据进行了分类整合,根据缓冲对象里的数据进行自由配置,从而得到自己需要的数据,然后放入一个对象里。比如我从一堆顶点缓冲和索引缓冲里拿出一部分数据组成正方体,然后给它一个VAO的名字,下次用的时候直接取出来就行,我还可以拿出一些数据组成四面体,然后再给它一个名字。这样看来,VBO和EBO里的数据就好像是可以重复使用的原材料,VAO则是使用这些原材料进行加工好了的对象。
VBO:CPU把顶点数据存储到VBO,然后每次调用的时候从VBO集中发送到GPU,能够大大降低cpu到gpu的交互次数。
EBO:主要是为了减少VBO中的重复数据,但是它不能减少数据的传输,如果一个正方体有36个顶点,用索引的话就要有36个索引值,但是不同的索引可能索引到同一个顶点。
VAO:VAO则是用来保存顶点数据和配置的,没有VAO的时候,我们需要不停地把配置从CPU传到GPU,以此让GPU来知道怎么取顶点,而有了VAO(VAO是放在显存里的),我们通过绑定的VAO可以很快拿到配置,而不用从CPU程序中传入。
数据传递流程:我们定义了顶点数组以后,需要将数据载入缓存对象也就是VBO中,同时我们必须告诉OpenGL如何配置这些数据,这就需要我们绑定一个VAO,这里面存储着相应的配置,同时VAO也与相应的顶点数组做了间接绑定,所以一般来说;一个顶点数组可以对应着多个VAO,但是一个VAO一般对应一个顶点数组。
openGL之glsl入门5--缓冲对象vbo、vao及ebo
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。