0%

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支持按需渲染和连续渲染;

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

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

1
2
3
4
5
6
7
8
9
// 手动渲染,也就是按需渲染
public final static int RENDERMODE_WHEN_DIRTY = 0;

// 自动渲染,大概16ms刷新一次
public final static int RENDERMODE_CONTINUOUSLY = 1;

public void setRenderMode(int renderMode) {
mGLThread.setRenderMode(renderMode);
}

可以看到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代码):

1
attribute vec4 position;

矩阵类型

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

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

uniform medium mat2 colorMatrix; // 2×2的浮点矩阵

纹理类型

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

1
uniform sampler2D texSampler;

内置函数与内置变量

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

1
vec4 gl_position;

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

1
float gl_pointSize;

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

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

1
vec4 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 渲染视图组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package cn.tim.opengl.widget;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.view.SurfaceHolder;

public class CameraView extends GLSurfaceView {
private CameraRender renderer;

public CameraView(Context context) {
this(context,null);
}

public CameraView(Context context, AttributeSet attrs) {
super(context, attrs);
//使用OpenGL ES 2.0 context.
setEGLContextClientVersion(2);
//设置渲染回调接口
renderer = new CameraRender(this);
setRenderer(renderer);

// 设置刷新方式
// RENDERMODE_WHEN_DIRTY 手动刷新,调用requestRender();
// RENDERMODE_CONTINUOUSLY 自动刷新,大概16ms自动回调一次onDraw方法
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
super.surfaceDestroyed(holder);
renderer.onSurfaceDestroyed();
}
}

CameraRender.java 渲染器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package cn.tim.opengl.widget;

import android.graphics.SurfaceTexture;
import android.opengl.GLSurfaceView;

import androidx.camera.core.Preview;
import androidx.lifecycle.LifecycleOwner;


import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import cn.tim.opengl.filter.ScreenFilter;
import cn.tim.opengl.utils.CameraHelper;

public class CameraRender implements GLSurfaceView.Renderer,
Preview.OnPreviewOutputUpdateListener,
SurfaceTexture.OnFrameAvailableListener {

private CameraView cameraView;
private CameraHelper cameraHelper;

// 摄像头的图像,用OpenGL ES画出来
private SurfaceTexture mCameraTexture;

private int[] textures;
private ScreenFilter screenFilter;

float[] mtx = new float[16];

public CameraRender(CameraView cameraView) {
this.cameraView = cameraView;
LifecycleOwner lifecycleOwner = (LifecycleOwner) cameraView.getContext();
cameraHelper = new CameraHelper(lifecycleOwner, this);

}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 创建OpenGL纹理 ,把摄像头的数据与这个纹理关联
textures = new int[1]; // 当做能在OpenGL用的一个图片的ID
mCameraTexture.attachToGLContext(textures[0]);
// 当摄像头数据有更新回调 onFrameAvailable
mCameraTexture.setOnFrameAvailableListener(this);

screenFilter = new ScreenFilter(cameraView.getContext());
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
screenFilter.setSize(width,height);
}

@Override
public void onDrawFrame(GL10 gl) {
// 更新纹理
mCameraTexture.updateTexImage();
// 获取CameraX中给定的校准参考矩阵
mCameraTexture.getTransformMatrix(mtx);

screenFilter.setTransformMatrix(mtx);
screenFilter.onDraw(textures[0]);
}

public void onSurfaceDestroyed() {

}

/**
* 更新
* @param output 预览输出
*/
@Override
public void onUpdated(Preview.PreviewOutput output) {
mCameraTexture = output.getSurfaceTexture();
}

@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
// 请求执行一次 onDrawFrame
cameraView.requestRender();
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package cn.tim.opengl.utils;

import android.os.HandlerThread;
import android.util.Size;

import androidx.camera.core.CameraX;
import androidx.camera.core.Preview;
import androidx.camera.core.PreviewConfig;
import androidx.lifecycle.LifecycleOwner;

public class CameraHelper {
private HandlerThread handlerThread;
private CameraX.LensFacing currentFacing = CameraX.LensFacing.BACK;
private Preview.OnPreviewOutputUpdateListener listener;

public CameraHelper(LifecycleOwner lifecycleOwner, Preview.OnPreviewOutputUpdateListener listener) {
this.listener = listener;
handlerThread = new HandlerThread("Analyze-thread");
handlerThread.start();
CameraX.bindToLifecycle(lifecycleOwner, getPreView());
}

private Preview getPreView() {
// 分辨率并不是最终的分辨率,CameraX会自动根据设备的支持情况
// 结合你的参数,设置一个最为接近的分辨率
PreviewConfig previewConfig = new PreviewConfig.Builder()
.setTargetResolution(new Size(640, 480))
.setLensFacing(currentFacing) // 前置或者后置摄像头
.build();
Preview preview = new Preview(previewConfig);
preview.setOnPreviewOutputUpdateListener(listener);
return preview;
}
}

编写顶点&片元着色器

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

顶点着色器 camera_vert.vert

1
2
3
4
5
6
7
8
9
10
11
12
13
14
attribute vec4 vPosition; // 变量 float[4] 一个顶点坐标,Java传过来的

attribute vec2 vCoord; // 纹理坐标,也是Java传过来的

varying vec2 aCoord; // aCoord是片元着色器传递过去的参数

uniform mat4 vMatrix; // Java传过来的CameraX坐标校正参数

void main(){
// 内置变量: 把坐标点赋值给gl_position
gl_Position = vPosition;
// aCoord是片元着色器传递过去的参数
aCoord = (vMatrix * vec4(vCoord,1.0,1.0)).xy;
}

片元着色器 camera_frag.frag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#extension GL_OES_EGL_image_external : require

// 摄像头数据比较特殊的一个地方
precision mediump float; // 数据精度
varying vec2 aCoord;

uniform samplerExternalOES vTexture; // samplerExternalOES: 图片,采样器

void main(){
// texture2D: vTexture采样器,采样 aCoord 这个像素点的RGBA值
vec4 rgba = texture2D(vTexture,aCoord); //rgba
gl_FragColor = vec4(rgba.r, rgba.g, rgba.b, rgba.a); // 原画
//gl_FragColor = vec4(1.-rgba.r,1.-rgba.g,1.-rgba.b,rgba.a); // 底片效果

// 公式灰度化
//float r = rgba.r * 0.299 + rgba.g * 0.587 + rgba.b * 0.114; // 灰度效果
//gl_FragColor = vec4(r, r, r, rgba.a);
}

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

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

编译&链接着色器程序

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

OpenGLUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package cn.tim.opengl.utils;

import android.content.Context;
import android.opengl.GLES20;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class OpenGLUtils {

private static final String TAG = "OpenGLUtils";

// 顶点坐标
public static final float[] VERTEX = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
};

// 纹理坐标
public static final float[] TEXURE = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};

// 读取RAW目录下的资源文件
public static String readRawTextFile(Context context, int rawId) {
InputStream is = context.getResources().openRawResource(rawId);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
StringBuilder sb = new StringBuilder();
try {
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (Exception e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}


// 创建显卡执行程序
public static int loadProgram(String vSource, String fSource) {
// =============== 创建顶点着色器 & 片元着色器 ======================
// 创建顶点着色器
int vShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
// 加载着色器代码
GLES20.glShaderSource(vShader, vSource);
// 编译(配置)
GLES20.glCompileShader(vShader);

// 查看配置是否成功
int[] status = new int[1];
GLES20.glGetShaderiv(vShader, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
//失败
throw new IllegalStateException("load vertex shader:" + GLES20.glGetShaderInfoLog
(vShader));
}

// 创建片元着色器,流程和上面一样
int fShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
// 加载片元着色器代码
GLES20.glShaderSource(fShader, fSource);
// 编译(配置)
GLES20.glCompileShader(fShader);

// 查看配置是否成功
GLES20.glGetShaderiv(fShader, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
//失败
throw new IllegalStateException("load fragment shader:" + GLES20.glGetShaderInfoLog
(vShader));
}

// =============== 创建显卡执行程序 ======================
int program = GLES20.glCreateProgram();
// 绑定顶点和片元到显卡执行程序中
GLES20.glAttachShader(program, vShader);
GLES20.glAttachShader(program, fShader);
// 链接显卡执行程序
GLES20.glLinkProgram(program);

// 获得状态
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
throw new IllegalStateException("link program:" + GLES20.glGetProgramInfoLog(program));
}
GLES20.glDeleteShader(vShader);
GLES20.glDeleteShader(fShader);
return program;
}
}

启动显卡执行程序

ScreenFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package cn.tim.opengl.filter;

import android.content.Context;
import android.opengl.GLES20;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import cn.tim.opengl.R;
import cn.tim.opengl.utils.OpenGLUtils;

public class ScreenFilter {
private final int vPosition;
private final int vCoord;
private final int vTexture;
private final int vMatrix;
private final int program;
FloatBuffer vertexBuffer; // 顶点坐标缓存区
FloatBuffer textureBuffer; // 纹理坐标
private int mWidth;
private int mHeight;
private float[] mtx; // CameraX的校正矩阵

public ScreenFilter(Context context) {
// 准备数据
vertexBuffer = ByteBuffer.allocateDirect(4 * 4 * 2)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexBuffer.clear();
vertexBuffer.put(OpenGLUtils.VERTEX);

textureBuffer = ByteBuffer.allocateDirect(4 * 4 * 2)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();

textureBuffer.clear();
textureBuffer.put(OpenGLUtils.TEXURE);

String vertexSharder = OpenGLUtils.readRawTextFile(context, R.raw.camera_vert);
String fragSharder = OpenGLUtils.readRawTextFile(context, R.raw.camera_frag);
// 着色器程序准备好
program = OpenGLUtils.loadProgram(vertexSharder, fragSharder);

// 获取程序中的变量索引
vPosition = GLES20.glGetAttribLocation(program, "vPosition");
vCoord = GLES20.glGetAttribLocation(program, "vCoord");
vTexture = GLES20.glGetUniformLocation(program, "vTexture");
vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
}

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

public void onDraw(int texture) {
// 设置绘制区域
GLES20.glViewport(0, 0, mWidth, mHeight);
// 启动显卡执行程序
GLES20.glUseProgram(program);

vertexBuffer.position(0);
// 归一化 normalized 把[2,2]转换为[-1,1],目前不启用归一化处理
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer);
// CPU传数据到GPU,默认情况下着色器无法读取到这个数据,需要我们启用一下才可以读取
GLES20.glEnableVertexAttribArray(vPosition);

textureBuffer.position(0);
// 归一化 normalized 把[2,2]转换为[-1,1],目前不启用归一化处理
GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
// CPU传数据到GPU,默认情况下着色器无法读取到这个数据,要我们启用一下才可以读取
GLES20.glEnableVertexAttribArray(vCoord);

// 相当于激活一个用来显示图片的图层
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture);
// 0: 图层ID GL_TEXTURE0,因为激活的是图层0
// GL_TEXTURE1,1
GLES20.glUniform1i(vTexture,0);

// 通过一致变量(uniform修饰的变量)引用将一致变量值传入渲染管线
GLES20.glUniformMatrix4fv(vMatrix, 1, false, mtx, 0);

// 通知画画
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
// 与图层0进行绑定
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
}

// 接收CameraX的校正矩阵
public void setTransformMatrix(float[] mtx) {
this.mtx = mtx;
}
}

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package cn.tim.opengl;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
// 申请权限请求码
private static final int REQUEST_CAMERA = 1001;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
verifyStoragePermissions(this);
}

// 检查摄像头权限
public static void verifyStoragePermissions(Activity activity) {
int cameraPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.CAMERA);

if (cameraPermission != PackageManager.PERMISSION_GRANTED) {
// 如果没有权限需要动态地去申请权限
ActivityCompat.requestPermissions(
activity,
// 权限数组
new String[]{
Manifest.permission.CAMERA
},
// 权限请求码
REQUEST_CAMERA
);
}
}
}

main_activity.xml

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

<cn.tim.opengl.widget.CameraView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

CameraX的依赖:

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

声明摄像头权限:

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

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

  • 本文作者: Tim
  • 本文链接: https://zouchanglin.cn/521116850.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!