OpenGl入門2:第一個窗口

本文是我的學習記錄,學習建議看教程 https://learnopengl-cn.github.io/
很是感謝原做者JoeyDeVries和兩位翻譯的gjy_1992, Krasjet提供的優質教程php

測試GLFW

在咱們的test.cpp中加入下面兩個頭文件html

#include <glad/glad.h>
#include <GLFW/glfw3.h>

接下來咱們在main函數裏寫上以下代碼,在main裏咱們將會實例化GLFW窗口:ios

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);//for Mac

    return 0;
}

首先,調用glfwInit()函數來初始化GLFW,而後使用glfwWindowHint()函數來配置GLFWc++

glfwWindowHint()函數的第一個參數表明選項的名稱,咱們能夠從不少以GLFW_開頭的枚舉值中選擇;第二個參數接受一個整型,用來設置這個選項的值
該函數的全部的選項以及對應的值均可以在 GLFW’s window handling 這篇文檔中找到
若是你如今編譯你的cpp文件會獲得大量的 undefined reference (未定義的引用) 錯誤,也就是說你並未順利地連接GLFW庫git

因爲我看的教程是基於OpenGL 3.3版本展開討論的,因此咱們須要告訴GLFW咱們要使用的OpenGL版本是3.3,因此咱們將主版本號(Major)和次版本號(Minor)都設爲3,這樣GLFW會在建立OpenGL上下文時作出適當的調整,這也能夠確保用戶在沒有適當的OpenGL版本支持的狀況下沒法運行github

咱們一樣明確告訴GLFW咱們使用的是核心模式(Core-profile)。明確告訴GLFW咱們須要使用核心模式意味着咱們只能使用OpenGL功能的一個子集(沒有咱們已再也不須要的向後兼容特性)函數

若是使用的是Mac OS X系統,還須要將上面的代碼解除註釋oop


接下來咱們建立一個窗口對象,這個窗口對象存放了全部和窗口相關的數據,並且會被GLFW的其餘函數頻繁地用到學習

GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
    std::cout << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

glfwCreateWindow()函數須要窗口的做爲它的前兩個參數,第三個參數表示這個窗口的名稱(標題),這裏我使用"LearnOpenGL",固然你也可使用你喜歡的名稱,最後兩個參數咱們暫時忽略測試

這個函數將會返回一個GLFWwindow對象,咱們會在其它的GLFW操做中使用到,建立完窗口咱們就能夠通知GLFW將咱們窗口的上下文設置爲當前線程的主上下文了

GLAD

GLAD是用來管理OpenGL的函數指針的,因此在調用任何OpenGL的函數以前咱們須要初始化GLAD

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
    std::cout << "Failed to initialize GLAD" << std::endl;
    return -1;
}

咱們給GLAD傳入了用來加載系統相關的OpenGL函數指針地址的函數。GLFW給咱們的是glfwGetProcAddress,它根據咱們編譯的系統定義了正確的函數。

視口

在咱們開始渲染以前,咱們必須告訴OpenGL渲染窗口的尺寸大小,即視口(Viewport),這樣OpenGL才只能知道怎樣根據窗口大小顯示數據和座標。咱們能夠經過調用glViewport函數來設置窗口的維度(Dimension):

glViewport(0, 0, 800, 600);

glViewport函數前兩個參數控制窗口左下角的位置,第三個和第四個參數控制渲染窗口的寬度和高度(像素)

咱們實際上也能夠將視口的維度設置爲比GLFW的維度小,這樣子以後全部的OpenGL渲染將會在一個更小的窗口中顯示,這樣子的話咱們也能夠將一些其它元素顯示在OpenGL視口以外

OpenGL幕後使用glViewport中定義的位置和寬高進行2D座標的轉換,將OpenGL中的位置座標轉換爲你的屏幕座標。例如,OpenGL中的座標(-0.5, 0.5)有可能(最終)被映射爲屏幕中的座標(200,450)。注意,處理過的OpenGL座標範圍只爲-1到1,所以咱們事實上將(-1到1)範圍內的座標映射到(0, 800)和(0, 600)。

然而,當用戶改變窗口的大小的時候,視口也應該被調整。咱們能夠對窗口註冊一個回調函數(Callback Function),它會在每次窗口大小被調整的時候被調用。這個回調函數的原型以下:

void framebuffer_size_callback(GLFWwindow* window, int width, int height);

這個幀緩衝大小函數須要一個GLFWwindow做爲它的第一個參數,以及兩個整數表示窗口的新維度。每當窗口改變大小,GLFW會調用這個函數並填充相應的參數供你處理

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

咱們還須要註冊這個函數,告訴GLFW咱們但願每當窗口調整大小的時候調用這個函數:

glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

當窗口被第一次顯示的時候framebuffer_size_callback也會被調用,對於視網膜(Retina)顯示屏,width和height都會明顯比原輸入值更高一點

咱們還能夠將咱們的函數註冊到其它不少的回調函數中,好比說,咱們能夠建立一個回調函數來處理手柄輸入變化,處理錯誤消息等,咱們會在建立窗口以後,渲染循環初始化以前註冊這些回調函數

渲染循環

咱們不但願只繪製一個圖像以後咱們的應用程序就當即退出並關閉窗口,而是但願程序在咱們主動關閉它以前不斷繪製圖像並可以接受用戶輸入,所以,咱們須要在程序中添加一個while循環,咱們能夠把它稱之爲渲染循環(Render Loop),它能在咱們讓GLFW退出前一直保持運行,下面幾行的代碼就實現了一個簡單的渲染循環:

while(!glfwWindowShouldClose(window))
{
    glfwSwapBuffers(window);
    glfwPollEvents();    
}
  • glfwWindowShouldClose函數在咱們每次循環的開始前檢查一次GLFW是否被要求退出,若是是的話該函數返回 true 而後渲染循環便結束了,以後爲咱們就能夠關閉應用程序了
  • glfwPollEvents函數檢查有沒有觸發什麼事件(好比鍵盤輸入、鼠標移動等)、更新窗口狀態,並調用對應的回調函數(能夠經過回調方法手動設置)
  • glfwSwapBuffers函數會交換顏色緩衝(它是一個儲存着GLFW窗口每個像素顏色值的大緩衝),它在這一迭代中被用來繪製,而且將會做爲輸出顯示在屏幕上

雙緩衝(Double Buffer)

應用程序使用單緩衝繪圖時可能會存在圖像閃爍的問題。 這是由於生成的圖像不是一會兒被繪製出來的,而是按照從左到右,由上而下逐像素地繪製而成的。最終圖像不是在瞬間顯示給用戶,而是經過一步一步生成的,這會致使渲染的結果很不真實。爲了規避這些問題,咱們應用雙緩衝渲染窗口應用程序。緩衝保存着最終輸出的圖像,它會在屏幕上顯示;而全部的的渲染指令都會在緩衝上繪製。當全部的渲染指令執行完畢後,咱們交換(Swap)前緩衝和後緩衝,這樣圖像就當即呈顯出來,以前提到的不真實感就消除了。

最後一件事

當渲染循環結束後咱們須要正確釋放/刪除以前的分配的全部資源,咱們能夠在main函數的最後調用glfwTerminate()來完成。

glfwTerminate();
return 0;

這樣便能清理全部的資源並正確地退出應用程序

如今咱們的代碼是這樣的

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }    


    while (!glfwWindowShouldClose(window))
    {
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

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

如今你能夠嘗試編譯並運行咱們的應用程序了,若是沒作錯的話,你將會看到以下的輸出:

一個很是無聊的黑色窗口,這就對了!


輸入

咱們一樣也但願可以在GLFW中實現一些輸入控制,這能夠經過使用GLFW的幾個輸入函數來完成。咱們將會使用GLFW的glfwGetKey函數,它須要一個窗口以及一個按鍵做爲輸入,這個函數將會返回這個按鍵是否正在被按下,咱們將建立一個processInput函數來讓全部的輸入代碼保持整潔

void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//是否按下了返回鍵
        glfwSetWindowShouldClose(window, true);
}

這裏咱們檢查用戶是否按下了返回鍵(Esc)(若是沒有按下,glfwGetKey 將會返回 GLFW_RELEASE,若是用戶的確按下了返回鍵,咱們將經過 glfwSetwindowShouldClose 使用把 WindowShouldClose 屬性設置爲 true 的方法關閉GLFW,下一次while循環的條件檢測將會失敗,程序將會關閉

咱們接下來在渲染循環的每個迭代中加入 processInput 的調用:

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

    glfwSwapBuffers(window);
    glfwPollEvents();
}

這就給咱們一個很是簡單的方式來檢測特定的鍵是否被按下,並在每一幀作出處理

渲染

咱們要把全部的渲染(Rendering)操做放到渲染循環中,由於咱們想讓這些渲染指令在每次渲染循環迭代的時候都能被執行,代碼將會是這樣的:

爲了測試一切都正常工做,咱們使用一個自定義的顏色清空屏幕

在每一個新的渲染迭代開始的時候咱們老是但願清屏,不然咱們仍能看見上一次迭代的渲染結果(這多是你想要的效果,但一般這不是)

咱們能夠經過調用glClear函數來清空屏幕的顏色緩衝,它接受一個緩衝位(Buffer Bit)來指定要清空的緩衝,可能的緩衝位有 GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT 和 GL_STENCIL_BUFFER_BIT

因爲如今咱們只關心顏色值,因此咱們只清空顏色緩衝 GL_COLOR_BUFFER_BIT,講下述代碼加入到渲染循環的渲染指令部分

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

注意,除了glClear以外,咱們還調用了glClearColor來設置清空屏幕所用的顏色,當調用glClear函數,清除顏色緩衝以後,整個顏色緩衝都會被填充爲glClearColor裏所設置的顏色

如今個人代碼是這樣的:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }


    // 渲染循環
    while (!glfwWindowShouldClose(window))
    {
        // 輸入
        processInput(window);

        // 渲染指令
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // 檢查並調用事件,交換緩衝
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 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)//是否按下了返回鍵
        glfwSetWindowShouldClose(window, true);
}

編譯運行:

咱們將屏幕設置爲了相似黑板的深藍綠色,而且按Esc纔會結束運行

glClearColor函數是一個狀態設置函數,而glClear函數則是一個狀態使用的函數,它使用了當前的狀態來獲取應該清除爲的顏色。

至此,如今咱們已經作好開始在渲染循環中添加許多渲染調用的準備了

參考:教程源代碼

相關文章
相關標籤/搜索