第三節:OpenGL框架實現
首先需要在實現程式碼的語句最前面包含如下程式碼:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
Note: 確認在包含GLFW的標頭檔案之前包含了GLAD的標頭檔案。GLAD的標頭檔案包含了正確的OpenGL標頭檔案(例如GL/gl.h),所以需要在其他依賴於OpenGL的標頭檔案之前包含GLAD;
建立視窗部分:
GLFWwindow* initWindow(GLuint vMajVersion, GLuint vMinVersion, const std::string& vWindTile, GLuint vScrWidth, GLuint vScrHeight) { _ASSERT(vMajVersion > 0 && vMajVersion <= 4 && vMinVersion > 0 && vMajVersion <= 5 && vScrWidth > 0 && vScrHeight > 0); glfwInit();//初始化GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, vMajVersion);//設定主版本 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, vMinVersion);//設定次版本 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//使用OpenGL核心模式 #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);//在蘋果上執行時使用 #endif GLFWwindow* pWindow = glfwCreateWindow(vScrWidth, vScrHeight, vWindTile.c_str(), nullptr, nullptr); if (pWindow == nullptr) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate();//如果建立視窗失敗,則結束執行 return nullptr; } glfwMakeContextCurrent(pWindow);//使用當前視窗作為上下文1 //設定滑鼠運動,視窗改變,滑鼠縮放的回撥函式 glfwSetCursorPosCallback(pWindow, mouse_callback); glfwSetFramebufferSizeCallback(pWindow, framebuffer_size_callback); glfwSetScrollCallback(pWindow, scroll_callback); glfwSetInputMode(pWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); //Glad是用來管理OpenGL的函式指標的,在任何呼叫OpenGL的函式之前我們需要初始化GlAD if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return nullptr; } return pWindow; }
視口:
在開始之前還有一件重要的事情要做,我們必須告訴OpenGL渲染視窗的尺寸和大小,即視口(Viewport),這樣OpenGL才能知道怎樣根據視窗大小顯示資料和座標。
可以通過glViewport函式來設定視窗的維度:前兩個引數用來控制視窗左下角的位置,後兩個引數用來控制渲染視窗的寬度和高度;
glViewport(LeftDownX, LeftDownY, Width, Height);
渲染視窗Viewport和視窗大小不是同一個概念;渲染視窗是OpenGL渲染作用區域;實際上也可以將視口的維度設定為比GLFW的維度小,這樣子所有的OpenGL渲染將會在一個更小的視窗中顯示。
OpenGL幕後使用glViewport中定義的位置和寬高進行2D座標的轉換,將OpenGL中的位置座標轉換為螢幕座標。處理過的OpenGL座標範圍只為-1到1,因此我們事實上將(-1到1)範圍內的座標對映到(LeftDownX, Width + LeftDownX)和(LeftDownY LeftDownY + Height);
視窗回撥函式:
視窗改變回調函式:內容可自定義
void framebuffer_size_callback(GLFWwindow* vWindow, int vWidth, int vHeight) { // make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays. _ASSERT(vWidth > 0 && vHeight > 0); _ASSERT(vWindow != nullptr); glViewport(0, 0, vWidth, vHeight); }
滑鼠移動回撥函式:內容可自定義,一般可用來改變相機方向
void mouse_callback(GLFWwindow* pWindow, double vXPos, double vYPos)
{
if (IsFirstMouse)
{
LastX = (float)vXPos;
LastY = (float)vYPos;
IsFirstMouse = false;
}
float XOffset = float(vXPos - LastX);
float YOffset = float(LastY - vYPos); // reversed since y-coordinates go from bottom to top
LastX = (float)vXPos;
LastY = (float)vYPos;
Camera.processMouseMovement(XOffset, YOffset);
}
滑鼠縮放函式:內容可自定義,一般用來縮放視野遠近
void scroll_callback(GLFWwindow* pWindow, double vXOffset, double vYOffset)
{
Camera.processMouseScroll((float)vYOffset);
}
渲染迴圈:
我們不希望只繪製一幀影象之後應用程式就立即退出並關閉視窗,希望程式在我們主動關閉它之前不斷繪製圖像並能夠接受使用者輸入。因此需要在程式中新增渲染迴圈。
glfwWindowShouldClose函式在每次迴圈開始前檢查一次GLFW是否被要求退出。
glfwPollEvents函式檢查有沒有觸發什麼事件(比如鍵盤輸入,滑鼠移動等),更新視窗狀態,並呼叫對應得回到函式。
glfwSwapBuffers函式會交換顏色緩衝;主要是由於建立的視窗為雙緩衝。如果使用單緩衝可能會存在影象閃爍的問題,這是因為生成的影象是按照從左到右,從上而下逐畫素地繪製而成的。前緩衝儲存最終輸出的影象,它會在螢幕上顯示。而所有的渲染指令都會在後緩衝上繪製。當所有指令執行完畢後,交換前後緩衝,這樣影象就立即呈現出來,消除了不真實感。
while (!glfwWindowShouldClose(pWindow))
{
//清空上一幀緩衝區,可能包括顏色,深度,模板緩衝
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(CL_COLOR_BUFFER_BIT);//此處清空顏色緩衝區
//處理輸入
processInput(pWindow);
//此處執行渲染指令
//檢查並呼叫事件,交換緩衝
glfwPollEvents();
glfwSwapBuffers(pWindow);
}
void processInput(GLFWwindow *pWindow)
{
//按下ESC,將會關閉視窗
if (glfwGetKey(pWindow, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(pWindow, true);
if (glfwGetKey(pWindow, GLFW_KEY_W) == GLFW_PRESS)
Camera.processKeyboard(FORWARD, DeltaTime);
if (glfwGetKey(pWindow, GLFW_KEY_S) == GLFW_PRESS)
Camera.processKeyboard(BACKWARD, DeltaTime);
if (glfwGetKey(pWindow, GLFW_KEY_A) == GLFW_PRESS)
Camera.processKeyboard(LEFT, DeltaTime);
if (glfwGetKey(pWindow, GLFW_KEY_D) == GLFW_PRESS)
Camera.processKeyboard(RIGHT, DeltaTime);
}
結束渲染:
結束渲染迴圈後需要正確釋放/刪除之前分配的所有資源,可以在main函式的最後呼叫glfwTerminate函式完成。
glfwTerminate();