OpenGL 作为跨平台的开放式图形库,在我们android平台自然也是有很大用处的。
这篇文章是我自己学习OpenGL的一个记录总结,同时写下我的理解,希望可以对你有帮助。
我们就使用OpenGL+GLSurfaceView+Camera 来实现使用Camera采集数据,通过OpenGL渲染到GLSurfaceView显示。
首先我们先在xml中写一个GLSurfaceView控件,获取到它的实例。那为什么我们要使用GLSurfaceView而不是用SurfaceView,因为我们渲染的过程需要和OpenGL建立联系才可以进行绘制的,而GLSurfaceView本身提供给我们使用就已经通过特殊的EGL环境与OpenGL建立了联系,而SurfaceView的话我们如果要使用需要自己配置EGL环境。
对GLSurfaceView进行一些配置:
GLSurfaceView glSurfaceview = findViewById(R.id.glsurfaceview);
glSurfaceview.setEGLContextClientVersion(2); //设置所使用的EGLContext版本号。
glSurfaceview.setRenderer(new GLSurfaceView.Renderer() { //设置渲染器
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
@Override
public void onDrawFrame(GL10 gl) {
}
});
glSurfaceview.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);//设置渲染方式按需渲染
渲染器回调:
onSurfaceCreated 在创建GLSurfaceView的时候会回调一次,我们可以做一些初始化操作。
onSurfaceChanged 在GLSurfaceView 大小发生改变的时候回调,包括屏幕方向等等,我们可以做一些更改设置的一些操作。
onDrawFrame 进行渲染的回调,这个里面我们进行绘制
设置渲染方式: 分为两种(一种按需渲染,一种连续渲染),这里我们使用按需渲染。
接下来我们要我们要从camera中获取采集到的图像数据:
我们在设置camera预览画面的时候进行离屏渲染,给他一个SurfaceTexture,传入一个纹理id,这样camera采集到的数据就绑定到那个纹理id上面,纹理泛指物体面上的线条或者花纹,这里就是采集到构成这个画面的元素。
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mTexture = new int[1];
GLES20.glGenTextures(1, mTexture, 0); // 创建一个纹理id
//将纹理id传入成功创建SurfaceTexture
mSurfaceTexture = new SurfaceTexture(mTexture[0]);
mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
glSurfaceview.requestRender();
}
});
//这个是我的camera工具类
mCameraHelper = new CameraHelper(Camera.CameraInfo.CAMERA_FACING_FRONT);
mCameraHelper.startPreview(mSurfaceTexture); //这个是设置相机的预览画面
//创建着色器程序 并且获取着色器程序中的部分属性
mProgramId = creatProgram(vertex, frag);
vPosition = GLES20.glGetAttribLocation(mProgramId, "vPosition");
vCoord = GLES20.glGetAttribLocation(mProgramId, "vCoord");
vMatrix = GLES20.glGetUniformLocation(mProgramId, "vMatrix");
vTexture = GLES20.glGetUniformLocation(mProgramId, "vTexture");
}
可以看到我们设置了SurfaceTexture的新数据监听,我们在回调中调用了GLSurfaceView的 requestRender方法。这个是请求渲染器渲染帧,因为我们设置的渲染方式是按需渲染,这里我们在有了新数据的时候再去主动要求渲染,就会回调onDrawFrame 这个监听。
创建一个着色器程序,并且获取着色器程序中的部分属性,下面是创建着色器程序的代码。
//顶点着色器代码
private String vertex = "attribute vec4 vPosition;\n" +
" attribute vec4 vCoord;\n" +
" uniform mat4 vMatrix;\n" +
" varying vec2 aCoord;\n" +
" void main(){\n" +
" gl_Position = vPosition; \n" +
" aCoord = (vMatrix * vCoord).xy;\n" +
" }";
//片元着色器代码
private String frag = "#extension GL_OES_EGL_image_external:require\n" +
" precision mediump float;\n" +
" varying vec2 aCoord;\n" +
" uniform samplerExternalOES vTexture;\n" +
" void main() {\n" +
" gl_FragColor = texture2D(vTexture,aCoord);\n" +
" }";
//创建着色器程序 返回着色器id
private int creatProgram(String vsi, String fsi) {
int vShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);//创建一个顶点着色器
GLES20.glShaderSource(vShader, vsi); //加载顶点着色器代码
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("顶点着色器创建失败!");
}
int fShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);//创建一个顶点着色器
GLES20.glShaderSource(fShader, fsi);//加载顶点着色器代码
GLES20.glCompileShader(fShader);
GLES20.glGetShaderiv(fShader, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
throw new IllegalStateException("片元着色器创建失败");
}
//创建着色器程序
int mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vShader);//将着色器塞入程序中
GLES20.glAttachShader(mProgram, fShader);
GLES20.glLinkProgram(mProgram);//链接
//获取状态,判断是否成功
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
throw new IllegalStateException("link program:" + GLES20.glGetProgramInfoLog(mProgram));
}
GLES20.glDeleteShader(vShader);
GLES20.glDeleteShader(fShader);
return mProgram;
}
将两段String字符传入,这两段式glsl代码,是OpenGL用于编写采样渲染的程序语言。
第一段字符是顶点着色器获取到外部传入的两个坐标,一个是OpenGL世界坐标,用于确定要绘制的形状,另一个是要采样器用于采样的采样坐标,还有一个矩阵,需要和采样坐标相乘才能获取到surfacetexture正确的采样坐标。
第二段字符是片元着色器,接收到顶点着色器确定的采样坐标,然后使用采样器采样到纹理的像素点颜色值,许许多多像素点的颜色值就构成了图像。
获取到画布宽高。
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mWidth = width;
mHeight = height;
}
做好准备工作,开始我们的渲染:
首先清理屏幕,将屏幕清理成指定的颜色,然后使用SurfaceTexture更新纹理数据,获取SurfaceTexture采样所需要的矩阵,确定要绘制屏幕的大小。接着使用着色器程序 将我们的OpenGL世界坐标,所要采样的目标坐标,和矩阵传入,激活图层,绑定纹理id,上面我们在创建SurfaceTexture时传入一个纹理id,这时我们需要将这个纹理id传入程序中,这样它在进行采样的时候就是在这个纹理id上面采样,然后开始渲染。
@Override
public void onDrawFrame(GL10 gl) {
//清理屏幕:可以清理成指定的颜色
GLES20.glClearColor(0, 0, 0, 0);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
mSurfaceTexture.updateTexImage();//更新纹理成为最新的数据
mSurfaceTexture.getTransformMatrix(mtx);
GLES20.glViewport(0, 0, mWidth, mHeight);//确定渲染的起始坐标,和宽高
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.glUniformMatrix4fv(vMatrix, 1, false, mtx, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0); //激活图层
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexture[0]);//绑定采样器采样的目标纹理
GLES20.glUniform1i(vTexture, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); //开始渲染
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); //解绑纹理
}
这个是我们传入着色器中的两个顶点数据,可以在onCreate中初始化。
mGLVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mGLVertexBuffer.clear();
float[] VERTEX = { //OpenGL世界坐标
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
mGLVertexBuffer.put(VERTEX);
mGLTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mGLTextureBuffer.clear();
float[] TEXTURE = { //目标采样的顶点坐标
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f,
};
mGLTextureBuffer.put(TEXTURE);
最后别忘了在AndroidManifest中加一个OpenGL版本号和相机权限,当然6.0以上需要动态申请。
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
以上就是OpenGL在Android平台上渲染的整个流程,我们使用OpenGL可以做很多事情,比如加贴纸,加滤镜,美颜等特效,但是想要做这些的前提就是要对OpenGL渲染的整个流程熟悉,熟悉之后我们才可以随心所欲的添加特效,在上面的整个流程中需要主要理解着色器程序的原理,里面的代码做了一些什么操作,这些我会在后面将我的一些理解整理一些分享出来,今天就先到这里。
代码GitHub地址 https://github.com/AndroidLiyan/openglDemo