在〈varying 傳遞著色資訊〉中談到,仔細看範例網頁效果話,會發現應該是看不見的背面也被繪製了。解決的方式之一是,讓較深的點不會繪製,這只要啟用測度測試就可以了:
gl.enable(gl.DEPTH_TEST); // 啟用深度測試
預設的深度比較函式是 gl.LESS
,在輸入值小於深度緩衝中的值時,才會通過進行像素繪製,也就是近物遮擋遠物,如果想要指定其他的深度比較函式,可以使用 depthFunc
函式,例如:
gl.depthFunc(gl.LEQUAL); // 輸入值小於或等於深度緩衝中的值時才會通過
除了 gl.LESS
與 gl.LEQUAL
之外,depthFunc
中還列出了其他的值,可以自行參考,就正四面體來說,只要使用預設的 gl.LESS
就可以了,例如按一下修改後的範例網頁,可以看到修正後的正確版本。
啟用深度測試之後,在清除繪製時,通常也會同時清除深度緩衝:
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
不過!嗯…因為使用漸層色,某些角度看不時出來是正四面體耶!那麼來試著把四個面分別著上白、紅、綠、藍好了,這該怎麼做?透過程式碼來計算,目前是在畫哪個面嗎?
嗯!這也是個方式啦!不過麻煩也容易出錯,透過索引陣列的方式,可以運用一個簡單的技巧,在談這個技巧之前,先改用索引陣列的方式來畫同一個四面體好了,主要就是索引陣列不要設錯就好了:
gl.enable(gl.DEPTH_TEST);
// 正四面體
const n = 0.25;
const verteices = [
n, -n, -n,
-n, -n, n,
n, n, n,
-n, n, -n
];
rotateXY(verteices, Math.PI / 3, 0);
// 頂點 Buffer
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verteices), gl.DYNAMIC_DRAW);
// 頂點索引
const indexes = [
0, 1, 2,
1, 3, 2,
0, 2, 3,
0, 3, 1
];
// 索引 Buffer
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array(indexes), gl.STATIC_DRAW);
const attr_position = gl.getAttribLocation(prog, 'position');
gl.vertexAttribPointer(attr_position, 3, gl.FLOAT, false, 0 , 0);
gl.enableVertexAttribArray(attr_position);
...略
按一下範例網頁來看看效果,就呈現結果而言是相同的,在進一步討論如何把四個面分別著上白、紅、綠、藍之前,先來想一個問題,在頂點索引的配置上,每個三角形的順序應該是順時針還是反時針或者是都可以呢?
如果你不在乎你看到的三角形是正面或反面的問題的話,就不用在意這個問題,例如,若只繪製正四面體的三個面,因為有個面沒畫,可以觀察到正四面體的內側,這時你會希望看到三角形的反面嗎?
就正四面體來說,某個面朝 z 正方面時,一定會被近面遮擋住,雖然啟用深度測試的話,就算畫了朝 z 軸的三角形之反面,近面在繪製時也會遮蓋掉,然而,若可以在一開始,就剔除掉三角形的反面不繪製,在效能上是可以有所助益的。
對 WebGL 來說,三角形頂點順序若是逆時針,對被視為正面,若為順時針就被視為反面,若想要有面剔除的功能,可以撰寫:
gl.enable(gl.CULL_FACE);
將這行程式碼,取代上面範例片段的 gl.enable(gl.DEPTH_TEST)
,呈現效果上也是相同,就效能上應該會比較好,因為這是個正四面體,而且我的索引陣列是逆時針順序檢索頂點,至今看過的範例也是,在可以的情況下,我會用逆時針來排列頂點。
當然,有些情況下沒辦法,例如〈varying 傳遞著色資訊〉中使用無索引頂點繪製四面體時,因為共用邊的關係,就沒辦法區分三角形正面或反面了。
在啟用面剔除功能後,預設是剔除反面,然而,也可以透過 cullFace
指定 gl.FRONT
、gl.BACK
(預設)或 gl.FRONT_AND_BACK
。
有時你必須同時啟用深度測試與面剔除,例如繪製多個正四面體的時候,近端的正四面體要遮蓋遠端的正四面體,同時啟用深度測試與面剔除就會很方便。
回過頭來看看如何把四個面分別著上白、紅、綠、藍,目前的顏色資訊,是透過頂點資訊自動計算而來,也就是依索引陣列檢索的頂點值,自動計算出座標轉換為顏色資訊,那麼,如果有個顏色緩衝區,索引陣列檢索出來的三組顏色值都是相同,那麼計算出來的每個內插值也都一樣,不就是繪製出一整個同色的面嗎?
因此,這邊分別為正四面體的各個三角面規畫頂點如下:
// 正四面體
const n = 0.25;
const verteices = [
n, -n, -n,
-n, -n, n,
n, n, n,
-n, -n, n,
-n, n, -n,
n, n, n,
n, -n, -n,
n, n, n,
-n, n, -n,
n, -n, -n,
-n, n, -n,
-n, -n, n
];
檢索這些頂點時,使用的索引陣列為:
const indexes = [
0, 1, 2,
3, 4, 5,
6, 7, 8,
9, 10, 11
];
要放入顏色 Buffer 的顏色陣列為 colors
,它必須也有 12 組值,以配合上頭的索引陣列:
const faceColors = [
[1.0, 1.0, 1.0, 1.0], // 白
[1.0, 0.0, 0.0, 1.0], // 紅
[0.0, 1.0, 0.0, 1.0], // 綠
[0.0, 0.0, 1.0, 1.0], // 藍
];
// 每個面會有三個頂點,因此相同顏色設定必須是三個一組
const colors = [];
for(let j = 0; j < faceColors.length; ++j) {
const c = faceColors[j];
Array.prototype.push.apply(colors, c.concat(c, c));
}
這麼一來,就可以繪製出四個純色的面了,可以看一下範例網頁的效果,完整的原始碼也請自行從中察看,因為只有一個正四面體,只要啟用面剔除就可以正確繪製了。