本文從下面鏈接翻譯過來:
Android Lesson Five: An Introduction to Blending
這節課,我們來學習混合(blending)在OpenGL中的基本使用。我們來看看如何打開或關閉混合,怎樣設置不同的混合模式,以及不同的混合模式如何模擬現實生活中的效果。在後面的課程中,我們還將介紹如何使用alpha通道,如何使用深度緩衝區在同一個場景中渲染半透明和不透明的物體,以及什麼時候按深度排序對象,以及爲什麼。 我們還將研究如何監聽觸摸事件,然後基於此更改渲染狀態。 |
假設和先決條件
本系列每個課程構建都是以前一個課程爲基礎。然而,對於這節課,如果您理解了Android OpenGL ES 2.0(一)---入門就足夠了。儘管代碼基本上是前一課的,但是光照和紋理部分已在本課中移除,所以我們僅關注混合。
混合(Blending)
混合是將一種顏色與另一種顏色組合以獲得第三種顏色的行爲。我們在現實世界任何時候都能看到混合:當光線穿過玻璃並在玻璃表面發生反射時,當光源本身疊加在背景上時,例如我們在晚上看到一盞明亮的路燈周圍的耀斑。
OpenGL有不同的混合模式,我們能使用它模擬這種效果。在OpenGL中,混合發生在渲染過程的後期:一旦片元着色器計算出片元的最終輸出顏色並且它即將被寫入幀緩衝區,就會發生這種情況。通常情況下,這片元會覆蓋之前所有內容,但如果啓用了混合,那麼該片元將與之前的片元混合。
默認情況下,OpenGL的默認混合方程式相當於調用函數glBlendEquation()設置值爲GL_FUNC_ADD:
output = (source factor * source fragment) + (destination factor * destination fragment)
Ps:destination fragment的意思是先在屏幕規定的某個區域繪製的像素,source fragment表示你後在同樣的區域繪製的像素,比如區域(50,50,100,100)。
OpenGL ES2.0 中還有另外兩種模式GL_FUNC_SUBTRACT和GL_FUNC_REVERSE_SUBTRACT。這些可能在以後的教程中介紹,然而,當我嘗試調用此函數時,我在Nexus S上遇到了UnsupportedOperationException,因此Android實現可能實際上不支持此功能。這不是世界末日,因爲你可以用GL_FUNC_ADD做很多事情。
使用函數glBlendFunc()設置源因子和目標因子。下面將給出幾個常見混合因子的概述;更多信息以及不同可能的因素的列舉,請參閱Khronos在線手冊。
截取(Clamping)
OpenGL預期的輸入被限制在[0,1]的範圍內,並且輸出也被限制在[0,1]。這在實踐中意味着當您進行混合時,顏色可以在色調中移動。如果繼續向幀緩衝區添加紅色(RGB = 1,0,0),最終顏色會是紅色。如果想添加一點兒綠色,您要添加(RGB = 1,0.1,0)到緩衝區,即使您開始帶紅色的色調,最後也會得到黃色!打開混合時,您可以在本課程的Demo中看到此效果:不同顏色的重疊後的顏色變得過渡飽和。
不同類型的混合的效果
加法混合(Additive blending)
RGB顏色相加模型
加法混合是我們將不同顏色相加到一起所做的混合類型。這就是我們的視覺與光一起工作的方式,這就是我們如何在我們的顯示器上感知數百萬種不同的顏色 - 它們實際上只是將三種不同的原色混合在一起。
這種混合在3D混合中很有用,例如在粒子效果中,它們似乎發出光線和覆蓋物,例如燈光周圍的光暈,或光劍周圍的發光效果。
相加混合能通過調用glBlendFunc(GL_ONE, GL_ONE)指定,
混合的結果等式輸出=(1 * 源片段) + (1 * 目標片段),運算後:輸出=源片段 + 目標片段
乘法混合(Multiplicative blending)
光照貼圖的一個例子
乘法混合(也稱爲調製)是另一種有用的混合模式,它表示光在通過濾色器時的行爲方式,或者從被點亮的物體反射並進入我們的眼睛時的行爲。 紅色物體對我們來說是紅色的,因爲當白光照射到物體上時,藍色和綠色光被吸收。 只有紅光反射回我們的眼睛。 在上面的例子中,我們可以看到表面反射一些紅色和一些綠色,基本沒有反射藍色。
當多紋理不可用時,乘法混合用於在遊戲中實現光照貼圖。紋理與光照貼圖相乘,以填充在明亮和陰影的區域。
相乘混合能通過調用glBlendFunc(GL_DST_COLOR, GL_ZERO)指定,
其混合的結果等式輸出=(目標片段 * 源片段)+ (0 * 目標片段),寫作:輸出=目標片段 * 源片段。
相乘混合能通過調用glBlendFunc(GL_DST_COLOR, GL_ZERO)指定,
其混合的結果等式輸出=(目標片段 * 源片段)+ (0 * 目標片段),寫作:輸出=目標片段 * 源片段。
插值混合(Interpolative blending)
插值混合結合了乘法和加法,以提供插值效果。與添加和調製本身不同,此混合模式也可是依賴繪製順序的。因此在某些情況下,如果您先畫出最遠的半透明物體,然後繪製更近的物體,結果纔會是正確。即使排序也不是完美,因爲三角形可能重疊並相交,但產生的僞像可能是可接受的。
插值通常是將相鄰的表面混合在一起,以及做有色玻璃或淡入淡出的效果。上面這個圖片顯示了兩個紋理使用插值混合在一起。
插值混合通過調用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)指定,
其混合結果等式輸出 = (源alpha * 源片段) + ((1 - 源alpha) * 目標片段)。這是一個例子:
這是一個例子:想象一下,我們正在繪製一個透明度爲25%的綠色(0,1,0),當前屏幕上的物體時紅色(1,0,0)。
output = (source factor * source fragment) + (destination factor * destination fragment)
output = (source alpha * source fragment) + ((1 – source alpha) * destination fragment)
output = (0.25 * (0r, 1g, 0b)) + (0.75 * (1r, 0g, 0b))
output = (0r, 0.25g, 0b) + (0.75r, 0g, 0b)
output = (0.75r, 0.25g, 0b)
注意,我們不需要對目標alpha做任何涉及,因爲這個幀緩衝區本身不需要alpha通道,這爲我們提供了更多的顏色通道位。
使用混合
在我們的課程中,我們的Demo將使用加法混合將立方體顯示爲可以自己發光的物體。因爲發光的物體不需要光照,所以這個Demo中沒有光照計算。我也刪除了紋理,雖然它可以很好地使用。本課程的着色器程序很簡單;我們只需要一個可傳遞顏色的着色器。
頂點着色器
uniform mat4 u_MVPMatrix; // A constant representing the combined model/view/projection matrix.
attribute vec4 a_Position; // Per-vertex position information we will pass in.
attribute vec4 a_Color; // Per-vertex color information we will pass in.
varying vec4 v_Color; // This will be passed into the fragment shader.
// The entry point for our vertex shader.
void main()
{
// Pass through the color.
v_Color = a_Color;
// gl_Position is a special variable used to store the final position.
// Multiply the vertex by the matrix to get the final point in normalized screen coordinates.
gl_Position = u_MVPMatrix * a_Position;
}
片元着色器
precision mediump float; // Set the default precision to medium. We don't need as high of a
// precision in the fragment shader.
varying vec4 v_Color; // This is the color from the vertex shader interpolated across the
// triangle per fragment.
// The entry point for our fragment shader.
void main()
{
// Pass through the color
gl_FragColor = v_Color;
}
開啓混合
開啓混合很簡單,調用下面的方法:
// No culling of back faces
GLES20.glDisable(GLES20.GL_CULL_FACE);
// No depth testing
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
// Enable blending
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE);
我們關閉背面剔除,是因爲如果立方體是半透明的,那麼現在我們能看到立方體的背面。我們需要繪製它們,否則可能看起來會很奇怪。出於同樣的原因我們關閉了深度測試。
監聽觸摸事件並對其進行相應
您將注意到,當您運行Demo時,可以通過點擊屏幕來打開和關閉混合。 請參考文章 “Listening to Android Touch Events, and Acting on Them” 獲取更多更與觸摸事件響應的信息。
進一步練習
這個Demo目前僅使用相加混合,嘗試改變其爲插值混合並重新添加燈光和紋理。如果您只在黑色背景上繪製兩個半透明紋理,繪製順序是否重要?
可以從GitHub上的項目站點下載本課程的完整源代碼。