1. 程式人生 > >Android OpenGLES濾鏡開發之仿抖音靈魂出竅

Android OpenGLES濾鏡開發之仿抖音靈魂出竅

前言

前幾篇寫的濾鏡效果比如美顏、大眼、貼紙效果都是在錄製視訊之前,這個靈魂出竅的效果是在錄製視訊之後,可以對視訊新增效果。

思路

可以觀察到靈魂出竅的效果,其實其主影象本沒有什麼變化,只是新增了一張進行縮放的紋理,跟主影象的alpha進行線性融合的。
怎麼去取靈魂呢,靈魂是跟著視訊所播放的內容不斷更新的,不可能一直只是同一個影象。所以這裡的思路是每X幀拷貝一幀作為靈魂,然後將靈魂按比例放大,最後將靈魂與主影象進行混合。

實現

片元著色器

因為視訊錄製出來的是YUV格式的資料,但是在OpenGL中是需要的RGB顏色,所以需要將YUV格式的資料轉化成RGB格式的,這裡需要將YUV格式的資料進行分離之後,再轉換。轉換是有公式的,用公式進行計算就好啦。

uniform sampler2D sampler_y; //yuv
uniform sampler2D sampler_u;
uniform sampler2D sampler_v;

//透明度
uniform float alpha;

void main(){
    //4個float資料 y、u、v儲存在向量中的第一個
    // 0.5是128資料歸一後的
    float y = texture2D(sampler_y,aCoord).r;
    float u = texture2D(sampler_u,aCoord).r - 0.5;
    float v = texture2D
(sampler_v,aCoord).r - 0.5; // yuv轉rgb的公式 //R = Y + 1.402 (v-128) //G = Y - 0.34414 (u - 128) - 0.71414 (v-128) //B = Y + 1.772 (u- 128) vec3 rgb; //u - 128 //1、glsl中 不能直接將int與float進行計算 //2、rgba取值都是:0-1 (128是0-255 歸一化為0-1 128就是0.5) rgb.r = y + 1.402 * v; rgb.g = y - 0.34414 * u - 0.71414
* v; rgb.b = y + 1.772 * u; //rgba gl_FragColor = vec4(rgb,alpha);
渲染主影象

首先就是獲取索引,然後建立Y、U、V三個紋理,分別進行傳值,然後先畫主影象,不進行任何的縮放平移。

        //分離yuv資料,然後傳值
        bodyImage.initData(yuv);
        if(!bodyImage.hasImage()){
            return;
        }
        GLES20.glUseProgram(mGLProgramId);

        //不進行任何的縮放平移
        Matrix.setIdentityM(matrix,0);
        GLES20.glUniformMatrix4fv(vMatrix,1,false,matrix,0);
        //主影象不透明
        GLES20.glUniform1f(mAlpha,1);
        //傳值
        onDrawBody(bodyImage);

上面程式碼就是首先將主影象的YUV資料進行分離,分離YUV就不介紹了,比較簡單,然後設定setIdentityM設定一個單位矩陣,這個矩陣是沒有任何縮放平移效果的。這個方法的原始碼是

    /** 最終得出的矩陣
    * 1 0 0 0
    * 0 1 0 0
    * 0 0 1 0
    * 0 0 0 1
    */
 public static void setIdentityM(float[] sm, int smOffset) {
        for (int i=0 ; i<16 ; i++) {
            sm[smOffset + i] = 0;
        }
        for(int i = 0; i < 16; i += 5) {
            sm[smOffset + i] = 1.0f;
        }
    }

這個矩陣需要個傳入的4個頂點座標進行運算,為什麼這樣的單位矩陣就是沒有任何效果的呢,可以來進行矩陣運算一下,輸入頂點座標為(1,1,0,0)的話,與矩陣運算之後的結果還是(1,1,0,0)

設定完需要的矩陣,YUV三個紋理,就需要將這些頂點,YUV紋理傳遞給著色器就可以了。這個裡就看一下Y資料的傳遞,UV資料傳遞也是一樣的。

      //傳遞yuv資料
       //啟用紋理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        //繫結mTextures[0]紋理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,mTextures[0]);
    //  glTexImage2D方法是指定一個二維紋理影象 這裡就是將Y資料與mTextures[0]這個紋理相繫結
    //第一個引數target 是指定紋理單元的目標紋理,二維就是GLES20.GL_TEXTURE_2D是有取值範圍的
    //第二個引數是level,是指定詳細編號,0表示基本影象級別,n就是第n個縮圖
    //第三個引數internalformat ,指定紋理的內部格式,必須是下列符號常量之一:GL_ALPHA,GL_LUMINANCE,GL_LUMINANCE_ALPHA,GL_RGB,GL_RGBA。因為我們這次傳遞的是YUV格式資料,所以需要給GL_LUMINANCE,意思是量度模型
    //第四個引數width,紋理影象的寬,第五個就是紋理影象的高,第六個就是邊框的寬度border必須為0
    //第七個引數format,指定紋理資料的格式,和internalformat是相匹配的
    //第八個引數type,是指紋理資料的型別,這裡給的是無符號的byte型別
    //第九個引數java.nio.Buffer pixels,畫素值,這個給的是Y資料,如果是RGA格式的,一般就是0畫素
   GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,0,GLES20.GL_LUMINANCE,
                mOutputWidth,mOutputHeight,0,GLES20.GL_LUMINANCE,
                GLES20.GL_UNSIGNED_BYTE,bodyImage.getY());
        GLES20.glUniform1i(mSamplerY,0);
渲染靈魂

因為靈魂出竅這個效果,靈魂是不斷的更新放大,所以我們這裡是採用了間隔X幀拷貝一幀作為靈魂。

       interval++ ;
        //沒有儲存靈魂或者 使用次數已經達到上限了
        // 靈魂只能使用X次,因為需要不斷的更新靈魂
        if (!soulImage.hasImage() || interval > mFps){
            interval = 1;
            // 記錄新靈魂
            soulImage.initData(yuv);
        }

        if (!soulImage.hasImage()){
            return;
        }

        //畫靈魂
        //需要和主影象進行混合的,所以需要開啟混合模式
        GLES20.glEnable(GLES20.GL_BLEND);
        //這兩個引數之前也說過了,這次的源是靈魂,目標是主影象
        //這次是主影象不變,也就是目標不變
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA,GLES20.GL_ONE);

        //設定matrix為單位矩陣
        Matrix.setIdentityM(matrix,0);
        //設定縮放大小
        //本次放大為 1+ 當前靈魂次數佔總次數 * 2的比例
        //這個是自己設定的慢慢放大的效果
        float scale = 1.0f + interval/(mFps * 2.f);
        //矩陣的縮放函式,結果儲存在martix中
        Matrix.scaleM(matrix,0,scale,scale,0);
        GLES20.glUniformMatrix4fv(vMatrix,1,false,matrix,0);
        GLES20.glUniform1f(mAlpha,0.1f + (mFps -interval) / 100.f);

吐槽

這樣就差不多可以實現了靈魂出竅的效果了,我本來是使用MediaCodec編解碼來進行錄製和播放的,但是錄製沒問題,播放的時候就有問題了,MediaCodec的相容性真滴太差了,它在個別機型上面播放的時候,可能相容不了,就會播放出黑白視訊,木有顏色~~所以這裡就不放效果圖了,改天我用FFmeg來解碼播放吧,還是FFmeg比較好用些。