Android OpenGL+Camera2渲染(1) —— OpenGL简单介绍
Android OpenGL+Camera2渲染(2) —— OpenGL实现Camera2图像预览
Android OpenGL+Camera2渲染(3) —— 大眼,贴纸功能实现
Android OpenGL+Camera2渲染(4) —— 美颜功能实现
Android OpenGL+Camera2渲染(5) —— 录制视频,实现快录慢录
使用GlRenderView(继承自GLSurfaceView)来进行预览。
构造方法中配置EGL版本和渲染模式,通过setRenderer设置GlSurfaceView中渲染监听 GLSurfaceView.Renderer。
public GlRenderView(Context context, AttributeSet attrs) {
super(context, attrs);
//设置EGL 版本
setEGLContextClientVersion(2);
glRender = new GlRenderWrapper(this);
setRenderer(glRender);
//手动渲染模式
setRenderMode(RENDERMODE_WHEN_DIRTY);
}
GLSurfaceView.Renderer
public interface Renderer {
//surface 创建监听
void onSurfaceCreated(GL10 gl, EGLConfig config);
//surface改变监听
void onSurfaceChanged(GL10 gl, int width, int height);
//负责纹理绘制
void onDrawFrame(GL10 gl);
}
我们配置的是手动渲染模式,这里如果想执行onDrawFrame方法,需要每来一帧图像,调用 requestRender()。
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
camera2Helper = new Camera2Helper((Activity) glRenderView.getContext());
mTextures = new int[1];
//创建一个纹理
GLES20.glGenTextures(mTextures.length, mTextures, 0);
//将纹理和离屏buffer绑定
mSurfaceTexture = new SurfaceTexture(mTextures[0]);
//监听有新图像到来
mSurfaceTexture.setOnFrameAvailableListener(this);
//使用fbo 将samplerExternalOES 输入到sampler2D中
cameraFilter = new CameraFilter(glRenderView.getContext());
//负责将图像绘制到屏幕上
screenFilter = new ScreenFilter(glRenderView.getContext());
}
onSurfaceCreated中,实例Camera2Helper对象,创建一个SurfaceTexture和纹理,并进行绑定。这个SurfaceTexture会传给Camera2中,camera2负责输入图像到SurfaceTexture中,这里的SurfaceTexture是一个离屏buffer。并且实例CameraFilter和ScreenFilter。
当有新图像来了,会执行onFrameAvailable,更新图像。
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
glRenderView.requestRender();
}
CameraFilter 的作用下面会讲到,问什么要将 samplerExternalOES 输入到sampler2D中?
ScreenFilter:负责将图像绘制到屏幕上(加完滤镜美颜等效果,也是用这个类进行展示的)
CameraFilter的顶点着色器。
// 把顶点坐标给这个变量, 确定要画画的形状
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);
}
在实例化CameraFilter,会创建着色器程序。拿到着色器中的变量。
public BaseFilter(Context mContext, int mVertexShaderId, int mFragShaderId) {
this.mContext = mContext;
this.mVertexShaderId = mVertexShaderId;
this.mFragShaderId = mFragShaderId;
mGlVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mGlVertexBuffer.clear();
// float[] VERTEXT = {
// -1.0f, -1.0f,
// 1.0f, -1.0f,
// -1.0f, 1.0f,
// 1.0f, 1.0f
// };
float[] VERTEXT = {
-1.0f, 1.0f,
1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f
};
mGlVertexBuffer.put(VERTEXT);
mGlTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mGlTextureBuffer.clear();
// float[] TEXTURE = {
// 0.0f, 1.0f,
// 1.0f, 1.0f,
// 0.0f, 0.0f,
// 1.0f, 0.0f,
// };
float[] TEXTURE = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
mGlTextureBuffer.put(TEXTURE);
initilize(mContext);
resetCoordinate();
}
BaseFilter 是父类,首先初始化了顶点坐标和纹理坐标的值,
private void initilize(Context mContext) {
//读取着色器信息
mVertexShader = OpenGlUtils.readRawShaderFile(mContext, mVertexShaderId);
mFragShader = OpenGlUtils.readRawShaderFile(mContext, mFragShaderId);
//创建着色器程序
mProgramId = OpenGlUtils.loadProgram(mVertexShader, mFragShader);
//获取着色器变量,需要赋值
vPosition = GLES20.glGetAttribLocation(mProgramId, "vPosition");
vCoord = GLES20.glGetAttribLocation(mProgramId, "vCoord");
vMatrix = GLES20.glGetUniformLocation(mProgramId, "vMatrix");
vTexture = GLES20.glGetUniformLocation(mProgramId, "vTexture");
}
会把着色器里的信息以String读取出来,OpenGlUtils是个工具类,OpenGlUtils.loadProgram创建着色器程序。
ScreenFilter也是一样的,但是不同的是在片元着色器中,接收的纹理是Sampler2D,而不是 samplerExternalOES。这是因为,在CameraFilter中,传入的直接是SurfaceTexture,它不属于OpenGL中定义的东西,所以使用samplerExternalOES,经过CameraFilter使用FBO处理后,后续的所有操作都使用Sampler2D就可以了。
precision mediump float;
varying vec2 aCoord;
uniform sampler2D vTexture;
void main(){
gl_FragColor = texture2D(vTexture, aCoord);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//1080 1899
camera2Helper.setPreviewSizeListener(this);
camera2Helper.setOnPreviewListener(this);
//打开相机
camera2Helper.openCamera(width, height, mSurfaceTexture);
float scaleX = (float) mPreviewHeight / (float) width;
float scaleY = (float) mPreviewWdith / (float) height;
float max = Math.max(scaleX, scaleY);
screenSurfaceWid = (int) (mPreviewHeight / max);
screenSurfaceHeight = (int) (mPreviewWdith / max);
screenX = width - (int) (mPreviewHeight / max);
screenY = height - (int) (mPreviewWdith / max);
//prepare 传如 绘制到屏幕上的宽 高 起始点的X坐标 起使点的Y坐标
cameraFilter.prepare(screenSurfaceWid, screenSurfaceHeight, screenX, screenY);
screenFilter.prepare(screenSurfaceWid, screenSurfaceHeight, screenX, screenY);
}
onSurfaceChanged中开启相机,传入screenFilter要预览的大小参数。但对于cameraFilter..prepare中,还会创建FBO。
private void loadFOB() {
if (mFrameBuffers != null) {
destroyFrameBuffers();
}
//创建FrameBuffer
mFrameBuffers = new int[1];
GLES20.glGenFramebuffers(mFrameBuffers.length, mFrameBuffers, 0);
//穿件FBO中的纹理
mFBOTextures = new int[1];
OpenGlUtils.glGenTextures(mFBOTextures);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFBOTextures[0]);
//指定FBO纹理的输出图像的格式 RGBA
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, mOutputWidth, mOutputHeight,
0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);
//将fbo绑定到2d的纹理上
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, mFBOTextures[0], 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
onDrawFrame方法
@Override
public void onDrawFrame(GL10 gl) {
int textureId;
// 配置屏幕
//清理屏幕 :告诉opengl 需要把屏幕清理成什么颜色
GLES20.glClearColor(0, 0, 0, 0);
//执行上一个:glClearColor配置的屏幕颜色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//更新获取一张图
mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(mtx);
//cameraFiler需要一个矩阵,是Surface和我们手机屏幕的一个坐标之间的关系
cameraFilter.setMatrix(mtx);
textureId = cameraFilter.onDrawFrame(mTextures[0]);
int id = screenFilter.onDrawFrame(textureId);
}
mTextures[0]是SUrfaceTexture绑定的纹理ID
@Override
public int onDrawFrame(int textureId) {
//锁定绘制的区域 绘制是从左下角开始的
GLES20.glViewport(0, 0, mOutputWidth, mOutputHeight);
//绑定FBO,在FBO上操作
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);
//使用着色器
GLES20.glUseProgram(mProgramId);
//赋值vPosition
mGlVertexBuffer.position(0);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mGlVertexBuffer);
GLES20.glEnableVertexAttribArray(vPosition);
//赋值vCoord
mGlTextureBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mGlTextureBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
//赋值vMatrix
GLES20.glUniformMatrix4fv(vMatrix, 1, false, matrix, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//SurfaceTexture 对应 GL_TEXTURE_EXTERNAL_OES 类型
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
//赋值vTexture
GLES20.glUniform1i(vTexture, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
return mFBOTextures[0];
}
在FBO上操作,把输入的纹理ID上的图像,输出到FBO的纹理ID上然后返回。
public int onDrawFrame(int textureId) {
GLES20.glViewport(x, y, mOutputWidth, mOutputHeight);
GLES20.glUseProgram(mProgramId);
mGlVertexBuffer.position(0);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mGlVertexBuffer);
GLES20.glEnableVertexAttribArray(vPosition);
mGlTextureBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mGlTextureBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//传如的是GL_TEXTURE_2D类型
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
GLES20.glUniform1i(vTexture, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
return textureId;
}
这里拿到的textureId是CameraFilter中FBO的纹理ID,赋值着色器变量,直接glDrawArrays绘制。这里
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);的意思是绘制三角形,从第一个点开始,总共有4个点。
1 2 3 画一个三角,2 3 4画一个三角,这四个点就是顶点坐标。这里两个三角正好绘制出来的是一个矩形。
github项目地址:https://github.com/wangchao0837/OpenGlCameraRender