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代码):
纹理类型
一般仅在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_Position(gl_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