美文网首页
Android中基于OpenGL的特效

Android中基于OpenGL的特效

作者: oceanLong | 来源:发表于2018-08-05 23:30 被阅读210次

    前言

    大家都知道,给图片加滤镜加特效,通常是对图像进行矩阵运算。通过颜色矩阵的乘法,我们可以对图像中的元素进行变换。
    但是,如果需要对实时变化的图像进行实时处理,就不是每种图像变换的方式都可以用了。因为,实时变化的预览图像,会有帧率的压力,我们的处理一定要快。

    上一篇中,我们已经展示了Android中,通过OpenGL展示相机预览图片的方法。

    这一篇主要展示,如何在预览的图片中,加入一些简单的特效。

    特效概述

    QQ20180805-232214-HD.gif

    这个特效的详细过程是,点击屏幕时,会在屏幕中间显示一个画中画,然后,画中画慢慢放大,逐渐透明。同时,原始预览图层的颜色不断随机变化。

    代码展示

    为了方便浏览,我将代码写的比较简单,完全没有考虑扩展性和封装相关的问题。而且,只展示了onDrawFrame生命周期的代码。同时,用animValue控制动画的进度。

    首先,我们来看一下着色器的代码。由于特效既需要形变,也需要颜色变化,我们在gl_Position和gl_FragColor中,都引入了一个变化矩阵。

        private final String vertexShaderCode = "uniform mat4 textureTransform;\n" +
                "attribute vec2 inputTextureCoordinate;\n" +
                "attribute vec4 position;            \n" +//NDK坐标点
                "varying   vec2 textureCoordinate; \n" +//纹理坐标点变换后输出
                "uniform mat4 position_transform_matrix;\n" +
                "\n" +
                " void main() {\n" +
                "     gl_Position = position_transform_matrix * vec4(position.x,-position.y,position.z,position.w);\n" +
                "     textureCoordinate = vec2(inputTextureCoordinate.x,inputTextureCoordinate.y);\n" +
                " }";
    
        private final String fragmentShaderCode = "#extension GL_OES_EGL_image_external : require\n" +
                "precision mediump float;\n" +
                "uniform samplerExternalOES videoTex;\n" +
                "varying vec2 textureCoordinate;\n" +
                "uniform mat4 color_transform_matrix;\n" +
                "\n" +
                "void main() {\n" +
                "    vec4 tc = texture2D(videoTex, textureCoordinate);\n" +
                "    vec4 color = vec4(tc.r,tc.g,tc.b,tc.a); \n"+
                "    gl_FragColor = color_transform_matrix * color;\n" +
    //            "    gl_FragColor = vec4(tc.r,tc.g,tc.b,1.0);\n" +
                "}";
    

    我们通过改变position_transform_matrix,来进行形变。通过改变color_transform_matrix,来进行色彩的变换。

    以下是点击事件的代码,我们会在点击后,周期性地传入一个随机的颜色矩阵,用于颜色的变换。

                mCameraGlsurfaceView.setOnClickListener {
                    val timer = Timer()
                    var i = 0;
                    timer.schedule(object : TimerTask() {
                        override fun run() {
                            i++
                            LogUtils.d("glsurfaceview frame update i : $i")
                            glRenderer.animValue = i/20.0f;
                            glRenderer.animing = true
                            glRenderer.setFilter(object :AbsFilter {
                                override fun getColorMatrix():FloatArray{
                                    var v1:Float = Math.random().toFloat()
                                    var v2:Float = Math.random().toFloat()
                                    var v3:Float = Math.random().toFloat()
                                    return floatArrayOf(
                                            v1, 0f, 0f, 0f,
                                            0f, v2, 0f, 0f,
                                            0f, 0f, v3, 0f,
                                            0f, 0f, 0f, i/20.0f)
                                }
                            })
                            if (i == 20){
                                glRenderer.animing = false
                                glRenderer.animValue = 0.001f
                                glRenderer.setFilter(object :AbsFilter {
                                    override fun getColorMatrix():FloatArray{
                                        return floatArrayOf(
                                                1f, 0f, 0f, 0f,
                                                0f, 1f, 0f, 0f,
                                                0f, 0f, 1f, 0f,
                                                0f, 0f, 0f, 1f)
                                    }
                                })
    
                                timer.cancel()
                            }
    
                        }
                    }, 0, 20)
    
                }
    

    以下是onDrawFrame
    原始预览图层的颜色变化:

    
       @Override
       public void onDrawFrame(GL10 gl) {
           activeProgram();
    
           if (mSurfaceTexture != null) {
               GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
               mSurfaceTexture.updateTexImage();
               GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mPosCoordinate.length / 2);
           }
           renderEffect();
       }
    
       private FloatBuffer convertFloatBuffer (float[] vectices , int sizeByte){
           FloatBuffer floatBuffer;
           floatBuffer =  ByteBuffer.allocateDirect(
                   vectices.length*sizeByte)
                   .order(ByteOrder.nativeOrder()).asFloatBuffer();
           floatBuffer.put(vectices).position(0);
           return floatBuffer;
       }
    
       private float[] createColorTransVertices(){
           if (mEffectFilter == null){
               return new float[] {
                       1, 0, 0, 0,
                       0, 1, 0, 0,
                       0, 0, 1, 0,
                       0, 0, 0, 1
               };
           }
           return mEffectFilter.getColorMatrix();
       }
       private void activeProgram() {
           // 将程序添加到OpenGL ES环境
           GLES20.glUseProgram(mProgram);
           mSurfaceTexture.setOnFrameAvailableListener(mOnFrameAvailableListener);
           // 获取顶点着色器的位置的句柄
           uPosHandle = GLES20.glGetAttribLocation(mProgram, "position");
           aTexHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
    
           FloatBuffer mPosBuffer = convertToFloatBuffer(mPosCoordinate);
           FloatBuffer mTexBuffer;
           if(camera_status == 0){
               mTexBuffer = convertToFloatBuffer(mTexCoordinateBackRight);
           }else{
               mTexBuffer = convertToFloatBuffer(mTexCoordinateForntRight);
           }
    
           GLES20.glVertexAttribPointer(uPosHandle, 2, GLES20.GL_FLOAT, false, 0, mPosBuffer);
           GLES20.glVertexAttribPointer(aTexHandle, 2, GLES20.GL_FLOAT, false, 0, mTexBuffer);
    
           // 启用顶点位置的句柄
           GLES20.glEnableVertexAttribArray(uPosHandle);
           GLES20.glEnableVertexAttribArray(aTexHandle);
    
           FloatBuffer mColorTransMatrixBuffer = convertFloatBuffer(createColorTransVertices() , 4);
           GLES20.glUniformMatrix4fv(mColorTransMatrixHandler , 1  , false , mColorTransMatrixBuffer);
           GLES20.glEnableVertexAttribArray(mColorTransMatrixHandler);
           float[] posTrans = new float[] {
                   1, 0, 0, 0,
                   0, 1, 0, 0,
                   0, 0, 1, 0,
                   0, 0, 0, 1
           };
           FloatBuffer mPosTransMatrixBuffer = convertFloatBuffer(posTrans , 4);
           GLES20.glUniformMatrix4fv(mPosTransMatrixHandler , 1  , false , mPosTransMatrixBuffer);
           GLES20.glEnableVertexAttribArray(mPosTransMatrixHandler);
       }
    

    可以看到,在原始画面的渲染中,我们的pos转换矩阵,使用了单位矩阵。而颜色转换矩阵,则使用了mEffectFilter.getColorMatrix(),即外部传入的颜色矩阵,进行随机颜色变换。


    接下来,是画中画特效的渲染过程:

        private void renderEffect(){
            // --- for click effect
    
            FloatBuffer mPosBuffer = convertToFloatBuffer(mPosCoordinate);
            FloatBuffer mTexBuffer;
            if(camera_status == 0){
                mTexBuffer = convertToFloatBuffer(mTexCoordinateBackRight);
            }else{
                mTexBuffer = convertToFloatBuffer(mTexCoordinateForntRight);
            }
    
            GLES20.glVertexAttribPointer(uPosHandle, 2, GLES20.GL_FLOAT, false, 0, mPosBuffer);
            GLES20.glVertexAttribPointer(aTexHandle, 2, GLES20.GL_FLOAT, false, 0, mTexBuffer);
    
            // 启用顶点位置的句柄
            GLES20.glEnableVertexAttribArray(uPosHandle);
            GLES20.glEnableVertexAttribArray(aTexHandle);
            float[] colorTrans = new float[] {
                    1.0f, 0, 0, 0,
                    0, 1.0f, 0, 0,
                    0, 0, 1.0f, 0,
                    0, 0, 0, 0.5f*(1- animValue /1.0f)
            };
            // 半透明显示,需要开启
            GLES20.glEnable(GLES20.GL_BLEND);
            GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
    
            FloatBuffer mColorTransMatrixBuffer = convertFloatBuffer(colorTrans , 4);
            GLES20.glUniformMatrix4fv(mColorTransMatrixHandler , 1  , false , mColorTransMatrixBuffer);
            GLES20.glEnableVertexAttribArray(mColorTransMatrixHandler);
            float[] posTrans = new float[] {
                    1.0f, 0, 0, 0,
                    0, 1.0f, 0, 0,
                    0, 0, 1, 0,
                    0, 0, 0, 1
            };
            if (animing){
                posTrans = new float[] {
                        0.5f+ animValue, 0, 0, 0,
                        0, 0.5f+ animValue, 0, 0,
                        0, 0, 1, 0,
                        0, 0, 0, 1
                };
            }
            FloatBuffer mPosTransMatrixBuffer = convertFloatBuffer(posTrans , 4);
            GLES20.glUniformMatrix4fv(mPosTransMatrixHandler , 1  , false , mPosTransMatrixBuffer);
            GLES20.glEnableVertexAttribArray(mPosTransMatrixHandler);
    
            if (mSurfaceTexture != null) {
                mSurfaceTexture.updateTexImage();
                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mPosCoordinate.length / 2);
            }
        }
    

    与activeProgram方法的流程类似,唯一不同的只是位置矩阵和颜色矩阵。在画中画的特效中,颜色矩阵接近于一个单位矩阵,只是透明度会渐渐变小。而顶点坐标的矩阵,则会随着特效动画的进程不断变化。xy值不对增大。


    以上就是一个简单的基于OpenGL的动画特效。OpenGL动效的关键在于根据着色器的代码,插入需要变换的变量。如顶点变换矩阵和颜色变换矩阵,然后根据时间或其他参数,对矩阵进行变换,从而达到改变渲染的目的。

    如有问题,欢迎指正。

    相关文章

      网友评论

          本文标题:Android中基于OpenGL的特效

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