OpenGL第7~8章
裁剪,光柵化和隱藏面消除
光柵化是將對象處理成片元的過程。
必須完成的兩件事;第一,使每個集合對象經過圖形系統
第二,給顏色緩衝中要顯示的每個像素附一個顏色值。
圖形繪製系統的4個主要任務
建模—>幾何處理à光柵化à片元處理à幀緩衝
建模:得到一組表示幾何對象的頂點資料。
幾何處理:作用於頂點資料,確定哪些幾何對象會在螢幕上顯示,並把灰階值和顏色值賦給對象的頂點。經過一系列的變換矩陣的變換處理
片元處理:只有光柵化之後的片元處理階段才會用到紋理值,
第8課 合成技術:go over blending
這節課會先瀏覽一遍合成技術,同時會粗略的看看深度緩衝(depth buffer)是怎樣工作的。
深度緩衝
從第二節課我們知道,當你告訴WebGL去畫東西的時候,會有一個大概的過程,
1運行頂點渲染器來計算每個頂點的位置
2在頂點間進行線性插值,來告訴系統哪些片段需要被畫。
3對每一個片元運行片段渲染器來計算它的顏色
4寫入幀緩衝
因此,幀緩衝是最後需要被執行的。但是如果要畫兩個物體呢?例如,如果你畫一個中心在(0,0,-5)的正方形和一個中心在(0,0,-10)的正方形,你不會想要畫第二個正方形並把第一個覆蓋掉,因為它在很遠的地方並且應該被覆蓋。
WebGL使用深度緩衝來處理這種情況。當片元經過片元渲染器處理之後寫入幀緩衝中,同一般的RGBA顏色值一樣,它也會儲存一個與片元Z值有關的深度值。
為什麼說“與Z值相關”呢?WebGL希望所有的Z值的範圍都在0~1之內,0表示最近的(closese)1表示最遠的(furthese away)。這些之前都被我們之前在drawScene函數內調用的perspective函數中的投影矩陣隱藏了。到目前你所需要知道的就是Z-buffer值越大,離的越遠。這和我們平常的座標剛好相反(越遠Z座標越小,是負數)。
那麼這就是深度緩衝了。現在,你可能還記得我們從第一節課就開始用來初始化WebGL內容相關的一段代碼:
gl.enable(gl.DEPTH_TEST);
這是在告訴系統在把片元寫入幀緩衝時要做些什麼。也就是“注意下深度緩衝”。他和另一系統設定the depth function 一起聯合使用。它通常有一個預設值,但如果我們要設定它的值的話可以這樣設定:
gl.depthFunc(gl.less);
這意味著,“如果我們的片元有一個比當前Z值還要小的Z值,那麼就使用新的這個片元,拋棄當前片元。”這些設定及使用它的這些代碼就能夠為我們提供足夠多的behavior了。
合成
合成可以簡單的認為是上述過程的另一個選擇。我們使用depth function 和depth-testing來決定是否要用新片元替換當前的片元。而如果我們使用合成方法時,我們會用一個合成函數(blending function)來組合當前片元和新片元的顏色併產生一個新的片元,之後把這個新片元寫入緩衝。
現在我們來看看這段代碼。這些代碼和上一節課幾乎完全相同,所有重要的部分都是在drawScene函數的短的片段裡面。首先,我們檢查合成複選框是否被選擇。
var blending=document.getElementById(“blending”).checked;
如果是,就設定一個用來組合兩個片元的函數:
if(blending){
gl.blendFunc(gl.SRC_ALPHA,gl.ONE);
這個函數的參數決定怎樣來合成。這需要技巧,但不會很難。第一步,我們先決定兩個要求:片元的來源是我們正在畫的這個,目標片元是已經在幀緩衝。gl.blendFunc的第一個參數決定源因子,第二個決定了目標因子。這些因子是在合成函數中使用的數字。這樣,我們就可以說源因子是源片元的alpha值,而目標因子是一個為1的常量。當然也有其他可能,例如,如果你使用SRC_COLOR來確定源的顏色,則源因子的RGBA值就和初始的RGBA組件一樣。%>_<%-_-!-_-|||=_=-_-#
假設WebGL試圖計算一個片元的顏色(Rd, Gd, Bd, Ad),新來的片元的RGBA值為(Rd, Gd, Bd, Ad);源因子的RGBAz值是(Sr, Sg, Sb, Sa),目標因子的RGBA是(Dr, Dg, Db, Da)。
對於每一個顏色組件,系統將會進行下面這些運算:
Rresult = Rs * Sr + Rd * Dr
Gresult = Gs * Sg + Gd * Dg
Bresult = Bs * Sb + Bd * Db
Aresult = As * Sa + Ad * Da
所以,在這種情況下,我們可以得到(只給出紅色組件):
Rresult = Rs * As + Rd
這不是獲得透明對象的一個好方法,但是恰好能很好的工作在lighting 的情況下。這一點很值得強調。合成和透明是不同的,他只是一個能夠獲得透明效果的許多方法中的一個。
好的,現在繼續:
gl.enable(gl.BLEND);
如同WebGL中的許多東西一樣,合成預設是關掉的,所以我們需要先開啟。
gl.disable(gl.DEPTH_TEST);
這個就更有趣了,我們必須先關掉深度測試。不關掉會怎樣呢?舉個例子,假如我們畫一個立方體,如果我們先畫背面然後再畫正面,這樣正面會被合成在背面上來,這就沒有問題,但如果我們先畫正面,背面由於離我們更遠就會在合成之前由於深度測試而被無視掉。
讀者們可能會注意到合成對畫圖的順序有很大倚賴,這在之前是我們未曾注意的。這個等會兒再說,我們先完成剩下的這些代碼:
gl.uniform1f(shaderProgram.alphaUniform, parseFloat(document.getElementById("alpha").value));
在這裡我們從JS中獲得一個alpha值,並將其匯入渲染器中。這是因為我們使用的紋理沒有它自己的alpha通道(它只有RGB,所以每一個像素的alpha值都是1)。
drawScene中剩下的就只是一些關掉合成後所要處理的必要代碼。
} else {
gl.disable(gl.BLEND);
gl.enable(gl.DEPTH_TEST);
}
片元渲染器代碼中也有一些小的修改以便於使用alpha值。
#ifdef GL_ES
precision highp float;
#endif
varying vec2 vTextureCoord;
varying vec3 vLightWeighting;
uniform float uAlpha;
uniform sampler2D uSampler;
void main(void) {
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a * uAlpha);
}
以上就是代碼中的所以改變了。
現在回頭來看下畫圖順序。從這個例子裡面我們能得到很好的透明效果,看起來就像真的教堂玻璃一樣。現在重新試一下,改變光照方向使方向從Z軸正方向過來——只需要把“-”去掉就行了,看起來也不錯,但是真實感完全沒有 了。
原因在於在初始光照條件下,立方體背面總是模糊的,也就是說背面的RGB值很小,所以等式:
Rresult = Rs * Ra + Rd
的計算結果就不是那麼強烈。也就是說,我們剛好獲得了使背面可見度更低的光照。如果我們把光照轉到正面,正面的可見度就降低,透明效果就沒那麼好了。
那麼怎樣解決這個問題呢?OpenGL FAQ(常見問題)說你需要用一個SRC_ALPHA的源因子,和一個ONE_MINUS_SRC_ALPHA的目標因子。但是由於源片元和目標片元的處理方式的不同,對畫圖順序還是會有依賴。
所以用合成來處理透明度是需要技巧的,但如果你可以有效控制情境,就像這節課裡控制光源方向,你就可以適當地使用這個功能,當然你需要注意畫圖順序。
幸運的是,合成在其他方面還是很有用的,你將會在下節課中看到。