OpenGL日常-三角形(下)

大家好,歡迎來到聽風的OpenGL日常。

寫在前面

本文代碼

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

本文重點

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

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

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

在講清楚這個概念之前,我們還要補充一些概念;

glBegin/glEnd

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

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

這樣每進行一次glVertex調(diào)用就會向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

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

VA

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

002.png

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

// 每次繪制都將 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的特點,既方便傳輸,又要兼顧修改。

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

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

003.png

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

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

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

// 繪制時直接從VBO中取得頂點數(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進行渲染沒有可以參考的代碼,本人也是進行了實驗但是最終沒有畫出來,希望有厲害的小伙伴可以一起來交流一下這個問題,這里就留作一個探索。

VAO

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

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

VAO.png

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

本文中,我們先分別生成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è)置對緩沖區(qū)訪問的步長為3以及相位為0,告訴著色器,這個數(shù)據(jù)輸入到著色器的第一個(索引為0)輸入變量,數(shù)據(jù)的長度是3個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"是頂點著色器代碼中的頂點,還記得嗎?

004.png

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

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

重點討論一下這個函數(shù):

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

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

005.png

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

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

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

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

res.png

EBO

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

個人覺得索引這個概念特別好,將頂點用索引作標記,當使用頂點時用索引間接訪問,如圖:

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, // 第一個三角形
    1, 2, 3  // 第二個三角形
};

建立索引緩沖對象:

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

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

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

最終進行繪制:

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

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

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

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

res2.png

總結(jié)

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

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

本文參考:

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

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

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