OpenGL 学习笔记(六)

一、OpenGL索引缓冲对象(EBO)

如果我们要用OpenGL绘制一个正方形,能想到的是用两个三角形来拼凑,这个时候就要用到 EBO 了。

1、VBO、VAO 与 EBO 之间的联系与区别

(1).顶点缓冲对象 VBO 是在显卡存储空间中开辟出的一块内存缓存区,用于存储顶点的各类属性信息,如顶点坐标、顶点法向量、顶点颜色数据等。在渲染时,可以直接从 VBO 中取出顶点的各类属性数据,由于 VBO 在显存而不是在内存中,不需要从CPU传输数据,所以处理效率更高。所以可以理解为 VBO 就是显存中的一个存储区域,可以保持大量的顶点属性信息。并且可以开辟很多个 VBO ,每个 VBO 在 OpenGL 中有它的唯一标识 ID ,这个 ID 对应着具体的 VBO 的显存地址,通过这个 ID 可以对特定的 VBO 内的数据进行存取操作。

(2).VAO 是一个保存了所有顶点数据属性的状态结合,它存储了顶点数据的格式以及顶点数据所需的 VBO 对象的引用。

因为 VBO 保存了一个模型的顶点属性信息,每次绘制模型之前需要绑定顶点的所有信息。当数据量很大时,重复这样的动作变得非常麻烦。VAO 可以把这些所有的配置都存储在一个对象中,每次绘制模型时,只需要绑定这个 VAO 对象就可以了。另外,VAO 本身并没有存储顶点的相关属性数据,这些信息是存储在 VBO 中的,VAO 相当于是对很多个 VBO 的引用,把一些 VBO 组合在一起作为一个对象统一管理。

(3).索引缓冲对象 EBO 相当于 OpenGL 中的顶点数组的概念,是为了解决同一个顶点多次重复调用的问题,可以减少内存空间浪费,提高执行效率。当需要使用重复的顶点时,通过顶点的位置索引来调用顶点,而不是对重复的顶点信息重复记录,重复调用。

  EBO 中存储的内容就是顶点位置的索引 indices,EBO 跟 VBO 类似,也是在显存中的一块内存缓冲器,只不过 EBO 保存的是顶点的索引。

2、EBO

  ● 首先,我们现在要绘制正方形,则需要 4 个顶点。在主函数中我们将 4 个顶点的位置信息给出。并用顶点位置的索引 indices 数组将它们 “缝合” 起来。示意图如下:

 代码如下:

/* 编写各顶点位置 */
GLfloat vertices_1[] =
{
    //position                    
    0.5f, 0.5f, 0.0f,            // top right        0
    0.5f, -0.5f, 0.0f,            // bottom right        1
    -0.5f, -0.5f, 0.0f,            // bottom left        2
    -0.5f, 0.5f, 0.0f,            // top left            3
};

/* 四个顶点的连接信息给出来 */
GLuint indices_1[] =
{
    0, 1, 3,        //序号为 0、1、3 的顶点组合成一个三角形
    1, 2, 3        //序号为 1、2、3 的顶点组合成一个三角形
};
       然后,在创建完VAO和VBO后,再创建EBO并绑定,用 glBufferData(以GL_ELEMENT_ARRAY_BUFFER为参数)把索引存储到 EBO 中:
GLuint EBO;
glGenBuffers(1, &EBO);                                         //绑定 EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);      //使用 glBindBuffer 函数把新创建的索引缓冲对象绑定到 GL_ELEMENT_ARRAY_BUFFER目标上
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices_1), indices_1, GL_STATIC_DRAW); // GL_STATIC_DRAW:静态的画图(因为要频繁地读)
      当用 EBO 绑定顶点索引的方式绘制模型时,需要使用 glDrawElements 而不是 glDrawArrays :
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);            //绑定 EBO
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);    //画两个三角形 从第0个顶点开始 一共画 6 次(顺序为0,1,3,1,2,3)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);            //解绑定 EBO
     当用 EBO 绑定顶点索引的方式绘制模型时,需要使用 glDrawElements 而不是 glDrawArrays :
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);            //绑定 EBO
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);    //画两个三角形 从第0个顶点开始 一共画 6 次(顺序为0,1,3,1,2,3)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);            //解绑定 EBO

glDrawElements() 函数说明:

(1).第一个参数:绘制的模式

(2).第二个参数:绘制的顶点个数

(3).第三个参数:索引的数据类型

(4).第四个参数:可选的 EBO 中偏移量设定

二、Uniform使用说明

若要画一个颜色跟着时间变化的正方形,着色器的传入就是随时间变化的。以往的知识满足不了这一点,这时就需要用到 Uniform。

  Uniform 是一种从 CPU中的应用 向 GPU中的着色器 发送数据的方式,但 uniform 和顶点属性有些不同。首先,uniform 是全局的(Global)。全局意味着 uniform 变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。其次,无论你把 uniform 值设置成什么,uniform 会一直保存它们的数据,直到它们被重置或更新。

修改过后的顶点着色器:

#version 330 core        //3.30版本
layout(location = 0) in vec3 position;//位置变量的属性位置值为0
void main()
{
    gl_Position = vec4(position, 1.0f);//核心函数(位置信息赋值)
} 
        修改过后的片元着色器:
#version 330 core         //3.30版本
out vec4 FragColor;        //输出是四个浮点数构成的一个向量 RGB+aerfa
uniform vec4 time;        //在OpenGL程序代码中设定这个变量(uniform:实时地变量表示)
void main()
{
    FragColor = time;    //颜色随时间变化
}

“ Shader.h ”头文件,不用修改(和彩色三角形的一样)。

        这个 uniform 现在还是空的。我们还没有给它添加任何数据。首先需要用 glGetUniformLocation() 函数找到着色器中 uniform 属性的索引 (即位置值) 。当我们得到 uniform 的索引后,就可以用 glUniform…() 相关函数来更新它的值了。

float time = glfwGetTime();                //获取时间(运行的秒数)
float redValue = sin(time) / 2.0f + 0.5f;        //红色数值计算,范围[0,1]
float greenValue = 1 - redValue;            // 绿色数值计算,范围[0.1]。且满足 “redValue + greenValue = 1”
int vertexColorLocation = glGetUniformLocation(ourShader.Program, "time");//找到 “time” 的索引
glUniform4f(vertexColorLocation, redValue, greenValue, 0.0f, 1.0f );//更新颜色

        说明:OpenGL在其核心是一个 C库,所以它不支持类型重载,在函数参数不同的时候就要为其定义新的函数。glUniform 函数是一个典型例子,它有一个特定的后缀。标识设定为 uniform 的类型时,可能的后缀有:

三、绘制正方形

        使用OpenGL绘制正方形代码如下:

/* 引入相应的库 */
#include <iostream>
using namespace std;
#define GLEW_STATIC    
#include<glew.h>    
#include<glfw3.h> 
#include"Shader.h"

/* 编写各顶点位置 */
GLfloat vertices_1[] =
{
    //position                    
    0.5f, 0.5f, 0.0f,            // top right        0
    0.5f, -0.5f, 0.0f,            // bottom right        1
    -0.5f, -0.5f, 0.0f,            // bottom left        2
    -0.5f, 0.5f, 0.0f,            // top left            3
};

/* 四个顶点的连接信息给出来 */
GLuint indices_1[] =
{
    0, 1, 3,        // 序号为 0、1、3 的顶点组合成一个三角形
    1, 2, 3            // 序号为 1、2、3 的顶点组合成一个三角形
};
const GLint WIDTH = 600, HEIGHT = 600;        // 正方形窗口

int main()
{
    glfwInit();
    GLFWwindow* window_1 = glfwCreateWindow(WIDTH, HEIGHT, "Learn OpenGL Triangle test", nullptr, nullptr);
    int screenWidth_1, screenHeight_1;
    glfwGetFramebufferSize(window_1, &screenWidth_1, &screenHeight_1);
    glfwMakeContextCurrent(window_1);
    glewInit();

    /* 将我们自己设置的着色器文本传进来 */
    Shader ourShader = Shader("shader_v.txt", "shader_f.txt");        // 相对路径

    /* 设置顶点缓冲对象(VBO) + 设置顶点数组对象(VAO)  */
    GLuint VAO, VBO;                
    glGenVertexArrays(1, &VAO);        
    glGenBuffers(1, &VBO);            
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);    
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_1), vertices_1, GL_STATIC_DRAW);    

    /* 设置链接顶点属性 */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);    // 通道 0 打开

    /* 设置索引缓冲对象    */
    GLuint EBO;
    glGenBuffers(1, &EBO);        
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices_1), indices_1, GL_STATIC_DRAW); 

// draw loop 画图循环
    while (!glfwWindowShouldClose(window_1))
    {
        // 视口 + 时间 
        glViewport(0, 0, screenWidth_1, screenHeight_1);
        glfwPollEvents();

        // 渲染 + 清除颜色缓冲
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        /*  绘制图形 */
        ourShader.Use();
        float time = glfwGetTime();                        // 获取时间
        float redValue = sin(time) / 2.0f + 0.5f;        // 红色数值计算,范围[0,1]
        float greenValue = 1 - redValue;                // 绿色数值计算,范围[0.1]。且满足 “redValue + greenValue = 1”
        int vertexColorLocation = glGetUniformLocation(ourShader.Program, "time");
        glUniform4f(vertexColorLocation, redValue, greenValue, 0.0f, 1.0f );

        glBindVertexArray(VAO);                                    // 绑定 VAO
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);                // 绑定 EBO
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);    // 画两个三角形 从第0个顶点开始 一共画6次
        glBindVertexArray(0);                                    // 解绑定 VAO
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);                // 解绑定 EBO

        // 交换缓冲
        glfwSwapBuffers(window_1);
    }

    glDeleteVertexArrays(1, &VAO);    // 释放资源
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glfwTerminate();                // 结束
    return 0;
}

运行效果:

在这里插入图片描述