如何正確理解 opengl 的 vao ?
正常的opengl vao操作是 在bind 和 unbind之間,去初始化及綁定一系列相關的vbo,那麼最後,opengl在顯存里記錄的到底是什麼數據,這些vbo?還是類似於顯示列表,記錄了這之間的操作?比如,我在vao 的bind 和 unbind之間,調用 glGenBuffer(),是不是每次綁定該vao時都會重新 gen 一次buffer?
VAO 記錄的是
- vertex attribute 的格式,由 glVertexAttribPointer 設置。
- vertex attribute 對應的 VBO 的名字, 由一對 glBindBuffer 和 glVertexAttribPointer 設置。
- #當前#綁定的 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, b0x1, y1, z1, r1, g1, b1
x2, y2, z2, r2, g2, b2VAO可以定義一種格式說,我每次只取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?
綁定,就是指定你當前要操作的對象。(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.xhtmlglBindVertexArray 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 在現在圖形管線中可以負責多少部分?