1. 著色器的含義
著色器是GPU上的小程序囚衔,為圖形渲染管線的某個(gè)特定部分運(yùn)行题篷。換句話說(shuō)镇辉,著色器就是將輸入轉(zhuǎn)化為輸出的程序屡穗。
著色器是由一種叫做GLSL的類C語(yǔ)言編寫成的。
2. GLSL
著色器的開(kāi)頭要聲明版本忽肛、輸入輸出村砂、uniform和main函數(shù)。比如頂點(diǎn)著色器屹逛,它的輸入變量叫做頂點(diǎn)屬性础废,我們能聲明的頂點(diǎn)屬性是有上限的汛骂,一般由硬件決定。OpenGL至少確保有16個(gè)包含4分量的頂點(diǎn)屬性可用评腺。
一個(gè)簡(jiǎn)單著色器如下:
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
int main()
{
// 處理輸入并進(jìn)行一些圖形操作
...
// 輸出處理過(guò)的結(jié)果到輸出變量
out_variable_name = weird_stuff_we_processed;
}
2.1 數(shù)據(jù)類型
- 默認(rèn)數(shù)據(jù)類型 :int float double unit bool
- 容器類型:向量(Vector)和矩陣(Matrix)
2.2 輸入輸出
每個(gè)著色器使用關(guān)鍵字in和out決定輸入輸出帘瞭,只要輸出變量與下一個(gè)著色器的輸入相匹配,它就會(huì)不斷傳遞下去蒿讥。頂點(diǎn)著色器與片段著色器會(huì)有點(diǎn)不一樣蝶念。
- 頂點(diǎn)著色器:我們必須使用location這個(gè)一元數(shù)據(jù)去指定輸入變量,這樣我們才可以在GPU上設(shè)置頂點(diǎn)屬性芋绸。頂點(diǎn)著色器需要為它的輸入變量提供額外的layout標(biāo)識(shí)媒殉,這樣我們才能將它鏈接到頂點(diǎn)數(shù)據(jù)。
- 片段著色器:它需要一個(gè)vec4顏色輸出變量摔敛,因?yàn)槠沃髯罱K會(huì)生成顏色适袜,如果你沒(méi)有設(shè)置它的輸出變量,那么OpenGL就會(huì)自動(dòng)將你的物體渲染為黑色或白色舷夺。
所以苦酱,如果我們打算從一個(gè)著色器向另一個(gè)著色器發(fā)送數(shù)據(jù),我們必須在發(fā)送方著色器中聲明一個(gè)輸出给猾,在接收方著色器中聲明一個(gè)類似的輸入疫萤。當(dāng)類型和名字都一樣的時(shí)候,OpenGL就會(huì)把兩個(gè)變量鏈接到一起敢伸,它們之間就能發(fā)送數(shù)據(jù)了(這是在鏈接程序?qū)ο髸r(shí)完成的)扯饶。
- 頂點(diǎn)著色器
#version 330 core
layout (location = 0) in vec3 aPos; // 位置變量的屬性位置值為0
out vec4 vertexColor; // 為片段著色器指定一個(gè)顏色輸出
void main()
{
gl_Position = vec4(aPos, 1.0); // 注意我們?nèi)绾伟岩粋€(gè)vec3作為vec4的構(gòu)造器的參數(shù)
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把輸出變量設(shè)置為暗紅色
}
- 片段著色器
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // 從頂點(diǎn)著色器傳來(lái)的輸入變量(名稱相同、類型相同)
void main()
{
FragColor = vertexColor;
}
-
最終效果
2.3 Uniform
Uniform是一種從CPU應(yīng)用向GPU著色器發(fā)送數(shù)據(jù)的一種方式池颈。Uniform是全局的尾序,在某一著色器里聲明了它,其他著色器就可以使用它躯砰。
比如下面我們將在片段著色器里聲明一個(gè)Uniform
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 在OpenGL程序代碼中設(shè)定這個(gè)變量
void main()
{
FragColor = ourColor;
}
下面我們來(lái)看一個(gè)特殊的函數(shù)glUniform4f哈打,4f代表什么?4f其實(shí)就是函數(shù)需要4個(gè)float作為它的值奴拦。
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
如下是我們函數(shù)名可能有的后綴
下面我們打算當(dāng)每一次渲染迭代都更新Uniform
while(!glfwWindowShouldClose(window))
{
// 輸入
processInput(window);
// 渲染
// 清除顏色緩沖
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 記得激活著色器
glUseProgram(shaderProgram);
// 更新uniform顏色
float timeValue = glfwGetTime();
float greenValue = sin(timeValue) / 2.0f + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
// 繪制三角形
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 交換緩沖并查詢IO事件
glfwSwapBuffers(window);
glfwPollEvents();
}
2.4 從文件流中讀取著色器內(nèi)容
Shader(const char* vertexPath, const char* fragmentPath)
{
// 1. 從文件路徑中獲取頂點(diǎn)/片段著色器
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// 保證ifstream對(duì)象可以拋出異常:
vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
try
{
// 打開(kāi)文件
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// 讀取文件的緩沖內(nèi)容到數(shù)據(jù)流中
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// 關(guān)閉文件處理器
vShaderFile.close();
fShaderFile.close();
// 轉(zhuǎn)換數(shù)據(jù)流到string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch(std::ifstream::failure e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
[...]
3. 頂點(diǎn)緩沖對(duì)象(VBO)
3.1 定義
頂點(diǎn)緩沖對(duì)象是在顯卡里開(kāi)辟一塊內(nèi)存畦攘,用于存儲(chǔ)頂點(diǎn)的各類屬性信息。在渲染時(shí)李茫,可以直接從VBO中取出頂點(diǎn)數(shù)據(jù)進(jìn)行渲染揭保,由于頂點(diǎn)數(shù)據(jù)存儲(chǔ)在顯卡中而不是內(nèi)存中,不需要從CPU傳輸數(shù)據(jù)魄宏,渲染效率更高秸侣。
每個(gè)VBO在OpenGL里都有對(duì)應(yīng)的ID,這個(gè)ID對(duì)應(yīng)VBO的顯存地址,通過(guò)這個(gè)ID可以對(duì)VBO的數(shù)據(jù)進(jìn)行存取
3.2 VBO的創(chuàng)建和配置
- 開(kāi)辟顯存空間并分配VBO的ID
//創(chuàng)建vertex buffer object對(duì)象
GLuint vboId;//vertex buffer object句柄
glGenBuffers(1, &vboId);
- 創(chuàng)建后需要通過(guò)分配的ID來(lái)綁定VBO味榛,對(duì)于同一種類型的頂點(diǎn)數(shù)據(jù)一次只能綁定一個(gè)VBO方篮。
第一個(gè)參數(shù)指的是綁定的數(shù)據(jù)類型:GL_ARRAY_BUFFER(頂點(diǎn)數(shù)組傳值), GL_ELEMENT_ARRAY_BUFFER(索引數(shù)組傳值), GL_PIXEL_PACK_BUFFER,GL_PIXEL_UNPACK_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, vboId);
- 將用戶數(shù)據(jù)傳輸?shù)疆?dāng)前綁定的顯存緩沖區(qū)中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
- 頂點(diǎn)數(shù)據(jù)傳入GPU后励负,還需要告訴OpenGL如何解析這些頂點(diǎn)數(shù)據(jù)
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
第一個(gè)參數(shù)指定頂點(diǎn)屬性位置(與頂點(diǎn)著色器的layout(location=0)有關(guān))
第二個(gè)參數(shù)指定頂點(diǎn)數(shù)據(jù)的大小
第三個(gè)參數(shù)指定頂點(diǎn)數(shù)據(jù)類型
第四個(gè)參數(shù)指定頂點(diǎn)數(shù)據(jù)是否被標(biāo)準(zhǔn)化
第五個(gè)參數(shù)是步長(zhǎng)藕溅,指定頂點(diǎn)之間的間隔
第六個(gè)參數(shù)是位置數(shù)據(jù)在緩沖區(qū)起始的偏移量
頂點(diǎn)屬性glVertexAttribPointer默認(rèn)是關(guān)閉的,使用時(shí)要以頂點(diǎn)屬性位置值為參數(shù)調(diào)用glEnableVertexAttribArray開(kāi)啟继榆。如glEnableVertexAttribArray(0)巾表;
總的來(lái)講,VBO的配置如下:
- 開(kāi)辟顯存空間并分配ID
- 通過(guò)分配的ID來(lái)綁定VBO
- 傳入用戶數(shù)據(jù)到綁定的顯存緩沖區(qū)BO
- 設(shè)置OpenGL如何解析頂點(diǎn)數(shù)據(jù)
4. 頂點(diǎn)數(shù)組對(duì)象(VAO)
4.1 優(yōu)點(diǎn)
當(dāng)我們使用VBO時(shí)略吨,每次繪制模型的時(shí)候都需要綁定頂點(diǎn)的所有信息集币,數(shù)據(jù)量大的時(shí)候就顯得十分麻煩。VAO可以將所有的配置信息都保存在對(duì)象中翠忠,下一次繪制模型的時(shí)候直接綁定VAO對(duì)象就可以了鞠苟。
4.2 含義
VAO保存了所有頂點(diǎn)屬性的狀態(tài)集合,它存儲(chǔ)了頂點(diǎn)數(shù)據(jù)的格式以及頂點(diǎn)數(shù)據(jù)所需的VBO對(duì)象的引用.它不能單獨(dú)使用秽之,都是結(jié)合VBO來(lái)一起使用的
4.3 VAO的配置
- 生成一個(gè)VAO對(duì)象并綁定VAOID当娱,之后我們所有關(guān)于頂點(diǎn)數(shù)據(jù)的設(shè)置都會(huì)被存儲(chǔ)在VAO中,在設(shè)置完成之后一般會(huì)解綁VAO考榨,然后在需要繪制的時(shí)候啟用相應(yīng)的VAO對(duì)象跨细。
//創(chuàng)建VAO
GLuint VAO;
glGenVertexArrays(1, &VAO);
//設(shè)置當(dāng)前VAO,之后所有操作(注意:這些操作必須是上文VAO中包含的內(nèi)容所注明的調(diào)用河质,其他非VAO中存儲(chǔ)的內(nèi)容即使調(diào)用了也不會(huì)影響VAO)存儲(chǔ)在該VAO中
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); //設(shè)置了VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//設(shè)置VBO中的數(shù)據(jù)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); //設(shè)置頂點(diǎn)屬性(索引為0的屬性冀惭,與shader中的內(nèi)容有交互)
glEnableVertexAttribArray(0); //設(shè)置開(kāi)啟頂點(diǎn)屬性(索引為0的屬性,與shader中的內(nèi)容有交互)
glBindVertexArray(0); //解綁VAO(解綁主要是為了不影響后續(xù)VAO的設(shè)置,有點(diǎn)類似于C++中指針delete后置空掀鹅,是個(gè)好習(xí)慣)
當(dāng)我們需要繪制的時(shí)候
glUseProgram(shaderProgram);
glBindVertexArray(VAO); //綁定我們需要的VAO散休,會(huì)導(dǎo)致上面所有VAO保存的設(shè)置自動(dòng)設(shè)置完成
someOpenGLFunctionThatDrawsOurTriangle();
glBindVertexArray(0); //解綁VAO
VAO對(duì)象中主要包含以下幾個(gè)信息:
- VAO開(kāi)啟或關(guān)閉的狀態(tài)(glEnableVertexAttribArray和glDisableVertexAttribArray)
- 使用glVertexAttribPointer設(shè)置頂點(diǎn)屬性信息
- 存儲(chǔ)頂點(diǎn)數(shù)據(jù)的VBO對(duì)象
4. 索引緩沖對(duì)象(EBO)
4.1 含義
索引緩沖對(duì)象是為了解決同一頂點(diǎn)重復(fù)調(diào)用的問(wèn)題,可以減少內(nèi)存浪費(fèi)提高執(zhí)行效率乐尊。當(dāng)需要使用重復(fù)頂點(diǎn)的時(shí)候戚丸,可以通過(guò)頂點(diǎn)索引來(lái)調(diào)用頂點(diǎn),而不是重復(fù)記錄科吭。
是不是看起來(lái)有點(diǎn)糊里糊涂昏滴,下面我們用一個(gè)小例子來(lái)說(shuō)明索引緩存對(duì)象的用處。
比如我們?cè)诶L制一個(gè)正方體的時(shí)候对人,它的結(jié)構(gòu)如下:
正方體的頂點(diǎn)只有八個(gè),但是我們?cè)跇?gòu)造它的數(shù)組的時(shí)候可以發(fā)現(xiàn)有許多重復(fù)的值拂共,最好的方式應(yīng)該是每個(gè)頂點(diǎn)只需要存儲(chǔ)一次牺弄,當(dāng)我們需要這些頂點(diǎn)時(shí),只需要調(diào)用頂點(diǎn)的索引來(lái)引用的需要的頂點(diǎn)數(shù)據(jù)
GLfloat vertices[] = {
//前
-1,-1,1, //v4
1, -1, 1 //v5
1, 1, 1 //v6
-1, 1, 1 //v7
//左
-1,1,-1 //v0
-1,-1,1 //v4
-1,1,1 //v7
-1,1,-1 //v3
......
};
4.2 索引的使用
- 創(chuàng)建EBO(與VBO類似)
GLuint eboID;
glGenBuffers(1, &eboID);
- 綁定EBO宜狐,傳入索引數(shù)據(jù)到EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //綁定EBO
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//傳入索引數(shù)據(jù)到EBO中
- 繪制幾何體(glDrawElements势告,如果不使用EBO蛇捌,就是使用glDrawArrays進(jìn)行繪制)
void glDrawElements(
GLenum mode, //繪制模式,可以是GL_TRIANGLES咱台、GL_POINTS等
GLsizei count, //繪制頂點(diǎn)的次數(shù)
GLenum type, //索引數(shù)據(jù)的類型
const GLvoid * indices //EBO中的偏移量(如果不使用EBO络拌,那么indices指向的是索引數(shù)組的指針)
);
當(dāng)我們使用了EBO后,VAO中也會(huì)存儲(chǔ)EBO的信息
引用:
OpenGL緩沖區(qū)對(duì)象之VAO
OpenGL緩沖區(qū)對(duì)象之EBO
OpenGL圖形渲染管線回溺、VBO春贸、VAO、EBO概念及用例
官方文檔-著色器