WebGL 中當透明和半透明物體共存時,相關設定不正確的話,物體表面會出現破碎雜亂的斷面,非常影響效果,我們接著就來解決這個問題。 完成的展示Demo請看: 半透明物體和透明物體共存
α 混合
讓物體實現半透明效果需要用到顏色的α分量。該功能被稱為a混合(alpha blending) 或 混合 blending,WebGL已經內建該功能,但需要開啟,如果只設定了顏色的第四個分量 α 是看不到透明效果的,這第四分量α其實和 css 樣式的 rgba / hsla 顏色模式 中的 α 是一樣的,或者類似 opacity 屬性。必須要執行下面兩個步驟才能看到透明效果:
- 開啟混合功能:gl.enable(gl.BLEND)。
- 指定混合函式:gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)。
gl.blendFunc(src_factor, dst_factor)
a混合函式,指定如如何混合兩者的顏色,權重因子的型別多種多樣,引數:
- src_factor: 指定源顏色在混合顏色的權重因子,如下表所示
- dst_factor: 指定目標顏色在混合後顏色的權重因子,如下表所示
// 混合顏色計算公式:
<混合後的顏色> = <源顏色> * src_factor + <目標顏色> * dst_factor
// 一般半透明效果常用如下形式
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
複製程式碼
權重因子列表
(Rs,Gs,Bs,As) 表示源顏色各分量, (Rd,Gd,Bd,Ad) 表示目標顏色的各分量
常量 | R分量的係數 | G分量的係數 | B分量的係數 |
---|---|---|---|
gl.ZERO | 0.0 | 0.0 | 0.0 |
gl.ONE | 1.0 | 1.0 | 1.0 |
gl.SRC_COLOR | Rs | Gs | Bs |
gl.ONE_MINUS_SRC_COLOR | 1-Rs | 1-Gs | 1-Bs |
gl.DST_COLOR | Rd | Gd | Bd |
gl.ONE_MINUS_DST_COLOR | 1-Rd | 1-Gd | 1-Bd |
gl.SRC_ALPHA | As | As | As |
gl.ONE_MINUS_SRC_ALPHA | 1-As | 1-As | 1-As |
gl.DST_ALPHA | Ad | Ad | Ad |
gl.ONE_MINUS_DST_ALPHA | 1-Ad | 1-Ad | 1-Ad |
gl.SRC_ALPHA_SATUREATE | min(As,Ad) | min(As,Ad) | min(As,Ad) |
透明和不透明物體共存
實現 a 混合最簡單的方式是遮蔽掉隱藏面消除功能,即去掉 gl.enable(gl.DEPTH_TEST),但關閉隱藏面消除功能是一個粗暴的解決方案,並不能滿足實際需求。其實可通過某些機制,同時實現隱藏面消除和半透明效果,步驟如下:
//1.開啟隱藏面消除功能:
gl.enable(gl.DEPTH_TEST)。
//2.繪製所有不透明的物體(a == 1.0)
//3.鎖定深度緩衝區的寫入操作,使之只讀 (深度緩衝區用於隱藏面消除):
gl.depthMask(false);
//4.繪製所有半透明的物體 a < 1.0,注意將物體按深度排序,a 最小最先繪製
//5.釋放深度緩衝區,使之可讀可寫:
gl.depthMask(true)
複製程式碼
gl.depthMask(mask)
鎖定或釋放深度緩衝區的寫入操作
mask: 鎖定深度緩衝區的寫入操作 false,釋放 true
實現效果
我們寫個Demo來實際演示效果,比如我要繪製8個物體,其中前面4個是非透明的物體,即 α 分量值則為1,剩餘物體的 α 分量分別從 0.1至0.4不等。
for (var i = 0; i < 8; i++) {
let color = randomColor();
color[3] = i > 3 ? (i - 3)/10 :1;// 透明物體 α 分量小於1,非透明物體則等於1
Polygons.push({
x: random(-9,9),
y: random(0, 6),
z: random(-5,5),
color: color
});
}
複製程式碼
如果是繪製的物體佇列是無序的,則必須手動排序。但我這裡建立圖形時已經排好序,前4個為不透明物體,剩餘是透明物體,所以可直接按順序繪製,針對是否為透明物體,分別設定緩衝區寫入和隱藏面刪除功能。
if(i < 4){ // 非透明物體
gl.depthMask(true);
gl.disable(gl.BLEND);
} else { //透明物體
gl.depthMask(false);
gl.enable(gl.BLEND);
gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
}
drawBufferInfo(gl, vao);
複製程式碼
最終完成效果請看:半透明物體和透明物體共存