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);
源码地址