opengl基礎

opengl

opengl是一個由Khronos組織制定並維護的規範(Specification) 。是一系列的圖形軟件編程接口,和gdi相似。opengl有不少封裝的庫最有名的GLFW庫。接下來不少東西以GLFW 爲例子來講明一些api的使用問題,但這並不影響opengl自己的邏輯表述。ios

狀態機

OpenGL自身是一個巨大的狀態機(State Machine):一系列的變量描述OpenGL此刻應當如何運行。OpenGL的狀態一般被稱爲OpenGL上下文(Context)。咱們一般使用以下途徑去更改OpenGL狀態:設置選項,操做緩衝。最後,咱們使用當前OpenGL上下文來渲染。編程

假設當咱們想告訴OpenGL去畫線段而不是三角形的時候,咱們經過改變一些上下文變量來改變OpenGL狀態,從而告訴OpenGL如何去繪圖。一旦咱們改變了OpenGL的狀態爲繪製線段,下一個繪製命令就會畫出線段而不是三角形。api

當使用OpenGL的時候,咱們會遇到一些狀態設置函數(State-changing Function),這類函數將會改變上下文。以及狀態使用函數(State-using Function),這類函數會根據當前OpenGL的狀態執行一些操做。只要你記住OpenGL本質上是個大狀態機,就能更容易理解它的大部分特性。函數

對象

在OpenGL中一個對象是指一些選項的集合,它表明OpenGL狀態的一個子集 。oop

當咱們使用一個對象時,一般看起來像以下同樣:線程

// OpenGL的狀態
struct OpenGL_Context {
    ...
    object* object_Window_Target;
    ...     
};
// 建立對象
unsigned int objectId = 0;
glGenObject(1, &objectId);
// 綁定對象至上下文
glBindObject(GL_WINDOW_TARGET, objectId);
// 設置當前綁定到 GL_WINDOW_TARGET 的對象的一些選項
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// 將上下文對象設回默認
glBindObject(GL_WINDOW_TARGET, 0);

這一段代碼展示了使用OpenGL時常見的工做流。咱們首先建立一個對象,而後用一個id保存它的引用(實際數據被儲存在後臺)。而後咱們將對象綁定至上下文的目標位置(例子中窗口對象目標的位置被定義成GL_WINDOW_TARGET)。接下來咱們設置窗口的選項。最後咱們將目標位置的對象id設回0,解綁這個對象。設置的選項將被保存在objectId所引用的對象中,一旦咱們從新綁定這個對象到GL_WINDOW_TARGET位置,這些選項就會從新生效。code

程序結構

咱們要開始一個圖形渲染程序,首要是要選擇gl庫,由於要使用api.而後建立窗口、設置視口、設置窗口大小調整後的回調在回調中要處理視口、接着是渲染循環、還要處理處輸入等。對象

實例化配置glfw
int main()
{
    glfwInit();  //初始化glfw
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //配置glfw
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    return 0;
}

首先,咱們在main函數中調用glfwInit函數來初始化GLFW,而後咱們可使用glfwWindowHint函數來配置GLFW。glfwWindowHint函數的第一個參數表明選項的名稱,咱們能夠從不少以GLFW_開頭的枚舉值中選擇;第二個參數接受一個整形,用來設置這個選項的值接口

建立窗口

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

GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

glfwCreateWindow函數須要窗口的寬和高做爲它的前兩個參數。第三個參數表示這個窗口的名稱(標題),。這個函數將會返回一個GLFWwindow對象,建立完窗口咱們就能夠通知GLFW將咱們窗口的上下文設置爲當前線程的主上下文了。

視口

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

glViewport(0, 0, 800, 600);

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

OpenGL幕後使用glViewport中定義的位置和寬高進行2D座標的轉換,將OpenGL中的位置座標轉換爲你的屏幕座標,處理過的OpenGL座標範圍只爲-1到1。

當窗口大小發生變換的時候須要設置視口的大小:

glfwSetFramebufferSizeCallback(window, [](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);
}
渲染循環

須要在程序中添加一個while循環,咱們能夠把它稱之爲渲染循環(Render Loop),它能在咱們讓GLFW退出前一直保持運行。下面幾行的代碼就實現了一個簡單的渲染循環:

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

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

    // 檢查並調用事件,交換緩衝
    glfwPollEvents();
    glfwSwapBuffers(window);
}
glfwTerminate();
  • glfwWindowShouldClose函數在咱們每次循環的開始前檢查一次GLFW是否被要求退出,若是是的話該函數返回true而後渲染循環便結束了,以後爲咱們就能夠關閉應用程序了。
  • glfwPollEvents函數檢查有沒有觸發什麼事件(好比鍵盤輸入、鼠標移動等)、更新窗口狀態,並調用對應的回調函數(能夠經過回調方法手動設置)。
  • glfwSwapBuffers函數會交換顏色緩衝(它是一個儲存着GLFW窗口每個像素顏色值的大緩衝),它在這一迭代中被用來繪製,而且將會做爲輸出顯示在屏幕上。
  • glClear函數來清空屏幕的顏色緩衝,它接受一個緩衝位(Buffer Bit)來指定要清空的緩衝,可能的緩衝位有GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT。
  • 調用了glClearColor來設置清空屏幕所用的顏色
  • glClearColor函數是一個狀態設置函數,而glClear函數則是一個狀態使用的函數,它使用了當前的狀態來獲取應該清除爲的顏色。
  • glfwTerminate(); 釋放全部申請的資源
雙緩衝(Double Buffer)

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

最終的程序結構

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

#include <iostream>

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

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    //glfw初始化和設置
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    //建立窗口配置窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        glfwTerminate();
        return -1;
    }
    //設置問當前窗口上下文
    glfwMakeContextCurrent(window);
    //設置窗口大小改變的回調 處理視口變化
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // render loop
    while (!glfwWindowShouldClose(window))
    {
        // input
        processInput(window);

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

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    glfwTerminate();
    return 0;
}

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

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}
相關文章
相關標籤/搜索