OpenGLES在Android上除了可以用来做游戏、处理图片也可以用来处理视频图像、做相机预览美颜等等。本篇博客将介绍利用OpenGLES做相机预览的基本实现。
一、流程
-
理解OpenGL坐标系
-
camera相机预览和opengl关联
-
布局中使用 GLSurfacView 作为预览窗口。
-
准备相关的顶点属性数据和着色器文件。
-
实现 GLSurfaceView.Render 接口,编写具体的渲染代码。
二、具体实现
1、理解OpenGL坐标系
2、camera相机预览和opengl关联
Android 相机的预览数据可以输出到 SurfaceTexture 上,所以用 opengl 做相机预览的主要思路是
- 在 GLSurfaceView.Render 中创建一个纹理,再使用该纹理创建一个 SurfaceTexture
- 使用该 SurfaceTexture 创建一个 Surface 传给相机,相机预览数据就输出到一个纹理上了
- 使用 GLSurfaceView.Render 将该纹理渲染到 GLSurfaceView 窗口上
- 使用 SurfaceTexture 的 setOnFrameAvailableListener 方法 给 SurfaceTexture 添加一个数据帧数据可用的监听器,在监听器中 调用 GLSurfaceView 的 requestRender 方法渲染该帧数据,这样相机每次输出一帧数据就可以渲染一次,在GLSurfaceView窗口中就可以看到相机的预览数据了
@Override
public void onSurfaceChanged(GL10 gl10, int i, int i1) {
//开启预览、传人创建好的mSurfaceTexture
mCameraHelper.startPreview(mSurfaceTexture);
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
mFunCameraView.requestRender();
}
3、布局中使用 GLSurfacView 作为预览窗口。
public class FunCameraView extends GLSurfaceView {
public FunCameraView(Context context) {
this(context,null);
}
public FunCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
//设置opengl版本
setEGLContextClientVersion(2);
//自定义渲染器
setRenderer(new FunCameraRender(this));
//按需渲染
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
4、准备相关的顶点属性数据和着色器文件。
提醒:如果想编写着色器文件时候有高亮,可以安装“GLSL Support"插件,或者你可以直接写成String
顶点着色器
// 把顶点坐标给这个变量, 确定要画画的形状
attribute vec4 vPosition;
//接收纹理坐标,接收采样器采样图片的坐标
attribute vec4 vCoord;
//变换矩阵, 需要将原本的vCoord(01,11,00,10) 与矩阵相乘 才能够得到 surfacetexure(特殊)的正确的采样坐标
uniform mat4 vMatrix;
//传给片元着色器 像素点
varying vec2 aCoord;
void main(){
//内置变量 gl_Position ,我们把顶点数据赋值给这个变量 opengl就知道它要画什么形状了
gl_Position = vPosition;
// 进过测试 和设备有关
aCoord = (vMatrix * vCoord).xy;
//aCoord = vec2((vCoord*vMatrix).x,(vCoord*vMatrix).y);
}
片元着色器
#extension GL_OES_EGL_image_external : require
//SurfaceTexture比较特殊
//float数据是什么精度的
precision mediump float;
//采样点的坐标
varying vec2 aCoord;
//采样器
uniform samplerExternalOES vTexture;
void main(){
//变量 接收像素值
// texture2D:采样器 采集 aCoord的像素
//赋值给 gl_FragColor 就可以了
gl_FragColor = texture2D(vTexture,aCoord);
}
5、实现 GLSurfaceView.Render 接口,编写具体的渲染代码。
public class FunCameraRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
private FunCameraView mView;
private CameraHelper mCameraHelper;
private SurfaceTexture mSurfaceTexture;
private float[] mtx = new float[16];
private int[] mTextures;
private FloatBuffer mTextureBuffer;
private FloatBuffer mVertexBuffer;
private int vTexture;
private int vMatrix;
private int vCoord;
private int vPosition;
private int mProgram;
private int mWidth;
private int mHeight;
public FunCameraRenderer(FunCameraView funCameraView) {
mView = funCameraView;
}
/**
* 画布创建好啦
*/
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//初始化的操作
mCameraHelper = new CameraHelper(Camera.CameraInfo.CAMERA_FACING_BACK);
//准备好摄像头绘制的画布
//通过opengl创建一个纹理id
mTextures = new int[1];
GLES20.glGenTextures(mTextures.length, mTextures, 0);
mSurfaceTexture = new SurfaceTexture(mTextures[0]);
//
mSurfaceTexture.setOnFrameAvailableListener(this);
//camera_vertex 中的内容读出字符串
String vertexSource = OpenUtils.readRawTextFile(context, R.raw.camera_vertex);
String fragSource = OpenUtils.readRawTextFile(context, R.raw.camera_frag);
//通过字符串(代码)创建着色器程序
//使用opengl
//1、创建顶点着色器
// 1.1获取顶点着色器
int vShaderId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
// 1.2 绑定代码到着色器中去
GLES20.glShaderSource(vShaderId, vertexSource);
// 1.3 编译着色器代码
GLES20.glCompileShader(vShaderId);
//主动获取成功、失败 (如果不主动查询,只输出 一条 GLERROR之类的日志,很难定位到到底是那里出错)
int[] status = new int[1];
GLES20.glGetShaderiv(vShaderId, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
throw new IllegalStateException("ScreenFilter 顶点着色器配置失败!");
}
//2、创建片元着色器
int fShaderId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
GLES20.glShaderSource(fShaderId, fragSource);
GLES20.glCompileShader(fShaderId);
GLES20.glGetShaderiv(fShaderId, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
throw new IllegalStateException("ScreenFilter 片元着色器配置失败!");
}
//3、创建着色器程序 (GPU上的小程序)
mProgram = GLES20.glCreateProgram();
//把着色器塞到程序当中
GLES20.glAttachShader(mProgram, vShaderId);
GLES20.glAttachShader(mProgram, fShaderId);
//链接着色器
GLES20.glLinkProgram(mProgram);
//获得程序是否配置成功
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
throw new IllegalStateException("ScreenFilter 着色器程序配置失败!");
}
//因为已经塞到着色器程序中了,所以删了没关系
GLES20.glDeleteShader(vShaderId);
GLES20.glDeleteShader(fShaderId);
//获得着色器程序中的变量的索引, 通过这个索引来给着色器中的变量赋值
/**
* 顶点
*/
vPosition = GLES20.glGetAttribLocation(mProgram, "vPosition");
vCoord = GLES20.glGetAttribLocation(mProgram, "vCoord");
vMatrix = GLES20.glGetUniformLocation(mProgram, "vMatrix");
//片元
vTexture = GLES20.glGetUniformLocation(mProgram, "vTexture");
//创建一个数据缓冲区
//4个点 每个点两个数据(x,y) 数据类型float
//顶点坐标
mVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer();
mVertexBuffer.clear();
float[] v = {-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f};
mVertexBuffer.put(v);
mTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer();
mTextureBuffer.clear();
//镜像
float[] t = {1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f
};
mTextureBuffer.put(t);
}
/**
* 画布发生了改变
*
* @param gl
* @param width
* @param height
*/
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//开启预览
mCameraHelper.startPreview(mSurfaceTexture);
mWidth = width;
mHeight = height;
}
/**
* 开始画画吧
*
* @param gl
*/
@Override
public void onDrawFrame(GL10 gl) {
// 配置屏幕
//清理屏幕 :告诉opengl 需要把屏幕清理成什么颜色
GLES20.glClearColor(0, 0, 0, 0);
//执行上一个:glClearColor配置的屏幕颜色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 把摄像头的数据先输出来
// 更新纹理,然后我们才能够使用opengl从SurfaceTexure当中获得数据 进行渲染
mSurfaceTexture.updateTexImage();
//surfaceTexture 比较特殊,在opengl当中 使用的是特殊的采样器 samplerExternalOES (不是sampler2D)
//获得变换矩阵
mSurfaceTexture.getTransformMatrix(mtx);
//1、设置窗口大小
//画画的时候 你的画布可以看成 10x10,也可以看成5x5 等等
//设置画布的大小,然后画画的时候, 画布越大,你画上去的图像就会显得越小
// x与y 就是从画布的哪个位置开始画
GLES20.glViewport(0, 0, mWidth, mHeight);
//使用着色器程序
GLES20.glUseProgram(mProgram);
// 怎么画? 其实就是传值
//2:xy两个数据 float的类型
//1、将顶点数据传入,确定形状
mVertexBuffer.position(0);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
//传了数据之后 激活
GLES20.glEnableVertexAttribArray(vPosition);
//2、将纹理坐标传入,采样坐标
mTextureBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
//3、变换矩阵
GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0);
//片元 vTexture 绑定图像数据到采样器
//激活图层
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 图像数据
// 正常:GLES20.GL_TEXTURE_2D
// surfaceTexure的纹理需要
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture);
//传递参数 0:需要和纹理层GL_TEXTURE0对应
GLES20.glUniform1i(vTexture,0);
//参数传完了 通知opengl 画画 从第0点开始 共4个点
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
}
/**
* surfaceTexture 有一个有效的新数据的时候回调
*
* @param surfaceTexture
*/
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
mView.requestRender();
}
}
三、总结
本文梳理了使用 opengl 将相机预览数据渲染到 GLSurfaceView 的基本流程,后续以此为基础,结合 opengl 的离屏渲染机制实现实时滤镜功能,现在渲染器代码太长,后面会对代码进行抽取。
网友评论