OpenGL着色器语言 —— GLSL

OpenGL(Open Graphics Library)定义了一个跨编程语言、跨平台编程的专业图形程序接口规范,所以说OpenGL只是一套规范,不要理解成FFmpeg那种开源库了。本篇文章会涉及OpenGL的着色语言GLSL,以及如何使用OpenGL来渲染摄像头图像到屏幕上,并且通过着色器可以实现灰度化,底片等效果。

OpenGL简介

OpenGL可用于二维或三维图像的处理和渲染,它是一个功能强大、调用方便的底层图形库规范。OpenGL ES是OpenGL的子集,专门针对嵌入式设备的版本。需要注意的是:OpenGL 是个状态机,面向过程编程。

很多人认为OpenGL是一套API,包含了一系列可以操作图形、图像的函数。事实上,OpenGL仅仅是一个由 Khronos组织 制定并维护的一套规范。OpenGL规范严格规定了每个函数该如何执行,以及它们的输出值。至于内部具体每个函数是如何实现的,将由OpenGL库的开发者自行决定,通常OpenGL库的实现者是显卡厂商。因为OpenGL规范并没有规定实现的细节,具体的OpenGL库允许使用不同的实现,只要其功能和结果与规范相匹配,所以作为用户不会感受到功能上的差异。

Android支持的OpenGL ES对应的版本如下:

OpenGL ES 1.0 和 1.1 - 此 API 规范受 Android 1.0 及更高版本的支持。

OpenGL ES 2.0 - 此 API 规范受 Android 2.2(API 级别 8)及更高版本的支持。

OpenGL ES 3.0 - 此 API 规范受 Android 4.3(API 级别 18)及更高版本的支持。

OpenGL ES 3.1 - 此 API 规范受 Android 5.0(API 级别 21)及更高版本的支持。

GlSurfaceView

调用任何 OpenGL 函数前,必须已经创建了 OpenGL 上下文。Android中GLSurfaceView中会为我们初始化OpenGL上下文。GlSurfaceView继承至SurfaceView,它内嵌的Surface专门负责OpenGL渲染。

GlSurfaceView用于管理Surface与EGL;

GlSurfaceView允许自定义渲染器(render);

GlSurfaceView支持按需渲染和连续渲染;

 1public void setRenderer(Renderer renderer) {
 2    checkRenderThreadState();
 3    if (mEGLConfigChooser == null) {
 4        mEGLConfigChooser = new SimpleEGLConfigChooser(true);
 5    }
 6    if (mEGLContextFactory == null) {
 7        mEGLContextFactory = new DefaultContextFactory();
 8    }
 9    if (mEGLWindowSurfaceFactory == null) {
10        mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
11    }
12    mRenderer = renderer;
13    mGLThread = new GLThread(mThisWeakRef);
14    mGLThread.start();
15}

可以看到GlSurfaceView在设置渲染器的时候通过GLThread线程去执行渲染任务。

1// 手动渲染,也就是按需渲染
2public final static int RENDERMODE_WHEN_DIRTY = 0;
3
4// 自动渲染,大概16ms刷新一次
5public final static int RENDERMODE_CONTINUOUSLY = 1;
6
7public void setRenderMode(int renderMode) {
8    mGLThread.setRenderMode(renderMode);
9}

可以看到GlSurfaceView支持手动渲染和自动渲染两种模式,注意必须在setRenderer 之后才能指定渲染模式。

GLSL —— OpenGL着色语言

在介绍OpenGL运行过程的文章 《OpenGL渲染管线》 中,提到了其中需要手动编写的就是顶点着色器程序和片元着色器程序,那么该怎么编写呢?这个时候就会用到OpenGL的着色语言——GLSL。

OpenGL着色语言(OpenGL Shading Language) ,开发人员利用这种语言编写程序运行在GPU(Graphic Processor Unit,图形图像处理单元,可以理解为是一种高并发的运算器)上以进行图像的处理或渲染。GLSL着色器代码分为两个部分,即Vertex Shader(顶点着色器)与Fragment Shader(片元着色器)两部分,分别完成各自在OpenGL渲染管线中的功能。

下面来看看GLSL的语法:

GLSL的语法与C语言非常类似,首先要看它的数据类型表示,然后再学习具体的运行流程。对于GLSL,其数 据类型表示具体如下:

变量修饰符

修饰符 用途 & 作用范围
const 用于声明只读的编译时常量变量。
attribute 属性变量。用于经常更改的信息,只能用于顶点着色器输入数据。如顶点坐标、纹理坐标、颜色等。
uniform 一致变量。在着色器执行期间一致变量的值是不变的。类似常量,但不同的是,这个值在编译时期是未知的,由着色器外部初始化。可用于顶点着色器和片元着色器。
varying 易变变量。用于修饰从顶点着色器向片元着色器传递的变量。

数据类型

基本数据类型与float修饰符

基本数据类型,int、float、bool,这些与C语言都是一致的,但是float是有一个修饰符的,即可以指定精度。三种修饰符的范围(范围一般视显卡而定)和应用情况具体如下:

float的修饰符 大小 应用场景
highp 32bit 一般用于顶点坐标(vertex Coordinate)
medium 16bit 一般用于纹理坐标(texture Coordinate)
lowp 8bit 一般用于颜色表示(color)

向量类型

向量类型是Shader中非常重要的一个数据类型,因为在做数据传递的时候需要经常传递多个参数,相较于写多个基本数据类型,使用向量类型是非常好的选择。列举一个最经典的例子,要将物体坐标和纹理坐标传递到Vertex Shader中,每一个顶点都是一个四维向量(x、y、 z、w,其中w是缩放因子),在Vertex Shader中利用这两个四维向量即可完成自己的纹理坐标映射操作。声明方式如下(GLSL代码):

1attribute vec4 position;

矩阵类型

比如需要做卷积运算,需要传入卷积核,那么就可以使用矩阵类型。声明方式如下(GLSL代码):

1uniform lowp mat4 colorMatrix; // 4×4的浮点矩阵
2
3uniform medium mat2 colorMatrix; // 2×2的浮点矩阵

纹理类型

一般仅在Fragment Shader中使用这个类型,二维纹理的声明方式如下(GLSL代码):

1uniform sampler2D texSampler

内置函数与内置变量

Vertex Shader的内置变量(GLSL代码):

1vec4 gl_position;

上述代码用来设置顶点转换到屏幕坐标的位置,Vertex Shader一定要去更新这个数值。另外还有一个内置变量,代码如下(GLSL代码):

1float gl_pointSize;

在粒子效果的场景下,需要为粒子设置大小,改变该内置变量的值就是为了设置每一个粒子矩形的大小。

Fragment Shader的内置变量(GLSL代码):

1vec4 gl_FragColor;

用于指定当前纹理坐标所代表的像素点的最终颜色值。

内置函数,具体的函数可以去官方文档中查询,这里仅介绍几个常用的函数:

函数 用途
abs(genType x) 绝对值函数
floor(genType x) 向下取整函数
ceil(genType x) 向上取整函数
mod(genType x,genType y) 取模函数
min(genType x,genType y) 取得最小值函数
max(genType x,genType y) 取得最大值函数
clamp(genType x,genType y,genType z) 取得中间值函数
step(genType edge,genType x) 如果x < edge,则返回0.0,否则返回1.0
smoothstep(genType edge0,genType edge1,genType x) 如果x≤edge0,则返回0.0;如果x≥edge1,则返回1.0;如果edge0<x<edge1,则执行0~1之间的平滑差值.
mix(genType x,genType y,genType a) 返回线性混合的x和y,用公式表示为:x*(1-a)+y*a,这个函数在mix两个纹理图像的时候非常有用。

其他的角度函数、指数函数、几何函数可以去官方文档进行查询。

流程控制语句

GLSL的控制流与C语言非常类似,既可以使用for、while以及do-while实现循环,也可以使用if和if-else进行条件分支的操作,在后面的实践过程中及GLSL代码中都会用到这些控制流,在这里将不再讲解这些枯燥的语法。这里是他的官网文档 https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/index.php

需要注意的是GLSL没有switch语句,但是过多的if或if-else语句会降低着色器的执行速度 。

结合上图我们发现,其实对于顶点着色器和片元着色器来讲,其重要的几个内置变量就是:

gl_Positiongl_Position内置变量主要和顶点相关,出现的位置是顶点着色器语言的main函数中。gl_Position内置变量表示最终传入片元着色器片元化要使用的顶点位置坐标)、gl_PointSize(用来设置顶点渲染出来的方形点像素大小)、gl_FragColor(用于指定当前纹理坐标所代表的像素点的最终颜色值)。其他的内置变量等需要用的时候再查阅官方文档即可。

创建显卡执行程序

其实,根据OpenGL渲染管线的流程,我们需要做的就是两件事情:

1、编写顶点着色器

2、编写片元着色器

编写了GLSL代码,那么如何让显卡来运行写好的着色器代码呢?下面是创建并启动显卡执行程序的过程中要用到的函数。总体上看就是创建程序,创建(顶点+片元)着色器,绑定并链接着色器,启动显卡执行程序,如下图:

OpenGL坐标系统

为了简单方便理解,OpenGL坐标系可分为:世界坐标系和纹理坐标系。

世界坐标系:在OpenGL中,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的。面对屏幕,你的右边是x正轴,上面是y正轴,屏幕指向你的为z正轴。OpenGL标准化设备坐标规定了一切坐标点都在-1到1之间。

纹理坐标系:是绘制物体时的坐标系。OpenGL使用右手坐标,从左到右,x递增,从下到上,y递增,从远到近,z递增。

实现渲染摄像头画面

CameraView extends GLSurfaceView,最终渲染的结果将会展示在这个View上。

CameraRender implements GLSurfaceView.Renderer, Preview.OnPreviewOutputUpdateListener, SurfaceTexture.OnFrameAvailableListener,CameraRender作为渲染器,从其实现的接口来看,实现了GLSurface的Renderer接口,提供具体渲染实现。还有预览输出更新的回调,比如横竖屏切换等等。还有第一帧可用的回调。

准备CameraX 与渲染视图

CameraView.java 渲染视图组件

 1package cn.tim.opengl.widget;
 2
 3import android.content.Context;
 4import android.opengl.GLSurfaceView;
 5import android.util.AttributeSet;
 6import android.view.SurfaceHolder;
 7
 8public class CameraView extends GLSurfaceView {
 9    private  CameraRender renderer;
10
11    public CameraView(Context context) {
12        this(context,null);
13    }
14
15    public CameraView(Context context, AttributeSet attrs) {
16        super(context, attrs);
17        //使用OpenGL ES 2.0 context.
18        setEGLContextClientVersion(2);
19        //设置渲染回调接口
20        renderer = new CameraRender(this);
21        setRenderer(renderer);
22
23        // 设置刷新方式
24        // RENDERMODE_WHEN_DIRTY 手动刷新,调用requestRender();
25        // RENDERMODE_CONTINUOUSLY 自动刷新,大概16ms自动回调一次onDraw方法
26        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
27    }
28
29    @Override
30    public void surfaceDestroyed(SurfaceHolder holder) {
31        super.surfaceDestroyed(holder);
32        renderer.onSurfaceDestroyed();
33    }
34}

CameraRender.java 渲染器:

 1package cn.tim.opengl.widget;
 2
 3import android.graphics.SurfaceTexture;
 4import android.opengl.GLSurfaceView;
 5
 6import androidx.camera.core.Preview;
 7import androidx.lifecycle.LifecycleOwner;
 8
 9
10import javax.microedition.khronos.egl.EGLConfig;
11import javax.microedition.khronos.opengles.GL10;
12
13import cn.tim.opengl.filter.ScreenFilter;
14import cn.tim.opengl.utils.CameraHelper;
15
16public class CameraRender implements GLSurfaceView.Renderer,
17        Preview.OnPreviewOutputUpdateListener,
18        SurfaceTexture.OnFrameAvailableListener {
19
20    private CameraView cameraView;
21    private CameraHelper cameraHelper;
22
23    // 摄像头的图像,用OpenGL ES画出来
24    private SurfaceTexture mCameraTexture;
25
26    private  int[] textures;
27    private ScreenFilter screenFilter;
28
29    float[] mtx = new float[16];
30
31    public CameraRender(CameraView cameraView) {
32        this.cameraView = cameraView;
33        LifecycleOwner lifecycleOwner = (LifecycleOwner) cameraView.getContext();
34        cameraHelper = new CameraHelper(lifecycleOwner, this);
35
36    }
37
38    @Override
39    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
40        // 创建OpenGL纹理 ,把摄像头的数据与这个纹理关联
41        textures = new int[1];  // 当做能在OpenGL用的一个图片的ID
42        mCameraTexture.attachToGLContext(textures[0]);
43        // 当摄像头数据有更新回调 onFrameAvailable
44        mCameraTexture.setOnFrameAvailableListener(this);
45
46        screenFilter = new ScreenFilter(cameraView.getContext());
47    }
48
49    @Override
50    public void onSurfaceChanged(GL10 gl, int width, int height) {
51        screenFilter.setSize(width,height);
52    }
53
54    @Override
55    public void onDrawFrame(GL10 gl) {
56        // 更新纹理
57        mCameraTexture.updateTexImage();
58        // 获取CameraX中给定的校准参考矩阵
59        mCameraTexture.getTransformMatrix(mtx);
60
61        screenFilter.setTransformMatrix(mtx);
62        screenFilter.onDraw(textures[0]);
63    }
64
65    public void onSurfaceDestroyed() {
66
67    }
68
69    /**
70     * 更新
71     * @param output 预览输出
72     */
73    @Override
74    public void onUpdated(Preview.PreviewOutput output) {
75        mCameraTexture = output.getSurfaceTexture();
76    }
77
78    @Override
79    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
80        // 请求执行一次 onDrawFrame
81        cameraView.requestRender();
82    }
83}

CameraHelper.java CamerX的帮助工具类,生命周期与Activity绑定。

 1package cn.tim.opengl.utils;
 2
 3import android.os.HandlerThread;
 4import android.util.Size;
 5
 6import androidx.camera.core.CameraX;
 7import androidx.camera.core.Preview;
 8import androidx.camera.core.PreviewConfig;
 9import androidx.lifecycle.LifecycleOwner;
10
11public class CameraHelper {
12    private HandlerThread handlerThread;
13    private CameraX.LensFacing currentFacing = CameraX.LensFacing.BACK;
14    private Preview.OnPreviewOutputUpdateListener listener;
15
16    public CameraHelper(LifecycleOwner lifecycleOwner, Preview.OnPreviewOutputUpdateListener listener) {
17        this.listener = listener;
18        handlerThread = new HandlerThread("Analyze-thread");
19        handlerThread.start();
20        CameraX.bindToLifecycle(lifecycleOwner, getPreView());
21    }
22
23    private Preview getPreView() {
24        // 分辨率并不是最终的分辨率,CameraX会自动根据设备的支持情况
25        // 结合你的参数,设置一个最为接近的分辨率
26        PreviewConfig previewConfig = new PreviewConfig.Builder()
27                .setTargetResolution(new Size(640, 480))
28                .setLensFacing(currentFacing) // 前置或者后置摄像头
29                .build();
30        Preview preview = new Preview(previewConfig);
31        preview.setOnPreviewOutputUpdateListener(listener);
32        return preview;
33    }
34}

编写顶点&片元着色器

下面是重点,编写顶点着色器与片元着色器,在raw目录下新建两个文件存放顶点着色器与片元着色器的代码,

顶点着色器 camera_vert.vert

 1attribute vec4 vPosition; // 变量 float[4] 一个顶点坐标,Java传过来的
 2
 3attribute vec2 vCoord;  // 纹理坐标,也是Java传过来的
 4
 5varying vec2 aCoord; // aCoord是片元着色器传递过去的参数
 6
 7uniform mat4 vMatrix; // Java传过来的CameraX坐标校正参数
 8
 9void main(){
10    // 内置变量: 把坐标点赋值给gl_position
11    gl_Position = vPosition;
12    // aCoord是片元着色器传递过去的参数
13    aCoord = (vMatrix * vec4(vCoord,1.0,1.0)).xy;
14}

片元着色器 camera_frag.frag

 1#extension GL_OES_EGL_image_external : require
 2
 3// 摄像头数据比较特殊的一个地方
 4precision mediump float; // 数据精度
 5varying vec2 aCoord;
 6
 7uniform samplerExternalOES  vTexture;  // samplerExternalOES: 图片,采样器
 8
 9void main(){
10    //  texture2D: vTexture采样器,采样  aCoord 这个像素点的RGBA值
11    vec4 rgba = texture2D(vTexture,aCoord);  //rgba
12    gl_FragColor = vec4(rgba.r, rgba.g, rgba.b, rgba.a); // 原画
13    //gl_FragColor = vec4(1.-rgba.r,1.-rgba.g,1.-rgba.b,rgba.a); // 底片效果
14
15    // 公式灰度化
16    //float r = rgba.r * 0.299 + rgba.g * 0.587 + rgba.b * 0.114; // 灰度效果
17    //gl_FragColor = vec4(r, r, r, rgba.a);
18}

写成两个文件是因为方便读取和编写,其实这些着色器的代码写在哪里都是可以的,无论是硬编码到Java字符串中,还是从网络请求,结果都是一样的,都要读取成字符串,方便编译得到着色器程序即可。

如果要拿到摄像头的原始数据并展示在屏幕上,那么必须使用ExternalOES这个采样器。另外要开启ExternalOES:#extension GL_OES_EGL_image_external : require 这句

编译&链接着色器程序

现在开始生成着色器程序,并且把着色器连接到显卡执行程序中:

OpenGLUtils.java

  1package cn.tim.opengl.utils;
  2
  3import android.content.Context;
  4import android.opengl.GLES20;
  5
  6import java.io.BufferedReader;
  7import java.io.File;
  8import java.io.FileOutputStream;
  9import java.io.IOException;
 10import java.io.InputStream;
 11import java.io.InputStreamReader;
 12
 13public class OpenGLUtils {
 14
 15    private static final String TAG = "OpenGLUtils";
 16
 17    // 顶点坐标
 18    public static final float[] VERTEX = {
 19            -1.0f, -1.0f,
 20            1.0f, -1.0f,
 21            -1.0f, 1.0f,
 22            1.0f, 1.0f
 23    };
 24    
 25	// 纹理坐标
 26    public static final float[] TEXURE = {
 27			0.0f, 0.0f,
 28            1.0f, 0.0f,
 29            0.0f, 1.0f,
 30            1.0f, 1.0f
 31    };
 32    
 33    // 读取RAW目录下的资源文件
 34    public static String readRawTextFile(Context context, int rawId) {
 35        InputStream is = context.getResources().openRawResource(rawId);
 36        BufferedReader br = new BufferedReader(new InputStreamReader(is));
 37        String line;
 38        StringBuilder sb = new StringBuilder();
 39        try {
 40            while ((line = br.readLine()) != null) {
 41                sb.append(line);
 42                sb.append("\n");
 43            }
 44        } catch (Exception e) {
 45            e.printStackTrace();
 46        }
 47        try {
 48            br.close();
 49        } catch (IOException e) {
 50            e.printStackTrace();
 51        }
 52        return sb.toString();
 53    }
 54
 55
 56    // 创建显卡执行程序
 57    public static int loadProgram(String vSource, String fSource) {
 58        // =============== 创建顶点着色器 & 片元着色器 ======================
 59        // 创建顶点着色器
 60        int vShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
 61        // 加载着色器代码
 62        GLES20.glShaderSource(vShader, vSource);
 63        // 编译(配置)
 64        GLES20.glCompileShader(vShader);
 65
 66        // 查看配置是否成功
 67        int[] status = new int[1];
 68        GLES20.glGetShaderiv(vShader, GLES20.GL_COMPILE_STATUS, status, 0);
 69        if (status[0] != GLES20.GL_TRUE) {
 70            //失败
 71            throw new IllegalStateException("load vertex shader:" + GLES20.glGetShaderInfoLog
 72                    (vShader));
 73        }
 74
 75        // 创建片元着色器,流程和上面一样
 76        int fShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
 77        // 加载片元着色器代码
 78        GLES20.glShaderSource(fShader, fSource);
 79        // 编译(配置)
 80        GLES20.glCompileShader(fShader);
 81
 82        // 查看配置是否成功
 83        GLES20.glGetShaderiv(fShader, GLES20.GL_COMPILE_STATUS, status, 0);
 84        if (status[0] != GLES20.GL_TRUE) {
 85            //失败
 86            throw new IllegalStateException("load fragment shader:" + GLES20.glGetShaderInfoLog
 87                    (vShader));
 88        }
 89        
 90        // =============== 创建显卡执行程序 ======================
 91        int program = GLES20.glCreateProgram();
 92        // 绑定顶点和片元到显卡执行程序中
 93        GLES20.glAttachShader(program, vShader);
 94        GLES20.glAttachShader(program, fShader);
 95        // 链接显卡执行程序
 96        GLES20.glLinkProgram(program);
 97        
 98        // 获得状态
 99        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0);
100        if (status[0] != GLES20.GL_TRUE) {
101            throw new IllegalStateException("link program:" + GLES20.glGetProgramInfoLog(program));
102        }
103        GLES20.glDeleteShader(vShader);
104        GLES20.glDeleteShader(fShader);
105        return program;
106    }
107}

启动显卡执行程序

ScreenFilter.java

 1package cn.tim.opengl.filter;
 2
 3import android.content.Context;
 4import android.opengl.GLES20;
 5
 6import java.nio.ByteBuffer;
 7import java.nio.ByteOrder;
 8import java.nio.FloatBuffer;
 9
10import cn.tim.opengl.R;
11import cn.tim.opengl.utils.OpenGLUtils;
12
13public class ScreenFilter {
14    private final int vPosition;
15    private final int vCoord;
16    private final int vTexture;
17    private final int vMatrix;
18    private final int program;
19    FloatBuffer vertexBuffer; // 顶点坐标缓存区
20    FloatBuffer textureBuffer; // 纹理坐标
21    private int mWidth;
22    private int mHeight;
23    private float[] mtx; // CameraX的校正矩阵
24
25    public ScreenFilter(Context context) {
26        // 准备数据
27        vertexBuffer = ByteBuffer.allocateDirect(4 * 4 * 2)
28                .order(ByteOrder.nativeOrder())
29                .asFloatBuffer();
30        vertexBuffer.clear();
31        vertexBuffer.put(OpenGLUtils.VERTEX);
32
33        textureBuffer = ByteBuffer.allocateDirect(4 * 4 * 2)
34                .order(ByteOrder.nativeOrder())
35                .asFloatBuffer();
36        
37        textureBuffer.clear();
38        textureBuffer.put(OpenGLUtils.TEXURE);
39
40        String vertexSharder = OpenGLUtils.readRawTextFile(context, R.raw.camera_vert);
41        String fragSharder = OpenGLUtils.readRawTextFile(context, R.raw.camera_frag);
42        // 着色器程序准备好
43        program = OpenGLUtils.loadProgram(vertexSharder, fragSharder);
44
45        // 获取程序中的变量索引
46        vPosition = GLES20.glGetAttribLocation(program, "vPosition");
47        vCoord = GLES20.glGetAttribLocation(program, "vCoord");
48        vTexture = GLES20.glGetUniformLocation(program, "vTexture");
49        vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
50    }
51
52    public void setSize(int width, int height) {
53        mWidth = width;
54        mHeight = height;
55    }
56
57    public void onDraw(int texture) {
58        // 设置绘制区域
59        GLES20.glViewport(0, 0, mWidth, mHeight);
60        // 启动显卡执行程序
61        GLES20.glUseProgram(program);
62        
63        vertexBuffer.position(0);
64        // 归一化 normalized 把[2,2]转换为[-1,1],目前不启用归一化处理
65        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer);
66        // CPU传数据到GPU,默认情况下着色器无法读取到这个数据,需要我们启用一下才可以读取
67        GLES20.glEnableVertexAttribArray(vPosition);
68        
69        textureBuffer.position(0);
70        // 归一化 normalized 把[2,2]转换为[-1,1],目前不启用归一化处理
71        GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
72        // CPU传数据到GPU,默认情况下着色器无法读取到这个数据,要我们启用一下才可以读取
73        GLES20.glEnableVertexAttribArray(vCoord);
74
75        // 相当于激活一个用来显示图片的图层
76        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
77        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture);
78        // 0: 图层ID GL_TEXTURE0,因为激活的是图层0
79        // GL_TEXTURE1,1
80        GLES20.glUniform1i(vTexture,0);
81
82        // 通过一致变量(uniform修饰的变量)引用将一致变量值传入渲染管线
83        GLES20.glUniformMatrix4fv(vMatrix, 1, false, mtx, 0);
84
85        // 通知画画
86        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
87        // 与图层0进行绑定
88        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
89    }
90
91    // 接收CameraX的校正矩阵
92    public void setTransformMatrix(float[] mtx) {
93        this.mtx = mtx;
94    }
95}

MainActivity.java

 1package cn.tim.opengl;
 2
 3import androidx.appcompat.app.AppCompatActivity;
 4import androidx.core.app.ActivityCompat;
 5
 6import android.Manifest;
 7import android.app.Activity;
 8import android.content.pm.PackageManager;
 9import android.os.Bundle;
10
11public class MainActivity extends AppCompatActivity {
12    // 申请权限请求码
13    private static final int REQUEST_CAMERA = 1001;
14
15    @Override
16    protected void onCreate(Bundle savedInstanceState) {
17        super.onCreate(savedInstanceState);
18        setContentView(R.layout.activity_main);
19        verifyStoragePermissions(this);
20    }
21
22    // 检查摄像头权限
23    public static void verifyStoragePermissions(Activity activity) {
24        int cameraPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.CAMERA);
25
26        if (cameraPermission != PackageManager.PERMISSION_GRANTED) {
27            // 如果没有权限需要动态地去申请权限
28            ActivityCompat.requestPermissions(
29                    activity,
30                    // 权限数组
31                    new String[]{
32                            Manifest.permission.CAMERA
33                    },
34                    // 权限请求码
35                    REQUEST_CAMERA
36            );
37        }
38    }
39}

main_activity.xml

 1<?xml version="1.0" encoding="utf-8"?>
 2<RelativeLayout
 3    xmlns:android="http://schemas.android.com/apk/res/android"
 4    xmlns:tools="http://schemas.android.com/tools"
 5    android:layout_width="match_parent"
 6    android:layout_height="match_parent"
 7    tools:context=".MainActivity">
 8
 9    <cn.tim.opengl.widget.CameraView
10        android:layout_width="match_parent"
11        android:layout_height="match_parent" />
12</RelativeLayout>

CameraX的依赖:

1implementation "androidx.camera:camera-core:1.0.0-alpha05"
2implementation "androidx.camera:camera-camera2:1.0.0-alpha05"

声明摄像头权限:

1<uses-permission android:name="android.permission.CAMERA"/>

这个是分步骤介绍的程序源码,完整程序代码请见: https://github.com/zouchanglin/render-camera