WebGL半透明物體的繪製

JeffZhong發表於2019-05-01

WebGL 中當透明和半透明物體共存時,相關設定不正確的話,物體表面會出現破碎雜亂的斷面,非常影響效果,我們接著就來解決這個問題。 完成的展示Demo請看: 半透明物體和透明物體共存

alpha混合

α 混合

讓物體實現半透明效果需要用到顏色的α分量。該功能被稱為a混合(alpha blending) 或 混合 blending,WebGL已經內建該功能,但需要開啟,如果只設定了顏色的第四個分量 α 是看不到透明效果的,這第四分量α其實和 css 樣式的 rgba / hsla 顏色模式 中的 α 是一樣的,或者類似 opacity 屬性。必須要執行下面兩個步驟才能看到透明效果:

  1. 開啟混合功能:gl.enable(gl.BLEND)。
  2. 指定混合函式: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);
複製程式碼

最終完成效果請看:半透明物體和透明物體共存

相關文章