學習路線?
學習 OpenGL 需要掌握一系列基礎知識和技能,這些內容涵蓋了計算機圖形學的基本概念、編程語言、數學知識以及 OpenGL 的具體 API 使用。以下是學習 OpenGL 所需的主要知識點:
1.?計算機圖形學基礎
-
圖形學概念:了解圖形學的基本概念,如像素、分辨率、顏色模型(RGB、RGBA)、光柵化、光柵圖形等。
-
圖形管線:理解圖形渲染管線的工作原理,包括頂點處理、光柵化、片段處理等階段。
-
圖形對象:熟悉常見的圖形對象,如點、線、多邊形、紋理、光照等。
2.?數學基礎
-
線性代數:
-
向量:向量的基本運算(加法、減法、點積、叉積)。
-
矩陣:矩陣的基本運算(乘法、逆矩陣、轉置)。
-
變換:平移、旋轉、縮放等變換的矩陣表示。
-
-
幾何知識:
-
坐標系:理解世界坐標系、物體坐標系、視圖坐標系、屏幕坐標系等。
-
投影:正交投影和透視投影的概念及實現。
-
-
光柵化算法:
-
直線繪制算法:如 Bresenham 算法。
-
多邊形填充算法:如掃描線填充算法。
-
3.?編程語言
-
C/C++:OpenGL 主要使用 C/C++ 進行編程,因此需要熟練掌握 C/C++ 的基本語法和編程技巧。
-
GLSL(OpenGL Shading Language):用于編寫著色器程序,包括頂點著色器、片段著色器等。
4.?OpenGL API
-
OpenGL 基礎:
-
初始化:創建窗口、初始化 OpenGL 上下文(可以使用 GLFW 等庫)。
-
狀態管理:了解 OpenGL 的狀態機模型,如何設置和查詢狀態。
-
緩沖區對象:如頂點緩沖區對象(VBO)、頂點數組對象(VAO)、幀緩沖區對象(FBO)等。
-
-
著色器編程:
-
著色器類型:頂點著色器、片段著色器、幾何著色器等。
-
著色器程序:編寫、編譯、鏈接著色器程序。
-
變量傳遞:如何將數據從 CPU 傳遞到 GPU(如 uniform 變量、attribute 變量)。
-
-
渲染技術:
-
基本渲染:繪制點、線、三角形等基本圖形。
-
紋理映射:加載和應用紋理,理解紋理坐標、紋理過濾、紋理環繞等概念。
-
光照:理解光照模型(如 Phong 照明模型),實現平行光、點光源、環境光等。
-
陰影:實現陰影映射等高級光照效果。
-
-
高級技術:
-
曲面細分:使用曲面細分著色器生成更平滑的曲面。
-
計算著色器:用于通用計算任務。
-
多視口渲染:在多個視口同時渲染不同的場景。
-
后處理效果:如模糊、HDR、抗鋸齒等。
-
5.?輔助工具和庫
-
GLFW:用于創建窗口和處理輸入。
-
GLAD:用于加載 OpenGL 函數指針。
-
GLM(OpenGL Mathematics):用于處理數學運算,如向量和矩陣運算。
-
FreeGLUT:用于創建窗口和處理輸入(與 GLFW 類似)。
-
Assimp:用于加載 3D 模型文件。
-
SOIL:用于加載紋理文件。
6.?調試和優化
-
調試工具:使用調試工具(如 RenderDoc、gDEBugger)來調試 OpenGL 程序。
-
性能優化:了解常見的性能瓶頸(如 CPU/GPU 瓶頸、內存帶寬瓶頸)和優化方法。
7.?實踐項目
-
小型項目:從簡單的項目開始,如繪制一個旋轉的立方體。
-
復雜項目:逐步實現更復雜的場景,如加載 3D 模型、實現簡單的游戲引擎等。
學習資源推薦
-
書籍:
-
《OpenGL SuperBible》:適合初學者和進階學習者。
-
《OpenGL Programming Guide》(紅寶書):經典教材。
-
-
在線教程:
-
LearnOpenGL:非常詳細的 OpenGL 教程,適合初學者。
-
OpenGL Tutorial:包含豐富的示例和代碼。
-
-
視頻教程:
-
YouTube 上有許多 OpenGL 教程,如 The Cherno 的 OpenGL 系列。
-
初試OpenGL
1.首先需要編譯glfw-3.4的動態庫,和glad庫,使用vs2021進行程序編寫。
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的頭文件。
#include "GLFW/glfw3.h"using namespace std;
/*
* 創建glfw的窗體系統
*/
int main(int argc,char**argv)
{cout << "===================================" << endl;//1. 初始化GLFW基本環境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//啟用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 創建窗體對象GLFWwindow* win = glfwCreateWindow(800, 600, "OpenGlStudy", NULL, NULL);//設置窗體對象為Opengl的繪制舞臺glfwMakeContextCurrent(win);// // 3. 執行窗體循環// while (!glfwWindowShouldClose(win)){//接受并且分發窗體消息//檢查消息隊列是否有需要處理的鼠標,鍵盤等信息//如果有的話就將消息批量處理,清空隊列。glfwPollEvents();}// 4. 退出程序前做相關清理glfwTerminate();cout << "===================================" << endl;return 0;
}
?CMakeLists.txt
cmake_minimum_required(VERSION 3.12)project(OpenGL_Lecture)set(CMAKE_CXX_STANDARD 11)include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/thrirdParty/include
)
link_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/thrirdParty/lib)
add_executable(openglStudy "main.cpp" "glad.c")target_link_libraries(openglStudy glfw3.lib)
?事件響應
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的頭文件。
#include "GLFW/glfw3.h"using namespace std;
/*
* 創建glfw的窗體系統
* 加入窗體變化的事件回調
*/void frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{std::cout << "窗體的最新大小為:" << width << "高度為:" << height << std::endl;}void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){// key 字母按鍵碼 // scancode 物理按鍵碼 // action:0抬起1按下2長按// mods:是否有shift(1)或ctrl(2) cout << "key = " << key << " scancode = " << scancode << " action = " << action << " mods = " << mods << endl;if (key == GLFW_KEY_W){//按下了w}else if (action == GLFW_PRESS){//按下了}else if (action == GLFW_RELEASE){//抬起}}int main(int argc,char**argv)
{cout << "===================================" << endl;//1. 初始化GLFW基本環境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//啟用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 創建窗體對象GLFWwindow* win = glfwCreateWindow(800, 600, "OpenGlStudy", NULL, NULL);//設置窗體對象為Opengl的繪制舞臺glfwMakeContextCurrent(win);//設置監聽幀緩沖窗口大小回調函數。glfwSetFramebufferSizeCallback(win, frameBufferSizeCallback);glfwSetKeyCallback(win, keyCallback);// // 3. 執行窗體循環// while (!glfwWindowShouldClose(win)){//接受并且分發窗體消息//檢查消息隊列是否有需要處理的鼠標,鍵盤等信息//如果有的話就將消息批量處理,清空隊列。glfwPollEvents();}// 4. 退出程序前做相關清理glfwTerminate();cout << "===================================" << endl;return 0;
}
函數加載
Opengl運行環境是一個巨大的狀態機,每一個函數都會改變狀態機的狀態或者觸發其執行某個行為。
OpenGL采用雙緩沖機制 ,利用兩個緩沖區進行交替展示。
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的頭文件。
#include "GLFW/glfw3.h"using namespace std;
/*
* 創建glfw的窗體系統
* 加入窗體變化的事件回調
*/void frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{std::cout << "窗體的最新大小為:" << width << "高度為:" << height << std::endl;//更新窗體的大小glViewport(0, 0, width, height);}void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){// key 字母按鍵碼 // scancode 物理按鍵碼 // action:0抬起1按下2長按// mods:是否有shift(1)或ctrl(2) cout << "key = " << key << " scancode = " << scancode << " action = " << action << " mods = " << mods << endl;if (key == GLFW_KEY_W){//按下了w}else if (action == GLFW_PRESS){//按下了}else if (action == GLFW_RELEASE){//抬起}}int main(int argc,char**argv)
{cout << "===================================" << endl;//1. 初始化GLFW基本環境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//啟用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 創建窗體對象GLFWwindow* win = glfwCreateWindow(800, 600, "OpenGlStudy", NULL, NULL);//設置窗體對象為Opengl的繪制舞臺glfwMakeContextCurrent(win);//設置監聽幀緩沖窗口大小回調函數。glfwSetFramebufferSizeCallback(win, frameBufferSizeCallback);glfwSetKeyCallback(win, keyCallback);// 使用glad加載所有當前版本的OpenGL函數,加載后才能使用if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) ) {cout << "Failed to initialize glad " << endl;}//設置OpenGL視口以及清理顏色glViewport(0,0,800,600);glClearColor(0.2f,0.3f,0.3f,1.0f);// 3. 執行窗體循環// while (!glfwWindowShouldClose(win)){//接受并且分發窗體消息glfwPollEvents();glClear(GL_COLOR_BUFFER_BIT);//渲染操作// //切換雙緩沖glfwSwapBuffers(win);}// 4. 退出程序前做相關清理glfwTerminate();cout << "===================================" << endl;return 0;
}
OpenGL函數錯誤處理
main.cpp
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的頭文件。
#include "GLFW/glfw3.h"
#include <assert.h>
#include "wrapper/checkerror.h"using namespace std;/*
* 創建glfw的窗體系統
* 加入窗體變化的事件回調
*/void frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{std::cout << "窗體的最新大小為:" << width << "高度為:" << height << std::endl;//更新窗體的大小glViewport(0, 0, width, height);}void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){// key 字母按鍵碼 // scancode 物理按鍵碼 // action:0抬起1按下2長按// mods:是否有shift(1)或ctrl(2) cout << "key = " << key << " scancode = " << scancode << " action = " << action << " mods = " << mods << endl;if (key == GLFW_KEY_W){//按下了w}else if (action == GLFW_PRESS){//按下了}else if (action == GLFW_RELEASE){//抬起}}int main(int argc,char**argv)
{cout << "===================================" << endl;//1. 初始化GLFW基本環境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//啟用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 創建窗體對象GLFWwindow* win = glfwCreateWindow(800, 600, "OpenGlStudy", NULL, NULL);//設置窗體對象為Opengl的繪制舞臺glfwMakeContextCurrent(win);//設置監聽幀緩沖窗口大小回調函數。glfwSetFramebufferSizeCallback(win, frameBufferSizeCallback);glfwSetKeyCallback(win, keyCallback);// 使用glad加載所有當前版本的OpenGL函數,加載后才能使用if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) ) {cout << "Failed to initialize glad " << endl;}//設置OpenGL視口以及清理顏色glViewport(0,0,800,600);glClearColor(0.2f,0.3f,0.3f,1.0f);// 3. 執行窗體循環// while (!glfwWindowShouldClose(win)){//接受并且分發窗體消息glfwPollEvents();GL_CALL( glClear(GL_COLOR_BUFFER_BIT) );//glClear(-1);//設置錯誤顏色不會崩潰,會出現黑色//checkError();//GL_CALL(glClear(-1) ); //渲染操作// //切換雙緩沖glfwSwapBuffers(win);}// 4. 退出程序前做相關清理glfwTerminate();cout << "===================================" << endl;return 0;
}
wrapper/checkerror.cpp
頭文件checkerror.h就是void checkError();
#include "checkerror.h"
#include "glad/glad.h"
#include <string>
#include <iostream>
#include <assert.h>using namespace std;void checkError()
{GLenum errornum = glGetError();//cout << "errornum = " << errornum << endl; //錯誤碼1281 GL_INVALID_VALUEstd::string errorstr = "";if (errornum != GL_NO_ERROR){cout << "GL有錯誤" << endl;switch (errornum){case GL_INVALID_ENUM:errorstr = "無效的枚舉 GL_INVALID_ENUM"; break;case GL_INVALID_VALUE:errorstr = "無效的值 GL_INVALID_VALUE"; break;case GL_INVALID_OPERATION:errorstr = "無效的操作 GL_INVALID_OPERATION"; break;case GL_OUT_OF_MEMORY:errorstr = "無效的內存 GL_OUT_OF_MEMORY"; break;default:errorstr = "UNKNOWN";break;}cout << "errorstr = " << errorstr << endl;assert(false); //斷言根據bool決定是否停止程序。}
}
./CMakeLists.txt
cmake_minimum_required(VERSION 3.12)project(OpenGL_Lecture)set(CMAKE_CXX_STANDARD 11)include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/thrirdParty/include
)
link_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/thrirdParty/lib)add_subdirectory(wrapper)#在項目夾加入全局的預編譯宏
add_definitions(-DCHECK_ERROR)add_executable(openglStudy "main.cpp" "glad.c" )target_link_libraries(openglStudy glfw3.lib wrapper)
wrapper/CMakeLists.txt
#遞歸將本文件夾下的所有cpp放到WRAPPER中file(GLOB_RECURSE WRAPPER ./ *.cpp)#將所有cpp文件鏈接到這個wrapper lib庫中。 默認生成靜態庫,動態庫編譯不過去
add_library(wrapper ${WRAPPER})
Application封裝
?application.cpp
#include "application.h"
#include "application.h"
#include "application.h"#include "glad/glad.h" //需要先引用glad的頭文件。 用于加載 OpenGL 函數指針
#include "GLFW/glfw3.h" // 用于創建窗口和處理輸入。 Application* Application::m_app = nullptr;
Application* Application::getInst()
{if (m_app == nullptr){m_app = new Application();}return m_app;
}int Application::init(const int width, const int height)
{cout << "Application::init" << endl;m_Width = width;m_Height = height;//1. 初始化GLFW基本環境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//啟用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 創建窗體對象m_Window = glfwCreateWindow(m_Width, m_Height, "OpenGlStudy", NULL, NULL);if (m_Window == NULL){return -1;}//設置窗體對象為Opengl的繪制舞臺glfwMakeContextCurrent(m_Window);// 使用glad加載所有當前版本的OpenGL函數,加載后才能使用if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){cout << "Failed to initialize glad " << endl;return -1;}//GLFW監聽必須要使用static進行聲明。//窗口大小改變的事件glfwSetFramebufferSizeCallback(m_Window, frameBufferSizeCallback);//鍵盤的事件glfwSetKeyCallback(m_Window, keyCallback);//this就是全局Application對象glfwSetWindowUserPointer(m_Window, this);//將this暫時存在窗體中。return 0;
}int Application::update()
{if (glfwWindowShouldClose(m_Window)){return -1;}//接受并且分發窗體消息glfwPollEvents();//切換雙緩沖glfwSwapBuffers(m_Window);return 0;
}int Application::destroy()
{cout << "Application::destroy" << endl;// 4. 退出程序前做相關清理glfwTerminate();return 0;
}Application::Application()
{}void Application::frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{//因為是靜態函數,所以不能調用全局唯一實例。// 靜態成員函數屬于類本身,而不是類的某個具體對象。//它不依賴于任何對象實例,因此不能訪問非靜態成員變量//(因為非靜態成員變量需要對象實例來存儲其值)。cout << "frameBufferSizeCallback " << endl;//m_resizeCallBack();// //面試題:如何在靜態成員函數中調用非靜態函數?//if(Application::getInst()->m_resizeCallBack != nullptr)// Application::getInst()->m_resizeCallBack(width,height);Application* self = (Application*) glfwGetWindowUserPointer(win);if(self->m_resizeCallBack != nullptr)self->m_resizeCallBack(width, height);}void Application::keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{cout << "keyCallback " << endl;Application* self = (Application*)glfwGetWindowUserPointer(window);if (self->m_keyCallBack != nullptr)self->m_keyCallBack(window, key,scancode,action,mods);}Application::~Application()
{}
application.h
#pragma once#include <iostream>#define app Application::getInst()class GLFWwindow; //提前聲明class,避免頭文件重復定義的問題.using resizeCallBack = void(*)(int width,int height);
using keyCallBack = void(*)(GLFWwindow* window,int key, int scancode, int action, int mods);using namespace std;//單例類實現class Application
{
public:~Application();static Application* getInst();void test() { cout << "test" << endl; };uint32_t getWidth() const { return m_Width; }; //const含義:不改變類中的成員uint32_t getHeight() const { return m_Height; };int init(const int width = 800, const int height = 500);int update();int destroy(); void setResizeCallBack(resizeCallBack call) { m_resizeCallBack = call; };void setkeyCallBack(keyCallBack call) { m_keyCallBack = call; };private:Application();static Application* m_app;uint32_t m_Width{ 0 }; //列表初始化uint32_t m_Height{ 0 };GLFWwindow* m_Window{ nullptr };resizeCallBack m_resizeCallBack{nullptr};keyCallBack m_keyCallBack{ nullptr };private:static void frameBufferSizeCallback(GLFWwindow*win,int width,int height);static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);};
main.cpp
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的頭文件。 用于加載 OpenGL 函數指針
#include "GLFW/glfw3.h" // 用于創建窗口和處理輸入。
#include <assert.h>
#include "wrapper/checkerror.h"
#include "application/application.h"using namespace std;/*
* 創建glfw的窗體系統
* 加入窗體變化的事件回調
*/void onResize(int width,int height)
{GL_CALL(glViewport(0, 0, width, height));cout << "onResize " << endl;
}void frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{std::cout << "窗體的最新大小為:" << width << "高度為:" << height << std::endl;//更新窗體的大小glViewport(0, 0, width, height);}void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){// key 字母按鍵碼 // scancode 物理按鍵碼 // action:0抬起1按下2長按// mods:是否有shift(1)或ctrl(2) cout << "key = " << key << " scancode = " << scancode << " action = " << action << " mods = " << mods << endl;if (key == GLFW_KEY_W){//按下了w}else if (action == GLFW_PRESS){//按下了}else if (action == GLFW_RELEASE){//抬起}}int main(int argc,char**argv)
{cout << "===================================" << endl;if (app->init(800,600) == -1){return -1;}//設置監聽幀緩沖窗口大小回調函數。//glfwSetFramebufferSizeCallback(win, frameBufferSizeCallback);//glfwSetKeyCallback(win, keyCallback);app->setResizeCallBack(onResize);app->setkeyCallBack(keyCallback);//設置OpenGL視口以及清理顏色glViewport(0,0,800,600);glClearColor(0.2f,0.3f,0.3f,1.0f);// 3. 執行窗體循環// while (app->update() == 0 ){GL_CALL( glClear(GL_COLOR_BUFFER_BIT) );//渲染操作}// 4. 退出程序前做相關清理app->destroy();cout << "===================================" << endl;return 0;
}