OpenGL(Open Graphics Library)是一个跨编程语言、跨平台的编程图形程序接口,主要用于图像的渲染。Android提供了简化版的OpenGL接口,即OpenGL ES。
查看系统支持的OpenGL版本
/**
* 获取支持的OpenGL版本,前16位代表主要版本,后16位代表次要版本,如0x30002,表示OpenGL 3.2
*/
public int getGLVersion(){
ActivityManager activityManager = getSystemService(ActivityManager.class);
ConfigurationInfo info = activityManager.getDeviceConfigurationInfo();
return info.reqGlEsVersion;
}
OpenGL及GLSurfaceView的初始化
Android系统提供了GLSurfaceView,在其内部做了OpenGL初始化工作,可直接使用GLSurfaceView。GLSurfaceView实际上创建了一个window,并在视图层穿了个洞,让底层的surface显示出来,与常规的View不同的是它没有动画或变形特效,因为它是window的一部分。
GLSurfaceView的初始化主要有
- 设置EGL版本
glSurfaceView.setEGLContextClientVersion(3);
- 设置渲染器
glSurfaceView.setRenderer(render);
- 设置渲染模式,需要在设置渲染器之后
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
GLSurfaceView.Renderer
GLSurfaceView内部的一个接口,需要重写三个方法
- onSurfaceCreated():surface第一次创建或画面重新resume会调用,通常用来加载着色器,以及设置清屏时的颜色:
GLES20.glClearColor(r,g,b,a);
- onSurfaceChanged():surface尺寸发生变化时调用,通常在这里计算坐标投影矩阵,以及设置OpenGL窗口的大小
GLES20.glViewport(0, 0, width, height);
- onDrawFrame():每次绘制都会调用,主要做清屏以及绘制,如果之前设置的渲染模式是RENDERMODE_WHEN_DIRTY,则需要调用
surfaceView.requestRender(),然后才会走这个方法
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_STENCIL_BUFFER_BIT);//清屏
if (surfaceTexture != null) {
surfaceTexture.updateTexImage();
}
shaderElement.draw();//绘制着色器元素
}
着色器(Shader)
Android中的OpenGL支持顶点着色器(VertexShader)和片元着色器(FragmentShader)。通常在res/raw中保存着色器程序(xx.glsl),建议在android studio中下载glsl的插件。
VertexShader
用来处理图形每个顶点变换,包括旋转、平移、投影等,下面例子是顶点着色器程序vertex.glsl
#version 300 es
in vec4 verTexPosition;//顶点向量,数据来自java数组,只读
in vec4 textureCoordinate;//纹理的向量
uniform mat4 textureMatrix;//与surfaceTexture相关联的纹理坐标变换矩阵,一致变量,在着色器执行期间是不变的,与片段着色器共享
uniform mat4 projectionMatrix;//坐标投影矩阵
out vec2 aCoor;//顶点着色器的输出,作为片段着色器的输入
out float weights[9];
out vec2 coordinates[9];
void main() {
gl_Position=projectionMatrix*verTexPosition;
aCoor=(textureMatrix*textureCoordinate).xy;
}
上面代码是OpenGL3.0的写法,与2.0有所区别;
gl_Position为内置变量,表示顶点的位置
FragmentShader
计算纹理的颜色并填充,片元着色器有多个,每个可以理解为一个点。
相机输出到surface采用的是外部纹理
samplerExternalOES
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
in vec2 aCoor;
out vec4 gl_FragColor;
uniform samplerExternalOES fragmentSampler;
void main() {
gl_FragColor=texture(fragmentSampler, aCoor);//采样函数texture()
}
gl_FragColor为内置变量,表示输出的颜色,在OpenGL3.0中需要用out修饰声明; 其它的纹理一般是用sampler2D,结合采样函数texture()输出纹理颜色
Shader中的变量修饰符
shader中有三种变量修饰符:uniform,attribute,varying;OpenGL3.0中用in替换顶点着色器中的attibute和片元着色器中的varying,用out替换顶点着色器的varying及修饰输出的gl_FragColor。
uniform变量
外部程序传递给shader的变量,相当于常量,shader中不可修改,可用于顶点及片元着色器中;如果在vertex和fragment两者之间声明方式完全一样,则它可以在vertex和fragment共享使用;应用端通过glUniformXX()传值;一般用来表示:变换矩阵,材质,光照参数和颜色等信息。
attribute变量(3.0用 in)
只能在vertex shader中使用,在OpenGL3.0中的声明用in;一般用于表示顶点数据如:顶点坐标,法线,纹理坐标,顶点颜色等;应用端通过glVertexAttribXX()传值。
varying变量
用于vertex和fragment之间的数据传递,在二者之间的声明必须一致;在OpenGL3.0中,对应vertex中的out和fragment中的in;应用端不能使用此变量。
加载着色器程序
private static int loadShader(int type, @RawRes int resId) {
int shaderId = GLES20.glCreateShader(type);//创建shader
GLES20.glShaderSource(shaderId, readCodes(resId));//加载glsl代码
GLES20.glCompileShader(shaderId);//编译shader
int[] status = new int[1];
GLES20.glGetShaderiv(shaderId, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
Log.e(TAG, "loadShader: failed, type=" + type + " message=" + GLES20.glGetShaderInfoLog(shaderId));
GLES20.glDeleteShader(shaderId);
return -1;
}
return shaderId;
}
/**
* 加载着色器程序
* @param vid vertex shader的资源id
* @param fid fragment shader的资源id
* @return OpenGL的programId,可以用来查找着色器程序的变量位置
*/
public static int loadProgram(@RawRes int vid, @RawRes int fid) {
int vShaderId = loadShader(GLES20.GL_VERTEX_SHADER, vid);//顶点着色器
int fShaderId = loadShader(GLES20.GL_FRAGMENT_SHADER, fid);//片段着色器
int program = GLES20.glCreateProgram();
if (vShaderId != -1) {
GLES20.glAttachShader(program, vShaderId);//指定要附加的着色器对象
}
if (fShaderId != -1) {
GLES20.glAttachShader(program, fShaderId);
}
GLES20.glLinkProgram(program);
int[] status = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
throw new RuntimeException("program link failed! reason=" + GLES20.glGetProgramInfoLog(program));
}
return program;
}
从res中读取glsl的字符串,并加载glsl代码,并编译shader,这里可以封装一个帮助类。
OpenGL相关常用的java API
获取glsl中的变量位置:
int locationA=GLES20.glGetAttribLocation(programId,name);//获取attribute变量的位置,OpenGL3对应是in
int locationU=GLES20.glGetUniformLocation(programId, name1);//获取uniform变量的位置
创建buffer,用来存放要传给着色器的数组,通常包括顶点数组、纹理数组、颜色数组
private FloatBuffer createBuffer(float[] arr) {
FloatBuffer buffer = ByteBuffer.allocateDirect(arr.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(arr);
buffer.position(0);
return buffer;
}
创建纹理并设置参数
protected int createTexture() {//generate texture and set params
int[] texture = new int[1];
glGenTextures(texture.length, texture, 0);
int target = forCamera ? GLES11Ext.GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
glBindTexture(target, texture[0]);
//设置纹理参数
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//缩小,最近邻过滤
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//放大,双线性过滤
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
glBindTexture(target, 0);
return texture[0];
}
指定使用的着色器
glUseProgram(programId);
设置attribute变量的值
glEnableVertexAttribArray(location);
glVertexAttribPointer(location, size, GL_FLOAT, false, 0, buffer);//size是每个元素的大小,比如二维坐标,就是2
设置uniform变量的值
glUniform1f(highLocation, 1f);//float 类型
glUniform1i(extSampYLocation, 3);//int 类型
glUniform4f(rectLocation, left, top, right, bottom);//float 数组
glUniformMatrix3fv(location, 1, true, matrix, 0);//矩阵传值
传递大数据量的数据,可以使用纹理的方式
glActiveTexture(GL_TEXTURE2);//之前的纹理已绑定到TEXTURE2
Buffer buffer = ByteBuffer.allocateDirect(table.length).put(table).position(0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, table.length / 3, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
特别注意:向着色器程序传递数据,或者读取数据,只能在GL渲染线程进行,因此传值时通常是
surfaceView.queueEvent(runnable);
绘制图形:OpenGL ES 只支持点、线和三角形绘制,矩形可以用多个三角形组成
glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
着色器元素的绘制,大概是这样
public void draw() {
glUseProgram(programId);
/*bindAttribureData(vetexLocation, vertexSize, vertexBuffer);//顶点,之前已绑定,忽略
bindAttribureData(textureLocation, textureSize, textureBuffer);//纹理
bindAttribureData(colorLocation, colorSize, colorBuffer);//颜色*/
glUniformMatrix4fv(projectionMatrixLocation, 1, false, projectionMatrix, 0);//投影矩阵
glUniformMatrix4fv(textureMatrixLocation, 1, false, textureMatrix, 0);//纹理矩阵
glActiveTexture(GL_TEXTURE0);//选择当前活跃的纹理单元
glBindTexture(GL_TEXTURE_2D, textureId);
glUniform1i(samplerLocation, 0);//对应GL_TEXTURE0
int count = vertexBuffer.capacity() / vertexSize;
glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
glBindTexture(GL_TEXTURE_2D, 0);//取消之前绑定的纹理
}
结合上面onDrawFrame()中的清屏操作,基本就完整了。