我的OpenGL学习进阶之旅介绍 顶点数组对象VAO并实战一下

Posted 欧阳鹏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅介绍 顶点数组对象VAO并实战一下相关的知识,希望对你有一定的参考价值。

一、顶点数组对象VAO

通过前面的博客

我们已经介绍了加载顶点属性的两种方式:

  • 使用客户顶点数组
  • 使用顶点缓冲区对象VBO
    顶点缓冲区对象VBO优于顶点数组,因为它们能够减少CPUGPU之间复制的数据量,从而获得更好的性能。

OpenGL ES 3.0中 引入了一个新特性,使顶点数组的操作更加高效:顶点数组对象(VAO)
正如前面的例子,我们可以发现: 使用顶点缓冲区对象VBO设置绘图操作可能需要多次调用glBindBufferglVertexAttribPointerglEnableVertexAttribArray

为了更快地在顶点数组配置之间切换,OpenGL ES 3.0推出了顶点数组对象。VAO提供包含在顶点数组/顶点缓冲区对象配置之间切换所需要的所有状态的单一对象。


二、VAO的相关API介绍

2.1 glGenVertexArrays函数

实际上,OpenGL ES 3.0中总是有一个活动的顶点数组对象。要创建新的顶点数组对象,可以使用glGenVertexArrays函数。

void glGenVertexArrays (GLsizei n, GLuint *arrays);

参数说明:

  • n
    要返回的顶点数组对象名称的数量
  • arrays
    指向一个n个缘故的数组的指针,该数组是分配的顶点数组对象返回的位置

2.2 glBindVertexArray函数

一旦创建,就可以使用glBindVertexArray函数绑定顶点数组对象供以后使用。

void glBindVertexArray (GLuint array);

参数说明:

  • array
    被指定为当前顶点数组对象的对象

每个VAO都包含一个完整的状态向量,描述所有顶点缓冲区绑定和启用的顶点客户状态。
绑定VAO时,它的状态向量提供缓冲区状态的当前设置。

glBindVertexArray函数绑定顶点数组对象后,更改顶点数组状态的后续调用(glBindBufferglVertexAttribPointerglEnableVertexAttribArrayglDisableVertexAttribArray)将影响新的VAO。

这样,应用程序可以通过绑定一个已经设置状态的顶点数组对象快速地在顶点数组配置之间切换。
所有变化可以在一个函数调用中完成,没有必要多次调用以更改顶点数组状态。

2.3 glDeleteVertexArrays函数

当应用程序结束一个或者多个顶点数组对象的使用时,可以用glDeleteVertexArrays删除它们。

void glDeleteVertexArrays (GLsizei n, const GLuint *arrays);

参数说明:

  • n
    要删除的顶点数组对象名称的数量
  • arrays
    包含需要删除的顶点数组对象的有n个元素的数组

三、实战一下

下面的例子演示了顶点数组对象VAO在初始化时用于设置顶点数组状态。然后,在绘图的时候,使用 glBindVertexArray在一次函数调用中设置顶点数组状态。

  • NativeTriangleVertextArrayObject.h
#pragma once

#include <BaseGLSample.h>

#define VERTEX_POS_SIZE       3 // x, y and z
#define VERTEX_COLOR_SIZE     4 // r, g, b, and a

#define VERTEX_POS_INDX       0
#define VERTEX_COLOR_INDX     1

#define VERTEX_STRIDE         ( sizeof(GLfloat) * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE ) )

class NativeTriangleVAO : public BaseGLSample 
public:
    NativeTriangleVAO() = default;

    virtual ~NativeTriangleVAO() = default;

    virtual void create();

    virtual void draw();

    virtual void shutdown();

private:
    // VertexBufferObject Ids
    GLuint vboIds[2];

    // VertexArrayObject Id
    /**
     * 顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。
     * 这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
     * 这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中
     *
     * OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
     * 一个顶点数组对象会储存以下这些内容:
            glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
            通过glVertexAttribPointer设置的顶点属性配置。
            通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
     */
    GLuint vaoId;

;


  • NativeTriangleVertextArrayObject.cpp
#include <cstring>
#include "NativeTriangleMapBuffers.h"
#include "NativeTriangleVertextArrayObject.h"


// 可以参考这篇讲解: https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
// NDK OpenGL ES 3.0 开发(四):VBO、EBO 和 VAO https://blog.csdn.net/Kennethdroid/article/details/98088890

//VBO 和 EBO
//VBO(Vertex Buffer Object)是指顶点缓冲区对象,
//而 EBO(Element Buffer Object)是指图元索引缓冲区对象,VAO 和 EBO 实际上是对同一类 Buffer 按照用途的不同称呼。

// OpenGLES2.0 编程中,用于绘制的顶点数组数据首先保存在 CPU 内存,
// 在调用 glDrawArrays 或者 glDrawElements 等进行绘制时,需要将顶点数组数据从 CPU 内存拷贝到显存。
// 但是很多时候我们没必要每次绘制的时候都去进行内存拷贝,如果可以在显存中缓存这些数据,就可以在很大程度上降低内存拷贝带来的开销。

// OpenGLES3.0 VBO 和 EBO 的出现就是为了解决这个问题。
// VBO 和 EBO 的作用是在显存中提前开辟好一块内存,用于缓存顶点数据或者图元索引数据,
// 从而避免每次绘制时的 CPU 与 GPU 之间的内存拷贝,可以提升渲染性能,降低内存带宽和功耗。
// OpenGLES3.0 支持两类缓冲区对象:顶点数组缓冲区对象、图元索引缓冲区对象。
//    GL_ARRAY_BUFFER 标志指定的缓冲区对象用于保存顶点数组,
//    GL_ELEMENT_ARRAY_BUFFER 标志指定的缓存区对象用于保存图元索引。
// VBO 可以参考图片:docs/vertex_attribute_pointer_interleaved


// VAO(Vertex Array Object)是指顶点数组对象,VAO 的主要作用是用于管理 VBO 或 EBO ,
// 减少 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 这些调用操作,高效地实现在顶点数组配置之间切换。
// VAO 与 VBO、EBO之间的关系  可以参考图片: docs/vertex_array_objects.png 和 docs/vertex_array_objects_ebo.png


// 3 vertices, with (x,y,z) ,(r, g, b, a)  per-vertex
static GLfloat vertices[3 * (VERTEX_POS_SIZE + VERTEX_COLOR_SIZE)] =
        
                // 逆时针 三个顶点
                0.0f, 0.5f, 0.0f,            // v0 上角
                1.0f, 0.0f, 0.0f, 1.0f,      // c0

                -0.5f, -0.5f, 0.0f,          // v1 左下角
                0.0f, 1.0f, 0.0f, 1.0f,      // c1

                0.5f, -0.5f, 0.0f,           // v2 右下角
                0.0f, 0.0f, 1.0f, 1.0f       // c2
        ;

// Index buffer data
static GLushort indices[3] = 0, 1, 2;

void NativeTriangleVAO::create() 
    GLUtils::printGLInfo();

    // 顶点着色器
    VERTEX_SHADER = GLUtils::openTextFile(
            "vertex/vertex_shader_hello_triangle2.glsl");
    // 片段着色器
    FRAGMENT_SHADER = GLUtils::openTextFile(
            "fragment/fragment_shader_hello_triangle2.glsl");
    mProgram = GLUtils::createProgram(&VERTEX_SHADER, &FRAGMENT_SHADER);

    if (!mProgram) 
        LOGD("Could not create program")
        return;
    

    //  Generate VBO Ids and load the VBOs with data
    // 创建 2 个 VBO(EBO 实际上跟 VBO 一样,只是按照用途的另一种称呼)
    glGenBuffers(2, vboIds);

    // 绑定第一个 VBO,拷贝顶点数组到显存
    // GL_STATIC_DRAW 标志标识缓冲区对象数据被修改一次,使用多次,用于绘制。
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 绑定第二个 VBO(EBO),拷贝图元索引数据到显存
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // Generate VAO Id
    // 创建一个VAO
    glGenVertexArrays(1, &vaoId);

    // Bind the VAO and then setup the vertex attributes
    // 绑定VAO之后,操作 VBO ,当前 VAO 会记录 VBO 的操作
    // 要想使用VAO,要做的只是使用glBindVertexArray绑定VAO。
    // 从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。
    // 当我们打算绘制一个物体的时候,我们只要在绘制物体前简单地把VAO绑定到希望使用的设定上就行了。
    glBindVertexArray(vaoId);

    // 把顶点数组复制到缓冲中供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);

    glEnableVertexAttribArray(VERTEX_POS_INDX);
    glEnableVertexAttribArray(VERTEX_COLOR_INDX);

    // 设置顶点属性指针
    glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
                          GL_FLOAT, GL_FALSE, VERTEX_STRIDE, nullptr);

    glVertexAttribPointer(VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE,
                          GL_FLOAT, GL_FALSE, VERTEX_STRIDE,
                          (const void *) (VERTEX_POS_SIZE * sizeof(GLfloat)));

    // Reset to the default VAO
    glBindVertexArray(0);

    // 设置清除颜色
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);


void NativeTriangleVAO::draw() 
    // Clear the color buffer
    // 清除屏幕
    // 在OpenGL ES中,绘图中涉及多种缓冲区类型:颜色、深度、模板。
    // 这个例子,绘制三角形,只向颜色缓冲区中绘制图形。在每个帧的开始,我们用glClear函数清除颜色缓冲区
    // 缓冲区将用glClearColor指定的颜色清除。
    // 这个例子,我们调用了GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f); 因此屏幕清为白色。
    // 清除颜色应该由应用程序在调用颜色缓冲区的glClear之前设置。
    glClear(GL_COLOR_BUFFER_BIT);

    // Use the program object
    // 在glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用这个程序对象(也就是之前写的着色器)了。
    // 当我们渲染一个物体时要使用着色器程序 , 将其设置为活动程序。这样就可以开始渲染了
    glUseProgram(mProgram);

    // Bind the VAO
    glBindVertexArray(vaoId);

    // Draw with the VAO settings
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr);

    // Return to the default VAO
    glBindVertexArray(0);


void NativeTriangleVAO::shutdown() 
    // Delete program object
    GLUtils::DeleteProgram(mProgram);

    glDeleteBuffers(2, &vboIds[0]);
    glDeleteVertexArrays(1, &vaoId);

  • vertex/vertex_shader_hello_triangle2.glsl
#version 300 es
// 位置变量的属性位置值为 0
layout(location = 0) in vec4 a_position;
// 颜色变量的属性位置值为 1
layout(location = 1) in vec4 a_color;
// 向片段着色器输出一个颜色
out vec4 v_color;

void main()

    v_color = a_color;
    gl_Position = a_position;

  • fragment/fragment_shader_hello_triangle2.glsl
#version 300 es
// 表示OpenGL ES着色器语言V3.00

// 声明着色器中浮点变量的默认精度
precision mediump float;

// 声明由上一步顶点着色器传入进来的颜色值
in vec4 v_color;

// 声明一个输出变量fragColor,这是一个4分量的向量,
// 写入这个变量的值将被输出到颜色缓冲器
out vec4 o_fragColor;

void main()

	o_fragColor = v_color;

四、程序源码

本例子可以在Github找到源代码 https://github.com/ouyangpeng/OpenGLESDemo

以上是关于我的OpenGL学习进阶之旅介绍 顶点数组对象VAO并实战一下的主要内容,如果未能解决你的问题,请参考以下文章

我的OpenGL学习进阶之旅介绍顶点缓冲区对象VBO和元素数组缓冲区对象EBO,并对比使用VBO和不使用VBO绘制三角形的效果

我的OpenGL学习进阶之旅顶点属性顶点数组

我的OpenGL学习进阶之旅顶点属性顶点数组

我的OpenGL学习进阶之旅介绍一下 图元的类型:三角形直线和点精灵

我的OpenGL学习进阶之旅介绍一下OpenGL ES的图元装配:坐标系统透视分割视口变化

我的OpenGL学习进阶之旅介绍一下OpenGL ES的图元装配:坐标系统透视分割视口变化