OpenGL入門之入門


programs on the GPU-------shader

頂點着色器--》形狀(圖元)裝配--》幾何着色器--》光柵化--》片段着色器--》測試與混合

 

圖形渲染管線的第一個部分是頂點着色器(Vertex Shader),它把一個單獨的頂點作為輸入。
頂點着色器主要的目的是把3D坐標轉為另一種3D坐標(后面會解釋),同時頂點着色器允許我們對頂點屬性進行一些基本處理。

 

圖元裝配(Primitive Assembly)階段將頂點着色器輸出的所有頂點作為輸入
(如果是GL_POINTS,那么就是一個頂點),並所有的點裝配成指定圖元的形狀;本節例子中是一個三角形。

 

圖元裝配階段的輸出會傳遞給幾何着色器(Geometry Shader)。
幾何着色器把圖元形式的一系列頂點的集合作為輸入,它可以通過產生新頂點構造出新的(或是其它的)圖元來生成其他形狀。例子中,它生成了另一個三角形。

 

幾何着色器的輸出會被傳入光柵化階段(Rasterization Stage),
這里它會把圖元映射為最終屏幕上相應的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。
在片段着色器運行之前會執行裁切(Clipping)。裁切會丟棄超出你的視圖以外的所有像素,用來提升執行效率。

 

片段着色器的主要目的是計算一個像素的最終顏色,這也是所有OpenGL高級效果產生的地方。
通常,片段着色器包含3D場景的數據(比如光照、陰影、光的顏色等等),這些數據可以被用來計算最終像素的顏色。

 

在所有對應顏色值確定以后,最終的對象將會被傳到最后一個階段,我們叫做Alpha測試和混合(Blending)階段。
這個階段檢測片段的對應的深度(和模板(Stencil))值(后面會講),
用它們來判斷這個像素是其它物體的前面還是后面,決定是否應該丟棄。
這個階段也會檢查alpha值(alpha值定義了一個物體的透明度)並對物體進行混合(Blend)。
所以,即使在片段着色器中計算出來了一個像素輸出的顏色,在渲染多個三角形的時候最后的像素顏色也可能完全不同。

 

先要配置環境,然后創建窗口。

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

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

//頂點着色器
const char* vertexShaderSource =
"#version 330 core                                             \n"
"layout(location = 0) in vec3 aPos;                            \n"// 位置變量的屬性位置值為0
"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        ";

//函數在main之前存檔
void processInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
    {//退出鍵關閉窗口
        glfwSetWindowShouldClose(window, true);
    }
}
int main(int argc, char* argv[]) {

    glfwInit();//初始化和創建窗口
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//提示用主版本號為3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本號為3,即為3.3版本的OpenGL
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    //open GLFW window
    GLFWwindow* window = glfwCreateWindow(800, 600, "Test window", NULL, NULL);//800*600的窗口

    if (window == NULL) {//如果為空指針
        //std::cout << "open window failed." << std::endl;//打印失敗
        printf("open window failes.");
        glfwTerminate();//終止
        return -1;
        //return EXIT_FAILURE;
    }

    glfwMakeContextCurrent(window);

    //init GLEW
    glewExperimental = true;

    if (glewInit() != GLEW_OK)
    {
        printf("Init GLEW failed.");
        //std::cout << "glew init failed." << std::endl;
        glfwTerminate();
        return -1;//-1代表不正常退出
    }

    glViewport(0, 0, 800, 600);//前兩個參數控制窗口左下角的位置,后兩個參數設置可繪制的像素大小
    //逆時針作為三角形的正面
    //glEnable(GL_CULL_FACE);
    //glCullFace(GL_FRONT);//剔除正面,back--剔除背面

    //第一個參數表示我們打算將其應用到所有的三角形的正面和背面,第二個參數告訴我們用線來繪制
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//線框模式
    //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//關掉線框模式

    unsigned int VAO;//頂點數組對象
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);


    unsigned int VBO;//頂點緩沖對象
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//頂點緩沖對象的緩沖類型是GL_ARRAY_BUFFER,使用glBindBuffer函數把新創建的緩沖綁定到GL_ARRAY_BUFFER目標上
    //調用glBufferData函數,它會把之前定義的頂點數據復制到緩沖的內存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    /*
    glBufferData是一個專門用來把用戶定義的數據復制到當前綁定緩沖的函數。它的第一個參數是目標緩沖的類型:
    頂點緩沖對象當前綁定到GL_ARRAY_BUFFER目標上。第二個參數指定傳輸數據的大小(以字節為單位);
    用一個簡單的sizeof計算出頂點數據大小就行。第三個參數是我們希望發送的實際數據。

第四個參數指定了我們希望顯卡如何管理給定的數據。它有三種形式:

GL_STATIC_DRAW :數據不會或幾乎不會改變。
GL_DYNAMIC_DRAW:數據會被改變很多。
GL_STREAM_DRAW :數據每次繪制時都會改變。
    */

    unsigned int EBO;//索引緩沖對象,儲存索引
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    unsigned int vertexShader;//創建一個着色器對象,注意還是用ID來引用的
    vertexShader = glCreateShader(GL_VERTEX_SHADER);//創建這個着色器
    //把要編譯的着色器對象作為第一個參數。第二參數指定了傳遞的源碼字符串數量,這里只有一個。第三個參數是頂點着色器真正的源碼,第四個參數我們先設置為NULL。
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    unsigned int fragmentShader;//創建一個着色器對象,注意還是用ID來引用的
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);//創建這個着色器
    //把要編譯的着色器對象作為第一個參數。第二參數指定了傳遞的源碼字符串數量,這里只有一個。第三個參數是頂點着色器真正的源碼,第四個參數我們先設置為NULL。
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    unsigned int shaderProgram;//創建一個着色器程序對象
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);//鏈接(Link)為一個着色器程序對象

    //頂點屬性,頂點屬性的大小(vec3),數據類型,是否數據被標准化,步長,強制類型轉換(表示位置數據在緩沖中起始位置的偏移量)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);//啟用頂點屬性

    while (!glfwWindowShouldClose(window))//渲染循環,關閉它之前不斷繪制圖像並能夠接受用戶輸入
    {

        processInput(window);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0);//清屏顏色填充RGB,透明度
        glClear(GL_COLOR_BUFFER_BIT);

        glBindVertexArray(VAO);//綁定
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glUseProgram(shaderProgram);//激活這個程序對象
        //glDrawArrays(GL_TRIANGLES, 0, 3);//OpenGL圖元的類型,頂點數組的起始索引,打算繪制多少個頂點,繪制三角形
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//改為glDrawElements即可。6為索引個數,0表示偏移。
        glfwSwapBuffers(window);
        glfwPollEvents();//檢查有沒有觸發什么事件
    }

    glfwTerminate();//釋放/刪除之前的分配的所有資源

    return 0;
}

結果如圖:

 

函數介紹:

使用glVertexAttribPointer函數告訴OpenGL該如何解析頂點數據(應用到逐個頂點屬性上)了:

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

glVertexAttribPointer函數的參數非常多,所以我會逐一介紹它們:

第一個參數指定我們要配置的頂點屬性。還記得我們在頂點着色器中使用layout(location = 0)
定義了position頂點屬性的位置值(Location)嗎?它可以把頂點屬性的位置值設置為0。
因為我們希望把數據傳遞到這一個頂點屬性中,所以這里我們傳入0。

第二個參數指定頂點屬性的大小。頂點屬性是一個vec3,它由3個值組成,所以大小是3。

第三個參數指定數據的類型,這里是GL_FLOAT(GLSL中vec*都是由浮點數值組成的)。

下個參數定義我們是否希望數據被標准化(Normalize)。如果我們設置為GL_TRUE,
所有數據都會被映射到0(對於有符號型signed數據是-1)到1之間。我們把它設置為GL_FALSE。

第五個參數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔。
由於下個組位置數據在3個float之后,我們把步長設置為3 * sizeof(float)。
要注意的是由於我們知道這個數組是緊密排列的(在兩個頂點屬性之間沒有空隙)
我們也可以設置為0來讓OpenGL決定具體步長是多少(只有當數值是緊密排列時才可用)。
一旦我們有更多的頂點屬性,我們就必須更小心地定義每個頂點屬性之間的間隔,
我們在后面會看到更多的例子(譯注: 這個參數的意思簡單說就是從這個屬性第二次出現的地方
到整個數組0位置之間有多少字節)。

最后一個參數的類型是void*,所以需要我們進行這個奇怪的強制類型轉換。
它表示位置數據在緩沖中起始位置的偏移量(Offset)。由於位置數據在數組的開頭,所以這里是0。
我們會在后面詳細解釋這個參數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM