OpenGL Android課程五:介紹混合(Blending)

翻譯文

原文標題:Android Lesson Five: An Introduction to Blending
原文鏈接:http://www.learnopengles.com/android-lesson-five-an-introduction-to-blending/


介紹混合(Blending)

這節(jié)課,我們來學習混合(blending)在OpenGL中的
基本使用。我們來看看如何打開或關閉混合,怎樣設置
不同的混合模式,以及不同的混合模式如何模仿顯示生
活中的效果。在后面的課程中,我們還將介紹如何使用
alpha通道,如何使用深度緩沖區(qū)在同一個場景中渲染
半透明和不透明的物體,以及什么時候按深度排序對象,
以及為什么。

我們還將研究如何監(jiān)聽觸摸事件,然后基于此更改渲染
狀態(tài)。
display
display

基本混合

前提條件

本系列每個課程構建都是以前一個課程為基礎。然而,對于這節(jié)課,如果您理解了OpenGL Android課程一:入門就足夠了。盡管代碼基本上是前一課的,照明和紋理部分已在本課中移除,因此我們僅關注混合。

混合(Blending)

混合是將一種顏色與另一種顏色組合以獲得第三種顏色的行為。我們在現實世界任何時候都能看到混合:當光穿過玻璃時,當它從表面反射時,當光源本身疊加在背景上時,例如我們在晚上看到一盞明亮的路燈周圍的耀斑。

OpenGL有不同的混合模式,我們能使用它模擬這種效果。在OpenGL中,混合發(fā)生在渲染過程的后期:一旦片段著色器計算出片段的最終輸出顏色并且它即將被寫入幀緩沖區(qū),就會發(fā)生這種情況。通常情況下,這片段會覆蓋之前所有內容,但如果啟用了混合,那么該片段將與之前的片段混合。

默認情況下,當glBlendEquation()設置為默認值GL_FUNC_ADD時OpenGL的默認混合方程式為:

// 輸出 = (源因子 * 源片段) + (目標因子 * 目標片段)
output = (source factor * source fragment) + (destination factor * destination fragment)

OpenGL ES 2 中還有另外兩種模式GL_FUNC_SUBTRACTGL_FUNC_REVERSE_SUBTRACT。
這些可能在以后的教程中介紹,然而,當我嘗試調用此函數時,我在Nexus S上遇到了
UnsupportedOperationException,因此Android實現可能實際上不支持此功能。
這不是世界末日,因為你可以用GL_FUNC_ADD做很多事情。

使用函數glBlendFunc()設置源因子和目標因子。下面將給出幾個常見混合因子的概述;更多信息以及不同可能的因素的列舉,請參閱Khronos在線手冊

截取(Clamping)

OpenGL預期的輸入被限制在[0,1]的范圍內,并且輸入也被限制在[0,1]。這在實踐中意味著當您進行混合時,顏色可以在色調中移動。
如果繼續(xù)想幀緩沖區(qū)添加紅色(RGB = 1,0,0),最終顏色會是紅色。如果想添加一點兒綠色,您要添加(RGB = 1,0.1,0)到緩沖區(qū),即使您開始帶紅色的色調,最后也會得到黃色!
打開混合時,您可以在本課程的Demo中看到此效果:不同顏色的重疊的顏色變得過飽和。

不同類型的混合以及它們有怎樣不同的效果

相加混合(Additive blending)

rgb
rgb
RGB顏色相加模型; 來源:Wikipedia

相加混合是當我們添加不同顏色在一起的混合,這就是我們的視覺與光一起工作的模式,這就是我們如何在我們的顯示器上感知數百萬種不同的顏色——它們實際上只是將三種不同的原色混合在一起。

這種混合在3D混合中很有用,例如在粒子效果中,它們似乎發(fā)出光線和覆蓋物,例如燈光周圍的光暈,或光劍周圍的發(fā)光效果。

相加混合能通過調用glBlendFunc(GL_ONE, GL_ONE)指定,
混合的結果等式輸出=(1 * 源片段) + (1 * 目標片段),運算后:輸出=源片段 + 目標片段

相乘混合(Multiplicative blending)

rg
rg
光照貼圖的一個例子

相乘混合(也稱為調制)是另一種有用的混合模式,它表示光在通過過濾器時的行為方式,或從被點燃的物體反射并進入我們的眼睛。一個紅色的物體看上去是紅色是因為白光照射到這個物體上,藍光和綠光被吸收,只有紅光反射回我們的眼睛。在上面的例子中,我們能看到一些紅色和綠色,但是很少會有一點藍色。

當多紋理不可用時,乘法混合用于在游戲中實現光照貼圖。紋理與光照貼圖相乘,以填充在明亮和陰影的區(qū)域。

相乘混合能通過調用glBlendFunc(GL_DST_COLOR, GL_ZERO)指定,
其混合的結果等式輸出=(目標片段 * 源片段)+ (0 * 目標片段),寫作:輸出=目標片段 * 源片段

插值混合(Interpolative blending)

textures
textures
一個兩個紋理一起插值的案例

插值混合結合了乘法和加法,以提供插值效果。與添加和調制本身不同,此混合模式也可是依賴繪制順序的。因此在某些情況下,如果您先畫出最遠的半透明物體,然后繪制更近的物體,結果才會是正確。即使排序也不是完美,因為三角形可能重疊并相交,但產生的偽像可能是可接受的。

插值通常是將相鄰的表面混合在一起,以及做有色玻璃或淡入淡出的效果。上面這個圖片顯示了兩個紋理(紋理來自公共領域紋理)使用插值混合在一起。

插值混合能通過調用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)指定,
其混合結果等式輸出 = (源alpha * 源片段) + ((1 - 源alpha) * 目標片段)。這是一個例子:

想象一下,我們正在繪制一個只有25%不透明的綠色(0,1,0),當前屏幕上的物體時紅色(1,0,0)。

輸出 = (源因子 * 源片段) + (目標因子 * 目標片段)
輸出 = (源alpha * 源片段) + ((1 - 源alpha) * 目標片段)

輸出 = (0.25 * (0, 1, 0)) + (0.72 * (1, 0, 0))
輸出 = (0, 0.25, 0) + (0.75, 0, 0)
輸出 = (0.75, 0.25, 0)

注意,我們不需要對目標alpha做任何涉及,因為這個幀緩沖區(qū)本身不需要alpha通道,這為我們提供了更多的顏色通道位。

使用混合

在我們的課程中,我們的Demo將使用相加混合將立方體顯示為光的發(fā)射器。發(fā)光的東西不需要其他光源照亮,因此這個Demo中沒有燈光。我也刪除了紋理,雖然它可以很好地使用。本課程的著色器程序很簡單;我們只需要一個可傳遞顏色的著色器。

頂點著色器

uniform mat4 u_MVPMatrix;
attribute vec4 a_Position;
attribute vec4 a_Color;

varying vec4 v_Color;

void main()
{
    v_Color = a_Color;
    gl_Position = u_MVPMatrix * a_Position;
}

片段著色器

precision mediump float;
varying vec4 v_Color;

void main()
{
    gl_FragColor = v_Color;
}

打開混合

打開混合就像是做一些方法調用那么簡單:

// 關閉剔除去掉背面
GLES20.glDisable(GLES20.GL_CULL_FACE);
// 關閉深度測試
GLES20.glDisable(GLES20.GL_DEPTH_TEST);

// 啟動混合
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE);

我們關閉背面剔除,是因為如果立方體是半透明的,那么現在我們能看到立方體的背面。我們需要繪制它們,否則可能看起來會很奇怪。出于同樣的原因我們關閉了深度測試。

學習觸摸事件并進行操作

你將注意到,當您運行Demo時,可以通過點擊屏幕來打開和關閉混合。

現實觸摸事件,您首先需要創(chuàng)建您的GLSurfaceView自定義view。在這個view中,創(chuàng)建一個默認構造用來調用父類,創(chuàng)建一個新的方法來接收特定的渲染器替換常用接口,并覆寫onTouchEvent()。我們傳入一個具體的渲染器類,因為我們將要在onTouchEvent()方法中調用這個類的特定方法。

在Android中,OpenGL渲染器在獨立的線程中完成,因此我們還將看看如何安全的從正在監(jiān)聽觸摸事件的主線程調度到單獨的渲染器線程。

public class LessonFiveGLSurfaceView extends GLSurfaceView {

    private LessonFiveRenderer mRenderer;

    public LessonFiveGLSurfaceView(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (
                event == null
                || event.getAction() != MotionEvent.ACTION_DOWN
                || mRenderer == null) {
            return super.onTouchEvent(event);
        }
        // 確保我們在OpenGL線程上調用switchMode()
        // queueEvent() 是GLSurfaceView的一個方法,它將為我們做到這點
        queueEvent(new Runnable() {
            @Override
            public void run() {
                mRenderer.switchMode();
            }
        });
        return true;
    }

    public void setRenderer(LessonFiveRenderer renderer) {
        mRenderer = renderer;
        super.setRenderer(renderer);
    }
}

LessonFiveRenderer中實現switchMode()

public void switchMode() {
    mBlending = !mBlending;

    if (mBlending) {
        // 關閉剔除去掉背面
        GLES20.glDisable(GLES20.GL_CULL_FACE);
        // 關閉深度測試
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);

        // 啟動混合
        GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE);
    } else {
        GLES20.glEnable(GLES20.GL_CULL_FACE);
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
        GLES20.glDisable(GLES20.GL_BLEND);
    }
}

仔細看LessonFiveGLSurfaceView::onTouchEvent(),主要記住觸摸事件都是在UI主線程中
,而GLSurfaceView在一個單獨的線程中創(chuàng)建OpenGL ES上下文,這意味著我們的渲染器的回調也在一個單獨的線程中運行。這是一個需要記住的重點,因為我們不能再其他線程調用OpenGL并希望其工作。

辛運的是,編寫GLSurfaceView的人也想到了這點,并提供了一個queueEvent()方法,這使得你可以調用OpenGL線程上的東西。因此,當我們想通過點擊屏幕打開和關閉混合時,我們確保通過在UI線程中使用queueEvent()來正確調用OpenGL線程中的內容。

進一步練習

這個Demo目前僅使用相加混合,嘗試改變其為插值混合并重新添加燈光和紋理。如果您只在黑色背景上繪制兩個半透明紋理,繪制順序是否重要?什么時候重要?

教程目錄

打包教材

可以在Github下載本課程源代碼:下載項目
本課的編譯版本也可以再Android市場下:google play 下載apk
“我”也編譯了個apk,方便大家下載:github download

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容