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.0和更高的版本支持。

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);

源码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值