【影象】【OpenGL】VAO和VBO的關係
注: VAO和VBO都是用來儲存頂點資訊的,並把這些資訊送入頂點著色器。至於什麼是頂點和頂點著色器,這裡就不多說了,不瞭解的讀者可自行CSDN。
VBO的B為Buffer之意,用來儲存頂點資料;VAO的A為Array,但我認為理解為 Attribute(屬性) 之意更好,意思是 Buffer(VBO)的屬性。
即,我們用VBO來儲存資料,通過VAO來告訴計算機這些資料分別有什麼屬性、起什麼作用。
一、VBO的作用
VBO是 CPU 和 GPU 之間傳遞資訊的橋樑,我們把資料存入VBO(這一步在CPU執行),然後VBO會自動把資料送入GPU。
送入GPU這一步,不需任何人為操作,使用者只負責往VBO中存入資料就可以了。如下:

但是,對GPU來說,VBO中存的就只是一堆數字而已,要怎麼解釋它們呢?這就要用到VAO了。
二、VAO的作用
VBO是為了向GPU傳遞頂點資料,那麼VAO就是為了向GPU解釋頂點資料。有的讀者會奇怪,頂點資料無非是一個個三維座標,三個為一組,傳就傳了,為什麼還需要解釋呢?那麼我們來看下面這個VBO中的資料:
float buffer = {
//頂點座標(3個一組) //頂點顏色(3個一組) //紋理座標(2個一組)
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
//以上資料僅為本博文編造,不具有實操意義
如果我們向VBO中傳入了以上buffer,並且VBO把它們送入了GPU。
然而,頂點著色器不知道該如何解釋這些數字,到底是把它們3個一組,還是先3個一組、後2個一組,或者是3個、2個、3個?
GPU並不知道。
這就需要VAO的參與了,它負責告訴GPU,VBO中的資訊到底該以幾個為一組。如下這一段程式,就是把對VBO中資料的描述存到了一個VAO中:
//vertex coord
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// texture coord attribute
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
上面程式看似有三段(glVertexAttribPointer() + glEnableVertexAttribArray()為一段),但它們都存在同一個VAO裡。(這裡就不介紹glVertexAttribPointer()和glEnableVertexAttribArray()函式的作用了,不懂的讀者可以上網去查。)
在一些複雜的OpenGL程式中,VBO可能會有多個,但VAO只有一個。那麼,如何用一個VAO來解釋多個VBO呢?
三、一個VAO和多個VBO
在OpenGL程式中,VBO可能會有多個,但VAO只有一個。那麼,如何用一個VAO來解釋多個VBO呢?
這就涉及到OpenGL的上下文知識:一個完整的OpenGL程式相當於一個容器,我們在用到VAO、VBO時,需要先繫結(Bind操作)、再使用,沒有經過繫結的VAO/VBO是不起作用的。
一個VAO和多個VBO之間的關係大致如下所示:

那麼,根據一個VAO與多個VBO的關係,以及OpenGL上下文(繫結)的知識,用一個VAO來解釋多個VBO的操作流程就如下:
首先,繫結VAO,以告知OpenGL程式該使用這個VAO來對VBO做出解釋。
然後,繫結第一個VBO,向這個VBO中寫入資料,告知VAO該如何解釋這個VBO的資訊;
然後,解綁這個VBO。
然後,繫結第二個VBO,向這個VBO中寫入資料,並在VAO中儲存該如何解釋這個VBO的資訊;
然後,解綁這個VBO。
……
如下一段程式:
unsigned int VBO[2], VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(2, VBO);
//=========================繫結VAO===============================
glBindVertexArray(VAO);
//===============================================================
//=======================繫結第一個VBO============================
glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
//===============================================================
glBufferData(GL_ARRAY_BUFFER, sphereVertices.size() * sizeof(float), &sphereVertices[0], GL_STATIC_DRAW); //向第一個VBO中寫入資料
//================告知VAO該如何解釋第一個VBO的資訊=================
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//===============================================================
//=======================解綁第一個VBO===========================
glBindBuffer(GL_ARRAY_BUFFER, 0);
//===============================================================
//=======================繫結第二個VBO============================
glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
//===============================================================
glBufferData(GL_ARRAY_BUFFER, sizeof(texVertrices), texVertrices, GL_STATIC_DRAW);//向第二個VBO中寫入資料
//================告知VAO該如何解釋第二個VBO的資訊=================
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
//=============================================================
