首页 > 其他分享 >OpenGL入门——使用EBO绘制三角形

OpenGL入门——使用EBO绘制三角形

时间:2023-09-03 21:36:41浏览次数:40  
标签:入门 OpenGL 0.0 EBO 0.5 三角形 GL 绘制

上一节OpenGL入门——第一个三角形(1) - 一只小瓶子 - 博客园 (cnblogs.com)介绍了opengl怎么使用VAO和VBO绘制一个三角形


这一节介绍一下使用EBO绘制

 

元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO)。

为什么会需要用到元素缓冲对象呢?因为上一节我们提到了绘制三角形就是输入3个顶点进行绘制,但是当我们在构建一个平面需要用到很多个三角形的时候,这里面就会有一些三角形的顶点是是重叠的。最简单的例子就是一个矩形,可以看出是两个三角形,但是这两个三角形有两个顶点重叠了

float vertices[] = {
    // 第一个三角形
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, 0.5f, 0.0f,  // 左上角
    // 第二个三角形
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

如上所示,右下角和左上角出现了两次,这样就会产生额外的开销,矩形其实使用4个顶点就够了。同样的,如果一个模型由成千上万个三角形组成的,就会产生一大堆浪费。

对于这个物体,OpenGL提供了EBO缓冲对象,存储绘制的顶点索引(称为索引绘制)。这样我们就不需要重复定义顶点了,如下所示

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = {
    // 注意索引从0开始! 
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形

    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

使用EBO对象和VAO、VBO类似,先创建一个EBO对象,把函数调用放在绑定和解绑函数调用之间

    //生成EBO对象,缓冲ID为EBO
    unsigned int EBO;
    glGenBuffers(1, &EBO);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//数据传入缓冲内存中
        ...
        ...

    while (!glfwWindowShouldClose(window))
    {
        ...
                ...
        ///绘制物体
        glUseProgram(shaderProgram);//激活程序对象

        glBindVertexArray(VAO);
        
        //使用EBO绘制, 缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//缓冲目标GL_ELEMENT_ARRAY_BUFFER
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//绘制图元为三角形,绘制顶点数量6,索引类型,偏移量0
        ...
        ...
    }

这里看一下VAO、VBO、EBO三者的关系:

如上图所示,在绑定VAO时,绑定的最后一个EBO对象存储为VAO的元素缓冲区对象,所以在绘制的时候绑定到VAO也会自动绑定该EBO。

注意事项:在VAO未解绑前,不可以解绑EBO,否则会没有这个EBO配置了。

 

完整代码

//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h),所以需要在其它依赖于OpenGL的头文件之前包含GLAD
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "shader.h"
#include "stb_image.h"

const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;// 位置变量的属性位置值为 0 \n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";

const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);//\n"
"}\0";

//改变窗口大小
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

//输入
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//点击ESC键退出绘制
        glfwSetWindowShouldClose(window, true);
}

GLFWwindow* init_window()
{
    ///窗口初始化
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号,当API以不兼容的方式更改时,该值会增加。
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本号,当特性被添加到API中时,它会增加,但是它保持向后兼容。
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//使用核心模式,不兼容已废弃函数

    //创建glfw窗口
    GLFWwindow* window = glfwCreateWindow(800, 600, "ping-window", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "failed to create GLFW window" << std::endl;
        glfwTerminate();//释放/删除之前的分配的所有资源
        return nullptr;
    }
    glfwMakeContextCurrent(window);//将窗口的上下文设置为当前线程的主上下文
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//注册为调整窗口回调函数

    //GLAD是用来管理OpenGL的函数指针的,在调用任何OpenGL的函数之前初始化GLAD
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))//给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数
    {
        std::cout << "failed to intialize GLAD" << std::endl;
        return nullptr;
    }

    glViewport(0, 0, 800, 600);//处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)

    return window;
}

int triangle_by_EBO()
{
    GLFWwindow* window = init_window();

    ///定义着色器
    //创建一个顶点着色器对象,注意还是用ID来引用的
    unsigned int vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);

    //着色器源码附加到着色器对象上
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//要编译的着色器对象作为第一个参数。第二参数指定了传递的源码字符串数量,这里只有一个。第三个参数是顶点着色器真正的源码,第四个参数我们先设置为NULL
    glCompileShader(vertexShader);//编译源码
    int  success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);//用glGetShaderiv检查是否编译成功
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //创建一个片段着色器对象,注意还是用ID来引用的
    unsigned int fragmentShader;
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);//编译源码
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);//用glGetShaderiv检查是否编译成功
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //创建一个着色器对程序
    unsigned int shaderProgram;
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);//把之前编译的着色器附加到程序对象上
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);//glLinkProgram链接它们
    glGetProgramiv(shaderProgram, GL_COMPILE_STATUS, &success);//用glGetProgramiv检查是否编译成功
    if (!success)
    {
        glGetShaderInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINK_FAILED\n" << infoLog << std::endl;
    }

    //链接后即可删除
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);//*/

    CShader shader("hello_triangle.vs", "hello_triangle.fs");

    //定义顶点对象
    float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
    };

    unsigned int indices[] = {
        // 注意索引从0开始! 
        // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
        // 这样可以由下标代表顶点组合成矩形

        0, 1, 3, // 第一个三角形
        1, 2, 3  // 第二个三角形
    };

    //生成VAO对象,缓冲ID为VAO
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);//绑定VAO,从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO,供之后使用

    //生成VBO对象,缓冲ID为VBO
    unsigned int VBO;
    glGenBuffers(1, &VBO);//第一个参数GLsizei是要生成的缓冲对象的数量,第二个GLuint是要输入用来存储缓冲对象名称的数组

    //生成EBO对象,缓冲ID为EBO
    unsigned int EBO;
    glGenBuffers(1, &EBO);

    //绑定到目标对象,VBO变成了一个顶点缓冲类型
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//第一个就是缓冲对象的类型,第二个参数就是要绑定的缓冲对象的名称
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//数据传入缓冲内存中,GL_STATIC_DRAW:数据不会或几乎不会改变; GL_DYNAMIC_DRAW:数据会被改变很多; GL_DYNAMIC_DRAW:数据会被改变很多

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);//不要在VAO处于活动状态时取消绑定EBO,因为绑定的元素缓冲区对象存储在VAO中;保持EBO绑定。

    //设置顶点属性指针,如何解析顶点数据
    /*
    第一个参数指定我们要配置的顶点属性,顶点着色器中使用layout(location = 0)定义
    第二个参数指定顶点属性的大小
    第三个参数指定数据的类型
    第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间
    第五个参数步长(Stride),它告诉我们在连续的顶点属性组之间的间隔
    最后一个参数的类型是void*,数据在缓冲中起始位置的偏移量(Offset)
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);//启用顶点属性layout(location = 0),顶点属性默认是禁用的
    glBindBuffer(GL_ARRAY_BUFFER, 0);//设置完属性,解绑VBO

    glBindVertexArray(0);//配置完VBO及其属性,解绑VAO


    //绘制模式为线条GL_LINE,填充面GL_FILL
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//正反面

    while (!glfwWindowShouldClose(window))
    {
        processInput(window);


        //清空屏幕
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);


        ///绘制物体
        glUseProgram(shaderProgram);//激活程序对象

        glBindVertexArray(VAO);
        
        //glDrawArrays(GL_TRIANGLES, 0, 3);//使用VAO绘制:绘制图元为三角形,起始索引0,绘制顶点数量3
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//使用EBO绘制:绘制图元为三角形,绘制顶点数量6,索引类型,偏移量0

        //glBindVertexArray(0);//只绘制一个物体,不需要重复绑定和解绑VAO
        //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);//不要在VAO处于活动状态时取消绑定EBO,因为绑定的元素缓冲区对象存储在VAO中;保持EBO绑定。

        glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲)
        glfwPollEvents();//检查有没有触发什么事件
    }

    //释放对象
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);//VAO解绑后,可以解绑EBO。
    glDeleteProgram(shaderProgram);

    std::cout << "finish!" << std::endl;
    glfwTerminate();//释放/删除之前的分配的所有资源
    return 0;
}

int main()
{
    triangle_by_EBO();

    return 0;
}

运行结果

 PS.为了能看出是绘制的两个三角形,这里使用的是绘制线框模式

    //绘制模式为线条GL_LINE,填充面GL_FILL
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//正反面

 

标签:入门,OpenGL,0.0,EBO,0.5,三角形,GL,绘制
From: https://www.cnblogs.com/ping-code/p/17675603.html

相关文章

  • 梯度下降算法入门
    提到梯度下降我们知道梯度下降算法是很多机器学习算法、深度学习算法的基础。首先我们需要明确一些概念什么是梯度:梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。梯度的数......
  • vue3入门_demo
    新建项目参考:Vuevscode创建vue项目流程【超详细】_vuevscode创建vue项目流程【超详细】_怎么用vscode写vue_一颗不甘坠落的流_一颗不甘坠落的流星的博客-CSDN博客项目结构:App.vue<template><Main></Main></template><script>importMainfrom"./components......
  • Java入门(02):Java的环境配置
    前言Java是一门广泛应用于各种场景的编程语言,不同于其它编程语言,Java的代码需要在Java虚拟机(JavaVirtualMachine,JVM)上运行,因此在学习和使用Java时,我们需要先进行Java环境的配置。本文将介绍Java环境的配置方法,包括JDK和IDE的安装与配置。摘要本文主要涵盖以下内容:JDK的安......
  • etcd简单入门
    介绍etcd是CoreOS团队发起的一个开源项目,实现了分布式键值存储和服务发现,etcd和ZooKeeper/Consul非常相似,都提供了类似的功能,以及RESTAPI的访问操作,具有以下特点:简单:安装和使用简单,提供了RESTAPI进行操作交互安全:支持HTTPSSSL证书快速:支持并发10k/s的读写......
  • 具有200MHz入门级微控制器R7FA6E2BB2CBC、R7FA6E2BB3CNE、R7FA6E2B92CBB是高性能、小
    一、简介RA6E2组是RA6系列中最新的入门级微控制器,基于带有TrustZone的200MHzArm®Cortex®-M33内核。RA6E2MCU作为入门级微控制器,在追求成本优化的同时提供了最佳的性能。与RA4E2组的引脚和外设兼容,使其成为要求更高性能、小尺寸和低引脚数的应用的理想选择。二、器件规格1......
  • Git从入门到精通
    Git从入门到精通学习Git之前,我们需要先明白一个概念,版本控制!学习视频,B站:遇见狂神说https://www.bilibili.com/video/BV1FE411P7B3/?spm_id_from=333.999.0.0&vd_source=b1c9346178fc41766e00c3d88901f1cf狂神笔记https://mp.weixin.qq.com/s/Bf7uVhGiu47uOELjmC5uXQ版本控制......
  • 区间dp入门选讲
    目录区间dp入门选讲合并果子括号匹配PalindromeAgainPalindromeStringpainter搬寝室配对区间dp入门选讲合并果子传送门设\(f_{i,j}\)表示合并区间\([i,j]\)的最小代价,\(\begin{aligned}s_i=\sum^{i}_{k=1}a_k\end{aligned}\),显然有\(\begin{aligned}f_{i,j}=\min(f_{......
  • 从零开发Java入门项目--十天掌握
    ​ 原文网址:从零开发Java入门项目--十天掌握_IT利刃出鞘的博客-CSDN博客简介这是一个靠谱的Java入门项目实战,名字叫蚂蚁爱购。从零开发项目,视频加文档,十天就能学会开发Java项目,教程路线是:搭建环境=>安装软件=>创建项目=>添加依赖和配置=>通过表生成代码=>编写Java代码=>......
  • 从零开发Java入门项目--十天掌握
    简介这是一个靠谱的Java入门项目实战,名字叫蚂蚁爱购。从零开发项目,视频加文档,十天就能学会开发Java项目,教程路线是:搭建环境=>安装软件=>创建项目=>添加依赖和配置=>通过表生成代码=>编写Java代码=>代码自测=>前后端联调=>准备找工作。学完即可成为合格的Java开发,心里有底,再......
  • Java入门
    Java初识Java发展史时间节点1991年,Sun公司进军嵌入式开发,让电视、冰箱、微波炉等设备能够用上编程语言,成立了Green项目小组;1992年,由于C++语言的繁琐且不支持跨平台,研发团队基于C++开发了Oak语言;1995年,互联网大爆发,跨平台的特性使得Oak语言得到飞速发展,同时正式更名为Java(爪......