美文网首页
OpenGL从入门到放弃 #03-PR

OpenGL从入门到放弃 #03-PR

作者: MisakiMel | 来源:发表于2019-07-24 18:27 被阅读0次

  本节将对上节学习的内容稍作补充,然后开始复习及拓展训练,以提高熟练度。对于习题的答案并不一定是最优解,练习为主,并不做过多文字上的解释。

索引缓冲对象(Element Buffer Object,EBO)


  如果我们现在的需求不是一个三角形,而是一个矩形。我们可以通过绘制两个三角形的办法来组成一个矩形(OpenGL主要处理三角形)。那么就要改变我们的输入顶点数据:

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   // 左上角
};

  可以看到这两个三角形有两个顶点重复了,第一个是右下角0.5f, -0.5f, 0.0f,第二个是左上角-0.5f, 0.5f, 0.0f。目前看来重复的顶点数据并不会带来什么严重的后,但是如果是面对一个非常精致的模型,里面包含了海量的三角形,这种情况下的重复所带来的性能开销是不可忽视的。不过考虑周详的OpenGL显然早已经知道了这个问题并提供了很好的解决办法,索引缓冲对象(Element Buffer Object,EBO)为此而生。EBO与VBO一样,也是一个缓冲,它专门存储索引,存储的是顶点数据的索引,用索引的重复代替顶点的重复,那么如何设置顶点数据的索引呢?首先,我们要定义一个数组存储不重复的顶点,像列出一个矩形的所有顶点一样:

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, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

  接下来要做的就是创建一个EBO对象,然后再把索引数组送进EBO里。

    unsigned int EBO;
    glGenBuffers(1, &EBO);

  当然要想对这个EBO进行操作还是要先绑定这个EBO。

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

  一切都与讨论VBO时相似,唯一不同的就是缓冲的类型为GL_ELEMENT_ARRAY_BUFFER
  现在才是需要稍加注意的地方,如果我们使用了EBO,那么绘制的函数就不再是glDrawArrays函数,而是glDrawElements函数,指明是从索引缓冲渲染。

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

  第一个参数指定了我们绘制的模式,这个和glDrawArrays的一样;第二个参数是我们打算绘制的顶点个数,这里是6;第三个是参数是索引的类型;最后一个参数指定EBO中的偏移量,由于这里并没有偏移量,所以是0。
  glDrawElements函数从当前绑定的EBO中获取索引,这意味着我们每次调用glDrawElements绘制不同的物体都要绑定不同的EBO,这虽然不是特别麻烦,但如果有人乐意帮我做这件事那自然最好。我们的善心人士VAO果真乐于助人,在VAO绑定时去绑定EBO,这个步骤就会被VAO所保存,以后在渲染循环中绑定VAO时就会自动把EBO也绑定了。

  当目标是GL_ELEMENT_ARRAY_BUFFER的时候,VAO会储存glBindBuffer的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。

  上述的提示我亲身去体验了一下,在我解绑VAO前就把EBO给解绑了确实报错:



  最后的代码如下:

// ..:: 初始化代码 :: ..
// 1. 绑定顶点数组对象
    glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: ..
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
    glBindVertexArray(0);

  接下来就可以运行程序了:



  补充的内容到此结束,接下来是习题时间。

1. 添加更多顶点到数据中,使用glDrawArrays,尝试绘制两个彼此相连的三角形

  我打算绘制一个正三角,一个倒三角,两者公用其中一条边,形成一个棱形:
顶点数据:

float vertices[] = {
    0.0f, 0.5f, 0.0f,
    0.5f, 0.0f, 0.0f,
    0.0f, -0.5f, 0.0f,
    -0.5f, 0.0f, 0.0f
};

索引数组:

unsigned int indices[] = { // 注意索引从0开始! 
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

  其余代码不做改变,得到结果:



  这道题稍显简单。

2. 创建相同的两个三角形,但对它们的数据使用不同的VAO和VBO

  那么当前图案的顶点数据和其所属的VBO及VAO不再另行改变,而是再新造一组顶点数据、VAO及VBO。我造一个漏斗吧:

float vertices2[] = {
    -0.5f, 0.5f, 0.0f,
    0.5f, 0.5f, 0.0f,
    0.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
};

unsigned int indices[] = { // 注意索引从0开始! 
    0, 1, 2, // 第一个三角形
    2, 3, 4  // 第二个三角形
};

  虽说不再改变,但是要做一些修改,譬如原本的三角形函数的某些参数要改为VAO[0]、VBO[0]和EBO[0]。这里是对原来作出的修改:

    //创建VAO
    unsigned int VAO[2];
    glGenVertexArrays(2, VAO);
    glBindVertexArray(VAO[0]);

    //创建VBO且与顶点数据绑定
    unsigned int VBO[2];
    glGenBuffers(2, VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);



    //创建EBO且与索引数据绑定
    unsigned int EBO[2];
    glGenBuffers(2, EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

  这里是新增的VBO、VAO和EBO的绑定:

    glBindVertexArray(VAO[1]);

    glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
    glEnableVertexAttribArray(0);

    glBindVertexArray(0);

  在渲染循环中,原本glBindVertexArray函数的参数是VAO,现在为了看第二个图案的效果,所以参数是VAO[1],用我们新创建好的VAO。

    glBindVertexArray(VAO[1]);
3.创建两个着色器程序,第二个程序使用一个不同的片段着色器,输出黄色;再次绘制这两个三角形,让其中一个输出为黄色

  那我们就直接让第二个图案(漏斗)输出黄色吧。
源码:

const char * fragmentShaderSource2 =
"   #version 330 core                           \n   "
"   out vec4 FragColor;                         \n   "
"   void main()                                 \n   "
"   {FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);} \n   ";

片段着色器:

    unsigned int fragmentShader2;
    fragmentShader2 = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(fragmentShader2, 1, &fragmentShaderSource2, NULL);
    glCompileShader(fragmentShader2);

链接着色器程序对象:

    unsigned int shaderProgram2;
    shaderProgram2 = glCreateProgram();

    glAttachShader(shaderProgram2, vertexShader);      //用的是原来的顶点着色器
    glAttachShader(shaderProgram2, fragmentShader2);
    glLinkProgram(shaderProgram2);

删除着色器:

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    glDeleteShader(fragmentShader2);

在渲染循环中,使用第二个着色器程序对象:

    while (!glfwWindowShouldClose(window))
    {
        //处理输入
        processInput(window);

        //渲染指令
        glClearColor(1.0f, 0, 0, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shaderProgram2);
        glBindVertexArray(VAO[1]);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
        

        //接收输入,交换缓冲
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

  包含3道题的所有代码在此:

#define GLEW_STATIC
#include<iostream>
#include<GL/glew.h>
#include<GLFW/glfw3.h>

void processInput(GLFWwindow*);

float vertices[] = {    //棱形顶点数据
    0.0f, 0.5f, 0.0f,
    0.5f, 0.0f, 0.0f,
    0.0f, -0.5f, 0.0f,
    -0.5f, 0.0f, 0.0f
};

unsigned int indices[] = { // 注意索引从0开始! 
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

float vertices2[] = {    //漏斗顶点数据
    -0.5f, 0.5f, 0.0f,
    0.5f, 0.5f, 0.0f,
    0.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
};

unsigned int indices2[] = { // 注意索引从0开始! 
    0, 1, 2, // 第一个三角形
    2, 3, 4  // 第二个三角形
};

const char * vertexShaderSource =
"#version 330 core                                  \n"
"layout (location = 0) in vec3 aPos;                \n"      
"void main()                                        \n"      
"{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);} \n";    

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

const char * fragmentShaderSource2 =
"   #version 330 core                           \n   "    //输出黄色
"   out vec4 FragColor;                         \n   "
"   void main()                                 \n   "
"   {FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);} \n   ";

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);  //使用的OpenGL版本号3.3
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);      //使用流水线配置模式

    GLFWwindow* window = glfwCreateWindow(800, 600, "Learn OpenGL 2019-07-24 17:25:23", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

    //初始化GLEW
    glewExperimental = true;
    if (glewInit() != GLEW_OK)
    {
        std::cout << "Failed to initial GLEW." << std::endl;
        glfwTerminate();
        return -1;
    }

    glViewport(0, 0, 800, 600);//渲染窗口大小
    //创建VAO
    unsigned int VAO[2];
    glGenVertexArrays(2, VAO);
    glBindVertexArray(VAO[0]);

    //创建VBO且与顶点数据绑定
    unsigned int VBO[2];
    glGenBuffers(2, VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);



    //创建EBO且与索引数据绑定
    unsigned int EBO[2];
    glGenBuffers(2, EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //顶点着色器
    unsigned int vertexShader;
    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, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //片段着色器
    unsigned int fragmentShader;
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

    unsigned int fragmentShader2;
    fragmentShader2 = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    glShaderSource(fragmentShader2, 1, &fragmentShaderSource2, NULL);
    glCompileShader(fragmentShader2);
    int  success1;
    char infoLog1[512];
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success1);

    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog1);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog1 << std::endl;
    }

    //着色器程序对象
    unsigned int shaderProgram;
    shaderProgram = glCreateProgram();

    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    unsigned int shaderProgram2;
    shaderProgram2 = glCreateProgram();

    glAttachShader(shaderProgram2, vertexShader);
    glAttachShader(shaderProgram2, fragmentShader2);
    glLinkProgram(shaderProgram2);

    //删除着色器对象
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    glDeleteShader(fragmentShader2);

    //链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
    glEnableVertexAttribArray(0);

    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(VAO[1]);

    glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
    glEnableVertexAttribArray(0);

    glBindVertexArray(0);


    while (!glfwWindowShouldClose(window))
    {
        //处理输入
        processInput(window);

        //渲染指令
        glClearColor(1.0f, 0, 0, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shaderProgram2);
        glBindVertexArray(VAO[1]);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
        

        //接收输入,交换缓冲
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    glfwTerminate();
    return 0;
}

void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window,GLFW_KEY_ESCAPE) == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(window, true);
    }
}

相关文章

网友评论

      本文标题:OpenGL从入门到放弃 #03-PR

      本文链接:https://www.haomeiwen.com/subject/kimxrctx.html