文章目錄
- 【OpenGL學習】(二)OpenGL渲染簡單圖形
- OpenGL渲染圖形流程
- 頂點,圖元和片元
- VAO,VBO ,EBO
- 著色器
- 示例:使用OpenGL渲染三角形
【OpenGL學習】(二)OpenGL渲染簡單圖形
OpenGL渲染圖形流程
CPU(中央處理器)↓
初始化窗口與OpenGL上下文↓
編譯并鏈接著色器程序 ↓
準備頂點數據(頂點位置、顏色、法線、紋理坐標等)↓
創建并綁定:- VAO(頂點數組對象)← 用于記錄 VBO/EBO 的綁定狀態和頂點屬性配置↓創建并綁定:- VBO(頂點緩沖對象)← 存儲所有頂點屬性數據(位置信息、顏色、紋理坐標等)- EBO(索引緩沖對象,可選)← 存儲索引數據,減少冗余頂點設置頂點屬性指針(glVertexAttribPointer)← 告訴 OpenGL 如何解析 VBO 中的頂點數據啟用頂點屬性數組(glEnableVertexAttribArray)↑(這些設置會被 VAO 記錄下來)調用:- glBufferData → 將頂點數據或索引數據從 CPU 傳輸到 GPU 顯存中(顯卡緩沖區)↓
==================== GPU 開始接管渲染流程 ====================
頂點著色器(Vertex Shader)- 每個頂點執行一次- 坐標變換:模型矩陣 × 視圖矩陣 × 投影矩陣- 傳出數據供后續階段使用(如顏色、紋理坐標)↓
圖元裝配(Primitive Assembly)- 將一組頂點組裝為圖元(如三角形、線段)↓
(可選)幾何著色器(Geometry Shader)- 每個圖元執行一次- 可動態生成新的頂點或圖元↓
光柵化(Rasterization)- 將圖元轉換為片元(像素候選)- 生成每個片元的屏幕位置↓
片段著色器(Fragment Shader)- 每個片元執行一次- 計算顏色值(光照、紋理采樣、顏色混合等)↓
測試與混合階段(由固定功能單元執行)- 深度測試、模板測試- α混合、遮擋判斷↓
幀緩沖(Framebuffer)- 最終圖像寫入幀緩沖 → 顯示在屏幕
圖片來源:https://geekdaxue.co/read/Learn-OpenGL-CN/01-Getting-Started-04-Hello-Triangle.md
頂點,圖元和片元
頂點(Vertex)是圖形的基本構建單位,表示圖形的一個頂點,通常包括:
- 位置坐標(Position):如三維空間中的 (x, y, z)
- 顏色信息(Color)
- 紋理坐標(UV)
- 法向量(Normal)
- 其他自定義屬性(如切線、位移等)
圖元(Primitive)是由多個頂點組成的幾何形狀單元,比如:
- 點(GL_POINTS)→ 每個頂點單獨成圖元
- 線段(GL_LINES)→ 每兩個頂點組成一條線
- 三角形(GL_TRIANGLES)→ 每三個頂點組成一個三角形
- 更多復合圖元(如 GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN)
片元(Fragment)是每個圖元經過光柵化后生成的像素候選者,這些片元會傳入片段著色器進行著色計算(顏色、紋理、光照等)。在經過深度測試、模板測試、混合等處理之后,部分片元會變成屏幕上的像素。
VAO,VBO ,EBO
VAO(頂點數組對象,Vertex Array Object)用于 存儲頂點屬性配置狀態,如頂點屬性指針、VBO綁定狀態、EBO綁定狀態等。每次繪制時只需綁定一次 VAO,而不用重復設置頂點屬性。
VBO(頂點緩沖對象,Vertex Buffer Object)用于在 GPU 顯存中存儲頂點數據(如位置、顏色、法線、紋理坐標等),避免每次繪制時從 CPU 向 GPU 頻繁傳輸數據,提升效率。
EBO(元素緩沖對象,Element Buffer Object)用于在 繪制圖形時按照索引重用頂點數據。節省存儲空間,避免重復頂點。
著色器
著色器(Shader)是運行在 GPU 上的小程序,用于控制圖形渲染的每個階段。它是實現可編程渲染管線的核心。
著色器的主要類型:
著色器類型 | 作用 |
---|---|
頂點著色器 (Vertex Shader ) | 處理每個頂點的位置變換、法線、紋理坐標等 |
片段著色器 (Fragment Shader ) | 處理每個像素(片元)的顏色計算、紋理映射、光照等 |
幾何著色器(可選) (Geometry Shader ) | 處理圖元(點、線、三角形),可生成新圖元 |
曲面細分控制/評估著色器(可選) | 用于對幾何細分 |
計算著色器(Compute Shader) | 用于并行計算任務,不用于圖形繪制 |
著色器使用的語言是GLSL(OpenGL Shading Language)。GLSL的語法類似于 C 語言,且可以運行于 GPU 上。我們需要在 C++ 中編寫 GLSL 代碼(字符串形式),然后使用 OpenGL API 編譯和鏈接著色器。
示例:使用OpenGL渲染三角形
#include <glad/glad.h> // 加載 OpenGL 函數指針
#include <GLFW/glfw3.h> // GLFW 用于創建窗口和處理輸入#include <iostream> // 回調函數聲明:當窗口大小發生改變時調用
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
// 處理輸入的函數聲明
void processInput(GLFWwindow* window);// 設置窗口寬高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;// 頂點著色器GLSL源碼
const char* vertexShaderSource = "#version 330 core\n" // 使用 OpenGL 3.3 對應的 GLSL 版本(即 GLSL 3.30)
"layout (location = 0) in vec3 aPos;\n" // 頂點位置屬性,位置值為0
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" // 設置頂點位置
"}\0";// 片段著色器GLSL源碼
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n" // 片段輸出顏色
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" // 設置輸出顏色為橙色
"}\n\0";int main()
{// 初始化并配置 GLFWglfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // OpenGL 主版本號glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // OpenGL 次版本號glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心模式#ifdef __APPLE__glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // MacOS 需要加這句
#endif// 創建 GLFW 窗口GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate(); // 初始化失敗,退出程序return -1;}glfwMakeContextCurrent(window); // 將窗口上下文設為當前線程上下文glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // 設置窗口大小改變時的回調// 初始化 GLAD,用于加載 OpenGL 函數if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// ------------------構建并編譯著色器程序------------------// 頂點著色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); // 創建著色器對象glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); // 附加源碼glCompileShader(vertexShader); // 編譯著色器// 檢查編譯錯誤int success;char infoLog[512];glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}// 片段著色器unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);// 創建一個片段著色器對象glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); // 將片段著色器源碼附加到著色器對象上glCompileShader(fragmentShader); // 編譯片段著色器源碼// 檢查編譯錯誤glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}// 著色器程序unsigned int shaderProgram = glCreateProgram(); // 創建程序對象glAttachShader(shaderProgram, vertexShader); // 附加著色器glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram); // 鏈接程序// 檢查鏈接錯誤glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);if (!success) {glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;}// 刪除已編譯的著色器對象,已鏈接到程序中不再需要glDeleteShader(vertexShader);glDeleteShader(fragmentShader);// --------------------------------------------------------// 設置頂點數據和緩沖,并配置頂點屬性float vertices[] = {-0.5f, -0.5f, 0.0f, // 左下角0.5f, -0.5f, 0.0f, // 右下角0.0f, 0.5f, 0.0f // 頂部};// unsigned int VBO, VAO;glGenVertexArrays(1, &VAO); // 創建頂點數組對象glGenBuffers(1, &VBO); // 創建頂點緩沖對象glBindVertexArray(VAO); // 綁定 VAOglBindBuffer(GL_ARRAY_BUFFER, VBO); // 綁定 VBO glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 傳入數據// 設置頂點屬性指針glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0); // 啟用頂點屬性glBindBuffer(GL_ARRAY_BUFFER, 0); // 解綁 VBO,為了安全,非必須glBindVertexArray(0); // 解綁 VAO// 可以取消注釋以使用線框模式繪制:也就是不填充圖形,只畫出邊框線// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// 渲染循環while (!glfwWindowShouldClose(window)){// 處理輸入processInput(window);// 清屏并設置背景顏色glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// 繪制三角形glUseProgram(shaderProgram); // 使用著色器程序glBindVertexArray(VAO); // 綁定 VAOglDrawArrays(GL_TRIANGLES, 0, 3); // 從第0個頂點繪制3個頂點構成的三角形// 交換緩沖區并查詢IO事件glfwSwapBuffers(window);glfwPollEvents();}// 可選:釋放所有資源glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteProgram(shaderProgram);// 釋放 GLFW 資源glfwTerminate();return 0;
}// 處理輸入:如果按下 ESC 鍵,則關閉窗口
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); // 設置 OpenGL 視口大小
}
只畫邊框線:
參考:
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/