轉載來自:http://www.jiazhengblog.com/blog/2016/02/19/2910/
很多WebGL教程都會從繪製一個三角形開始,如同其它語言教程中的Hello World。我也不能免俗,來一起看一下如何在WebGL中繪製三角形吧。本文將向你介紹如下內容:
- 如何定義三角形頂點
- 如何繪製三角形
- 如何繪製多個三角形
繪製一個三角形
完整的代碼可以看這裏。
如果你對比之前繪製點的代碼,發現繪製三角形僅在兩個地方進行了修改:
- 頂點定義部分
- 繪製部分
一個三角形包含三個頂點,因此vertex變量中數據內容增多了:
1
2
3
4
5
|
var vertex = [ -.5, -.2, 0, .5, -.2, 0, 0, .6, 0 ]; |
數組中包含了三個點的座標信息,每個點包含x、y、z三個維度,因此數組的長度爲9。爲了可讀性,頂點數據一般每行寫三個元素,這樣每一行就代表一個點,也比較容易看出點的數量。
三角形頂點的順序可以任意來寫,但是我的習慣是從左下角的頂點開始寫起,並按照逆時針的順序。無論採用什麼方案,只要在一個項目中保持一致即可。從下圖我們可以看到頂點的對應關係。
下面一個發生變化的地方是繪製代碼:
1
|
gl.drawArrays(gl.TRIANGLES, 0, 3); |
這裏繪製模式變爲gl.TRIANGLES,表示繪製爲三角形,即要繪製一個面,只有面纔會有填充顏色。第三個參數由1變爲3,表示要繪製三個頂點。
我們會在瀏覽器裏看到下面的效果:
繪製多個三角形
在WebGL中不論多麼複雜的幾何體最終都是由三角形構成,因此在繪製之前都需要將物體進行三角形化(Triangulation),也就是把複雜的多邊形分解爲一個個三角形,再交由WebGL繪製。在下面的例子中,我們來繪製一個矩形,其中包含兩個三角形,如下圖所示:
首先還是準備頂點數組,現在是兩個三角形,於是數組的長度變爲18(2個三角形 x 3個頂點 x 3個座標):
1
2
3
4
5
6
7
8
|
var vertex = [ -.5, -.3, 0, .5, -.3, 0, .5, .3, 0, -.5, -.3, 0, .5, .3, 0, -.5, .3, 0 ]; |
數組中座標對應關係如下圖所示:
最後我們修改drawArray代碼:
1
|
gl.drawArray(gl.TRIANGLES, 0, 6); |
可以看到一個矩形繪製出來了。
如果你是個細節控、或者愛問問題,那麼你可能會發現定義座標的時候存在數據冗餘,因爲兩個三角形中有兩個頂點是共用的。有沒有辦法來避免有冗餘數據呢?
索引緩衝區對象 Index Buffer Object(IBO)
除了VBO,WebGL還有另外一類緩衝區對象,稱作索引緩衝區對象(Index Buffer Object,IBO,後文均採用IBO一詞)。通過索引的方式可以達到去除冗餘頂點數據的目的。我們來看看它是怎麼做到的。
首先修改vertex數組,裏面只包含必須的4個頂點:
1
2
3
4
5
6
|
var vertex = [ -.5, -.3, 0, .5, -.3, 0, .5, .3, 0, -.5, .3, 0 ]; |
在給VBO填充數據之後我們增加創建IBO的代碼:
1
2
3
4
5
6
7
|
var index = [ 0, 1, 2, 0, 2, 3 ]; var indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.STATIC_DRAW); |
index中的數據指明每個三角形的頂點在VBO中的位置,第一行 0, 1, 2 表示第一個三角形由VBO中第0個、第1個和第2個頂點(注意是頂點,每個頂點包含三個座標,所以你要三個元素三個元素的看)構成,第二行的 0, 2, 3 表示第二個三角形由VBO中的第0個、第2個和第3個頂點構成。
接着創建緩衝區對象,這和之前創建VBO代碼一樣。在綁定緩衝區時,第一個參數爲gl.ELEMENT_ARRAY_BUFFER,此時WebGL才知道這是一個索引緩衝區,後面的bufferData的首個參數也是這個值,傳輸的數據同樣需要typed array,這裏我們使用Uint16Array,也就是每個索引值採用無符號16位整型數值,當然對於我們這個簡單的例子,用Uint8Array也是可以的。
最後我們需要使用WebGL另外一個繪製函數來繪製:
1
|
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); |
如果使用了IBO,就需要用drawElements方法來替代原來的drawArrays,drawElements原型如下:
1
|
void drawElements(GLenum mode, GLsizei count, GLenum type, GLintptr offset); |
這裏多了一個type,表示索引的數據類型。這裏我用使用unsigned short類型,它和之前Uint16Array是一致的。如果前面用了Uint8Array,那麼這裏需要改成gl.UNSIGNED_BYTE。
在採用了索引方式後,vertex內容確實減少了,在複雜的模型裏這會節省出不少的內存空間。如果你是一個追求極致的人,你會發現索引裏仍然存在冗餘,索引0和2出現了兩次。有沒有辦法把索引中的冗餘也去掉呢?請你往下看。
不同的繪製模式
不論是drawArrays還是drawElements,第一個參數都是指定繪製模式(mode),目前我們繪製三角形使用的模式都是gl.TRIANGLES,此外還有兩種與繪製三角形相關的模式:gl.TRIANGLE_STRIP和gl.TRIANGLE_FAN。
關於這三種模式的區別,我們用一張圖來說明:
- gl.TRIANGLES:用來繪製相互獨立的三角形。從緩衝區中每次獲取3個頂點的數據作爲一個三角形。
- gl.TRIANGLE_STRIP:用來繪製有共享邊的三角形。從第二個三角形開始,每次讀取一個頂點,並利用前面的末尾兩個頂點構成一個三角形,以此類推。
- gl.TRIANGLES_FAN:同樣用來繪製有共享邊的三角形。從第二個三角形開始,每次讀取一個頂點,並利用首個頂點和之前最後一個頂點來構成一個三角形,以此類推。
在繪製矩形的例子中,我們分別看如何使用strip和fan這兩種模式。
strip模式下,我們修改index爲:
1
2
3
|
var index = [ 1, 2, 0, 3 ]; |
fan模式下,修改index爲:
1
2
3
|
var index = [ 0, 1, 2, 3 ]; |