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比较好用些。

猜你喜欢

转载自blog.csdn.net/YuQing_Cat/article/details/84103123