GL_ARRAY_BUFFER 和 GL_ELEMENT_ARRAY_BUFFER


轉載請注明出處。系列教程: webgl-lesson.wysaid.org

第七話, 了解OpenGL的幾種Array Buffer,實現大量頂點的批量繪制, 以及映射紋理坐標

每一話都間隔很久,其實早就可以更新了,但是一直偷懶,實在不好意思。
本來想第七話講一講繪制茶壺之類的東西,但是剛好前幾天做了個小的網格demo,還是這種原創的東西比較討喜,就拿這個來作為本次的藍本吧
廢話就到這里,這一次的demo原型是下面這個樣子的,但是下面是html5做的,現在要使用WebGL,並且做得更加好看:(請用鼠標拖拽網格)

 

 

TARGET:

如果你還記得我們前面幾章的內容的話,應該知道,通常我們繪制部分會有類似於下面的代碼:

 

 

前面一直沒有解釋過為什么要這樣用,本章將對此作出一些解釋。並以此為主要功能制作一個小demo.

GL_ARRAY_BUFFER 和 GL_ELEMENT_ARRAY_BUFFER

假設本章講到的WebGL的Context對象就叫webgl,那么webgl.ARRAY_BUFFER和webgl.ELEMENT_ARRAY_BUFFER就分別指的GL_ARRAY_BUFFER和GL_ELEMENT_ARRAY_BUFFER這兩個東西了。
webgl.bindBuffer函數的第一個參數支持且僅支持這兩個參數。 桌面OpenGL中支持更多擴展但跟這里無關,不做任何解釋。

這兩個參數分別代表了綁定頂點緩沖區對象和頂點索引緩沖區對象。

而緩沖區對象則是通過webgl.createBuffer() 函數來創建的。 需要注意的是,緩沖區對象(buffer object)創建時並不需要指定其類型,而是在這個緩沖區對象第一次被綁定時,根據綁定時的第一個參數確定其真正的類型。

通常,我們把這兩種緩沖區對象均稱作VBO (Vertex Buffer Object). 同時提一下, OpenGL ES 2.0並不支持VAO,所以這里不做討論

有了創建和綁定,接下來當然需要上傳數據了, 上傳數據使用的函數叫: webgl.bufferData, 在OpenGL中函數原型大致為:

 

 

由此可見,webgl.bufferData函數包含三個參數, 第一個參數為webgl.ARRAY_BUFFER或webgl.ELEMENT_ARRAY_BUFFER,

第二個參數為buffer數據,通常為 Float*Array 或者 Uint*Array 系列數組 (此處的* 代表 64, 32, 16, 8).
通常 ARRAY_BUFFER 將使用 Float*Array 而 ELEMENT_ARRAY_BUFFER 必須使用 Uint*Array。 (請不要直接使用js里面的Array數組, 因為它可能是數據沒有對齊的, Array數組中的對象可能有不同的類型)
需要特別注意的是, 就目前來看,WebGL在使用 ELEMENT_ARRAY_BUFFER 時,不得使用精度超過 Uint16Array。 本章demo在編寫時,習慣性地使用了 Uint32Array,導致Chrome (版本:34.0.1847.131 m) 一直對此報錯。而改為 Uint16Array 之后運行正常,希望引起您的注意。(猜測原因可能是WebGL暫時還不能支持太大量的ARRAY_BUFFER數據從而限制了索引大小)

第三個參數為buffer數據的使用方式, 通常這一步跟性能有關。根據對數據使用情況的不同, OpenGL可能對數據進行一些優化,當然,你如果不關心這些的話,看心情隨便寫一個也是可以的。
這里貼一點khronos.org官方解釋,希望了解的請自己點擊下面的鏈接:

usage is a hint to the GL implementation as to how a buffer object’s data store will be accessed. This enables the GL implementation to make more intelligent decisions that may significantly impact buffer object performance. It does not, however, constrain the actual usage of the data store. usage can be broken down into two parts: first, the frequency of access (modification and usage), and second, the nature of that access. 
(From: http://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml)

bufferUsage有三種可能值:

webgl.DYNAMIC_DRAW: 動態繪制模式, 緩沖區對象中的數據需要常常更新和使用 (webgl.bufferData函數會頻繁調用), 這種模式下,OpenGL會將數據放置在能夠快速更新和使用的地方。例:繪制了一個面包,當你不去碰它時,它的每一幀繪制不會更改,不需要bufferData更新,但是一旦被咬或者擠壓就會變形,如果被不斷撕咬則持續更新bufferData

webgl.STATIC_DRAW: 靜態繪制模式, 緩沖區對象中的數據只指定1次,但是常常使用。這種模式下,OpenGL會將數據放在能夠快速渲染的地方。例: 繪制一堵無法被破壞的牆, 那么每一幀它都不會變。本章的demo中,紋理采樣頂點緩沖區網格頂點索引緩沖區不曾有變化,適合這種情況。

webgl.STREAM_DRAW: 流繪制模式, 緩沖區對象中的數據只指定1次,並且也不經常使用。 
一些人對STREAM_DRAW了解較不清楚,這里特別講一下。先是蘋果官方對於它的解釋:
GL_STREAM_DRAW is used when your application needs to create transient geometry that is rendered and then discarded. This is most useful when your application must dynamically change vertex data every frame in a way that cannot be performed in a vertex shader. To use a stream vertex buffer, your application initially fills the buffer using glBufferData, then alternates between drawing using the buffer and modifying the buffer.(From: https://developer.apple.com/library/mac/documentation/graphicsimaging/conceptual/opengl-macprogguide/opengl_vertexdata/opengl_vertexdata.html#//apple_ref/doc/uid/TP40001987-CH406-SW9)
我來翻譯一下, 這段話的意思大致為, STREAM_DRAW的應用場景為幾乎每一次 webgl.bindBuffer使用后都需要使用 webgl.bufferData 更新一遍數據的情況,就比如本章demo中網格頂點緩沖區數據的情況。

前面說了,如果不關心這些,可以隨便寫一個,bufferUsage參數只是對於OpenGL的一種暗示,並不一定非得按照描述的那樣吻合才行。你填寫任何一個都能正確運行,只是性能上可能存在一定差異。 對於,介於一些強迫症患者一定非得找到屬於自己的style,我的建議是,不要糾結描述,直接把三種方式全部試一遍,哪種最快,幀率最高就用哪個吧,其他的都是浮雲。

buffer 對象的使用

對於 webgl.ARRAY_BUFFER, 前面的章節已經提到過了, 直接綁定一個頂點屬性即可, 代碼類似於:

 

 

anyVertexAttributeIndex 為待使用的 Vertex Shader里面的某個attribute變量的location,
attributeSize 顧名思義為這個attribute含有幾個分量,如果為float,則是1,如果為vec2則是2,依此類推。

對於 webgl.ELEMENT_ARRAY_BUFFER, 前面的章節不曾使用過,這里特別解釋一下:
ELEMENT_ARRAY_BUFFER 本身並不是可以使用的頂點數據, 它存儲的是一系列的頂點索引值,所以它的值必須為整數,且為無符號整數。

用索引和直接使用buffer繪制的區別如下:

首先,我們要明白, shader里面 attribute屬性對應的數據為每個頂點一個的,比如你定義了 attribute vec4 color; 和 attribute vec4 vertex; 那么在傳遞 color和vertex兩個attribute的data時,大小必須一樣,且與最終繪制時使用到的頂點個數相同。

但是如果我們繪制的圖形頂點不多,但是圖形比較復雜,每個頂點要用到多次怎么辦? 
一般來說有兩種解決辦法,其一就是,把要多次用到的頂點在合適的位置多寫幾次,相應的,其他的attribute也對應增加。 若是頂點經常更新, 幾何圖形經常變動做起來就特別蛋疼。

其二就是我們可愛的 ELEMENT_ARRAY_BUFFER 了, 我們只需要記錄繪制時的頂點索引順序即可完成繪制,而頂點數據不需要任何重復。

以本章的demo為例,我們的網格是按三角形的方式繪制的,在網格中間,我們可以看到每個定點周圍有六條線, 也就是說這個頂點至少被用到六次,如果不使用 ELEMENT_ARRAY_BUFFER ,我們竟然要把每個頂點至少寫六次! 如果shader里面還有其他的 attribute,它們也莫名其妙地跟着增加,是不是很蛋疼呢? 
這還不算完, 頂點是要更新的,也就是說,每個頂點數據的更新,也需要修改六個地方……蛋疼哭了嗎,騷年?不要懷疑了, GL_ELEMENT_ARRAY_BUFFER 是解救你的神器。

到這里,對於這兩個buffer的解釋差不多夠了。那么如何繪制呢?

對於本章的demo, 需要三個VBO, 兩個 webgl.ARRAY_BUFFER和一個 webgl.ELEMENT_ARRAY_BUFFER

這三個VBO分別表示網格頂點緩沖區, 紋理頂點緩沖區 以及一個頂點繪制的索引緩沖區。

解釋一下用途吧,前面說過了,紋理頂點緩沖區是不需要更新的,索引緩沖區同樣不需要更新。 這兩個緩沖區描述了整個網格的構成以及紋理繪制方式(紋理填滿整個網格)

在OpenGL中,如果不考慮越界處理的話,紋理坐標的范圍應該是[0, 1], 而本網格中的紋理應該是剛好填充滿整個網格,所以紋理定點緩沖區里面的所有數據范圍都應該介於[0,1], 
而本demo並沒有引入深度的概念(可以看到存在先繪制部分被遮擋的情況),並且整張紋理都在xOy平面內,所以頂點只需要x和y值就夠了。那么剛好,x和y的范圍也剛好都在[0, 1]之間,並且均勻變化。

這里你可能會奇怪,因為本章的demo並沒有單獨創建一個紋理頂點的數據,而是直接將未變化時的網格頂點數據作為紋理頂點數據傳遞進去了。這里解釋一下:

本章demo中的頂點數據就是按紋理頂點數據方式創建的,范圍就是[0, 1],而shader內部 gl_Position 默認的全屏取值為 [-1, 1], 為了繪滿全屏, demo在vertex shader里面做了一次坐標映射,代碼大致為:

 

 

算是偷了個懶吧,以后如果你遇到了也可以這樣偷懶。

而對於整個網格的繪制,則主要在於頂點索引的設定了,設定代碼大致如下:

 

 

因為想保留繪制一個線型的網格的功能,所以不得不采用S形路線來繪制整個網格。 這里的頂點數據是一維數組, 每兩個為一對,分別表示x和y, 一次填充一個三角形,最終填滿整個網格。

小提示: 如果你注意觀察的話,可以發現每個小三角形的頂點順序都是逆時針方向的哦, 這樣可以區分網格的正反面,當然,本次demo並沒有體現出區別, 以后如果有機會講到光照和面剔除(CullFace)的話就會知道這樣做的意義了。

本章關於WebGL的東西差不多就這么多了,剩下的如果還有什么疑問請自己clone下本章的demo

本章的demo如下:(請用鼠標點擊。與上方html5版本是同步的,但是為了防止某些單核電腦卡頓,這兩個demo不會同時刷新)

 


還沒完哦, 鑒於你可能對這個網格如何做到擬物的比較感興趣,這里粗略地講一點。

首先聲明一下,這個並不是個人純原創,借鑒了一下某網友編寫的“碧波盪漾”的小程序。也看了下它的源代碼……但是這個源代碼呢,寫得比較晦澀,基本沒怎么看懂,放棄理解原版的源代碼。但是憑借寡人的想象力,不知道算不算”原創地”完成了本次demo,哦呵呵~

於是說一下這個網格的原理吧。 首先,我認為將這個demo理解為“碧波盪漾”是不恰當的,雖然看起來有點像水波,實質上是在模擬一個彈力網格。

我們來想象:我們用一些長橡皮繩編織出這么一張網, 網的連接線只有彈力沒有質量,只有結點有質量,相鄰結點與結點之間有一根橡皮繩相連。

學過物理的孩子都知道,物體加速度的方向與受力方向和其質量有關,設每個結點的質量相同(為m), 那么根據 F = ma, 受力越大加速度越大。

我們要求的是加速度,質量只是一個系數我們並不關心, 所以只需要知道每個結點的受力情況就可以算出來了。

(以html5版本顯示出的網格為視覺藍本)不難看出,每個結點都有四條線與之相連,分別受到這四根線的拉力。

換一個角度理解,假如此時處於平衡狀態(加速度為0),則四個方向的力的合力肯定是0 對吧?

那么如何計算合力呢? 根據本例的應用場景, 這里有一個簡單粗暴的方法,把這個頂點上下左右的坐標全部加起來除以四, 看看是否等於中間點坐標。(原理: 連接線的彈力與它被拉長的距離成正比, 兩個點之間的距離越大,它們之間的拉力也就越大, 所以如果某個點想得到平衡狀態,那么它跟左右兩個點之間的距離必須得相等, 這樣的話它的坐標剛好就應該等於兩邊點的坐標除以二, 因為不考慮重力, 垂直方向上也是一樣)

能夠相同上面那一層,整個網格的原理差不多弄清楚了。 假如一個點受力方向向左, 肯定是左側距離較大, 那么兩側點加起來除以二肯定小於當前點坐標,對吧 ?

而且,它們之間的坐標差值跟力的大小成正比,而力的大小跟加速度成正比,對吧? 我們只要假定一個系數乘上去, 一個簡單的網格就模擬出來了。

當然,到這里還沒完,我們必須模擬每個點的速度和加速度, 使用一個變量保存速度,然后累加每一次的加速度,整個程序就完成了。

好的,第七期教程到此為止,請關注lesson 8。系列教程地址:webgl-lesson.wysaid.org


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM