Android中的OpenGL使用初探
Posted 形上为道
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android中的OpenGL使用初探相关的知识,希望对你有一定的参考价值。
OpenGL(Open Graphics Library)是一个跨编程语言、跨平台的编程图形程序接口,主要用于图像的渲染。android提供了简化版的OpenGL接口,即OpenGL ES。
查看系统支持的OpenGL版本
/**
* 获取支持的OpenGL版本,前16位代表最高版本,后16位代表最低版本,如0x30002,支持OpenGL 2/3
*/
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()中的清屏操作,基本就完整了。
Android组件Activity初探
1.Activity是什么
Activity是Android系统中的四大组件之一,在MVC模式中属于C控制层
M(Model 模型):Model是应用程序的主体对象。
V(View 视图):是应用程序中负责生成用户界面的部分,使用XML作为编程语言。
C(Controller控制层)android的控制层的重任就要落在众多的activity的肩上了,所以在这里就要建议大家不要在activity中写太多的代码,尽量能过activity交割Model业务逻辑层处理。
一个应用程序通常由多个Activities组成,他们通常是松耦合关系。通常,应用程序中第一个被展示的Activity被指定为"Main" Activity。每一个Activity然后可以启动另一个Activity。每一次一个Activity启动,前一个activity就停止了。当一个新Activity启动,它被推送到栈顶(压栈),取得用户焦点。Back Stack符合简单“后进先出”原则,所以,当用户完成当前Activity然后点击back按钮,它被弹出栈(并且被摧毁),然后之前的Activity恢复。
2.Activity的生命周期
Activity生命周期图
Activity 的生命周期中的系统回调函数。
public class Activity extends ApplicationContext {
protected void onCreate(Bundle icicle);
protected void onStart();
protected void onRestart();
protected void onResume();
protected void onFreeze(Bundle outIcicle);
protected void onPause();
protected void onStop();
protected void onDestroy();
}
3.Activity的状态类型
Activity有四种状态:
活动状态(Activity):当Activity处于Stack(栈)顶时,就是手机当前的现实屏幕,这是 Activity就处于activity或者运行状态。
运行但是失去焦点(Pause):当Activity还处于运行状态时,但是屏幕是有另外一个Activity处于文档处于焦点状态,当前的Activity就处于pause。
停止(Stop),当Activity被另一个Activity完全覆盖的时候,就被停止了,其实就是虽然在运行,但是用户却看不见。
结束(Destroy),当Activity处于pause或者stop时,系统可以结束 Activity,回收资源,这是Activity就是处于结束状态了。
4.Activity的响应时间
当前Activity所在的线程为主线程,它的响应时间为5秒,如果在当前运行的Activity中进行耗时的操作且响应时间起过5秒,那么程序就会报ANR错误。所以,这也是不建议在Activity中写太多复杂代码的原因之一。
当然,有些代码只能写在Activity中,不然就运行不了(它们不是生命周期方法),比如你想要获得android系统或者硬件一的些信息,就必须在Activity中写出来,如果单独写一个工具类获得不了。
以上是关于Android中的OpenGL使用初探的主要内容,如果未能解决你的问题,请参考以下文章