美文网首页
[OpenGL]未来视觉4-Native层滤镜添加

[OpenGL]未来视觉4-Native层滤镜添加

作者: CangWang | 来源:发表于2019-01-28 14:38 被阅读177次

    大家好,我系苍王。
    以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。

    [Android]如何做一个崩溃率少于千分之三噶应用app--章节列表

    Android组件化架构热卖中

    上一节介绍了摄像头的帧采集,这一节将要介绍采集回来的摄像头数据如何显示到屏幕,以及对数据进行滤镜添加。

    现在demo中提供了大概有三十几种滤镜,其原来MagicCamera大致一样的。
    首先说一下采坑遇到的问题


    image.png

    1.图片格式不同的问题

    滤镜效果比较简单的理解就是原来的图像的基础上,混合上纹理显示出来的效果,而Opengl中纹理可以图片,也可以数据的形式来加载。一般是有png,jpg,bmp三种。
    移动端很少使用bmp图,因为图片比较压缩度低,一般bmp图片容量对比png,jpg大很多。png图和jpg图最大的不同是,png是RGBA的编码,而jpg是RGB编码,就是透明通道的区别,如果填写错误,我遇到过现实的滤镜会有黑条的问题(不是显示不出来,是黑条)。而Opengl很多都需要填写通道的格式和通道长度的,这点尤为重要。
    我一开始复写这个框架的时候就是因为对图片解码等问题不够深入,耽误了很长的时间(大概两周,才在这个坑口爬出来,所以入门的时候一定要认清这些图片格式确切的区别)

    2.图片加载的问题

    (1)如果你单纯用Android的OpenGL来读取图片纹理,非常简单,Android的OpenGL直接支持bitmap的输入作为纹理转换。
    (2)如果你单纯使用jni来读取图片,Android独有的android/bitmap.h框架可以直接从java层传递二进制图片数据到native层,而且能读取到图片的一些基本信息。
    (3)如果你想单纯通过native层来完成图片纹理读取解码以及native 的opengl加载,这是最高自由度的,可以根本上考虑代码的复用性,以及图片加载机制的了解。当然,我就选择了这种,途中遇到复杂的问题拆分为:

    如何让native读取到Android内置图片的地址?

    在native读取到内置图片的地址,这里要借助到android种asset这个文件夹,用来存放纹理图片,以及shader文件。(当然你也可以将shader文件也复制到代码中,这样更加压缩会更加好,但是维护比较麻烦就是了)。通过asset来读取到图片文件信息,这样做的好处在于图片的处理都在native层,当图片释放,可以立刻释放资源,不会想java那张图片可能还在缓存中等待回收。asset_manager_jni.h中提供了一套完整的读取asset文件夹的方法。

          //打开asset文件夹中的文件夹
            AAssetDir *dir = AAssetManager_openDir(manager,"filter");
            //初始化文件名指针
            const char *file = nullptr;
            //循环遍历
            while ((file =AAssetDir_getNextFileName(dir))!= nullptr) {
                //对比文件名
                if (strcmp(file, fileName) == 0) {
                    //拼接文件路径,以asset文件夹为起始
                    std::string *name = new std::string("filter/");
                    name->append(file);
                    //以流的方式打开文件
                    AAsset *asset = AAssetManager_open(manager, name->c_str(), AASSET_MODE_STREAMING);
    

    如果在native中读取到图片的信息?

    读取图片文件的信息,包括长宽,图片大小,通道大小,这里当然只能使用C的框架来读取图像数据了,这里使用了一个比较简便,且可接入性较高的图像解析库stb

    //引用stb库
    #define STB_IMAGE_IMPLEMENTATION
    #include "src/main/cpp/utils/stb_image.h"
    
                        int len = AAsset_getLength(asset);
                        int width=0,height=0,n=0;
                        //读取资源文件流
                        unsigned char* buff = (unsigned char *) AAsset_getBuffer(asset);
                        //读取图片长宽以及通道数据
                        unsigned char* data = stbi_load_from_memory(buff, len, &width, &height, &n, 0);
                        ALOGV("loadTextureFromAssets fileName = %s,width = %d,height=%d,n=%d,size = %d",fileName,width,height,n,len);
                        //关闭资源
                        AAsset_close(asset);
                        //关闭asset文件夹
                        AAssetDir_close(dir);
    

    这里一定要注意,引入stb_image.h的方式,请一定要遵循我这种引用方式,不然将会出现某些图像无法解析的可能。情况是能加载长宽一致的图片,但是无法加载例如256*3这样大小的图片,找了半天原来是库引用方式的问题,我就是在这里卡了几天,一度怀疑人生,想要换库操作。


    image.png

    读取完成后怎么加载图片的纹理信息?

    这里其实很明确使用glTexImage2D来将图片的数据生成一个2D纹理,但是有人会考虑使用mipmaps贴图,但是滤镜一般都不需要使用mimpas贴图的。千万别误用了mipmaps贴图,mipmaps贴图是2的次幂方,而且长和宽大小是一样的正方形,然而我真的是调试的时候自己傻傻的添加了,只怪我还是太年轻了。

                           if(data!=NULL) {
                            GLint format = GL_RGB;
                            if (n==3) { //RGB三通道,例如jpg格式
                                format = GL_RGB;
                            } else if (n==4) {  //RGBA四通道,例如png格式
                                format = GL_RGBA;
                            }
                            //将图片数据生成一个2D纹理
                            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
                        } else{
                            LOGE("load texture from assets is null,fileName = %s",fileName);
                        }
    

    做完这些后可以从Native直接读取到图片纹理,那么接下来就是将摄像头采集过来的帧图和滤镜纹理叠加,然后将其显示到屏幕当中,如何获取到帧缓冲图,请查看上一节的内容。


    image.png

    滤镜添加的流程图


    滤镜添加.png

    这里是绘制的公共代码。

    if (cameraInputFilter != nullptr){
    //        cameraInputFilter->onDrawFrame(mTextureId,matrix,VERTICES,TEX_COORDS);
            //获取帧缓冲id
            GLuint id = cameraInputFilter->onDrawToTexture(mTextureId,matrix);
            if (filter != nullptr)
                //通过滤镜filter绘制
                filter->onDrawFrame(id,matrix);
            //缓冲区交换
            glFlush();
            mEGLCore->swapBuffer();
        }
    
    int GPUImageFilter::onDrawFrame(const GLuint textureId, GLfloat *matrix,const float *cubeBuffer,
                                    const float *textureBuffer) {
        onDrawPrepare();
        glUseProgram(mGLProgId);
        if (!mIsInitialized) {
            ALOGE("NOT_INIT");
            return NOT_INIT;
        }
    
        //加载矩阵
    //    glUniformMatrix4fv(mMatrixLoc,1,GL_FALSE,matrix);
        glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, cubeBuffer);
        glEnableVertexAttribArray(mGLAttribPosition);
        glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureBuffer);
        glEnableVertexAttribArray(mGLAttribTextureCoordinate);
    
        if(textureId !=NO_TEXTURE){
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D,textureId);
            //加载纹理
            glUniform1i(mGLUniformTexture,0);
        }
        //滤镜参数加载
        onDrawArraysPre();
        glDrawArrays(GL_TRIANGLE_STRIP,0,4);
        //滤镜参数释放
        onDrawArraysAfter();
        //释放顶点绑定
        glDisableVertexAttribArray(mGLAttribPosition);
        glDisableVertexAttribArray(mGLAttribTextureCoordinate);
    
        if(textureId !=NO_TEXTURE) //激活回到默认纹理
            glBindTexture(GL_TEXTURE_2D,0);
        return ON_DRAWN;
    }
    

    用一个简单的Amaro滤镜来说明一下使用。

    //构造时加载program,以及shader
    MagicAmaroFilter::MagicAmaroFilter(AAssetManager *assetManager)
        : GPUImageFilter(assetManager,readShaderFromAsset(assetManager,"nofilter_v.glsl"), readShaderFromAsset(assetManager,"amaro.glsl")){
    
    }
    
    //初始化shader顶点和纹理参数
    void MagicAmaroFilter::onInit() {
        GPUImageFilter::onInit();
        inputTextureUniformLocations[0] = glGetUniformLocation(mGLProgId,"inputImageTexture2");
        inputTextureUniformLocations[1] = glGetUniformLocation(mGLProgId,"inputImageTexture3");
        inputTextureUniformLocations[2] = glGetUniformLocation(mGLProgId,"inputImageTexture4");
        mGLStrengthLocation = glGetUniformLocation(mGLProgId,"strength");
    }
    
    //从多个图中生成纹理
    void MagicAmaroFilter::onInitialized() {
        GPUImageFilter::onInitialized();
        inputTextureHandles[0] = loadTextureFromAssets(mAssetManager,"brannan_blowout.png");
        inputTextureHandles[1] = loadTextureFromAssets(mAssetManager,"overlaymap.png");
        inputTextureHandles[2] = loadTextureFromAssets(mAssetManager,"amaromap.png");
    }
    

    在绘制的时候onDrawArraysPre()加入绑定纹理,以及onDrawArraysAfter()取消绑定

    void MagicAmaroFilter::onDrawArraysPre() {
        glUniform1f(mGLStrengthLocation, 1.0f);
        if (inputTextureHandles[0] != 0) {
            glActiveTexture(GL_TEXTURE3);
            glBindTexture(GL_TEXTURE_2D, inputTextureHandles[0]);
            glUniform1i(inputTextureUniformLocations[0], 3);
        }
    ……
    }
    
    void MagicAmaroFilter::onDrawArraysAfter() {
        if (inputTextureHandles[0] != 0) {
            glActiveTexture(GL_TEXTURE3);
            glBindTexture(GL_TEXTURE_2D, inputTextureHandles[0]);
            glActiveTexture(GL_TEXTURE0);
        }
    ……
    }
    

    这里关键效果的叠加是Fragment Shader中计算绘制来完成。

    void main()
     {
         //从采样器中进程纹理采样
         vec4 originColor = texture(inputImageTexture, textureCoordinate);
         vec4 texel = texture(inputImageTexture, textureCoordinate);
         vec3 bbTexel = texture(inputImageTexture2, textureCoordinate).rgb;
         
         texel.r = texture(inputImageTexture3, vec2(bbTexel.r, texel.r)).r;
         texel.g = texture(inputImageTexture3, vec2(bbTexel.g, texel.g)).g;
         texel.b = texture(inputImageTexture3, vec2(bbTexel.b, texel.b)).b;
    
         //按比例分别混合RGB
         vec4 mapped;
         mapped.r = texture(inputImageTexture4, vec2(texel.r, .16666)).r;
         mapped.g = texture(inputImageTexture4, vec2(texel.g, .5)).g;
         mapped.b = texture(inputImageTexture4, vec2(texel.b, .83333)).b;
         mapped.a = 1.0;
         //mix(x, y, a): x, y的线性混叠, x(1-a) + y*a;
         mapped.rgb = mix(originColor.rgb, mapped.rgb, strength);
    
         gl_FragColor = mapped;
     }
    

    基本的加载滤镜的模板代码就到这里了。需要注意的是,如果你看原版的MagicCamera很多代码使用android的消息传输以及线程的简便,抛到到绘制onDraw的时候执行。而C++编写的时候,还是好好的严格遵循绘制的顺序来编写和初始化。

    image.png

    未来视觉-MagicCamera3实用开源库

    Android组件化群2

    相关文章

      网友评论

          本文标题:[OpenGL]未来视觉4-Native层滤镜添加

          本文链接:https://www.haomeiwen.com/subject/kejgdqtx.html