計算機圖形學編程(使用OpenGL和C++)(第2版)學習筆記 02.OpenGL圖像管線

1. OpenGL圖像管線

OpenGL(Open Graphics Library)是一個跨平臺的、功能強大的圖形渲染API,用于開發2D和3D圖形應用程序。它由Khronos Group維護,廣泛應用于游戲開發、圖形設計、虛擬現實等領域。

1.0.1. OpenGL的特點:

  1. 跨平臺:支持Windows、macOS、Linux等多個操作系統。 注:macOS上對OpenGL的支持不如Windows。
  2. 硬件加速:利用GPU進行高效的圖形渲染。
  3. 開放標準:由Khronos Group管理,提供靈活的擴展機制。
  4. 實時渲染:適用于需要高性能圖形渲染的應用。

1.0.2. OpenGL的主要功能:

  1. 圖形繪制:支持點、線、三角形等基本圖元的繪制。
  2. 著色器支持:通過GLSL(OpenGL Shading Language)實現自定義的頂點和片段著色器。
  3. 紋理映射:加載和應用紋理以增強圖形的視覺效果。
  4. 變換與投影:支持模型變換、視圖變換和投影變換。
  5. 光照與陰影:實現逼真的光照和陰影效果。

OpenGL的靈活性和高性能使其成為圖形開發的核心工具之一。

2. OpenGL 與 Direct3D 開發語言上的對比

特性OpenGLDirect3D
語言支持支持多種語言,包括 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圖像管線經常涉及的階段:

  1. 頂點處理:處理頂點數據,包括頂點坐標、顏色、紋理坐標等。
  2. 光柵化:將頂點數據轉換為片段,進行光柵化處理,生成片段。
  3. 片段處理:處理片段數據,包括顏色、深度、模板等。

其中,頂點處理和片段處理是核心階段,它們分別負責處理頂點和片段數據。光柵化階段則負責將頂點數據轉換為片段,進行光柵化處理。
我們需要編寫頂點著色器和片段著色器來處理頂點和片段數據,光柵化階段則由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);
}

整個程序的流程如下:

  1. 初始化GLFW庫,如果失敗則退出。
  2. 設置OpenGL版本為4.3。
  3. 創建一個600x600像素的窗口。
  4. 將新創建的窗口設置為當前OpenGL上下文。
  5. 初始化GLEW庫,用于管理OpenGL擴展,如果失敗則退出。
  6. 啟用垂直同步,防止畫面撕裂。
  7. 調用自定義的初始化函數。
  8. 渲染循環:直到窗口被關閉才退出。
  9. 調用顯示函數進行渲染,傳入當前時間用于動畫。
  10. 交換前后緩沖區,將渲染結果顯示到屏幕上。
  11. 處理窗口事件(如鍵盤輸入、鼠標移動等)。
  12. 清理資源并退出。

重點關注:

  1. init():自定義的初始化函數,負責初始化OpenGL的設置。目前為空
  2. 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 編碼

我們在上一節的基礎上進行添加,軟件思路:

  1. 創建、編譯和鏈接著色器程序
  2. 創建、綁定和配置頂點數組對象
  3. 調用顯示函數進行渲染
  4. 調用glPointSize()設置點的大小,由于OpenGL默認的點大小為1.0,只顯示一個點,會看不到效果,所以需要設置一個較大的點大小
  5. 調用glDrawArrays()繪制點,傳入參數為GL_POINTS,表示繪制點,傳入參數為0,表示從第0個頂點開始繪制,傳入參數為1,表示繪制1個點

GLSL 與普通編碼的過程不同,GLSL編譯發生在C++運行時,實際運行在GPU中,我們不太好觀察其編譯錯誤。因此,我們使用輔助函數來打印編譯錯誤,詳見代碼中的以下函數 :

  1. checkOpenGLError():檢查OpenGL錯誤
  2. printShaderLog():打印著色器的編譯錯誤
  3. 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_Positionvec4輸出的頂點位置,在裁剪空間中定義。
gl_PointSizefloat輸出的點的大小(像素),僅在點圖元時使用。
gl_VertexIDint輸入的頂點ID,表示當前頂點的索引。
gl_InstanceIDint輸入的實例ID,表示當前實例的索引(如果啟用了實例化渲染)。
gl_PrimitiveIDint輸入的圖元ID,表示當前圖元的索引(在幾何著色器中使用)。
gl_Layerint輸出的圖層ID,用于分層渲染(在幾何著色器中使用)。
gl_ViewportIndexint輸出的視口索引,用于多視口渲染(在幾何著色器中使用)。
in 變量自定義類型從頂點緩沖對象(VBO)傳遞的輸入頂點屬性,如位置、顏色、法線等。
out 變量自定義類型傳遞到片段著色器的輸出變量,如顏色、紋理坐標等。

2.5.1. 說明

  • inout 變量的具體類型和名稱由開發者根據需求定義。
  • 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);
}

注意:

  1. out vec4 color 表示輸出變量,其類型為vec4,表示一個4分量的向量,用于存儲顏色值。
  2. 在渲染過程中,片段(或片元)代表的是經過光柵化處理的潛在像素。它們可能最終成為屏幕上的像素,但在某些情況下(如深度測試或模板測試失敗),某些片段可能不會被顯示出來。因此,片段是處于渲染管線更早期的階段,而像素則是最終呈現在屏幕上的元素。

完整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中的一個著色器階段,用于增加渲染模型的細節。它通過在幾何圖元(如三角形或四邊形)的表面生成更多的頂點來細分這些圖元,從而提高渲染的細節水平。曲面細分著色器通常由兩個主要部分組成:

  1. 曲面細分控制著色器(Tessellation Control Shader,TCS):這個著色器階段決定了每個輸入圖元將被細分為多少個更小的圖元。它還可以設置每個新頂點的屬性。

  2. 曲面細分評估著色器(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. 讓三角形動起來

1

我們只需要不斷改變頂點的位置,就可以讓三角形動起來。
由于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);

否則,我們無法看到三角形在動,且會看到一些奇怪的圖形,這是因為圖形沒有清空,導致圖形疊加在一起。

學習筆記完整源代碼下載

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/904310.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/904310.shtml
英文地址,請注明出處:http://en.pswp.cn/news/904310.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux架構篇、第1章_02源碼編譯安裝Apache HTTP Server 最新穩定版本是 2.4.62

Linux_基礎篇 歡迎來到Linux的世界&#xff0c;看筆記好好學多敲多打&#xff0c;每個人都是大神&#xff01; 題目&#xff1a;源碼編譯安裝Apache HTTP Server 最新穩定版本是 2.4.62 版本號: 1.0,0 作者: 老王要學習 日期: 2025.05.01 適用環境: Centos7 文檔說明 本文…

算法基礎學習|03二分

一、思路 &#xff08;1&#xff09;mid(lr1)/2 if(check(mid)):1.true [mid,r] lmid 2.false [l,mid-1] rmid-1 &#xff08;2&#xff09;mid(lr)/2 if(check(mid)):1.true [l,mid] rmid 2.false [mid1,r] lmid1 二、模板 如何選擇模…

18. LangChain分布式任務調度:大規模應用的性能優化

引言&#xff1a;從單機到萬級并發的進化 2025年某全球客服系統通過LangChain分布式改造&#xff0c;成功應對黑五期間每秒12,000次的咨詢請求。本文將基于LangChain的分布式架構&#xff0c;詳解如何實現AI任務的自動擴縮容與智能調度。 一、分布式系統核心指標 1.1 性能基準…

Java泛型(補檔)

核心概念 Java 泛型是 Java SE 1.5 引入的一項重要特性&#xff0c;它的核心思想是 參數化類型&#xff08;Parameterized Types&#xff09;&#xff0c;即通過將數據類型作為參數傳遞給類、接口或方法&#xff0c;使代碼能夠靈活地處理多種類型&#xff0c;同時保證類型安全性…

LeetCode 熱題 100:普通數組

53. 最大子數組和 給你一個整數數組 nums &#xff0c;請你找出一個具有最大和的連續子數組&#xff08;子數組最少包含一個元素&#xff09;&#xff0c;返回其最大和。 子數組是數組中的一個連續部分。 示例 1&#xff1a; 輸入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5,4] 輸…

【kafka系列】消費者組

目錄 消費者組功能點 1. 動態負載均衡 2. 容錯高可用 3. 消費進度管理 4. 并行消費能力 5. 消費隔離性 其他要點 1. Rebalance過程詳解 2. 位移提交的精確語義 3. 消費者限速策略 4. 跨機房消費設計 消費者組功能點 1. 動態負載均衡 核心機制&#xff1a;通過Rebal…

黑馬點評day01(基于Redis)

1.7 Redis代替session的業務流程 1.7.1、設計key的結構 首先我們要思考一下利用redis來存儲數據&#xff0c;那么到底使用哪種結構呢&#xff1f;由于存入的數據比較簡單&#xff0c;我們可以考慮使用String&#xff0c;或者是使用哈希&#xff0c;如下圖&#xff0c;如果使用…

Python爬蟲實戰:獲取優美圖庫各類高清圖片,為用戶提供設計素材

一、引言 在互聯網時代,高清壁紙資源豐富多樣,而優美圖庫作為一個提供大量精美壁紙的網站,吸引了眾多用戶。通過 Python 爬蟲技術,可以自動化地從該網站獲取所需的壁紙資源,為用戶節省時間和精力。然而,網站通常會采取反爬措施來防止數據被惡意抓取,因此需要在爬蟲程序…

Go反射-通過反射調用結構體的方法(帶入參)

使用反射前&#xff0c;我們需要提前做好映射配置 papckage_struct_relationship.go package reflectcommonimport (api "template/api" )// 包名到包對象的映射 var structMap map[string]func() interface{}{"template/api": func() interface{} { re…

Git_.gitignore文件簡介及使用

.gitignore 這個文件的作用就是告訴Git哪些文件不需要添加到版本管理中。實際項目中&#xff0c;很多文件都是不需要版本管理的&#xff0c;比如Python的.pyc文件&#xff0c;Git會根據這個文件里配置的這些規則來判斷是否將文件添加到版本控制中。 注意&#xff0c;直接新建文…

HarmonyOS ArkUI安全控件開發指南:粘貼、保存與位置控件的實現與隱私保護實踐

目錄 安全控件1. 粘貼控件1.1 約束與限制1.2 開發步驟 2. 保存控件2.1 約束與限制2.2 開發步驟 3. 位置控件3.1 約束與限制3.2 開發步驟 安全控件 安全控件是系統提供的一組系統實現的ArkUI組件&#xff0c;其中保存控件在用戶首次使用時&#xff0c;會彈出通知彈窗&#xff0…

C++筆記之接口`Interface`

C++筆記之接口Interface code review! 一個簡潔簡短的 C++ 接口實現示例: #include <iostream>// 1. 定義接口(抽象類) class Shape {public:

動態圖表 -- eg1

問題&#xff1a; 前端vue&#xff0c;后端springboot&#xff0c;實現動態表格樣式&#xff0c;&#xff08;表格List<Student>&#xff0c;Student類有年級&#xff0c;班級&#xff0c;文理科分類&#xff0c;姓名&#xff0c;學號&#xff0c;等屬性。先根據年級分類…

C++學習之shell高級和正則表達式

目錄 1.正則表達式 2.C中使用正則 3.復習 4.sort命令 5.uniq命令 6.wc命令 7.grep命令 8.find命令 9.xargs命令 10.sed命令 11.awk命令 12.crontab 1.正則表達式 1 管道 使用| 將多個命令拼接在一起 原理&#xff0c;就是將前一個命令的標準輸出作為后一個…

【Vue】 實現TodoList案例(待辦事項)

目錄 組件化編碼流程&#xff08;通用&#xff09; 1.實現靜態組件&#xff1a;抽取組件&#xff0c;使用組件實現靜態頁面效果 2.展示動態數據&#xff1a; 1. 常規 HTML 屬性 3.交互——從綁定事件監聽開始 什么時候要用 event&#xff1a; 什么時候不需要用 event&am…

【Bootstrap V4系列】學習入門教程之 組件-卡片(Card)

Bootstrap V4系列 學習入門教程之 組件-卡片&#xff08;Card&#xff09; 卡片&#xff08;Card&#xff09;一、Example二、Content types 內容類型2.1 Body 主體2.2 Titles, text, and links 標題、文本和鏈接2.3 Images 圖片2.4 List groups 列表組2.5 Kitchen sink 洗滌槽…

java學習之數據結構:四、樹(代碼補充)

這部分主要是用代碼實現有序二叉樹、樹遍歷、刪除節點 目錄 1.構建有序二叉樹 1.1原理 1.2插入實現 2.廣度優先遍歷--隊列實現 3.深度優先遍歷--遞歸實現 3.1先序遍歷 3.2中序遍歷 3.3后序遍歷 4.刪除 4.1刪除葉子節點 4.2刪除有一棵子樹的節點 4.3刪除有兩棵子樹的節…

架構進階:什么是數據架構,如何理解數據架構?(華為)

數據架構是企業架構的重要組成部分,DAMA、IBM 及國內大廠對其定義各有側重。它包含數據資產目錄、數據標準、數據模型和數據分布四個組件。數據資產目錄可梳理企業數據資產,數據標準統一數據含義和規則,數據模型反映業務對象關聯關系,數據分布呈現數據流動情況。數據架構是…

Unity SpriteEditor(精靈圖片編輯器)

&#x1f3c6; 個人愚見&#xff0c;沒事寫寫筆記 &#x1f3c6;《博客內容》&#xff1a;Unity3D開發內容 &#x1f3c6;&#x1f389;歡迎 &#x1f44d;點贊?評論?收藏 &#x1f50e;SpriteEditor&#xff1a; 精靈圖片編輯器 &#x1f4cc;用于編輯2D游戲開發中使用的Sp…

【網絡原理】從零開始深入理解HTTP的報文格式(一)

本篇博客給大家帶來的是網絡HTTP協議的知識點, 重點介紹HTTP的報文格式. &#x1f40e;文章專欄: JavaEE初階 &#x1f680;若有問題 評論區見 ? 歡迎大家點贊 評論 收藏 分享 如果你不知道分享給誰,那就分享給薯條. 你們的支持是我不斷創作的動力 . 王子,公主請閱&#x1f68…