OpenGL從入門到放棄 #03-PR


??本節(jié)將對上節(jié)學(xué)習(xí)的內(nèi)容稍作補(bǔ)充,然后開始復(fù)習(xí)及拓展訓(xùn)練,以提高熟練度。對于習(xí)題的答案并不一定是最優(yōu)解,練習(xí)為主,并不做過多文字上的解釋。

索引緩沖對象(Element Buffer Object,EBO)


??如果我們現(xiàn)在的需求不是一個三角形,而是一個矩形。我們可以通過繪制兩個三角形的辦法來組成一個矩形(OpenGL主要處理三角形)。那么就要改變我們的輸入頂點數(shù)據(jù):

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

??可以看到這兩個三角形有兩個頂點重復(fù)了,第一個是右下角0.5f, -0.5f, 0.0f,第二個是左上角-0.5f, 0.5f, 0.0f。目前看來重復(fù)的頂點數(shù)據(jù)并不會帶來什么嚴(yán)重的后,但是如果是面對一個非常精致的模型,里面包含了海量的三角形,這種情況下的重復(fù)所帶來的性能開銷是不可忽視的。不過考慮周詳?shù)腛penGL顯然早已經(jīng)知道了這個問題并提供了很好的解決辦法,索引緩沖對象(Element Buffer Object,EBO)為此而生。EBO與VBO一樣,也是一個緩沖,它專門存儲索引,存儲的是頂點數(shù)據(jù)的索引,用索引的重復(fù)代替頂點的重復(fù),那么如何設(shè)置頂點數(shù)據(jù)的索引呢?首先,我們要定義一個數(shù)組存儲不重復(fù)的頂點,像列出一個矩形的所有頂點一樣:

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

??這是符合我們數(shù)學(xué)認(rèn)知的一個矩形的頂點表示。然后再定義一個數(shù)組存儲繪制出矩形所需的的索引,當(dāng)然是按繪制三角形的頂點順序存放索引:

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

??接下來要做的就是創(chuàng)建一個EBO對象,然后再把索引數(shù)組送進(jìn)EBO里。

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

??當(dāng)然要想對這個EBO進(jìn)行操作還是要先綁定這個EBO。

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

??一切都與討論VBO時相似,唯一不同的就是緩沖的類型為GL_ELEMENT_ARRAY_BUFFER。
??現(xiàn)在才是需要稍加注意的地方,如果我們使用了EBO,那么繪制的函數(shù)就不再是glDrawArrays函數(shù),而是glDrawElements函數(shù),指明是從索引緩沖渲染。

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

??第一個參數(shù)指定了我們繪制的模式,這個和glDrawArrays的一樣;第二個參數(shù)是我們打算繪制的頂點個數(shù),這里是6;第三個是參數(shù)是索引的類型;最后一個參數(shù)指定EBO中的偏移量,由于這里并沒有偏移量,所以是0。
??glDrawElements函數(shù)從當(dāng)前綁定的EBO中獲取索引,這意味著我們每次調(diào)用glDrawElements繪制不同的物體都要綁定不同的EBO,這雖然不是特別麻煩,但如果有人樂意幫我做這件事那自然最好。我們的善心人士VAO果真樂于助人,在VAO綁定時去綁定EBO,這個步驟就會被VAO所保存,以后在渲染循環(huán)中綁定VAO時就會自動把EBO也綁定了。

??當(dāng)目標(biāo)是GL_ELEMENT_ARRAY_BUFFER的時候,VAO會儲存glBindBuffer的函數(shù)調(diào)用。這也意味著它也會儲存解綁調(diào)用,所以確保你沒有在解綁VAO之前解綁索引數(shù)組緩沖,否則它就沒有這個EBO配置了。

??上述的提示我親身去體驗了一下,在我解綁VAO前就把EBO給解綁了確實報錯:



??最后的代碼如下:

// ..:: 初始化代碼 :: ..
// 1. 綁定頂點數(shù)組對象
    glBindVertexArray(VAO);
// 2. 把我們的頂點數(shù)組復(fù)制到一個頂點緩沖中,供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 復(fù)制我們的索引數(shù)組到一個索引緩沖中,供OpenGL使用
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 設(shè)定頂點屬性指針
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

[...]

// ..:: 繪制代碼(渲染循環(huán)中) :: ..
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
    glBindVertexArray(0);

??接下來就可以運(yùn)行程序了:



??補(bǔ)充的內(nèi)容到此結(jié)束,接下來是習(xí)題時間。

1. 添加更多頂點到數(shù)據(jù)中,使用glDrawArrays,嘗試?yán)L制兩個彼此相連的三角形

??我打算繪制一個正三角,一個倒三角,兩者公用其中一條邊,形成一個棱形:
頂點數(shù)據(jù):

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
};

索引數(shù)組:

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

??其余代碼不做改變,得到結(jié)果:



??這道題稍顯簡單。

2. 創(chuàng)建相同的兩個三角形,但對它們的數(shù)據(jù)使用不同的VAO和VBO

??那么當(dāng)前圖案的頂點數(shù)據(jù)和其所屬的VBO及VAO不再另行改變,而是再新造一組頂點數(shù)據(jù)、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  // 第二個三角形
};

??雖說不再改變,但是要做一些修改,譬如原本的三角形函數(shù)的某些參數(shù)要改為VAO[0]、VBO[0]和EBO[0]。這里是對原來作出的修改:

    //創(chuàng)建VAO
    unsigned int VAO[2];
    glGenVertexArrays(2, VAO);
    glBindVertexArray(VAO[0]);

    //創(chuàng)建VBO且與頂點數(shù)據(jù)綁定
    unsigned int VBO[2];
    glGenBuffers(2, VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);



    //創(chuàng)建EBO且與索引數(shù)據(jù)綁定
    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);

??在渲染循環(huán)中,原本glBindVertexArray函數(shù)的參數(shù)是VAO,現(xiàn)在為了看第二個圖案的效果,所以參數(shù)是VAO[1],用我們新創(chuàng)建好的VAO。

    glBindVertexArray(VAO[1]);
3.創(chuàng)建兩個著色器程序,第二個程序使用一個不同的片段著色器,輸出黃色;再次繪制這兩個三角形,讓其中一個輸出為黃色

??那我們就直接讓第二個圖案(漏斗)輸出黃色吧。
源碼:

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);

鏈接著色器程序?qū)ο螅?/p>

    unsigned int shaderProgram2;
    shaderProgram2 = glCreateProgram();

    glAttachShader(shaderProgram2, vertexShader);      //用的是原來的頂點著色器
    glAttachShader(shaderProgram2, fragmentShader2);
    glLinkProgram(shaderProgram2);

刪除著色器:

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

在渲染循環(huán)中,使用第二個著色器程序?qū)ο螅?/p>

    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[] = {    //棱形頂點數(shù)據(jù)
    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[] = {    //漏斗頂點數(shù)據(jù)
    -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);//渲染窗口大小
    //創(chuàng)建VAO
    unsigned int VAO[2];
    glGenVertexArrays(2, VAO);
    glBindVertexArray(VAO[0]);

    //創(chuàng)建VBO且與頂點數(shù)據(jù)綁定
    unsigned int VBO[2];
    glGenBuffers(2, VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);



    //創(chuàng)建EBO且與索引數(shù)據(jù)綁定
    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;
    }

    //著色器程序?qū)ο?    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);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容