资讯详情

Android OpenGL ES 实战实现拍摄预览黑白电视效果

1.OpenGL

OpenGL:全称(Oen Graphics Library)图形绘制语言也是如此GPU显卡语言是图形领域的工业标准,是一套跨编程语言、跨平台、专业的图形编程(软件)接口。用于二维、三维图像,是功能强大、调用方便的底层图形库。

可以在不同的平台上与硬件无关windows、Linux、Mac、Android、IOS移植之间。所以支持。OpenGL该软件移植性好,应用广泛。

使用移动端OpenGLES,这是专门针对客户端的精简版。

2.OpenGLES 支持的版本

OpenGLES 1.0和1.1 --Android1.支持更高的版本。

OpenGLES2.0 --Android2.2(API8)更高的版本。

OpenGLES3.0 --Android4.3(API18)和更高的版本支持。

OpenGLES3.1 --Android5.0(API21)和更高的版本支持。

以上是跟Android系统的对应关系

此外,还需要设备制造商的支持,目前广泛支持2.0版,直接配置2.0版就行了。

<uses-feature android:glEsVersion="0x00020000" android"required="true"/> 

3.GLSurfaceView

GLSurfaceView:继承这个控件SurfaceView,不仅拥有了SurfaceView所有功能都有OpenGL处理能力。

GLSurfaceView内嵌了surface专门负责OpenGL渲染,管理Surface和EGL。

允许自定义渲染器render,让渲染器在独立的线程中运行UI线程分离。

支持按渲染(on-demand)和连续渲染(continuous)两种模式。

OpenGL是跨平台操作GPU的API,但OpenGL本地视窗系统需要交互,需要中间控制层,EGL就是连接OpenGL ES介绍与本地视图窗口的系统接口EGL屏蔽不同平台上的差异。

4.OpenGL的绘制流程

三角形是图像领域的最小单元。

  • 位置排版,确定顶点位置(确定顶点位置)
  • 细节网格排版(光栅化)根据每个位置进行排版
  • 网络排版按各位置进行逻辑处理(光栅逻辑处理)
  • 准备颜色测试(纹理过滤)
  • 片元处理、着色处理、绘制成效果图(纹理填充)
  • 输出结果,GPU处理成人能看到的图片。

5.进入实践

作者在这里包装了渲染控件OpenGLView,继承自GLSurfaceView

/** * 相当于 自定义控件只是显示自定义控件, * Camera预览的画面(OpenGL的处理) */ public class OpenGLView extends GLSurfaceView { 
              public OpenGLView(Context context, AttributeSet attrs) { 
                 super(context, attrs);         init();     }       private void init() { 
                  /* * 设置EGL版本 * 2 代表是 OpenGLES 2.0 * */         setEGLContextClientVersion(2);           /* * 设置渲染器 * EGL 开启一个 GLThread.start run { Renderer.onSurfaceCreated * ...onSurfaceChanged onDrawFrame } * 如果这三个函数不允许,GLThread调用,会崩溃,所以他的内部设计,必须通过GLThread调用来调 * 用三个函数 * */         setRenderer(new OpenGlRenderer(this));            /* * 设置渲染模式 * RENDERMODE_WHEN_DIRTY 按需渲染,有帧数据时,会渲染( 效率高,麻烦,以后需要 * 手动调用一次) * RENDERMODE_CONTINUOUSLY 每16毫秒读取更新一次(如果上一帧没有显示) * */         setRenderMode(RENDERMODE_WHEN_DIRTY);      } } 

OpenGLRenderer的实现了GLSurfaceView.Renderer接口

/** * 自定义渲染器 * 渲染器的三个函数 onSurfaceCreated onSurfaceChanged onDrawFrame * 有可用的数据时,回调此函数,效率高,后面需要手动调用一次才行 */
public class OpenGLRenderer implements
        GLSurfaceView.Renderer // 
    , SurfaceTexture.OnFrameAvailableListener 
{ 
        

    private final OpenGLView myGLSurfaceView;
    private CameraHelper mCameraHelper;
    private int[] mTextureID;
    private SurfaceTexture mSurfaceTexture;
    private ScreenFilter mScreenFilter;
    /* 矩阵数据,变换矩阵*/
    float[] mtx = new float[16]; 

    public OpenGLRenderer(OpenGLView myGLSurfaceView) { 
        
        this.myGLSurfaceView = myGLSurfaceView;
    }

    /** * Surface创建时 回调此函数 */
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) { 
        
        mCameraHelper = new CameraHelper((Activity) myGLSurfaceView.getContext(), // 上下文
                Camera.CameraInfo.CAMERA_FACING_FRONT, // 前置摄像头
                800, 480);

        /* 获取纹理ID 可以理解成画布*/
        mTextureID = new int[1];
        /** * 1.长度 只有一个 1 * 2.纹理ID,是一个数组 * 3.offset:0 使用数组的0下标 */
        glGenTextures(mTextureID.length, mTextureID, 0);
        /* 实例化纹理了对象*/
        mSurfaceTexture = new SurfaceTexture(mTextureID[0]); 
        /*绑定好此监听 SurfaceTexture.OnFrameAvailableListener*/
        mSurfaceTexture.setOnFrameAvailableListener(this); 

        mScreenFilter = new ScreenFilter(myGLSurfaceView.getContext());
    }

    /** * Surface 改变时 回调此函数 */
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) { 
        
        mCameraHelper.startPreview(mSurfaceTexture); // 开始预览
        mScreenFilter.onReady(width,  height);
    }

    /** * 绘制一帧图像时 回调此函数 */
    @Override
    public void onDrawFrame(GL10 gl) { 
        
        /* 每次清空之前的 清理成红色的黑板一样*/
        glClearColor(255, 0 ,0, 0); 
        
        /* * mask 细节看看此文章:https://blog.csdn.net/z136411501/article/details/83273874 * GL_COLOR_BUFFER_BIT 颜色缓冲区 * GL_DEPTH_BUFFER_BIT 深度缓冲区 * GL_STENCIL_BUFFER_BIT 模型缓冲区 * */
        glClear(GL_COLOR_BUFFER_BIT);

        /* * 绘制摄像头数据 * 将纹理图像更新为图像流中最新的帧数据 刷新一下 * */
        mSurfaceTexture.updateTexImage();  

        /*画布,矩阵数据*/
        mSurfaceTexture.getTransformMatrix(mtx);

        mScreenFilter.onDrawFrame(mTextureID[0], mtx);
    }

    /* * 有可用的数据时,回调此函数,效率高,后面需要手动调用一次才行 * */
    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) { 
        
        myGLSurfaceView.requestRender(); // setRenderMode(RENDERMODE_WHEN_DIRTY); 配合用
    }
}

ScreenFilter的实现

/** * 显示到 GLSurfaceView 屏幕 */
public class ScreenFilter { 
        

    /*着色器代码*/
    private  String vertexSourceV ;
    /*着色器程序ID*/
    private final int mProgram; 

    /* 顶点着色器:位置*/
    private final int vPosition; 
    /*顶点着色器:纹理*/
    private final int vCoord;
    /*顶点着色器:矩阵*/
    private final int vMatrix;  

    /*片元着色器:采样器 摄像头数据*/
    private final int vTexture; 

    
    /*顶点坐标 nio的buffer缓存*/
    private FloatBuffer mVertexBuffer;  
    /* 纹理坐标 nio的buffer缓存*/
    private FloatBuffer mTextureBuffer; 
    /*宽度*/
    private int mWidth; 
    /* 高度*/
    private int mHeight;  

    public ScreenFilter(Context context) { 
        
        String vertexSource = readTextFileFromResource(context, R.raw.camera_vertex); // 查找到(顶点着色器)的代码字符串
        String fragmentSource = readTextFileFromResource(context, R.raw.camera_fragment); // 查找到(片元着色器)的代码字符串

        /* *配置顶点着色器 * * */
        /*创建顶点着色器*/
        int vShaderId = glCreateShader(GL_VERTEX_SHADER);
        /*绑定着色器源代码到 着色器(加载着色器的代码)*/
        glShaderSource(vShaderId, vertexSource);
        /*编译着色器代码(编译阶段:编译成功就能拿到顶点着色器ID,编译失败基本上就是着色器代码字符串写错了)*/
        glCompileShader(vShaderId);
        int[] status = new int[1];
        glGetShaderiv(vShaderId, GL_COMPILE_STATUS, status, 0);
        if (status[0] != GL_TRUE) { 
        
            throw new IllegalStateException("顶点着色器配置失败!");
        }

        /*配置片元着色器*/
        /*创建顶点着色器*/
        int fShaderId = glCreateShader(GL_FRAGMENT_SHADER);
        /*绑定着色器源代码到 着色器(加载着色器的代码)*/
        glShaderSource(fShaderId, fragmentSource);
        /*编译着色器代码(编译阶段:编译成功就能拿到顶点着色器ID,编译失败基本上就是着色器代码字符串写错了)*/
        glCompileShader(fShaderId);
        glGetShaderiv(fShaderId, GL_COMPILE_STATUS, status, 0);
        if (status[0] != GL_TRUE) { 
        
            throw new IllegalStateException("片元着色器配置失败!");
        }

        /*配置着色器程序*/
        /*创建一个着色器程序*/
        mProgram = glCreateProgram();
        /*将前面配置的 顶点 和 片元 着色器 附加到新的程序 上*/
        /*顶点*/
        glAttachShader(mProgram, vShaderId);  
        /* 片元*/
        glAttachShader(mProgram, fShaderId); 
        /* 链接着色器 mProgram着色器程序 是我们的成果*/
        glLinkProgram(mProgram); //
        glGetShaderiv(mProgram, GL_LINK_STATUS, status, 0);
        if (status[0] != GL_TRUE) { 
        
            throw new IllegalStateException("着色器程序链接失败!");
        }

        /*释放,删除着色器,因为用不到 顶点着色器/片元着色器了,只需要有着色器程序mProgram即可*/
        glDeleteShader(vShaderId);
        glDeleteShader(fShaderId);


        /* * 获取变量的索引值,通过索引来赋值 * 顶点着色器里面的如下: * */
        /*顶点着色器:的索引值*/
        vPosition = glGetAttribLocation(mProgram, "vPosition"); 
        /* 顶点着色器:纹理坐标,采样器采样图片的坐标 的索引值*/
        vCoord = glGetAttribLocation(mProgram, "vCoord"); 
        /*顶点着色器:变换矩阵 的索引值*/
        vMatrix = glGetUniformLocation(mProgram, "vMatrix"); 

        /*片元着色器:采样器*/
        vTexture = glGetUniformLocation(mProgram, "vTexture"); 


        /* * NIO Buffer(就是一个缓存,存和取) 着色器语言绑定 * 顶点坐标缓存(顶点:位置 排版) -- vPosition * */
        /*分配内存 坐标个数 * xy坐标数据类型 * float占几字节*/
        mVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4) 
                /*使用本地字节序,例如:大端模式,小端模式,这里设置为:跟随OpenGL的变化二变化*/
                .order(ByteOrder.nativeOrder()) 
                .asFloatBuffer();
        mVertexBuffer.clear();  
        /* OpenGL世界坐标*/
        float[] v = { 
          
                -1.0f, -1.0f,
                1.0f,  -1.0f,
                -1.0f,  1.0f,
                1.0f,  1.0f,
        };
        mVertexBuffer.put(v);

        /* * 纹理坐标缓存(纹理:上色 成果) -- vCoord === 和屏幕挂钩 * 分配内存 坐标个数 * xy坐标数据类型 * float占几字节 * */
        mTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4) 
                .order(ByteOrder.nativeOrder()) 
                .asFloatBuffer();
        mTextureBuffer.clear(); 
        
        /*屏幕坐标系*/

        /*旋转 180度 就纠正了*/
        float[] t = { 
          
                1.0f, 0.0f,
                0.0f,  0.0f,
                1.0f,  1.0f,
                0.0f,  1.0f,
        };

        mTextureBuffer.put(t);
    }

    public void onReady(int width, int height) { 
        
        mWidth = width;
        mHeight = height;
    }

    /** * 绘制操作 * @param mTextureID 画布 纹理ID * @param mtx 矩阵数据 */
    public void onDrawFrame(int mTextureID, float[] mtx) { 
        
        /*设置视窗大小,从0开始,这个是合理的*/
        glViewport(0, 0, mWidth, mHeight);
        /*执行着色器程序*/
        glUseProgram(mProgram); // 


        /*顶点坐标赋值 NIO的Buffer 用它就要归零 */
        mVertexBuffer.position(0); 
        /** * 传值(把float[]值传递给顶点着色器)把mVertexBuffer传递到vPosition == size:每次两个xy, stride:0 不跳步 * 1.着色器代码里面的 标记变量 attribute vec4 mPosition; * 2.xy 所以是两个 * 3.不用管 * 4.跳步 0 不跳步 */
        glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer);
        /*激活*/
        glEnableVertexAttribArray(vPosition);

        /*纹理坐标赋值*/
        mTextureBuffer.position(0);  
        /*传值(把float[]值传递给纹理) *把mTexturBuffer传递到vCoord == size:每次两个xy, stride:不跳步*/
        glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer);
        /*激活*/
        glEnableVertexAttribArray(vCoord);

        /*变换矩阵 把mtx矩阵数据 传递到 vMatrix*/
        glUniformMatrix4fv(vMatrix, 1, false, mtx, 0);

        /*vTexture*/
        glActiveTexture(GL_TEXTURE0);
        /*绑定纹理ID --- glBindTexture(GL_TEXTURE_2D ,textureId); *如果在片元着色器中的vTexture,不是samplerExternalOES类型,就可以这样写*/
        glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);  
        /*传递参数 给 片元着色器:采样器*/
        glUniform1i(vTexture, 0); 
        /*通知 opengl 绘制 ,从0开始,共四个点绘制*/
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }

    /** * 用于读取 GLSL文件中着色器代码 * @param context 上下文 * @param resourceId 传入raw * @return GLSL文件中的代码字符串 */
    public static String readTextFileFromResource(Context context, int resourceId) { 
        
        StringBuilder body = new StringBuilder();
        try { 
        
            InputStream inputStream = context.getResources().openRawResource(resourceId);
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String nextLine;
            while ((nextLine = bufferedReader.readLine()) != null) { 
        
                body.append(nextLine);
                body.append('\n');
            }
        } catch (IOException e) { 
        
            throw new RuntimeException("Could not open resource: " + resourceId, e);
        } catch (Resources.NotFoundException nfe) { 
        
            throw new RuntimeException("Resource not found: " + resourceId, nfe);
        }
        return body.toString();
    }
}

顶点着色器的实现

attribute vec4 vPosition; 
attribute vec4 vCoord;
uniform mat4 vMatrix; 
varying vec2 aCoord; 

void main() {
    //确定好位置排版
    gl_Position = vPosition; 
    aCoord = (vMatrix * vCoord).xy;

}

片元着色器

// 导入 samplerExternalOES 
#extension GL_OES_EGL_image_external : require

precision mediump float;
uniform samplerExternalOES vTexture;
varying vec2 aCoord;

void main() {
  
       vec4 rgba =texture2D(vTexture, aCoord);
    float gray = (0.30 * rgba.r   + 0.59 * rgba.g + 0.11* rgba.b); 
    gl_FragColor = vec4(gray, gray, gray, 1.0);
    
}

然后在开启预览的时候调用,将surfaceTexture传进去就可以了

mCamera.setPreviewTexture(surfaceTexture);

源码地址

标签: fv1连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台