要命的陰影
事情是這樣的,近日查一個問題,半透明的素材視頻 或者 圖片,渲染到屏幕的時候半透明部分會有一圈深色陰影。
渲染到屏幕時,星星的邊緣有一圈深色陰影
混合模式
基于以上問題,先來復習一下混合模式。OpenGL使用混合(Blending)技術來完成半透明圖像的渲染。
步驟如下:
// 1.開啟混合模式
glEnable(GLES20.GL_BLEND);
// 2.設置函數:源因子,目標因子
glBlendFunc(int sfactor, int dfactor);
// 3.渲染目標
// 4.渲染源
// 5.關閉混合模式
glDisable(GLES20.GL_BLEND);
混合模式的公式,一定要注意好哪個是源,哪個是目標。
Cˉresult=Cˉsource?Fsource+Cˉdestination?Fdestination
Cˉsource:源顏色向量。這是來自紋理的本來的顏色向量。
Cˉdestination:目標顏色向量。這是儲存在顏色緩沖中當前位置的顏色向量。
Fsource:源因子。設置了對源顏色的alpha值影響。
Fdestination:目標因子。設置了對目標顏色的alpha影響。
混合因子可選值如下:
選項 | 值 |
---|---|
GL_ZERO | 0 |
GL_ONE | 1 |
GL_SRC_COLOR | 源顏色向量Cˉsource |
GL_ONE_MINUS_SRC_COLOR | 1?Cˉsource |
GL_DST_COLOR | 目標顏色向量Cˉdestination |
GL_ONE_MINUS_DST_COLOR | 1?Cˉdestination |
GL_SRC_ALPHA | Cˉsource的alpha值 |
GL_ONE_MINUS_SRC_ALPHA | 1? Cˉsource的alpha值 |
GL_DST_ALPHA | Cˉdestination的alpha值 |
GL_ONE_MINUS_DST_ALPHA | 1? Cˉdestination的alpha值 |
GL_CONSTANT_COLOR | 常顏色向量Cˉconstant |
GL_ONE_MINUS_CONSTANT_COLOR | 1?Cˉconstant |
GL_CONSTANT_ALPHA | Cˉconstant的alpha值 |
GL_ONE_MINUS_CONSTANT_ALPHA | 1? Cˉconstant的alpha值 |
當然還可以使用glBlendFuncSeparate( int srcRGB, int dstRGB, int srcAlpha, int dstAlpha );
來分別對RGB 和 alpha 設置不同的因子。
使用glBlendEquation( int mode );
& glBlendEquationSeparate( int modeRGB, int modeAlpha );
配置混合函數運算符。
舉幾個例子,我們重點關注alpha的變化。(此時我還沒意識到預乘Premultiplied Alpha的作用)
源(1,1,0,0.6),目標(0.6,0.6,0.5,1)
- glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA)
result.r = 1 * 0.6 + 0.6 * (1 - 0.6)= 0.84
result.g = 1 * 0.6 + 0.6 * (1 - 0.6)= 0.84
result.b = 0 * 0.6 + 0.5 * (1 - 0.6)= 0.2
result.a = 0.6 * 0.6 + 1 * (1 - 0.6)= 0.76 - .glBlendFuncSeparate(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA, GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)
result.r = 1 * 0.6 + 0.6 * (1 - 0.6)= 0.84
result.g = 1 * 0.6 + 0.6 * (1 - 0.6)= 0.84
result.b = 0 * 0.6 + 0.5 * (1 - 0.6)= 0.2
result.a = 0.6 * 1 + 1 * (1 - 0.6)= 1 - glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)
result.r = 1 * 1 + 0.6 * (1 - 0.6)= 1.24
result.g = 1 * 1 + 0.6 * (1 - 0.6)= 1.24
result.b = 0 * 1 + 0.5 * (1 - 0.6)= 0.2
result.a = 0.6 * 1 + 1 * (1 - 0.6)= 1
實際上,Bitmap在加載圖像的時候,會進行預乘,預乘就是將alpha通道的值分別與r、g、b的值相乘,得到新的r、g、b的值,所以預乘后如果源使用GLES20.GL_SRC_ALPHA顯然值是不正確的,就需要使用GLES20.GL_ONE得到正確的值。
比如像素值(1,1,0,0.6)預乘后像素值(0.6,0.6,0,0.6)
看到這幾個圖的效果應該也知道問題怎么解決了,那么繼續看下前面的問題。
我項目中會有多個層級的混合,部分采用blendFunc、部分采用shader,此處是使用shader mix 混合的,mix的混合模式等同于GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
mix(a, b, c) = a * (1-c) + b * c
由上述可知,采用GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
可解決黑色陰影問題。對應公式:
resultColor = blendColor * 1.0 + baseColor * (1.0 - blendColor.a);
為防止曝光,我們增加min函數:
resultColor = min(resultColor, vec4(1.0));
為什么是黑色陰影
你會發現,無論你清屏顏色設置的是什么,最后的半透明處的顏色都不是清屏顏色,但 當你設置給控件背景色時,你會發現,半透明的顏色會跟背景色變化
如下是修復后的效果:
另需要注意的是,源使用GLES20.GL_ONE對與貼圖設置alpha<1(著色器中的像素值 * alpha)會顯示異常,alpha不生效,仍需要使用GLES20.GL_SRC_ALPHA,所以我們要根據自己的層級情況選擇設置什么混合模式或者shader中的計算公式。
更多資料
creater關于blend,關于預乘premultiply alpha,關于圖片白邊灰邊的幾點疑問
圖片Premultiplied Alpha
紋理混合遇到的問題 pre-multiplying OpenGL Android iOS