OpenGL.Shader:志哥教你写一个滤镜直播客户端:仿3个抖音滤镜效果(4镜像/电击/灵魂出窍)

OpenGL.Shader:志哥教你写一个滤镜直播客户端(可能是结束篇)

 

OpenGL.Shader基本的图像处理知识已经学习的7788了,所以这篇应该是滤镜直播客户端的最后一篇了,之后会出基于FFmpeg4.3的实战文章,可能还会穿插OpenCV系列的学习文章。

今天介绍几个抖音上比较流行的滤镜实现,网上可能已经存在很多相关的帖子,就是没有几个能提供demo,这不是耍流氓吗,所以还是自己动手来吧。不过u1s1,自己现在的实现效果可能也不太完美,因为之前写这框架没有考虑输入输出帧率的控制,很多好玩的滤镜其实需要掌握好帧率的控制,这是以前的我做得不足,有不足就要改进,所谓的框架迭代升级不就是这样吗?

0、动态帧率

在介绍滤镜实现之前,先说说如何识别现有框架下Android摄像头输出的动态帧率统计。有经验的同学都知道,Android系统的摄像头帧率输出一直都是动态变化着。在运行图像是静止的情况下,输出帧率可以稳定在25~30fps;但是一旦图像产生变化,及时帧内压缩有运动补偿,输出帧率就会降低至15~20fps。对于需要稳定获取Android摄像头输出帧的应用可说是“哑巴吃黄连——有口难言”,需要一种可靠的方法知道当前系统的输出帧率是多少,以便我们写的程序能在恰当的时机做出需要的调整。

实现思路很简单,视频帧率就是每秒内有多少帧图像显示的统计。我们只需要实现一个定时任务,每一秒回调一次次数统计并清空就可以了。具体代码如下:

头文件CELLTimer.h的接口定义

#ifndef CELLTIMER_H
#define CELLTIMER_H
#include <sys/types.h>

namespace CELL {

    class CELLTimerHandler {
    public:
        virtual void handlerCallback()=0;

        virtual ~CELLTimerHandler() {};
    };

    class CELLTimer {
    private:
        long m_second, m_microsecond;

        pthread_t thread_timer;

        static void *OnTimer_stub(void *p) {
            (static_cast<CELLTimer *>(p))->thread_proc();
            return NULL;
        }

        void thread_proc();

        CELLTimerHandler *m_handler;
        bool isStop;

    public:
        CELLTimer();

        CELLTimer(long second, long microsecond);

        virtual ~CELLTimer();

        void setTimer(long second, long microsecond, CELLTimerHandler *handler);

        void startTimer();

        void stopTimer();
    };
}
#endif // CELLTIMER_H

接口实现CELLTimer.cpp;实现的方法很简单,用POSIX标准的select函数实现。关于select和poll/epoll的多路复用内容是面试的常考知识点,大家还是要牢牢掌握好(这里推荐两篇收藏的好文,cpp角度https://www.cnblogs.com/aspirant/p/9166944.html,Java角度https://blog.csdn.net/qq_27529917/article/details/82945450)一些小巧妙(超时唤起当定时器)也可以略为了解。

namespace CELL {
//public methods//
    CELLTimer::CELLTimer() :
            m_second(0), m_microsecond(0) {
        thread_timer = -1;
    }

    CELLTimer::CELLTimer(long second, long microsecond) :
            m_second(second), m_microsecond(microsecond) {
        thread_timer = -1;
    }

    CELLTimer::~CELLTimer() {
    }

    void CELLTimer::setTimer(long second, long microsecond,
                             CELLTimerHandler *handler) {
        m_second = second;
        m_microsecond = microsecond;
        m_handler = handler;
    }

    void CELLTimer::startTimer() {
        if (thread_timer == -1)
            pthread_create(&thread_timer, NULL, OnTimer_stub, this);
    }

    void CELLTimer::stopTimer() {
        isStop = true;
        //pthread_join(thread_timer, NULL); //wait the thread stopped
        thread_timer = -1;
    }
//private methods//
    void CELLTimer::thread_proc() {
        while (!isStop) {
            if (m_handler != NULL)
                m_handler->handlerCallback();
            if (isStop) break;
            struct timeval tempval;
            tempval.tv_sec = m_second;
            tempval.tv_usec = m_microsecond;
            select(0, NULL, NULL, NULL, &tempval);
        }
        //LOGD("CELLTimer--thread_proc exit ... ");
    }

}

使用方法,在GpuFilterRender中定义一个CELL::CELLTimer mFpsTimer,并实现CELLTimer的回调接口CELLTimerHandler;一个静态变量mInputFps,一个私有变量mCurrentInputFps。在接收摄像头帧图输入的方法内统计计算mCurrentInputFps++,在CELLTimerHandler的回调就能及时统计出当前帧率。

// 头文件定义
int static      mInputFps;
int             mCurrentInputFps;
CELL::CELLTimer mFpsTimer;
// CELLTimer定时器Callback
virtual void handlerCallback()
{
    mInputFps = mCurrentInputFps;
    LOGI("InputFps : %d", mInputFps);
    mCurrentInputFps = 0;
}
// /
void GpuFilterRender::surfaceCreated(ANativeWindow *window)
{
    ... ...
    mFpsTimer.setTimer(1, 0, this);
    mFpsTimer.startTimer();
}
void GpuFilterRender::surfaceDestroyed()
{
    ... ...
    mFpsTimer.stopTimer();
}
void GpuFilterRender::feedVideoData(int8_t *data, int data_len, int previewWidth, int previewHeight)
{
    // 在帧图输入接口统计帧率
    mCurrentInputFps++;
}

 

1、4镜像滤镜

动态帧率有了,接下来先来一个简单的滤镜效果——4镜像,将屏幕它分割为四部分,每部分画一个相同的视频帧,因为屏幕被分割为4部分,我们的物体坐标在渲染时就不能设定为全屏的。在OpenGL中物体坐标,左下角为(-1,-1),右上角为(1,1),这样我们就可以分别计算出4部分的物体坐标。

确认好物体坐标后,我们接下来就要确认画什么?也就是将视频帧以什么样的方式画在物体坐标上,这时就需要控制纹理坐标,先前已介绍过OpenGL定义的纹理坐标:从左下角(0,0)到右上角(1,1),但实际上,系统视频帧图是一张以左上角为(0,0)到右下角(1,1)倒立过的图像。而且要想高效实现4镜像的渲染,需要借助fbo离屏渲染视频帧图,然后把视频帧当作一张纹理(此时的纹理就是按照OpenGL定义的纹理坐标),根据不同位置进行图像处理后再渲染,这样就可以实现简单的镜像效果。

实现代码如下,详情请参考https://github.com/MrZhaozhirong/NativeCppApp   /src/main/cpp/gpufilter/filter/DouYin4ImageFilter.hpp

#ifndef DOUYIN_4IMAGE_FILTER_HPP
#define DOUYIN_4IMAGE_FILTER_HPP
#include "GpuBaseFilter.hpp"
#include "../render/GpuFilterRender.h"

class DouYin4ImageFilter : public GpuBaseFilter {
public:
    int getTypeId() { return FILTER_TYPE_DOUYIN_4IMAGE; }

    DouYin4ImageFilter()
    {   // 不需要特殊的shader,沿用GpuBaseFilter就可以了。
        LOGI("---DouYin4ImageFilter构造, %p", this);
    }
    void init() {
        GpuBaseFilter::init(NO_FILTER_VERTEX_SHADER.c_str(), NO_FILTER_FRAGMENT_SHADER.c_str());
    }

    void onOutputSizeChanged(int width, int height) {
        GpuBaseFilter::onOutputSizeChanged(width, height);
        if (mFboTextureId != 9999) {
            glDeleteTextures(1, &mFboTextureId);
            mFboTextureId = 9999;
        }
        if (mFboId != 9999) {
            glDeleteFramebuffers(1, &mFboId);
            mFboId = 9999;
        }
        // 创建fbo,用于保存视频帧图作为纹理对象
        glGenFramebuffers(1, &mFboId);
        glGenTextures(1, &mFboTextureId);
        glBindTexture(GL_TEXTURE_2D, mFboTextureId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // GL_REPEAT
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // GL_REPEAT
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, 0);

        glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFboTextureId, 0);

        glBindTexture(GL_TEXTURE_2D, 0);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
                void* positionCords, void* textureCords)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
        GpuBaseFilter::onDraw(SamplerY_texId, SamplerU_texId, SamplerV_texId, positionCords, textureCords );
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        // 把视频帧的纹理对象当做二次输入,根据不同位置的顶点坐标和纹理坐标实现4镜像
        GpuBaseFilter::onDrawRGB(mFboTextureId, positionCords_left_top, texCoordinates_left_top);
        GpuBaseFilter::onDrawRGB(mFboTextureId, positionCords_right_top, texCoordinates_right_top);
        GpuBaseFilter::onDrawRGB(mFboTextureId, positionCords_left_bottom, texCoordinates_left_bottom);
        GpuBaseFilter::onDrawRGB(mFboTextureId, positionCords_right_bottom, texCoordinates_right_bottom);
    }

private:
    GLuint mFboId = 9999;
    GLuint mFboTextureId = 9999;
    // fbo纹理,遵循opengl的正立原则
    float texCoordinates_left_top[8] = {
            0.0f, 0.0f,     //左下
            1.0f, 0.0f,     //右下
            0.0f, 1.0f,     //左上
            1.0f, 1.0f,     //右上
    };
    float texCoordinates_right_top[8] = {
            1.0f, 0.0f,     //左下
            0.0f, 0.0f,     //右下
            1.0f, 1.0f,     //左上
            0.0f, 1.0f,     //右上
    };
    float texCoordinates_left_bottom[8] = {
            0.0f, 1.0f,     //左下
            1.0f, 1.0f,     //右下
            0.0f, 0.0f,     //左上
            1.0f, 0.0f,     //右上
    };
    float texCoordinates_right_bottom[8] = {
            1.0f, 1.0f,     //左下
            0.0f, 1.0f,     //右下
            1.0f, 0.0f,     //左上
            0.0f, 0.0f,     //右上
    };
    // 顶点坐标
    float positionCords_left_top[8] = {
            //x, y          //position
            -1.0f, -0.0f,   //左下
            0.0f, -0.0f,    //右下
            -1.0f, 1.0f,    //左上
            0.0f, 1.0f,     //右上
    };
    float positionCords_right_top[8] = {
            //x, y          //position
            -0.0f, -0.0f,   //左下
            1.0f, -0.0f,    //右下
            -0.0f, 1.0f,    //左上
            1.0f, 1.0f,     //右上
    };
    float positionCords_left_bottom[8] = {
            //x, y          //position
            -1.0f, -1.0f,   //左下
            0.0f, -1.0f,    //右下
            -1.0f, 0.0f,    //左上
            0.0f, 0.0f,     //右上
    };
    float positionCords_right_bottom[8] = {
            //x, y          //position
            -0.0f, -1.0f,   //左下
            1.0f, -1.0f,    //右下
            -0.0f, 0.0f,    //左上
            1.0f, 0.0f,     //右上
    };
};
#endif // DOUYIN_4IMAGE_FILTER_HPP

 

 

2、 电击效果

接下来一起来看看复杂一丢丢的特效实现方法,首先是电击效果,实际上它的实现就是反选的处理,只需要使用下面代码就可以:

gl_FragColor  = vec4((1.0 - texture.rgb), texture.w);

但想要达到一个很好的效果,其中还是有一些小技巧,也就是需要把握好节奏。假如我们现在有250ms运动的视频帧,再排上180ms静止的反选视频帧就可以实现了,如下方动图演示:假设50ms为一帧,那么对于10帧总时间为500ms的视频帧来说,前5帧都不变,依旧是正常的效果,从第6帧开始我们做反选并且保证画面是静止的,也就是说第7、8、9帧同样放第6帧,而第10帧时我们渲染正常的第10帧,这样周而复始就可以实现电击效果。这下就需要我们用到上方的动态帧率了,根据帧率我们要恰当的调整其电击效果的渲染。

原理实现的相关代码如下,详情请参考:https://github.com/MrZhaozhirong/NativeCppApp   /src/main/cpp/gpufilter/filter/DouYinElectricShockFilter.hpp

#ifndef DOUYIN_ELECTRIC_SHOCK_FILTER_HPP
#define DOUYIN_ELECTRIC_SHOCK_FILTER_HPP
#include "GpuBaseFilter.hpp"

class DouYinElectricShockFilter : public GpuBaseFilter {
public:
    int getTypeId() { return FILTER_TYPE_DOUYIN_SHOCK; }

    DouYinElectricShockFilter()
    {
        SHOCK_FRAGMENT_SHADER     ="precision mediump float;\n\
                                    varying highp vec2 textureCoordinate;\n\
                                    uniform sampler2D SamplerRGB;\n\
                                    uniform sampler2D SamplerY;\n\
                                    uniform sampler2D SamplerU;\n\
                                    uniform sampler2D SamplerV;\n\
                                    mat3 colorConversionMatrix = mat3(\n\
                                                       1.0, 1.0, 1.0,\n\
                                                       0.0, -0.39465, 2.03211,\n\
                                                       1.13983, -0.58060, 0.0);\n\
                                    vec3 yuv2rgb(vec2 pos)\n\
                                    {\n\
                                       vec3 yuv;\n\
                                       yuv.x = texture2D(SamplerY, pos).r;\n\
                                       yuv.y = texture2D(SamplerU, pos).r - 0.5;\n\
                                       yuv.z = texture2D(SamplerV, pos).r - 0.5;\n\
                                       return colorConversionMatrix * yuv;\n\
                                    }\n\
                                    uniform int is_shock;\n\
                                    void main()\n\
                                    {\n\
                                        vec4 textureColor = vec4(yuv2rgb(textureCoordinate), 1.0);\n\
                                        if (is_shock==0) \n\
                                        {\n\
                                            gl_FragColor = textureColor;\n\
                                        }\n\
                                        else\n\
                                        {\n\
                                            gl_FragColor = vec4( (1.0-textureColor.rgb), textureColor.w);\n\
                                        }\n\
                                    }";
        mInputFps = 0;
        mCurrentFps = 0;
    }
    ~DouYinElectricShockFilter() {
        if(!SHOCK_FRAGMENT_SHADER.empty()) SHOCK_FRAGMENT_SHADER.clear();
    }
    void init() {
        GpuBaseFilter::init(NO_FILTER_VERTEX_SHADER.c_str(), SHOCK_FRAGMENT_SHADER.c_str());
        mShockLocation = glGetUniformLocation(mGLProgId, "is_shock");
    }
    // 接收统计的静态帧率  和  当前帧率
    void setShockFps(int inputFps, int currentFps) {
        mInputFps = inputFps;
        mCurrentFps = currentFps;
    }
    void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
                void* positionCords, void* textureCords)
    {
        if (!mIsInitialized)
            return;
        glUseProgram(mGLProgId);
        glUniform1i(mDrawModeLocation, 0);

        glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords);
        glEnableVertexAttribArray(mGLAttribPosition);
        glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);
        glEnableVertexAttribArray(mGLAttribTextureCoordinate);
        // runPendingOnDrawTasks();
        if (mInputFps==0&&mCurrentFps==0) {
            mShock = 0;
        } else {
            int halfInputFps = mInputFps/2;
            if (mCurrentFps > halfInputFps)
            {
                mShock = 1;
            }
            else
            {
                mShock = 0;
            }
        }
        // 为了突出效果,我把一秒内的一半帧图都当作电击效果处理了。
        glUniform1i(mShockLocation, mShock);
        if (mShock==0) {
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, SamplerY_texId);
            glUniform1i(mGLUniformSampleY, 0);
            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D, SamplerU_texId);
            glUniform1i(mGLUniformSampleU, 1);
            glActiveTexture(GL_TEXTURE2);
            glBindTexture(GL_TEXTURE_2D, SamplerV_texId);
            glUniform1i(mGLUniformSampleV, 2);
        } else {
            //glActiveTexture(GL_TEXTURE0);
            //glActiveTexture(GL_TEXTURE1);
            //glActiveTexture(GL_TEXTURE2);
        }
        // onDrawArraysPre();
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        glDisableVertexAttribArray(mGLAttribPosition);
        glDisableVertexAttribArray(mGLAttribTextureCoordinate);
        glBindTexture(GL_TEXTURE_2D, 0);
    }
private:
    std::string SHOCK_FRAGMENT_SHADER;
    GLint mShockLocation;
    int mShock;
    int mInputFps;
    int mCurrentFps;
};
#endif // DOUYIN_ELECTRIC_SHOCK_FILTER_HPP

 

 

3、灵魂出窍

最后来个更好玩的滤镜效果——灵魂出窍。这个特效就是人影有一个向外扩散的效果,同样它的节奏也是非常重要的,尤其是能与音乐的配合才能达到一个完美的效果。在得到放大后的“灵魂”(拷贝帧),我们就需要考虑把“灵魂”和“肉体”(原本视频帧)混合起来,这里需要用到GLES的一个内嵌Mix函数将两个纹理进行mix即可。那么同理,我们还可以实现眩晕、影随的效果:眩晕是将每一帧向两侧做位移再与本帧进行mix,而影随则是将之前的帧缓存下来,以一定的间隔和当前帧做mix。

原理实现的相关代码如下,详情请参考:https://github.com/MrZhaozhirong/NativeCppApp   /src/main/cpp/gpufilter/filter/DouYinSouloutFilter.hpp

我个人更喜欢影随这个效果,所以尝试实现影随的效果。因为从原理上看,影随比灵魂出窍处理效果要好啊。实现方式大同小异,利用fbo在合适的时机保存“灵魂图像”,然后利用纹理坐标的归一化特性实现“视频帧”与“灵魂帧”的差异变化,最后就是利用GLSL的内置函数mix实现两帧图的混合。

#ifndef DOUYIN_SOULOUT_FILTER_HPP
#define DOUYIN_SOULOUT_FILTER_HPP
#include "GpuBaseFilter.hpp"

class DouYinSouloutFilter : public GpuBaseFilter {
public:
    int getTypeId() { return FILTER_TYPE_DOUYIN_SOULOUT; }

    DouYinSouloutFilter()
    {
        SOULOUT_VERTEX_SHADER   = "attribute vec4 position;\n\
                                   attribute vec4 inputTextureCoordinate;\n\
                                   attribute vec4 soulTextureCoordinate;\n\
                                   varying vec2 textureCoordinate;\n\
                                   varying vec2 soulCoordinate;\n\
                                   void main()\n\
                                   {\n\
                                      textureCoordinate = inputTextureCoordinate.xy;\n\
                                      soulCoordinate = soulTextureCoordinate.xy;\n\
                                      gl_Position = position;\n\
                                   }";

        SOULOUT_FRAGMENT_SHADER = "precision mediump float;\n\
                                   varying vec2 textureCoordinate;\n\
                                   varying vec2 soulCoordinate;\n\
                                   uniform sampler2D SamplerY;\n\
                                   uniform sampler2D SamplerU;\n\
                                   uniform sampler2D SamplerV;\n\
                                   uniform int mixFlag;\n\
                                   uniform int drawMode;\n\
                                   uniform sampler2D textureSoul;\n\
                                   mat3 colorConversionMatrix = mat3(\n\
                                                      1.0, 1.0, 1.0,\n\
                                                      0.0, -0.39465, 2.03211,\n\
                                                      1.13983, -0.58060, 0.0);\n\
                                   vec3 yuv2rgb(vec2 pos)\n\
                                   {\n\
                                      vec3 yuv;\n\
                                      yuv.x = texture2D(SamplerY, pos).r;\n\
                                      yuv.y = texture2D(SamplerU, pos).r - 0.5;\n\
                                      yuv.z = texture2D(SamplerV, pos).r - 0.5;\n\
                                      return colorConversionMatrix * yuv;\n\
                                   }\n\
                                   void main()\n\
                                   {\n\
                                      if (mixFlag==1) \n\
                                      {\n\
                                          gl_FragColor = vec4(yuv2rgb(textureCoordinate), 1.0);\n\
                                      }\n\
                                      else\n\
                                      {\n\
                                          vec4 normalColor = vec4(yuv2rgb(textureCoordinate), 1.0);\n\
                                          vec4 soulColor = texture2D(textureSoul, soulCoordinate);\n\
                                          gl_FragColor = mix(normalColor, soulColor, 0.35);\n\
                                      }\n\
                                   }";
    }
    ~DouYinSouloutFilter()
    {
        if(!SOULOUT_VERTEX_SHADER.empty()) SOULOUT_VERTEX_SHADER.clear();
        if(!SOULOUT_FRAGMENT_SHADER.empty()) SOULOUT_FRAGMENT_SHADER.clear();
    }

    void onOutputSizeChanged(int width, int height) {
        GpuBaseFilter::onOutputSizeChanged(width, height);
        if (mSoulTextureId != 9999) {
            glDeleteTextures(1, &mSoulTextureId);
            mSoulTextureId = 9999;
        }
        if (mFboId != 9999) {
            glDeleteFramebuffers(1, &mFboId);
            mFboId = 9999;
        }
        glGenFramebuffers(1, &mFboId);
        glGenTextures(1, &mSoulTextureId);
        glBindTexture(GL_TEXTURE_2D, mSoulTextureId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // GL_REPEAT
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // GL_REPEAT
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, 0);

        glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mSoulTextureId, 0);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    void init() {
        GpuBaseFilter::init(SOULOUT_VERTEX_SHADER.c_str(), SOULOUT_FRAGMENT_SHADER.c_str());
        mGLAttribSoulCoordinate = static_cast<GLuint>(glGetAttribLocation(mGLProgId, "soulTextureCoordinate"));
        mMixFlagLocation = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "mixFlag"));
        mGLUniformSampleSoul = static_cast<GLuint>(glGetUniformLocation(mGLProgId, "textureSoul"));
    }

    void setInputFps(int fps) {
        mInputFps = fps;
    }
    void setCurrentFps(int fps) {
        mCurrentFps = fps;
    }

    void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
                void* positionCords, void* textureCords)
    {
        if (mInputFps==0&&mCurrentFps==0) {
            mMixFlag = 0;
        } else {
            int halfInputFps = mInputFps/2;
            if (mCurrentFps>halfInputFps+0)
            //    || mCurrentFps==halfInputFps+1
            //    || mCurrentFps==halfInputFps+2
            //    || mCurrentFps==halfInputFps+3
            //    || mCurrentFps==halfInputFps+4
            //    || mCurrentFps==halfInputFps+5)
            {
                mMixFlag = 1;
            }
            else
            {
                mMixFlag = 0;
            }
        }
        if (mMixFlag == 1) //mMixFlag == 1
        { // 拷贝当前帧当灵魂
            glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
            GpuBaseFilter::onDraw(SamplerY_texId, SamplerU_texId, SamplerV_texId, positionCords, textureCords );
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
        }

        glUseProgram(mGLProgId);
        // 把相关参数传到shader当中。
        glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords); // 当前帧顶点坐标
        glEnableVertexAttribArray(mGLAttribPosition);
        glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);// 当前帧纹理坐标
        glEnableVertexAttribArray(mGLAttribTextureCoordinate);
        glVertexAttribPointer(mGLAttribSoulCoordinate, 2, GL_FLOAT, GL_FALSE, 0, soul_texCoordinates);// 灵魂帧纹理坐标
        glEnableVertexAttribArray(mGLAttribSoulCoordinate);

        glUniform1i(mMixFlagLocation, mMixFlag);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, SamplerY_texId);
        glUniform1i(mGLUniformSampleY, 0);

        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, SamplerU_texId);
        glUniform1i(mGLUniformSampleU, 1);

        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, SamplerV_texId);
        glUniform1i(mGLUniformSampleV, 2);

        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_2D, mSoulTextureId);
        glUniform1i(mGLUniformSampleSoul, 3);
        // onDrawArraysPre();
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        glDisableVertexAttribArray(mGLAttribPosition);
        glDisableVertexAttribArray(mGLAttribTextureCoordinate);
        glDisableVertexAttribArray(mGLAttribSoulCoordinate);
        glBindTexture(GL_TEXTURE_2D, 0);
    }
private:
    std::string SOULOUT_VERTEX_SHADER;
    std::string SOULOUT_FRAGMENT_SHADER;

    GLuint mFboId = 9999;
    GLuint mSoulTextureId = 9999;
    GLuint mMixFlagLocation;
    GLuint mGLAttribSoulCoordinate;
    GLuint mGLUniformSampleSoul;

    int mMixFlag;
    int mInputFps;
    int mCurrentFps;
    //灵魂顶点坐标,实现位置相对偏移。
    float soul_positionCords[8] = {
            //x, y          //position
            -1.0f, -1.0f,   //左下
            1.0f, -1.0f,    //右下
            -1.0f, 1.0f,    //左上
            1.0f, 1.0f,     //右上
    };
    //灵魂纹理坐标,相对原图取局内部分,然后填充到屏幕,实现相对放大。
    float soul_texCoordinates[8] = {
            0.1f, 0.1f,     //左下
            0.9f, 0.1f,     //右下
            0.1f, 0.9f,     //左上
            0.9f, 0.9f,     //右上
    };
};
#endif // DOUYIN_SOULOUT_FILTER_HPP

 

滤镜客户端系列文章到此告一段落。理论学习实质比较大,离实战商用还有一定的距离。正所谓人无完人,代码不可能完美没有bug。写博客的初衷就是为了温故而知新,谁不是笑看当年自己的代码?庆幸自己永远在学习总结的路上,也希望大家一直在路上。The end.

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值