如何正確理解 opengl 的 vao ?

正常的opengl vao操作是 在bind 和 unbind之間,去初始化及綁定一系列相關的vbo,那麼最後,opengl在顯存里記錄的到底是什麼數據,這些vbo?還是類似於顯示列表,記錄了這之間的操作?比如,我在vao 的bind 和 unbind之間,調用 glGenBuffer(),是不是每次綁定該vao時都會重新 gen 一次buffer?


VAO 記錄的是

  1. vertex attribute 的格式,由 glVertexAttribPointer 設置。

  2. vertex attribute 對應的 VBO 的名字, 由一對 glBindBuffer 和 glVertexAttribPointer 設置。
  3. #當前#綁定的 GL_ELEMENT_ARRAY_BUFFER 的名字,由 glBindBuffer 設置。

VAO 中並不保存#當前#綁定的 GL_ARRAY_BUFFER,VBO 和 vertex attribute 的綁定是在 glVertexAttribPointer 中完成的。

完整信息參考 Vertex Specification


剛看到這部分,嘗試解釋一下,歡迎指正!

OpenGL在畫畫的時候,要用到很多的信息,如頂點的坐標、紋理、顏色等等.... 假如一個頂點包含了坐標和顏色的信息,那麼這個頂點處的數據格式可能如下:

x, y, z, r, g, b

這些值會被存儲在一個叫VBO (vertex buffer object)的地方。

因而,可以把VBO理解成一個數據區域,這裡面存放了渲染所需要的一切信息。但需要注意的是,數據在VBO裡面的時候,OpenGL是並不知道這裡面的每個數據所代表的具體含義的,只是一堆數值罷了。

這時候,VAO(vertex array object)就派上用場了。

VAO指定了讀取VBO的方式~~

假如VBO裡面現在存放了如下的內容:

x0, y0, z0, r0, g0, b0

x1, y1, z1, r1, g1, b1

x2, y2, z2, r2, g2, b2

VAO可以定義一種格式說,我每次只取VBO裡面的x,y,z的值;也可以定義一種格式說,每次只取VBO裡面的r,g,b的值。這樣我們就可以在渲染的時候,綁定不同的VAO,實現按照不同的格式將VBO裡面的東西渲染出來~

通過定義VBO裡面數據存放的方式、VAO的格式以及合適的GLSL 語言,就能夠靈活地畫出我們所需要的各種圖形。


畫一張圖就很好理解了 所謂的VAO定義了頂點屬性 而VBO只是一堆本質上沒區別的數據


正常的opengl vao操作是 在bind 和
unbind之間,去初始化及綁定一系列相關的vbo,那麼最後,opengl在顯存里記錄的到底是什麼數據,這些vbo?還是類似於顯示列表,記錄了這
之間的操作?比如,我在vao 的bind 和 unbind之間,調用 glGenBuffer(),是不是每次綁定該vao時都會重新 gen
一次buffer?

--

vao,就是頂點數組。

綁定,就是指定你當前要操作的對象。(opengl 只有一個當前對象,把它想像成 current object,你的後續操作,都是在對這個 object 進行。你可以擁有很多個 object,然後用 bind 來指定 current object,所以你後面的操作,就不需要點名你在操作誰了)。

用綁定,可以隨時的在多個 object 之間切換當前對象。

genXXXXX,會返回一個整數形式的 ID(handle),這個數是這個對象的 ID,有如身份證號。但是由於我們還沒有給這個對象賦實際數據,所以我們可以設想,這類似於像 opengl 聲明了這個對象的存在(申領一個 unique id)。好比有了一個箭頭,但是箭頭另一端沒有掛接數據。

vao,就是一堆頂點,組成的一個數組。

那麼我們就得知道這裡的頂點是什麼,所謂頂點,就是一個點,對每個這樣的點,上面掛接了多個數據(通常都是1~4維向量),這些數據成為屬性。我們提到具體哪個屬性時,用索引值來標識。

比如說,坐標,就是頂點的一個必有的屬性,通常是第一個屬性,所以:

頂點.屬性[0] --&> [x,y,z];

除了坐標,還可以有顏色 [r,g,b], 紋理坐標 [u,v] ,法線方向等。

總之,頂點,有多個向量形式的屬性,多個頂點,組成一個 va。所以一個 va,想像成就是一個箭頭,指向了具有多個格子的指針數組,每一個格子又有一個箭頭,指向一堆向量形式的屬性(依次屬於每個頂點)。

對一個 vao,申領了 ID 以後,只是一個懸空的箭頭,所以要給這個 vao 設置屬性,通常至少要有坐標這個屬性。

所以需要先創建一個 buffer,把數據傳遞到 buffer,

genBuffer, 給 buffer 申領一個 id,

BufferData ,給 buffer 賦值(向 buffer 中拷貝數據)。這一步可能才是(opengl / 顯卡)實際分配內存。

VertextAttribPoint,把這個 buffer 設置為某個 vao 的某個屬性。這樣,vao 的某個屬性就有了實際數據。

畫個示意圖是這樣:

___________
vao ---&> | attrib[0] |--- vbo ---&> vec3 array { {x,y,z}, ... }; //3d-坐標
|-----------|
| attrib[1] |--- vbo ---&> vec2 array { {x,y}, ... }; //whatever
|-----------|
| ... |
|-----------|
| attrib[k] |--- vbo ---&> vec4 array { {x,y,z,w}, ... };
~~~~~~~~~~~

當然,這些屬性具體是什麼含義,主要取決於你怎麼(在 shader 中)使用它們。這些屬性,在 pipeline 中,送給 vertext shader 作為可使用的輸入數據。比如說,vertex shader 中可以用一個4x4 計算出來的矩陣,乘以頂點的屬性0,把模型坐標變換到窗口的客戶區坐標。

所以當你對某個 vao 設置完屬性以後,opengl/顯卡就已經知道了這些信息,所以除非你需要修改,否則你只需要做一次就夠了,然後你可以把它切換成非 current 對象。需要的時候隨時可以 bind 到前台使用。

當然了,實際存儲大量數據和需要較大內存的,是 vbo,也就是 buffer。一個 buffer 被掛接給某個 vao 的某個屬性。我們就可以因此知道這個頂點的屬性(數據)。

當你不需要對象的時候,要記得 delete 。申領的 id,只是交還給系統(標示為可以分配給用戶)。對於 buffer,會釋放 buffer 佔用的內存。


(1)顯示列表是一組預先編譯好的OpenGL函數語句,是記錄的一組過程;而VAO對應的是一組內存緩存區組成的對象,是在顯存里劃分的。

(2)VAO是一個對象,一個VAO可以包含都多個VBO,是一種關聯關係;而VBO又可以有保存不同的類型,包括頂點坐標、UV、法線、顏色、索引等,通過glBufferData給VBO可以指定相應的類型,再通過glVertexAttribPointer給VBO指定相應的數據格式。

(3)glBindVertexArray 可以設置當前綁定的VAO,然後glGenBuffers生成VBO後,通過glBindBuffer能夠關聯到當前的VAO。VAO里的VBOs都設置好了以後,在繪製的地方只需要設置當前綁定的VAO是哪個,就能按照初始化的VAO來繪製,即調用glDrawArrays。也就是說在初始化的時候,事先確定VAO的格式,在需要的調用的時候,只需要綁定這個特定的VAO就好。

(4)glBindVertexArray(0)表示當前不用VAO了,並不是VAO釋放了,所以不是你說的那樣要一直「bind,unbind,重新Gen」,而是在顯存里專門開闢好了這種VAO對應的格式,只需要填數據就好了,而VAO對象是作為一個整體在合適的時候bind。


看過一個教程。。

它說

vao是modernGL中取得比爛還有爛得不行的名字之一

因為這名字沒說出一點這是做啥事的。。。


我覺得vertex array是vertex buffer的meta data,以便與頂點shader的輸入屬性相對應。

這是因為vertex buffer既沒有成員信息也沒有類型信息,完全是裸的一塊binary data,而頂點shader的輸入需要成員類型、大小、組織方式等等。


我盡量說的簡單一點

VAO是一個狀態集,標記的是當前GL_ARRAY_BUFFER Target的數據狀態。

而單純的VBO只是一塊Data,並沒有狀態。

這句話的意思是,在你的程序中你可以gen N個 VAO,也可以gen N個VBO,但是,起作用的只是你bind到Target上的那一個。glBindVertexArray 為什麼沒有Target參數,因為Target只有一種。

@MichaelCheng你說的第三點是錯誤的,沒有這麼強的耦合關係

VAO只認GL_ARRAY_BUFFER Target,不認VBO。

VAO和VBO之間的耦合度相對是非常低的,依賴關係是靠Render Context,而不是對象本身。


問題:正常的opengl vao操作是 在bind 和 unbind之間,去初始化及綁定一系列相關的vbo,那麼最後,opengl在顯存里記錄的到底是什麼數據,這些vbo?還是類似於顯示列表,記錄了這之間的操作?比如,我在vao 的bind 和 unbind之間,調用 glGenBuffer(),是不是每次綁定該vao時都會重新 gen 一次buffer

回答:

前提:下面說的都是現代的基於Shader下的VAO,它包括了VertexArrayObject和VertexAttribute, 而非老版的CPU端的需要glEnableClientState的VAO(VertexArrayObject)。

第一個問題:顯存里記錄的是VBO圖形數據塊,和VAO索引數據。關於VAO索引數據結構

見:Vertex Specification

結構如下:

struct VertexAttribute

{

bool bIsEnabled = GL_FALSE;

int iSize = 4; //This is the number of elements in this attribute, 1-4.

unsigned int iStride = 0;

VertexAttribType eType = GL_FLOAT; // 頂點屬性類型

bool bIsNormalized = GL_FALSE;

bool bIsIntegral = GL_FALSE;

void * pBufferObjectOffset = 0; // 關聯的VBO下的偏移

BufferObject * pBufferObj = 0; // 關聯的VBO

};

struct VertexArrayObject

{

BufferObject *pElementArrayBufferObject = NULL;

VertexAttribute attributes[GL_MAX_VERTEX_ATTRIB];

}

第二個問題:不是類似顯示列表,顯示列表不僅記錄了數據(而且這些數據不再需要解析格式),還記錄了draw call渲染命令,放置在顯存中躺著,激活時候就是直接繪製。

而VBO, VAO顯存里記錄的是VBO圖形數據塊,和VAO索引數據,並沒有你說的之間的操作draw call調用。VAO draw call時glBindVertexArray 的時候將躺在顯存中的該VAO數組索引結構激活,將關聯的VBO數據按照VAO索引結構來取得數據(shader取得頂點時候是並行獨立的取得完整一個頂點信息的,頂點信息內部各個屬性pos,uv,color,等可能在不同的VBO,那麼按照根據VertexAttribute 數組索引,迭代循環取得頂點數據即可)。

第三個問題:

每次綁定該vao時都會重新 gen 一次buffer?不會的。但VAO索引結構中記錄的狀態設置卻是會重新激活的,關聯的VBO數據卻是沒有重新申請標示符,申請和填充數據塊的。因為glBufferData在CPU中存放的數據在第一次載入後,都直接丟棄了。見:https://www.opengl.org/sdk/docs/man/html/glBindVertexArray.xhtml

glBindVertexArray binds the vertex array object with name array. array is the name of a vertex array object previously returned from a call toglGenVertexArrays。If the bind is successful no change is made to the state of the vertex array object, and any previous vertex array object binding is broken.綁定只是設置當前VAO是該VAO和激活VAO相關的狀態設置,例如:glEnableVertexAttribArray狀態。但是不會重新生成VBO和申請填充VBO中的數據塊。


閱讀了多個回答以及其他文章,我寫了寫自己的理解:

OpenGL VAO, VBO 使用簡介


vao綁定到vbo,將頂點和索引數據壓縮到一個VBO中,試過不。 哈哈~


推薦閱讀:

一直說要對不同渲染狀態的對象進行排序 但是排序的優先順序應該是什麼呢?
Minecraft那些方塊用OpenGL怎麼畫比較好?
Unity-Shader和OpenGL-Shader有什麼不同之處?
Shader 在現在圖形管線中可以負責多少部分?

TAG:OpenGL | 計算機圖形學 |