從GLSL著色器到VAO/VBO/EBO知識(shí)梳理

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ù)名可能有的后綴


函數(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的配置如下:

  1. 開(kāi)辟顯存空間并分配ID
  2. 通過(guò)分配的ID來(lái)綁定VBO
  3. 傳入用戶數(shù)據(jù)到綁定的顯存緩沖區(qū)BO
  4. 設(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è)信息

  1. VAO開(kāi)啟或關(guān)閉的狀態(tài)(glEnableVertexAttribArray和glDisableVertexAttribArray)
  2. 使用glVertexAttribPointer設(shè)置頂點(diǎn)屬性信息
  3. 存儲(chǔ)頂點(diǎn)數(shù)據(jù)的VBO對(duì)象
頂點(diǎn)數(shù)組與頂點(diǎn)對(duì)象的聯(lián)系

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的信息

VAO

引用:
OpenGL緩沖區(qū)對(duì)象之VAO
OpenGL緩沖區(qū)對(duì)象之EBO
OpenGL圖形渲染管線回溺、VBO春贸、VAO、EBO概念及用例
官方文檔-著色器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遗遵,一起剝皮案震驚了整個(gè)濱河市萍恕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌车要,老刑警劉巖允粤,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異翼岁,居然都是意外死亡类垫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門琅坡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)阔挠,“玉大人,你說(shuō)我怎么就攤上這事脑蠕」汉常” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵谴仙,是天一觀的道長(zhǎng)迂求。 經(jīng)常有香客問(wèn)我,道長(zhǎng)晃跺,這世上最難降的妖魔是什么揩局? 我笑而不...
    開(kāi)封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮掀虎,結(jié)果婚禮上凌盯,老公的妹妹穿的比我還像新娘。我一直安慰自己烹玉,他們只是感情好驰怎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著二打,像睡著了一般县忌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天症杏,我揣著相機(jī)與錄音装获,去河邊找鬼。 笑死厉颤,一個(gè)胖子當(dāng)著我的面吹牛穴豫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逼友,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼精肃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了翁逞?” 一聲冷哼從身側(cè)響起肋杖,我...
    開(kāi)封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挖函,沒(méi)想到半個(gè)月后状植,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怨喘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年津畸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片必怜。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肉拓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梳庆,到底是詐尸還是另有隱情暖途,我是刑警寧澤,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布膏执,位于F島的核電站驻售,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏更米。R本人自食惡果不足惜欺栗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望征峦。 院中可真熱鬧迟几,春花似錦、人聲如沸栏笆。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)竖伯。三九已至存哲,卻和暖如春因宇,著一層夾襖步出監(jiān)牢的瞬間七婴,已是汗流浹背祟偷。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留打厘,地道東北人修肠。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像户盯,于是被迫代替她去往敵國(guó)和親嵌施。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容