LearnOpenGL(四):使用元素缓冲对象绘制矩形

目录

前言:

EMO:

全代码:


前言:

在前面几节我们已经能够绘制出一个简单的三角形,但在图像中,或者说是3D世界,一个模型它一定不单单只有3个顶点,如是我们绘制一个矩形,我们知道矩形需要四个顶点,但对于计算机来说,特别是OpernGL,它只能绘制三角,我们也知道,一个矩形可以由两个三角形组成,那么我们就需要6个顶点来确定两个三角形,而这六个顶点中,必定是由重复的数据。

比如这样:

// first triangle

0.5f, 0.5f, 0.0f, // top right

0.5f, -0.5f, 0.0f, // bottom right

-0.5f, 0.5f, 0.0f, // top left

// second triangle

0.5f, -0.5f, 0.0f, // bottom right

-0.5f, -0.5f, 0.0f, // bottom left

-0.5f, 0.5f, 0.0f // top left

可以看到,第一个三角形和第二个三角形的有重复的顶点数据,若是场景中几百上千的矩形都这样操作,那一定是多了许多重复的数据缓存,而元素缓冲对象(Element Buffer Object,EBO)又叫索引缓冲对象(Index Buffer Object,IBO)就是来解决这个问题的。

其实这个概念在游戏引擎和DCC工具中并不罕见,在DCC中它就是顶点索引顺序设置,在游戏引擎也是如此,但一般来说游戏引擎是直接读取模型文件,并不直接给用户设置模型顶点顺序,而需要用户在DCC中设置好。

EMO:

EBO和VBO一样,都是缓冲对象,两者声明也都是一样的,就是类型有所改变,可以看到一下的对比:

VBO:

glGenBuffers(1, &VBO);

glBindBuffer(GL_ARRAY_BUFFER, VBO);

glBufferData(GL_ARRAY_BUFFER, sizeof(vertexts), vertexts, GL_STATIC_DRAW);

glDeleteBuffers(1, &VBO);

EBO:

glGenBuffers(1, &EBO); 

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexs), indexs, GL_STATIC_DRAW);

glDeleteBuffers(1, &EBO);

 而在最后的调用上,不再采用glDrawArrays函数,而是采用glDrawElements,直接按照顶点顺序进行绘制

全代码:

/*引入OpneGL方法库头文件*/
#include <glad/glad.h>
#include <GLFW/glfw3.h>

/*引入文件里头文件,方便我们调用方法进行问题检查*/
#include <iostream>

/*视口变换回调函数*/
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    glViewport(0, 0, width, height);
}

/*输入设置,比如按下Esc键关闭窗口*/
void player_input(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

/*顶点Shader源码*/
const char *vertexShaderSource = "#version 330 core\n"
                                 "layout (location=0) in vec3 aPos;\n"
                                 "void main()\n"
                                 "{\n"
                                 "gl_Position = vec4(aPos.x,aPos.y,aPos.z,1.0);\n"
                                 "}\0";
/*片元Shader源码*/
const char *fragmentShaderSource = "#version 330 core\n"
                                   "out vec4 finalColor;\n"
                                   "void main()\n"
                                   "{\n"
                                   "finalColor = vec4(1.0);\n"
                                   "}\0";

/*声明屏幕的宽度和高度,分别为800和600*/
const unsigned int Screen_Width = 800;
const unsigned int Screen_Height = 600;

/*主函数*/
int main()
{
    /*初始化GLFW*/
    glfwInit();
    /*此函数基于OpenGL 3.3版本展开,需要告诉GLFW我们要使用的OpenGL版本是3.3,这样GLFW会在创建OpenGL上下文时做出适当的调整。
    这也可以确保用户在没有适当的OpenGL版本支持的情况下无法运行。将主版本号(Major)和次版本号(Minor)都设为3。同样你可以修改它们的版本*/
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

    /*我们明确告诉GLFW我们使用的是核心模式(Core-profile)
    明确告诉GLFW我们需要使用核心模式意味着我们只能使用OpenGL功能的一个子集(没有我们已不再需要的向后兼容特性)。*/
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

/* 如果使用的是Mac OS X系统,还需要加下面这行代码到你的初始化代码中这些配置才能起作用*/
#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
#endif

    /*设置窗口对象 */
    GLFWwindow *window = glfwCreateWindow(Screen_Width, Screen_Height, "LearOpenGLWidnow", NULL, NULL);

    /*如果窗口对象创建失败,在终端输出日志,并且释放内存*/
    if (window == NULL)
    {
        std::cout << "Fail to create window !" << std::endl;
        glfwTerminate();
        return -1;
    }

    /*窗口对象创建成功,使用这个窗口 */
    glfwMakeContextCurrent(window);
    /*窗口大小变换回调*/
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    /*检测是否加载所有glad方法库的函数 */
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Fail to initialize glad !" << std::endl;
        return -1;
    }

    /*构建编译顶点Shader*/
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    /*检查是否构建编译成功*/
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, sizeof(infoLog), NULL, infoLog);
        std::cout << "Error:Shader:VertexShader:Compile_Status_Failure" << infoLog << std::endl;
        return -1;
    }

    /*构建编译片元Shader*/
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    /*检查是否构建编译成功*/
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, sizeof(infoLog), NULL, infoLog);
        std::cout << "Error:Shader:FragmentShader:Compile_Status_Failure" << infoLog << std::endl;
        return -1;
    }

    /*链接顶点Shader和片元Shader组成Shader进程 */
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    /*检查是否链接成功*/
    glGetProgramiv(shaderProgram, GL_LINE, &success);
    if (!success)
    {
        glGetProgramInfoLog(shaderProgram, sizeof(infoLog), NULL, infoLog);
        std::cout << "Error:Program:Link:Link_Fialure" << infoLog << std::endl;
        return -1;
    }
    /*删除顶点Shader与片元Shader内存*/
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    /*设置三角形顶点数据 */
    float vertexts[]{
        -0.5, -0.5, 0.0, // 左下
        -0.5, 0.5, 0.0,  // 左上
        0.5, -0.5, 0.0,  // 右下
        0.5, 0.5, 0.0    // 右上
    };

    /*设置顶点绘制顺序*/
    unsigned int indexs[]{
        0, 1, 2,
        1, 2, 3};

    /*声明顶点缓冲物体(Vertex Buffer Object)id,顶点数组物体(Vertex Array Objet)id与元素缓冲物体(Element Buffer Object)id*/
    unsigned int VBO, VAO, EBO;
    /*生成顶点数组 */
    glGenVertexArrays(1, &VAO);
    /*生成顶点缓冲区*/
    glGenBuffers(1, &VBO);
    /*生成元素缓冲区*/
    glGenBuffers(1, &EBO);

    /*先绑定顶点数组,在绑定和设置缓冲区,然后绑定顶点属性*/
    /*绑定顶点数组*/
    glBindVertexArray(VAO);
    /*绑定顶点缓冲区数据*/
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexts), vertexts, GL_STATIC_DRAW);
    /*绑定元素缓冲区 */
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexs), indexs, GL_STATIC_DRAW);
    /*绑定顶点属性*/
    /*第一个参数指代的是顶点Shader中location=0的0,是一个id而不是值
      第二个参数指代顶点属性的大小,这里只有位置,为vec3,它是由3个值组成
      第三个参数指代数据的类型,vec在OpenGL中为float类型
      第四个参数指代是否归一化
      第五个参数指代第二个属性数显的步长,简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节
      第六个参数为位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0*/
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void *)0);
    /*以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的*/
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    /*当窗口没有被关闭时,执行渲染循环 */
    while (!glfwWindowShouldClose(window))
    {
        /* code */
        /*按键输入以及对应操作事件*/
        player_input(window);

        /*渲染*/
        /*清除颜色缓冲区*/
        glClear(GL_COLOR_BUFFER_BIT);
        /*使用Shader进程*/
        glUseProgram(shaderProgram);
        glBindVertexArray(VBO);

        /*绘制顶点数据*/
        // glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        /*交换缓存区 */
        glfwSwapBuffers(window);
        /*执行事件 */
        glfwPollEvents();
    }

    /*删除对象,缓存,进程*/
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glDeleteProgram(shaderProgram);

    /*退出循环后释放内存*/
    glfwTerminate();
    return 0;
}