OPenGL 学习笔记之 VBO VAO EBO 概念和使用方法总结

 

目录

一.  基本概念:

二. 理解缓冲对象 

glVertex 函数

顶点数组(Vertex Array)

三.  VBO(Vertex Buffer Object)顶点缓冲区对象

大体流程理解:

Qt 中使用QOpenGLWidget 的VBO 例子 

四. VAO(Vertex Array Object)顶点数组对象

Qt 中使用QOpenGLWidget 的VAO 例子      

五. EBO 索引缓冲对象

EBO示例    

六.  总结

七. 参考链接:


一.  基本概念:

  • VAO(vertex-array object)顶点数组对象,用来管理VBO。
  • VBO(vertex buffer object)顶点缓冲对象,用来缓存用户传入的顶点数据。
  • EBO(element buffer object)索引缓冲对象,用来存放顶点索引数据。

    这里的object指的都是GPU中的一块内存,每个内存对象都有不同的作用,但创建、绑定、数据传送等方式都比较类似,通过不同的类型加以区分,掌握了一种,其他的就很好理解。

二. 理解缓冲对象 

glVertex 函数

  最原始的设置顶点方法,在glBegin和glEnd之间使用。OpenGL3.0已经废弃此方法。每个glVertex与GPU进行一次通信,十分低效。

glBegin(GL_TRIANGLES);
    glVertex(0, 0);
    glVertex(1, 1);
    glVertex(2, 2);
glEnd();

顶点数组(Vertex Array)

  顶点数组也是收集好所有的顶点,一次性发送给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)顶点缓冲区对象

       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);

Qt 中使用QOpenGLWidget 的VBO 例子 

      使用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(Vertex Array Object)顶点数组对象

       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);

Qt 中使用QOpenGLWidget 的VAO 例子      

      标准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 索引缓冲对象

      这个很容易理解,就是该对象里存储的都是索引,索引的意义在于减少重复数据。

   使用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函数。

EBO示例    

例子源码如下:

#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中VBO及VAO区别

OpenGL VAO和VBO以及EBO的使用

openGL之glsl入门5--缓冲对象vbo、vao及ebo

基于Qt的Opengles可编程管线学习——VAO(QOpenGLVertexArrayObject的使用)

基于Qt的Opengl可编程管线学习—— VBO(QOpenGLBuffer的使用)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ppipp1109

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值