OpenGL学习笔记(九):索引缓冲器(EBO /IBE)的理解与使用,引入线框/填充模式


原博主博客地址:http://blog.csdn.net/qq21497936
本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78942027


《OpenGL学习笔记》系列博客目录地址:http://blog.csdn.net/qq21497936/article/category/7315532


OpenGL学习笔记():索引缓冲器(EBO /IBE)的理解与使用,引入线框/填充模式



前话


        前面一章节介绍了VAO、VBO和SHADER的基本使用,本章节将继续引入索引缓冲器,使用索引缓冲器建立一个六边形,并使六边形两两相邻的三个顶点构成一个三角形,共六个角形(每个顶点被使用3次),并引入绘画填充模式。

Demo效果图

demo3下载地址: http://download.csdn.net/download/qq21497936/10182276

OpenGL学习笔记(九):索引缓冲器(EBO /IBE)的理解与使用,引入线框/填充模式_第1张图片


专业名词


        EBO(Element Buffer Object)、EBO(Index Buffer Object):索引缓冲对象。


索引缓冲对象


        索引缓冲对象专门储存索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点,使用索引可重复使用同一个顶点。

不使用索引缓冲器绘制

        首先我们先看六边形的六个顶点坐标

       OpenGL学习笔记(九):索引缓冲器(EBO /IBE)的理解与使用,引入线框/填充模式_第2张图片

        在不使用索引的情况下,我们的顶点数据如下:

    // 7.顶点数据
    float vertices[] = {
        -0.866, -0.5, 0.0,
        -0.866,  0.5, 0.0,
         0.0  ,  1.0, 0.0, // 三角形1

        -0.866,  0.5, 0.0,
         0.0  ,  1.0, 0.0,
         0.866,  0.5, 0.0, // 三角形2

         0.0  ,  1.0, 0.0,
         0.866,  0.5, 0.0,
         0.866, -0.5, 0.0, // 三角形3

         0.866,  0.5, 0.0,
         0.866, -0.5, 0.0,
         0.0  , -1.0, 0.0, // 三角形4

         0.866, -0.5, 0.0,
         0.0  , -1.0, 0.0,
        -0.866, -0.5, 0.0, // 三角形5

         0.0  , -1.0, 0.0,
        -0.866, -0.5, 0.0,
        -0.866,  0.5, 0.0, // 三角形6
};

        绘制的顶点数量,是18个,所以修改程序的绘制顶点数量:

    while (!glfwWindowShouldClose(window))
    {
        ……
        // 绘制三角形:三角形        数组起始索引 绘制多少个顶点
        //        glDrawArrays(GL_TRIANGLES, 0,         3);
        glDrawArrays(GL_TRIANGLES, 0,         18); 
        ……
}

使用索引缓冲器绘制

        使用索引缓冲器,其实我们所有的三角形只使用到了六个顶点(需要重复使用的通过索引可重复使用),VAO中就保存该6个顶点可以了:

    // 7.0索引缓冲期 增加的索引缓冲器顶点索引
    unsigned int indices[] = { // 注意索引从0开始!
        0, 1, 2, // 第一个三角形
        1, 2, 3, // 第二个三角形
        2, 3, 4, // 第三个三角形
        3, 4, 5, // 第四个三角形
        4, 5, 0, // 第五个三角形
        5, 0, 1, // 第六个三角形
};

        使用EBO和VBO一样,首先是使用过程是一样的,其次是使用的注意点也是一样的,就是绑定EBO/VBO到解绑EBO/VBO(需要生效的)的过程一定要在绑定顶点数据和解绑顶点数据之间,否该绘制该顶点,缓存未被生效。

        以下是在原来的VAO,VBO代码部分新增了EBO的代码:

    // 8.使用VAO和VBO
    unsigned int VBO, VAO;
    // glGenVertexArrays() 创建一个顶点数组对象
    // 第一个参数:需要创建的缓存数量
    // 第二个参数:存储单一ID或多个ID的GLuint变量或数组的地址。
    glGenVertexArrays(1, &VAO);

    // glGenBuffers() 创建一个缓存对象并且返回缓存对象的标示符。
    glGenBuffers(1, &VBO);

    // 顶点对象创建之后,在使用缓存对象之前,需要将缓存对象连接到相应的缓存上。
    // glBindBuffer()有1个参数:buffer。(绑定和解绑的顺序很重要,勿更改)
    glBindVertexArray(VAO);

    // 缓存对象创建之后,在使用缓存对象之前,需要将缓存对象连接到相应的缓存上。
    // glBindBuffer()有2个参数:target与buffer。(绑定和解绑的顺序很重要,勿更改)
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 当缓存初始化之后,使用glBufferData()将顶点数据拷贝到缓存对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

#if 1  // 索引缓冲器-添加代码
    // 8.0 与VBO类似,先创建EBO对象,再先绑定EBO
    //     然后用glBufferData把索引复制到缓冲里
    //     同样,和VBO类似,把这些函数调用放在绑定和解绑函数调用之间
    //     区别在于,只不过这次我们把缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER。
    unsigned int EBO;
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
#endif
    // 设置顶点属性指针,glVertexAttribPointer()函数告诉OpenGL该如何解析顶点数据
    //                    顶点属性位置 顶点属性大小 数据的类型 是否被标准化 步长             偏移
    glVertexAttribPointer(0,         3,         GL_FLOAT, GL_FALSE,  3*sizeof(float), (void *)0);
    // glEnableVertexAttribArray()以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的
    glEnableVertexAttribArray(0);

    // 解绑缓存着色器(绑定和解绑的顺序很重要,勿更改)
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // 解绑顶点着色器(绑定和解绑的顺序很重要,勿更改)
    glBindVertexArray(0);

        修改后的绘制循环代码:

while (!glfwWindowShouldClose(window))
    {
        // 捕捉输入
        processInput(window);
        // 清空颜色缓冲
        // 添加渲染指令,背景RGBA的A不生效, 更改为白色
        glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        /* 新增代码 第二段/共三段----------------------------------START */
        // 当我们渲染一个物体时要使用着色器程序
        glUseProgram(shaderProgram);
        // 激活VAO表示的顶点缓存
        glBindVertexArray(VAO);
        // 每次重新更新视口,如果不这样,拉升窗口后,会变形。
        // 你把你要观察的视景体映射到你的视口就没有问题,但是如果你不设置视景体,
        // opengl默认会以屏幕坐标,就是屏幕中心为原点,到四周的距离为1的这个范围
        // 作为视景体的观察部分映射到你设置的视口上。
        // 可以尝试,屏蔽此句,然后运行程序拉升窗口试试效果,更好理解
//        glViewport(0, 0, 600, 600);
        // 绘制三角形:三角形        数组起始索引 绘制多少个顶点
        // 不使用索引缓冲器绘制顶点
#if 1
        // 绘制六边的两两连接的三角形
//        glDrawArrays(GL_TRIANGLES, 0,         18);
#endif
#if 1
        // 索引缓冲器-添加代码      18个索引点(三角形,3个所以组成一个三角形)
        //                        这里18代表是18个索引,顶点数坐标也是18,只是巧合罢了
        glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_INT, 0);
#endif
        // 绘制三角形:线条      数组起始索引 绘制多少个顶点
//        glDrawArrays(GL_LINES, 0          , 2);
        /* 新增代码 第二段/共三段----------------------------------END */

        // 检查并调用事件,交换缓冲(执行交换缓存才会更新新数据到界面)
        glfwSwapBuffers(window);

        glfwPollEvents();
}

运行结果

OpenGL学习笔记(九):索引缓冲器(EBO /IBE)的理解与使用,引入线框/填充模式_第3张图片

源代码

#include 
#include 
#include 
#include 

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

// 在GLFW中实现一些输入控制,使用GLFW的glfwGetKey函数
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

int main()
{
    glfwInit();
    /*
       请确认您的系统支持OpenGL3.3或更高版本,否则此应用有可能会
       崩溃或者出现不可预知的错误。如果想要查看OpenGL版本的话,
       在Linux上运行glxinfo,或者在Windows上使用其它的工具(例如
       OpenGL Extension Viewer)。如果你的OpenGL版本低于3.3,检
       查一下显卡是否支持OpenGL 3.3+(不支持的话你的显卡真的太老
       了),并更新你的驱动程序,有必要的话请更新显卡。
    */
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // 如果使用的是Mac OS X系统,你还需要加下面这行代码到你的
    // 初始化代码中这些配置才能起作用
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    // 创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据,
    // 而且会被GLFW的其他函数频繁地用到
    // glfwCreateWindow函数需要窗口的宽和高作为它的前两个参数。
    // 第三个参数表示这个窗口的名称(标题),最后两个参数暂时忽略。
    // 函数会返回一个GLFWwindow对象,我们会在其它的GLFW操作中使用到。
    GLFWwindow* window = glfwCreateWindow(800, 600, "QQ:21497936", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

    // GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数
    // 之前我们需要初始化GLAD。
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // 还需要注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数。
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // 我们必须告诉OpenGL渲染窗口的尺寸大小,即视口(Viewport),
    // 这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。
    // 可以通过调用glViewport函数来设置窗口的维度(Dimension)。
    // OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转
    // 换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL
    // 中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。
    // 注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上
    // 将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。
    glViewport(0, 0, 600, 600);

    // 在我们主动关闭它之前不断绘制图像并能够接受用户输入。因此,
    // 需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),
    // 它能在我们让GLFW退出前一直保持运行。

    /* 新增代码 第一段/共三段----------------------------------START */

    // 1.顶点着色器
    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"
        "}";
    // 2.片段着色器
    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"
        "}";
    // 3.创建顶点着色器并编译
    int success;
    char infoLog[512];
    int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if(!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 4.创建片段着色器并编译
    int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if(!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 5.连接着色器
    int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if(!success)
    {
        glGetShaderInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 6.删除已经使用的着色器(因为后面不再需要绑定了)
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
#if 0
    // 7.顶点数据
    float vertices[] = {
        -0.866, -0.5, 0.0,
        -0.866,  0.5, 0.0,
         0.0  ,  1.0, 0.0, // 三角形1

        -0.866,  0.5, 0.0,
         0.0  ,  1.0, 0.0,
         0.866,  0.5, 0.0, // 三角形2

         0.0  ,  1.0, 0.0,
         0.866,  0.5, 0.0,
         0.866, -0.5, 0.0, // 三角形3

         0.866,  0.5, 0.0,
         0.866, -0.5, 0.0,
         0.0  , -1.0, 0.0, // 三角形4

         0.866, -0.5, 0.0,
         0.0  , -1.0, 0.0,
        -0.866, -0.5, 0.0, // 三角形5

         0.0  , -1.0, 0.0,
        -0.866, -0.5, 0.0,
        -0.866,  0.5, 0.0, // 三角形6
    };
#endif
#if 1 // 索引缓冲器-添加代码
    // 7.顶点数据
    float vertices[] = {
        -0.866, -0.5, 0.0, //      1  三角形5
        -0.866,  0.5, 0.0, //      1 2  三角形6
         0.0  ,  1.0, 0.0, // 三角形1 2 3
         0.866,  0.5, 0.0, //   三角形2 3 4
         0.866, -0.5, 0.0, //     三角形3 4 5
         0.0  , -1.0, 0.0, //       三角形4 5 6
    };
    // 7.0索引缓冲期 增加的索引缓冲器顶点索引
    unsigned int indices[] = { // 注意索引从0开始!
        0, 1, 2, // 第一个三角形
        1, 2, 3, // 第二个三角形
        2, 3, 4, // 第三个三角形
        3, 4, 5, // 第四个三角形
        4, 5, 0, // 第五个三角形
        5, 0, 1 // 第六个三角形
    };
#endif
    // 8.使用VAO和VBO
    unsigned int VBO, VAO;
    // glGenVertexArrays() 创建一个顶点数组对象
    // 第一个参数:需要创建的缓存数量
    // 第二个参数:存储单一ID或多个ID的GLuint变量或数组的地址。
    glGenVertexArrays(1, &VAO);

    // glGenBuffers() 创建一个缓存对象并且返回缓存对象的标示符。
    glGenBuffers(1, &VBO);

    // 顶点对象创建之后,在使用缓存对象之前,需要将缓存对象连接到相应的缓存上。
    // glBindBuffer()有1个参数:buffer。(绑定和解绑的顺序很重要,勿更改)
    glBindVertexArray(VAO);

    // 缓存对象创建之后,在使用缓存对象之前,需要将缓存对象连接到相应的缓存上。
    // glBindBuffer()有2个参数:target与buffer。(绑定和解绑的顺序很重要,勿更改)
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 当缓存初始化之后,使用glBufferData()将顶点数据拷贝到缓存对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

#if 1  // 索引缓冲器-添加代码
    // 8.0 与VBO类似,先创建EBO对象,再先绑定EBO
    //     然后用glBufferData把索引复制到缓冲里
    //     同样,和VBO类似,把这些函数调用放在绑定和解绑函数调用之间
    //     区别在于,只不过这次我们把缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER。
    unsigned int EBO;
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
#endif
    // 设置顶点属性指针,glVertexAttribPointer()函数告诉OpenGL该如何解析顶点数据
    //                    顶点属性位置 顶点属性大小 数据的类型 是否被标准化 步长             偏移
    glVertexAttribPointer(0,         3,         GL_FLOAT, GL_FALSE,  3*sizeof(float), (void *)0);
    // glEnableVertexAttribArray()以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的
    glEnableVertexAttribArray(0);

    // 解绑缓存着色器(绑定和解绑的顺序很重要,勿更改)
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // 解绑顶点着色器(绑定和解绑的顺序很重要,勿更改)
    glBindVertexArray(0);
    /* 新增代码 第一段/共三段----------------------------------END */

    while (!glfwWindowShouldClose(window))
    {
        // 捕捉输入
        processInput(window);
        // 清空颜色缓冲
        // 添加渲染指令,背景RGBA的A不生效, 更改为白色
        glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        /* 新增代码 第二段/共三段----------------------------------START */
        // 当我们渲染一个物体时要使用着色器程序
        glUseProgram(shaderProgram);
        // 激活VAO表示的顶点缓存
        glBindVertexArray(VAO);
        // 每次重新更新视口,如果不这样,拉升窗口后,会变形。
        // 你把你要观察的视景体映射到你的视口就没有问题,但是如果你不设置视景体,
        // opengl默认会以屏幕坐标,就是屏幕中心为原点,到四周的距离为1的这个范围
        // 作为视景体的观察部分映射到你设置的视口上。
        // 可以尝试,屏蔽此句,然后运行程序拉升窗口试试效果,更好理解
//        glViewport(0, 0, 600, 600);
        // 绘制三角形:三角形        数组起始索引 绘制多少个顶点
        // 不使用索引缓冲器绘制顶点
#if 1
        // 绘制六边的两两连接的三角形
//        glDrawArrays(GL_TRIANGLES, 0,         18);
#endif
#if 1
        // 索引缓冲器-添加代码      18个索引点(三角形,3个所以组成一个三角形)
        //                        这里18代表是18个索引,顶点数坐标也是18,只是巧合罢了
        glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_INT, 0);
#endif
        // 绘制三角形:线条      数组起始索引 绘制多少个顶点
//        glDrawArrays(GL_LINES, 0          , 2);
        /* 新增代码 第二段/共三段----------------------------------END */

        // 检查并调用事件,交换缓冲(执行交换缓存才会更新新数据到界面)
        glfwSwapBuffers(window);

        glfwPollEvents();
    }

    /* 新增代码 第三段/共三段----------------------------------END */
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    /* 新增代码 第三段/共三段----------------------------------END */

    // 当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。
    glfwTerminate();
    return 0;
}


拓展

        只画边框(不填充,类似于画图的画刷),加上如下代码:

#if 1
    // 使用xiankuan线框模式 随便放哪里,只要再glad之后(因为glad之后才能准确找到gl函数地址)
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endi

线框模式(Wireframe Mode)

        想用线框模式绘制图形,可知填充模式,通过glPolygonMode(GL_FRONT_AND_BACK,GL_LINE)函数配置OpenGL如何绘制图元。第一个参数表示我们打算将其应用到所有的三角形的正面和背面,第二个参数告诉用线来绘制。之后的绘制调用会一直以线框模式绘制三角形,直到我们用glPolygonMode(GL_FRONT_AND_BACK,GL_FILL)将其设置回默认模式。

拓展效果

OpenGL学习笔记(九):索引缓冲器(EBO /IBE)的理解与使用,引入线框/填充模式_第4张图片






原博主博客地址:http://blog.csdn.net/qq21497936
本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78942027


你可能感兴趣的:(Qt开发,OpenGL,图形图像处理)