- 頂點(diǎn)緩沖對(duì)象(VBO): Vertex Buffer Object
- 頂點(diǎn)數(shù)組對(duì)象(VAO): Vertex Array Object
- 索引緩沖對(duì)象(EBO、IBO): Element Buffer Object凳厢、Index Buffer Object
圖形渲染管線
可以被劃分為兩個(gè)主要部分:
- 3D坐標(biāo)轉(zhuǎn)換為2D坐標(biāo)
- 2D坐標(biāo)轉(zhuǎn)變?yōu)閷?shí)際有顏色的像素
可以被劃分為幾個(gè)階段:
- 頂點(diǎn)著色器
- 形狀(圖元)裝配
- 幾何著色器
- 光柵化
- 片段著色器
- 測(cè)試與混合
頂點(diǎn)著色器
它把一個(gè)單獨(dú)的頂點(diǎn)作為輸入
主要目的是把3D坐標(biāo)轉(zhuǎn)換為另一種3D坐標(biāo)账胧,同時(shí)對(duì)頂點(diǎn)屬性進(jìn)行一些基本處理
圖元裝配
將頂點(diǎn)著色器輸出的所有頂點(diǎn)作為輸入
所有的點(diǎn)裝配成指定圖元形狀
幾何著色器
把圖元形式的一系列頂點(diǎn)的集合作為輸入
可以通過(guò)產(chǎn)生新頂點(diǎn)構(gòu)造出新的圖元來(lái)生成其他形狀
光柵化、裁切
幾何著色器的輸出作為輸入
把圖元映射為最終屏幕上相應(yīng)的像素先紫,生成供片段著色器使用的片段
在片段著色器運(yùn)行之前會(huì)執(zhí)行裁切治泥,丟棄超出視圖以外的所有像素,提升執(zhí)行效率
片段著色器
光柵化生成的片段作為輸入
主要目的是計(jì)算一個(gè)像素的最終顏色
測(cè)試與混合
片段著色器的輸出作為輸入
檢測(cè)片段的對(duì)應(yīng)的深度值泡孩,判斷這個(gè)像素是其它物體的前面還是后面车摄,決定是否應(yīng)該丟棄;檢查Alpha值并對(duì)物體進(jìn)行混合
數(shù)據(jù)輸入
定義一個(gè)float
數(shù)組:
//以標(biāo)準(zhǔn)化設(shè)備坐標(biāo)形式
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
通過(guò)VBO管理這個(gè)內(nèi)存仑鸥,代碼會(huì)像這樣:
//0:生成頂點(diǎn)緩沖對(duì)象
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//1:復(fù)制數(shù)據(jù)到緩沖內(nèi)存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
著色器
1)編寫(xiě)程序
//頂點(diǎn)著色器程序
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
//片段著色器程序
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";
2)編譯源碼吮播,代碼會(huì)像這樣:
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
GL_VERTEX_SHADER
表示頂點(diǎn)著色器類型
GL_FRAGMENT_SHADER
表示片段著色器類型
檢測(cè)編譯是否成功:
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;
}
3)鏈接著色器程序,代碼會(huì)像這樣:
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
檢測(cè)鏈接是否成功:
int success;
char infoLog[512];
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success)
{
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::LINK_FAILED\n" << infoLog << std::endl;
}
4)激活眼俊、使用程序
glUseProgram(shaderProgram);
把著色器對(duì)象鏈接到程序?qū)ο笠院笠夂荩浀脛h除著色器對(duì)象:
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
我們已經(jīng)把輸入頂點(diǎn)數(shù)據(jù)發(fā)送給了GPU,并指示了GPU如何在頂點(diǎn)和片段著色器中處理它疮胖。但是环戈,OpenGL還不知道它該如何解釋內(nèi)存中的頂點(diǎn)數(shù)據(jù),以及它該如何將頂點(diǎn)數(shù)據(jù)鏈接到頂點(diǎn)著色器的屬性上澎灸。
鏈接頂點(diǎn)屬性
使用glVertexAttribPointer
函數(shù)解析頂點(diǎn)數(shù)據(jù)院塞;
使用glEnableVertexAttribArray
啟動(dòng)頂點(diǎn)屬性,默認(rèn)是禁用的性昭;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
頂點(diǎn)數(shù)組對(duì)象
存儲(chǔ)內(nèi)容:
-
glEnableVertexAttribArray
和glDisableVertexAttribArray
的調(diào)用 - 通過(guò)
glVertexAttribPointer
設(shè)置的頂點(diǎn)屬性配置 - 通過(guò)
glVertexAttribPointer
調(diào)用與頂點(diǎn)屬性關(guān)聯(lián)的頂點(diǎn)緩沖對(duì)象
代碼會(huì)像這樣:
// ..:: 初始化代碼(只運(yùn)行一次 (除非你的物體頻繁改變)) :: ..
// 1. 綁定VAO
glBindVertexArray(VAO);
// 2. 把頂點(diǎn)數(shù)組復(fù)制到緩沖中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 設(shè)置頂點(diǎn)屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: 繪制代碼(渲染循環(huán)中) :: ..
// 4. 繪制物體
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();
繪制三角形
代碼會(huì)像這樣:
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
完整程序
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
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()
{
// glfw: 初始化和配置
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // 主版本號(hào)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // 次版本號(hào)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 核心模式
#ifdef __?__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // macOS中需要加上這行代碼拦止,配置才能生效
#endif
// glfw: 創(chuàng)建窗口對(duì)象
// --------------------
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); // 將窗口的上下文設(shè)置為當(dāng)前線程的主上下文
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// glad: 初始化,加載所有OpenGL函數(shù)指針
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 構(gòu)建和編譯著色器程序
// ------------------------------------
// 頂點(diǎn)著色器
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;
}
// 片段著色器
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;
}
// 鏈接著色器
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);
// 設(shè)置頂點(diǎn)數(shù)據(jù)(和緩沖區(qū))并配置頂點(diǎn)屬性
// ------------------------------------------------------------------
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);
// 首先綁定頂點(diǎn)數(shù)組對(duì)象糜颠,然后綁定和設(shè)置頂點(diǎn)緩沖區(qū)汹族,然后配置頂點(diǎn)屬性。
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, 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);
glBindVertexArray(0);
// 渲染循環(huán)
// -----------
while (!glfwWindowShouldClose(window))
{
// 輸入
// -----
processInput(window);
// 渲染
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 畫(huà)出第一個(gè)三角形
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 交換緩沖區(qū)和輪詢IO事件(按下/釋放鍵其兴,移動(dòng)鼠標(biāo)等)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
// 正確釋放/刪除之前的分配的所有資源
// ------------------------------------------------------------------
glfwTerminate();
return 0;
}
// 處理所有輸入:查詢GLFW是否按下/釋放了此幀的相關(guān)鍵顶瞒,并做出相應(yīng)的反應(yīng)
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
// 在每次窗口大小被調(diào)整的時(shí)候被調(diào)用
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
索引緩沖對(duì)象
和頂點(diǎn)緩沖對(duì)象一樣,EBO也是一個(gè)緩沖元旬,它專門(mén)儲(chǔ)存索引
代碼會(huì)像這樣:
// ..:: 初始化代碼 :: ..
// 1. 綁定頂點(diǎn)數(shù)組對(duì)象
glBindVertexArray(VAO);
// 2. 把我們的頂點(diǎn)數(shù)組復(fù)制到一個(gè)頂點(diǎn)緩沖中榴徐,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 復(fù)制我們的索引數(shù)組到一個(gè)索引緩沖中守问,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 設(shè)定頂點(diǎn)屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: 繪制代碼(渲染循環(huán)中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);