1. OpenGL圖像管線
OpenGL(Open Graphics Library)是一個跨平臺的、功能強大的圖形渲染API,用于開發2D和3D圖形應用程序。它由Khronos Group維護,廣泛應用于游戲開發、圖形設計、虛擬現實等領域。
1.0.1. OpenGL的特點:
- 跨平臺:支持Windows、macOS、Linux等多個操作系統。 注:macOS上對OpenGL的支持不如Windows。
- 硬件加速:利用GPU進行高效的圖形渲染。
- 開放標準:由Khronos Group管理,提供靈活的擴展機制。
- 實時渲染:適用于需要高性能圖形渲染的應用。
1.0.2. OpenGL的主要功能:
- 圖形繪制:支持點、線、三角形等基本圖元的繪制。
- 著色器支持:通過GLSL(OpenGL Shading Language)實現自定義的頂點和片段著色器。
- 紋理映射:加載和應用紋理以增強圖形的視覺效果。
- 變換與投影:支持模型變換、視圖變換和投影變換。
- 光照與陰影:實現逼真的光照和陰影效果。
OpenGL的靈活性和高性能使其成為圖形開發的核心工具之一。
2. OpenGL 與 Direct3D 開發語言上的對比
特性 | OpenGL | Direct3D |
---|---|---|
語言支持 | 支持多種語言,包括 C、C++、Python、Java 等 | 主要支持 C 和 C++,部分支持 .NET 語言 |
跨平臺性 | 跨平臺,支持 Windows、macOS、Linux 等 | 僅支持 Windows 和 Xbox 平臺 |
API 風格 | 面向過程的函數調用,簡單直接 | 面向對象的設計,使用 COM 接口 |
著色器語言 | 使用 GLSL(OpenGL Shading Language) | 使用 HLSL(High-Level Shading Language) |
開發工具鏈 | 工具鏈靈活,支持多種第三方工具和庫 | 集成在 Visual Studio,工具鏈較為封閉 |
學習曲線 | API 簡單,適合初學者,但高級功能較復雜 | 面向對象設計,初學者需要適應 COM 模型 |
社區支持 | 開源社區活躍,文檔和教程豐富 | 由微軟主導,官方文檔和支持較完善 |
2.1. 總結
- OpenGL 更加靈活,適合跨平臺開發,支持多種語言,適合需要兼容多平臺的項目。
- Direct3D 更適合 Windows 和 Xbox 平臺開發,工具鏈與 Visual Studio 集成,適合微軟生態系統的開發者。
OpenGL 與普通的軟件不同,其是運行在GPU上的。
2.2. 管線概覽
以下是OpenGL圖像管線的主要階段:
在實踐中,OpenGL圖像管線經常涉及的階段:
- 頂點處理:處理頂點數據,包括頂點坐標、顏色、紋理坐標等。
- 光柵化:將頂點數據轉換為片段,進行光柵化處理,生成片段。
- 片段處理:處理片段數據,包括顏色、深度、模板等。
其中,頂點處理和片段處理是核心階段,它們分別負責處理頂點和片段數據。光柵化階段則負責將頂點數據轉換為片段,進行光柵化處理。
我們需要編寫頂點著色器和片段著色器來處理頂點和片段數據,光柵化階段則由OpenGL自動完成。
2.3. 第一個OpenGL程序
我們不采用頂點著色器和片段著色器,而是直接使用OpenGL函數將屏幕繪制為全紅。 以下是運行結果 :
代碼:
/*
* 第2章 OpenGL圖形管線示例程序
* 這個程序演示了OpenGL的基本設置和渲染循環
* 創建一個600x600像素的窗口并將其背景設置為紅色
*/// GLEW是OpenGL的擴展加載庫
#include <GL\glew.h>
// GLFW提供了創建窗口和處理用戶輸入的功能
#include <GLFW\glfw3.h>
// 用于輸出錯誤信息
#include <iostream>using namespace std;/*** 初始化OpenGL的設置* @param window 目標窗口的指針*/
void init(GLFWwindow* window) { }/*** 渲染函數,負責實際的繪制操作* @param window 目標窗口的指針* @param currentTime 當前時間,可用于動畫*/
void display(GLFWwindow* window, double currentTime) {// 設置清除緩沖區時要使用的顏色(紅色)glClearColor(1.0, 0.0, 0.0, 1.0);// 使用上面設置的顏色清除顏色緩沖區glClear(GL_COLOR_BUFFER_BIT);
}/*** 主函數:程序入口點* 負責初始化GLFW和GLEW,創建窗口,并進入渲染循環*/
int main(void) {// 初始化GLFW庫,如果失敗則退出if (!glfwInit()) { exit(EXIT_FAILURE); }// 設置OpenGL版本為4.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);// 創建一個600x600像素的窗口GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter 2 - program 1", NULL, NULL);// 將新創建的窗口設置為當前OpenGL上下文glfwMakeContextCurrent(window);// 初始化GLEW庫,用于管理OpenGL擴展,如果失敗則退出if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }// 啟用垂直同步,防止畫面撕裂glfwSwapInterval(1);// 調用自定義的初始化函數init(window);// 渲染循環:直到窗口被關閉才退出while (!glfwWindowShouldClose(window)) {// 調用顯示函數進行渲染,傳入當前時間用于動畫display(window, glfwGetTime());// 交換前后緩沖區,將渲染結果顯示到屏幕上glfwSwapBuffers(window);// 處理窗口事件(如鍵盤輸入、鼠標移動等)glfwPollEvents();}// 清理資源并退出glfwDestroyWindow(window);glfwTerminate();exit(EXIT_SUCCESS);
}
整個程序的流程如下:
- 初始化GLFW庫,如果失敗則退出。
- 設置OpenGL版本為4.3。
- 創建一個600x600像素的窗口。
- 將新創建的窗口設置為當前OpenGL上下文。
- 初始化GLEW庫,用于管理OpenGL擴展,如果失敗則退出。
- 啟用垂直同步,防止畫面撕裂。
- 調用自定義的初始化函數。
- 渲染循環:直到窗口被關閉才退出。
- 調用顯示函數進行渲染,傳入當前時間用于動畫。
- 交換前后緩沖區,將渲染結果顯示到屏幕上。
- 處理窗口事件(如鍵盤輸入、鼠標移動等)。
- 清理資源并退出。
重點關注:
- init():自定義的初始化函數,負責初始化OpenGL的設置。目前為空
- display():自定義的渲染函數,負責實際的繪制操作。目前將屏幕設置為紅色。該函數會不斷被調用,直到窗口被關閉才退出。
2.4. 常用函數
函數名稱 | 描述 | 示例 |
---|---|---|
glfwInit() | 初始化GLFW庫 | if (!glfwInit()) { exit(EXIT_FAILURE); } |
glfwWindowHint() | 設置窗口提示 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); |
glfwCreateWindow() | 創建一個窗口 | GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter 2 - program 1", NULL, NULL); |
glfwMakeContextCurrent() | 將窗口的上下文設置為當前線程的上下文 | glfwMakeContextCurrent(window); |
glewInit() | 初始化GLEW庫 | if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); } |
glfwSwapInterval() | 設置交換間隔,用于垂直同步 | glfwSwapInterval(1); |
glfwDestroyWindow() | 銷毀窗口 | glfwDestroyWindow(window); |
glfwTerminate() | 終止GLFW庫 | glfwTerminate(); |
exit() | 退出程序 | exit(EXIT_SUCCESS); |
glClearColor() | 設置清空屏幕所用的顏色 | glClearColor(1.0, 0.0, 0.0, 1.0); |
glClear() | 清空屏幕 | glClear(GL_COLOR_BUFFER_BIT); |
glfwSwapBuffers() | 交換前后緩沖區 | glfwSwapBuffers(window); |
glfwPollEvents() | 處理所有待處理的事件 | glfwPollEvents(); |
glfwWindowShouldClose() | 檢查窗口是否應該關閉 | while (!glfwWindowShouldClose(window)) { /* ... */ } |
2.5. 第一個著色器程序
我們通過一個簡單的著色器程序來演示OpenGL的圖形管線。下圖是在屏幕上顯示一個點:
如果控制臺的輸出為亂碼,在程序中添加以下代碼:
system("chcp 65001 > nul"); // 設置為 UTF-8 編碼
我們在上一節的基礎上進行添加,軟件思路:
- 創建、編譯和鏈接著色器程序
- 創建、綁定和配置頂點數組對象
- 調用顯示函數進行渲染
- 調用glPointSize()設置點的大小,由于OpenGL默認的點大小為1.0,只顯示一個點,會看不到效果,所以需要設置一個較大的點大小
- 調用glDrawArrays()繪制點,傳入參數為GL_POINTS,表示繪制點,傳入參數為0,表示從第0個頂點開始繪制,傳入參數為1,表示繪制1個點
GLSL 與普通編碼的過程不同,GLSL編譯發生在C++運行時,實際運行在GPU中,我們不太好觀察其編譯錯誤。因此,我們使用輔助函數來打印編譯錯誤,詳見代碼中的以下函數 :
- checkOpenGLError():檢查OpenGL錯誤
- printShaderLog():打印著色器的編譯錯誤
- printProgramLog():打印著色器程序的鏈接錯誤
頂點著色器代碼
// 指定GLSL版本為4.30
#version 430/*** 頂點著色器的主函數* 負責設置頂點的位置*/
void main(void)
{// 設置頂點位置// vec4參數分別表示:// x=0.0:在x軸上居中// y=0.0:在y軸上居中// z=0.5:在z軸上的深度值// w=1.0:齊次坐標的w分量gl_Position = vec4(0.0, 0.0, 0.5, 1.0);
}
以下是OpenGL頂點著色器中常見的變量,無需定義,可以直接使用:
變量名稱 | 類型 | 描述 |
---|---|---|
gl_Position | vec4 | 輸出的頂點位置,在裁剪空間中定義。 |
gl_PointSize | float | 輸出的點的大小(像素),僅在點圖元時使用。 |
gl_VertexID | int | 輸入的頂點ID,表示當前頂點的索引。 |
gl_InstanceID | int | 輸入的實例ID,表示當前實例的索引(如果啟用了實例化渲染)。 |
gl_PrimitiveID | int | 輸入的圖元ID,表示當前圖元的索引(在幾何著色器中使用)。 |
gl_Layer | int | 輸出的圖層ID,用于分層渲染(在幾何著色器中使用)。 |
gl_ViewportIndex | int | 輸出的視口索引,用于多視口渲染(在幾何著色器中使用)。 |
in 變量 | 自定義類型 | 從頂點緩沖對象(VBO)傳遞的輸入頂點屬性,如位置、顏色、法線等。 |
out 變量 | 自定義類型 | 傳遞到片段著色器的輸出變量,如顏色、紋理坐標等。 |
2.5.1. 說明
in
和out
變量的具體類型和名稱由開發者根據需求定義。gl_
前綴的變量是OpenGL內置的變量,具有特定的用途。
片段著色器代碼
// 指定GLSL版本為4.30
#version 430// 定義輸出變量color,表示最終的像素顏色
out vec4 color;/*** 片段著色器的主函數* 負責設置每個片段(像素)的顏色*/
void main(void)
{// 設置輸出顏色// vec4參數分別表示:// r=0.0:紅色分量為0// g=0.0:綠色分量為0// b=1.0:藍色分量為1(純藍色)// a=1.0:完全不透明color = vec4(0.0, 0.0, 1.0, 1.0);
}
注意:
- out vec4 color 表示輸出變量,其類型為vec4,表示一個4分量的向量,用于存儲顏色值。
- 在渲染過程中,片段(或片元)代表的是經過光柵化處理的潛在像素。它們可能最終成為屏幕上的像素,但在某些情況下(如深度測試或模板測試失敗),某些片段可能不會被顯示出來。因此,片段是處于渲染管線更早期的階段,而像素則是最終呈現在屏幕上的元素。
完整cpp代碼
/*
* 第2章 OpenGL繪制點示例程序
* 這個程序演示了如何使用OpenGL的著色器程序繪制一個點
* 包含了著色器程序的加載、編譯和鏈接過程
*/// OpenGL相關庫
#include <GL\glew.h> // GLEW用于加載OpenGL函數
#include <GLFW\glfw3.h> // GLFW用于創建窗口和處理輸入// 標準庫
#include <iostream> // 用于控制臺輸出
#include <string> // 用于字符串處理
#include <fstream> // 用于文件讀取
#include <filesystem> // 用于文件路徑處理using namespace std;// 定義頂點數組對象(VAO)的數量
#define numVAOs 1// 全局變量
GLuint renderingProgram; // 存儲編譯后的著色器程序
GLuint vao[numVAOs]; // 存儲頂點數組對象/**
* 打印著色器編譯日志
* 用于調試著色器編譯錯誤
* @param shader 著色器對象的ID
*/
void printShaderLog(GLuint shader) {int len = 0; // 日志長度int chWrittn = 0; // 實際寫入的字符數char *log; // 日志內容// 獲取日志長度glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);if (len > 0) {log = (char *)malloc(len);// 獲取實際的日志信息glGetShaderInfoLog(shader, len, &chWrittn, log);cout << "著色器編譯日志: " << log << endl;free(log);}
}/**
* 打印著色器程序鏈接日志
* 用于調試著色器程序鏈接錯誤
* @param prog 著色器程序的ID
*/
void printProgramLog(int prog) {int len = 0; // 日志長度int chWrittn = 0; // 實際寫入的字符數char *log; // 日志內容// 獲取日志長度glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);if (len > 0) {log = (char *)malloc(len);// 獲取實際的日志信息glGetProgramInfoLog(prog, len, &chWrittn, log);cout << "程序鏈接日志: " << log << endl;free(log);}
}/**
* 檢查OpenGL錯誤
* 遍歷所有待處理的錯誤并打印
* @return 如果發現錯誤返回true,否則返回false
*/
bool checkOpenGLError() {bool foundError = false;int glErr = glGetError();while (glErr != GL_NO_ERROR) {cout << "OpenGL錯誤代碼: " << glErr << endl;foundError = true;glErr = glGetError();}return foundError;
}/**
* 從文件中讀取著色器源代碼
* @param filePath 著色器文件的路徑
* @return 包含著色器源代碼的字符串
*
* 這個函數負責:
* 1. 打開并讀取著色器源文件
* 2. 將文件內容轉換為字符串
* 3. 處理文件打開失敗的情況
*/
string readFile(const char *filePath) {string content;ifstream fileStream(filePath, ios::in);string line = "";// 檢查文件是否成功打開if (!fileStream.is_open()) {cout << "無法打開文件: " << filePath << endl;return content;}// 逐行讀取文件內容while (getline(fileStream, line)) {content.append(line + "\n");}fileStream.close();return content;
}
/**
* 創建、編譯和鏈接著色器程序
* @return 返回編譯和鏈接成功的著色器程序ID
*
* 這個函數完成以下步驟:
* 1. 創建著色器對象
* 2. 讀取并編譯頂點著色器和片段著色器
* 3. 創建著色器程序并鏈接著色器
* 4. 進行錯誤檢查和狀態報告
*/
GLuint createShaderProgram() {// 編譯狀態變量GLint vertCompiled; // 頂點著色器編譯狀態GLint fragCompiled; // 片段著色器編譯狀態GLint linked; // 程序鏈接狀態// 創建著色器和程序對象GLuint vShader = glCreateShader(GL_VERTEX_SHADER); // 創建頂點著色器GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER); // 創建片段著色器GLuint vfprogram = glCreateProgram(); // 創建著色器程序// 讀取著色器源代碼string vertShaderStr = readFile("./shader/vertShader.glsl");string fragShaderStr = readFile("./shader/fragShader.glsl");const char *vertShaderSrc = vertShaderStr.c_str();const char *fragShaderSrc = fragShaderStr.c_str();// 將源代碼加載到著色器對象中glShaderSource(vShader, 1, &vertShaderSrc, NULL);glShaderSource(fShader, 1, &fragShaderSrc, NULL);// 編譯頂點著色器glCompileShader(vShader);checkOpenGLError(); // 檢查OpenGL錯誤glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);if (vertCompiled == 1) {cout << "頂點著色器編譯成功" << endl;}else {cout << "頂點著色器編譯失敗" << endl;printShaderLog(vShader);}// 編譯片段著色器glCompileShader(fShader);checkOpenGLError(); // 檢查OpenGL錯誤glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);if (fragCompiled == 1) {cout << "片段著色器編譯成功" << endl;}else {cout << "片段著色器編譯失敗" << endl;printShaderLog(fShader);}// 將著色器附加到程序對象并鏈接glAttachShader(vfprogram, vShader); // 附加頂點著色器glAttachShader(vfprogram, fShader); // 附加片段著色器glLinkProgram(vfprogram); // 鏈接程序// 檢查鏈接狀態checkOpenGLError(); // 檢查OpenGL錯誤glGetProgramiv(vfprogram, GL_LINK_STATUS, &linked);if (linked == 1) {cout << "著色器程序鏈接成功" << endl;}else {cout << "著色器程序鏈接失敗" << endl;printProgramLog(vfprogram);}cout << "著色器程序創建完成,ID: " << vfprogram << endl;return vfprogram;
}/**
* 初始化OpenGL的設置
* @param window 目標窗口的指針
*/
void init(GLFWwindow* window) { renderingProgram = createShaderProgram();glGenVertexArrays(numVAOs, vao);glBindVertexArray(vao[0]);}/**
* 渲染函數,負責實際的繪制操作
* @param window 目標窗口的指針
* @param currentTime 當前時間,可用于動畫
*/
void display(GLFWwindow* window, double currentTime) {glUseProgram(renderingProgram);glPointSize(30.0f);glDrawArrays(GL_POINTS, 0, 1);
}/**
* 主函數:程序入口點
* 負責初始化GLFW和GLEW,創建窗口,并進入渲染循環
*/
int main(void) {// 獲取當前工作目錄的路徑std::filesystem::path currentPath = std::filesystem::current_path();// 獲取絕對路徑std::filesystem::path absolutePath = std::filesystem::absolute(currentPath);// 提取目錄名稱std::string directoryName = currentPath.filename().string();std::cout << "absolute_path: " << absolutePath.string() << std::endl;system("chcp 65001 > nul"); // 設置為 UTF-8 編碼// 初始化GLFW庫,如果失敗則退出if (!glfwInit()) { exit(EXIT_FAILURE); }// 設置OpenGL版本為4.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);// 創建一個600x600像素的窗口GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter 2 - program 2", NULL, NULL);// 將新創建的窗口設置為當前OpenGL上下文glfwMakeContextCurrent(window);// 初始化GLEW庫,用于管理OpenGL擴展,如果失敗則退出if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }// 啟用垂直同步,防止畫面撕裂glfwSwapInterval(1);// 調用自定義的初始化函數init(window);// 渲染循環:直到窗口被關閉才退出while (!glfwWindowShouldClose(window)) {// 調用顯示函數進行渲染,傳入當前時間用于動畫display(window, glfwGetTime());// 交換前后緩沖區,將渲染結果顯示到屏幕上glfwSwapBuffers(window);// 處理窗口事件(如鍵盤輸入、鼠標移動等)glfwPollEvents();}// 清理資源并退出glfwDestroyWindow(window);glfwTerminate();exit(EXIT_SUCCESS);
}
2.6. 曲面細分著色器
曲面細分著色器(Tessellation Shader)是OpenGL中的一個著色器階段,用于增加渲染模型的細節。它通過在幾何圖元(如三角形或四邊形)的表面生成更多的頂點來細分這些圖元,從而提高渲染的細節水平。曲面細分著色器通常由兩個主要部分組成:
-
曲面細分控制著色器(Tessellation Control Shader,TCS):這個著色器階段決定了每個輸入圖元將被細分為多少個更小的圖元。它還可以設置每個新頂點的屬性。
-
曲面細分評估著色器(Tessellation Evaluation Shader,TES):在這個階段,新的頂點位置被計算出來,并且可以應用自定義的數學函數來生成復雜的幾何形狀。TES還負責設置每個新頂點的最終屬性。
曲面細分著色器通常用于需要動態調整模型細節的場景,例如地形渲染、水面模擬或任何需要根據攝像機距離調整細節水平的對象。
對于地面,我們通常用兩個三角形(四個頂點)來表示,但是通過曲面細分,我們可以用更多的三角形(更多頂點)來表示,從而提高渲染的細節。
目前階段我們不會深入研究曲面細分著色器的細節。
2.7. 幾何著色器
幾何著色器是OpenGL渲染管線中的一個可選階段,位于頂點著色器和片段著色器之間。它的主要功能是處理圖元(如點、線、三角形)并生成額外的圖元。這使得開發者可以在渲染過程中動態地修改或創建新的幾何形狀。
頂點著色器及片段著色器只能處理單個頂點或片段,而幾何著色器可以操作圖元(如點、線、三角形),并生成新的圖元。這使得幾何著色器可以用于創建復雜的幾何形狀,如網格、粒子系統或地形。
比如拉伸、壓縮、旋轉、扭曲、刪除等操作。
比如下圖左邊是環面模型,右邊是幾何著色器修改后的模型。
通過這種方式,可以減少模型頂點數量,提高渲染效率。
2.8. 光柵化階段
光柵化的本質是將每對頂點進行插值,生成一系列的頂點。如下圖所示,將三角形的三個頂點進行插值,生成三條邊。
到此時,呈現出來的圖像將會是線框模型
即
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
而如果想要呈現出實心的三角形,需要將線框模式改為填充模式 ,這也是默認的模式,即完全光柵化
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
2.9. 繪制三角形
我們在頂點著色器中通過gl_VertexID
來獲取當前正在處理的頂點編號,設置頂點坐標,從而實現繪制三角形。 gl_VertexID
是OpenGL內置的變量,表示當前正在處理的頂點編號,從0開始計數。
2.9.1. 實心三角形
頂點著色器
// 指定GLSL版本為4.30
#version 430/*** 頂點著色器的主函數* 負責設置頂點的位置*/
void main(void)
{// 設置頂點位置// vec4參數分別表示:// x=0.0:在x軸上居中// y=0.0:在y軸上居中// z=0.5:在z軸上的深度值// w=1.0:齊次坐標的w分量//gl_Position = vec4(0.0, 0.0, 0.5, 1.0);if (gl_VertexID == 0){gl_Position = vec4(0.0, 0.0, 0.5, 1.0);}else if (gl_VertexID == 1){gl_Position = vec4(0.5, 0.0, 0.5, 1.0);}else if (gl_VertexID == 2){gl_Position = vec4(0.5, 0.5, 0.5, 1.0);}}
片段著色器
不用修改
main.cpp
void display(GLFWwindow *window, double currentTime)
{// 使用名為 renderingProgram 的著色器程序glUseProgram(renderingProgram);// 設置要繪制的點的大小為 1 像素glPointSize(1.0f);glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 設置繪制模式為填充//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// 繪制點圖元,從數組的第 0 個元素開始,繪制 3 個點glDrawArrays(GL_TRIANGLES, 0, 3);
}
2.9.2. 線框三角形
只需要 將填充模式改為線框模式即可
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2.10. 讓三角形動起來
我們只需要不斷改變頂點的位置,就可以讓三角形動起來。
由于display()函數每秒會持續不斷執行,因此我們只需要在每次執行時,將頂點的位置稍微移動一點,就可以讓三角形看起來在動。
我們可以在c++代碼中,增加一個變量offset,在每次執行display()函數時,將offset的值進行改變,同時將offset的值傳遞給頂點著色器,從而實現讓三角形動起來的效果。
在頂點著色器中,通過uniform
變量offsetX
來接收offset
的值,從而實現頂點位置的改變。
main.cpp
void display(GLFWwindow *window, double currentTime)
{// 清除顏色緩沖區和深度緩沖區glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);static float offset=0.0f; // 偏移量,用于動畫效果static float delta=0.01f; // 偏移增量offset+=delta; // 更新偏移量if(offset>0.5f) delta=-0.01f;if(offset<-0.5f) delta=0.01f; // 如果偏移量超過范圍,反轉增量// 使用名為 renderingProgram 的著色器程序glUseProgram(renderingProgram);// 設置要繪制的點的大小為 1 像素glPointSize(1.0f);//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 設置繪制模式為填充glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// 繪制點圖元,從數組的第 0 個元素開始,繪制 3 個點GLuint offsetLocation = glGetUniformLocation(renderingProgram, "offsetX"); // 獲取偏移量uniform變量的位置glUniform1f(offsetLocation, offset); // 設置偏移量uniform變量的值glDrawArrays(GL_TRIANGLES, 0, 3);
}
頂點著色器
// 指定GLSL版本為4.30
#version 430uniform float offsetX; // x軸偏移量
/*** 頂點著色器的主函數* 負責設置頂點的位置*/
void main(void)
{// 設置頂點位置// vec4參數分別表示:// x=0.0:在x軸上居中// y=0.0:在y軸上居中// z=0.5:在z軸上的深度值// w=1.0:齊次坐標的w分量//gl_Position = vec4(0.0, 0.0, 0.5, 1.0);if (gl_VertexID == 0){gl_Position = vec4(-0.3+offsetX, 0.0, 0.5, 1.0);}else if (gl_VertexID == 1){gl_Position = vec4(0.2+offsetX, 0.0, 0.5, 1.0);}else if (gl_VertexID == 2){gl_Position = vec4(0.2+offsetX, 0.5, 0.5, 1.0);}}
注意:
我們在display()函數中,需要調用以下代碼
// 清除顏色緩沖區和深度緩沖區glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
否則,我們無法看到三角形在動,且會看到一些奇怪的圖形,這是因為圖形沒有清空,導致圖形疊加在一起。
學習筆記完整源代碼下載