OpenGL日常-三角形(下)

大家好,歡迎來到聽風(fēng)的OpenGL日常。

寫在前面

本文代碼

上回說到,預(yù)期的三角形并沒有,并沒有,并沒有渲染出來,今天我們來補(bǔ)充下,看看問題出在哪里。

本文重點(diǎn)

我們先來搞清楚VAO,VBO緩存到底做的是什么工作?

首先是VBO(vertex buffer object),為什么我們要用VBO?

不使用VBO時(shí),我們每次繪制(glDrawArrays)圖形時(shí)都是從本地內(nèi)存處獲取頂點(diǎn)數(shù)據(jù)然后傳輸給OpenGL來繪制,這樣就會(huì)頻繁的操作CPU->GPU增大開銷,從而降低效率。
使用VBO,我們就能把頂點(diǎn)數(shù)據(jù)緩存到GPU開辟的一段內(nèi)存中,然后使用時(shí)不必再?gòu)谋镜孬@取,而是直接從顯存中獲取,這樣就能提升繪制的效率。

在講清楚這個(gè)概念之前,我們還要補(bǔ)充一些概念;

glBegin/glEnd

以此文的例子解釋,我們?cè)趥鬟f頂點(diǎn)位置數(shù)據(jù)的時(shí)候,在OpenGL舊版本里,是通過glVertex逐個(gè)從CPU傳遞到GPU的,代碼示例如下:

glBegin(GL_TRIANGLES);
    glVertex(0.0f, 0.0f);
    glVertex(1.0f, 0.0f);
    glVertex(0.0f, 1.0f);
glEnd();

這樣每進(jìn)行一次glVertex調(diào)用就會(huì)向GPU傳遞一次,由于傳輸是同步的,所以效率很低;

000.png

于是:

DL

Display List(顯示列表)的出現(xiàn)可以使CPU在傳輸?shù)倪^程中等待數(shù)據(jù)打包完成,待其結(jié)束一次性發(fā)送到GPU,代碼如:

GLuint listName = glGenLists (1);
glNewList (listName, GL_COMPILE);
    glBegin (GL_TRIANGLES);
        glVertex2f (0.0, 0.0);
        glVertex2f (1.0, 0.0);
        glVertex2f (0.0, 1.0);
    glEnd ();
glEndList ();
...
// 繪制(不傳輸數(shù)據(jù))
glCallList(listName);
001.png

顯示列表加快了傳輸效率,但是繪制時(shí)是一次性的,那么如果列表中的某單個(gè)頂點(diǎn)發(fā)生變化時(shí),那么就需要CPU重新生成新的頂點(diǎn)再發(fā)送到GPU,GPU收集完成后完成繪制,這樣做是極其浪費(fèi)資源的,當(dāng)每一幀都有變化時(shí),它就退化成了單個(gè)頂點(diǎn)傳輸?shù)姆绞健?/p>

VA

Vertex Array,頂點(diǎn)數(shù)據(jù)要區(qū)別于我們開頭提到的VAO,它跟緩存是沒有關(guān)系的,它也是一種傳輸方案。VA也是通過收集頂點(diǎn)的方式來減少傳輸次數(shù),但與顯示列表不同的是,CPU端將會(huì)負(fù)責(zé)收集所有頂點(diǎn),收集完成后一次性傳輸?shù)紾PU再進(jìn)行繪制。

002.png

這樣做導(dǎo)致的結(jié)果是,每次進(jìn)行繪制時(shí),都會(huì)進(jìn)行一次傳輸,所以繪制速度會(huì)低于顯示列表。

// 每次繪制都將 vertices 傳輸一次
GLfloat vertices[] = {
    0.0f, 0.0f,
    1.0f, 0.0f, 
    0.0f, 1.0f
}
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2,GL_FLOAT,0,vertices);
glDrawArray(GL_TRIANGLES, 0, 3); 

VBO

VBO是結(jié)合DL和VA的特點(diǎn),既方便傳輸,又要兼顧修改。

由于VA在CPU收集的頂點(diǎn)是一個(gè)整體,所以在GPU向渲染流水線提交數(shù)據(jù)是由一個(gè)整體提交的,無法在渲染時(shí)做修改;而DL雖然可以單個(gè)修改,但是渲染時(shí)卻需要等待CPU端修改完成等GPU端收集完成再進(jìn)行。

為了既提高傳輸效率,又可以使渲染時(shí)數(shù)據(jù)在GPU端也可以修改,VBO應(yīng)運(yùn)而生。

003.png

這樣一來VBO保存了一份頂點(diǎn)數(shù)據(jù),修改操作可以直接在GPU上進(jìn)行,修改完成直接繪制。

所以按照這樣理解,它的傳輸與修改是分開的,體現(xiàn)在代碼上:

//生成VBO,并傳輸保存到GPU上
GLuint vbo;
glGenBuffer(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
...

// 繪制時(shí)直接從VBO中取得頂點(diǎn)數(shù)據(jù)
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2, (void*)0);
glDrawArray(GL_TRIANGLES, 0, 3);

...

但是這里只用VBO進(jìn)行渲染沒有可以參考的代碼,本人也是進(jìn)行了實(shí)驗(yàn)但是最終沒有畫出來,希望有厲害的小伙伴可以一起來交流一下這個(gè)問題,這里就留作一個(gè)探索。

VAO

本文最終采用的是VAO結(jié)合VBO畫出的三角形,上篇我們討論過了,結(jié)果沒有出來,今天我們把坑填上。

Vertex Array Object,頂點(diǎn)數(shù)據(jù)對(duì)象是為了簡(jiǎn)化VBO的流程,當(dāng)所要傳輸?shù)腣BO有很多的時(shí)候,我們需要管理多個(gè)VBO,這樣對(duì)每個(gè)VBO都進(jìn)行記錄會(huì)比較亂,我們首先想到的管理方式就是用一個(gè)數(shù)組將它們保存起來,沒錯(cuò),這就是VAO,很直觀。

VAO.png

如圖所示,VAO保存了不同VBO的指針,用戶可以通過這些指針來對(duì)數(shù)據(jù)進(jìn)行操作。

本文中,我們先分別生成VAO和VBO,將VBO綁定到VAO中,將VAO綁定到緩存里

//VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

//VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//設(shè)置對(duì)緩沖區(qū)訪問的步長(zhǎng)為3以及相位為0,告訴著色器,這個(gè)數(shù)據(jù)輸入到著色器的第一個(gè)(索引為0)輸入變量,數(shù)據(jù)的長(zhǎng)度是3個(gè)float
GLuint uPos = glGetAttribLocation( shaderProgram, "aPos" );
glVertexAttribPointer(uPos, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(uPos);

//delete buffer and array
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

這里,glGetAttribLocation( shaderProgram, "aPos" );一句中,shaderProgram就是我們上節(jié)中編譯好的OpenGL程序,"aPos"是頂點(diǎn)著色器代碼中的頂點(diǎn),還記得嗎?

004.png

這里的location你可以修改下它的值,看看結(jié)果uPos的值,會(huì)有助于理解它的意義,當(dāng)然著色器關(guān)鍵字這里先不進(jìn)行討論。

glVertexAttribPointer(uPos, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

重點(diǎn)討論一下這個(gè)函數(shù):

第一個(gè)參數(shù):對(duì)應(yīng)著色器代碼中的location;
第二個(gè)參數(shù):頂點(diǎn)屬性的大小,在這里是vec3,所以是3;
第三個(gè)參數(shù):數(shù)據(jù)類型,沒什么好說的;
第四個(gè)參數(shù):定義是否希望數(shù)據(jù)被標(biāo)準(zhǔn)化(Normalize)。如果我們?cè)O(shè)置為GL_TRUE,所有數(shù)據(jù)都會(huì)被映射到0(對(duì)于有符號(hào)型signed數(shù)據(jù)是-1)到1之間;
第五個(gè)參數(shù):第五個(gè)參數(shù)叫做步長(zhǎng)(Stride),還是用圖來解釋一下吧,比較直觀;
第六個(gè)參數(shù):表示位置數(shù)據(jù)在緩沖中起始位置的偏移量(Offset),當(dāng)有多個(gè)VBO里,可以通過偏移量進(jìn)行位置鎖定;

上面的第五個(gè)參數(shù)中的步長(zhǎng)為3,即在VBO里每一個(gè)頂點(diǎn)占12個(gè)位置,每個(gè)位置所占字節(jié)由其保存數(shù)據(jù)類型決定。

005.png

函數(shù)glVertexAttribPointer給出了如何從VBO中取得頂點(diǎn)數(shù)據(jù)的方式,所謂的OpenGL位置學(xué)(我又開始胡說八道了)。

接下來,快結(jié)束戰(zhàn)斗了,畫三角形吧。

glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays( GL_TRIANGLES, 0, 3 );

那么我們就愉快的結(jié)束了。等下?。?!

res.png

EBO

對(duì)于VAO的這種方式,我們有點(diǎn)小想法,現(xiàn)有一個(gè)問題,如果我們要畫兩個(gè)三角形,你覺得最少可以用幾個(gè)頂點(diǎn)呢?答案肯定是4個(gè)。但是如果用前面的方法,恐怕我們需要至少6個(gè)來完成,那么EBO,索引緩沖對(duì)象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO)的出現(xiàn)就是為了重復(fù)利用頂點(diǎn)。

個(gè)人覺得索引這個(gè)概念特別好,將頂點(diǎn)用索引作標(biāo)記,當(dāng)使用頂點(diǎn)時(shí)用索引間接訪問,如圖:

EBO.png

我們來定義一下數(shù)據(jù)和索引;

float vertices2[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = { // 注意索引從0開始! 
    0, 1, 3, // 第一個(gè)三角形
    1, 2, 3  // 第二個(gè)三角形
};

建立索引緩沖對(duì)象:

unsigned int EBO;
glGenBuffers(1, &EBO);

接下來同VBO類似,將索引復(fù)制到緩沖里,

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

最終進(jìn)行繪制:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

數(shù)據(jù)調(diào)用的過程是,繪制時(shí)先從EBO從找到索引,再通過VAO中找到對(duì)應(yīng)的VBO中的頂點(diǎn),glDrawElements的參數(shù):

第一個(gè):與glDrawArrays一樣,設(shè)置顯示模式;
第二個(gè):總共要繪制的頂點(diǎn)的個(gè)數(shù);
第三個(gè):索引的類型;
第四個(gè):指定EBO中的偏移量,類似于VBO中的偏移量;

好了,就到這吧,EBO的部分不多做解釋了;

res2.png

總結(jié)

BE -> DL -> VA -> VBO -> VAO -> EBO;

如果你最終懂得了這個(gè)鏈條的來源,那么恭喜你已經(jīng)理解了。

本文參考:

最后這篇復(fù)現(xiàn)沒有成功,希望有復(fù)現(xiàn)的朋友可以給出點(diǎn)提示。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容