OpenGL是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API),而在嵌入式和移动平台的版本是OpenGL ES。Android最初就支持OpenGL ES的1.0版本,到现在已经支持到最新的3.2版本,下面的支持变化图
当然这个版本支持不是绝对的,还有看硬件是否支持,例如genymotion模拟器只有OpenGL ES 2.0版本,如果你使用了高版本的API会导致崩溃。OpenGL ES 1.x版本和2.0版本语法差异巨大,3.x版本向前兼容2.0版本。因此我们学习的版本是2.0既可兼容近100%的设备。
版本支持声明
可以在AndroidManifest.xml中加入下面这行使用特性的声明,Google Play将会过滤掉不支持指定OpenGL ES版本的用户,拒绝他们安装。
<!-- 需要OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
版本号的高16位表示主要版本,低16位表示次版本;那么如果需要3.0,3.1则可以如下配置
<!-- 需要OpenGL ES 3.0 -->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
<!-- 需要OpenGL ES 3.1 -->
<uses-feature android:glEsVersion="0x00030001" android:required="true" />
也可以在代码中判断gles的版本,version同样传入版本号即可(例如0x20000)
public static boolean checkOpenGL(Activity activity, int version) {
ActivityManager am = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
return am.getDeviceConfigurationInfo().reqGlEsVersion >= version;
}
return false;
}
基础
两个基本的类GLSurfaceView和GLSurfaceView.Renderer。
GLSurfaceView:继承自SurfaceView,用来显示渲染的图像。如果想操作你的图像,需要扩展触摸监听事件来处理。
GLSurfaceView.Renderer:GLSurfaceView的内部接口类,主要负责渲染图像。
GLSurfaceView主要方法:
- setEGLContextClientVersion:设置OpenGL ES的版本,只能设置主要版本(例如:1,2,3),不能设置次要版本。
- setEGLContextFactory:设置OpenGL ES的版本构建器,默认的构建器是根据版本设置的,可以自定义成版本自适应。
- setRenderer:设置Renderer,如果不设置,那么界面显示的就是一片空白。
- setRenderMode:设置Renderer的模式,有这么两种:1、RENDERMODE_WHEN_DIRTY(仅在创建时或调用requestRender时才会渲染内容);2、RENDERMODE_CONTINUOUSLY(不停的渲染)。
- onPause:暂停渲染,在页面不再显示时可以调用,减少性能开销,例如在Activity的onStop时。
- onResume:恢复渲染,类似onPause。
- requestRender:请求渲染,通常是RENDERMODE_WHEN_DIRTY模式时使用,必须在setRenderer后才能使用。
- queueEvent:插入一个Runnable任务到后台渲染线程上执行,必须在setRenderer后才能使用。
- setDebugFlags:设置debug模式,主要有两种:1、DEBUG_CHECK_GL_ERROR(当GL调用glError()方法后如果发生异常会打印。主要用来跟踪OpenGL错误。);2、DEBUG_LOG_GL_CALLS(打印所有GL的verbose级别的日志);
GLSurfaceView.Renderer的主要方法:
- onSurfaceCreated(GL10 gl, EGLConfig config):当Surface创建或重新创建时,这时可以进行初始化。
- onSurfaceChanged(GL10 gl, int width, int height):Surface尺寸改变时,返回当前surface宽高,可以进行下一步操作。
- onDrawFrame(GL10 gl):渲染绘制当前一帧时会调用。
OpenGL类:在包android.opengl下,主要有GLES20(OpenGL ES 2.0版本),GLES30,GLES31,GLES32和。
现在我们创建一个自定义的SurfaceView
public class BaseGLView extends GLSurfaceView {
public BaseGLView(Context context) {
this(context, null);
}
public BaseGLView(Context context, AttributeSet attrs) {
super(context, attrs);
setDebug();
init();
}
protected void setDebug() {
setDebugFlags(BuildConfig.DEBUG ? DEBUG_LOG_GL_CALLS : DEBUG_CHECK_GL_ERROR);
}
protected void init() {
// 设置版本
setEGLContextClientVersion(2);
// 设置Renderer
setRenderer(new BaseRenderer());
// 设置渲染模式(默认RENDERMODE_CONTINUOUSLY)
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
新建一个自定义的Renderer类
public class BaseRenderer implements GLSurfaceView.Renderer {
private int bg = Color.BLACK;
public BaseRenderer() {
}
public BaseRenderer(int bg) {
this.bg = bg;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置背景色
GLES20.glClearColor(Color.red(bg) / 255.0f, Color.green(bg) / 255.0f,
Color.blue(bg) / 255.0f, Color.alpha(bg) / 255.0f);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 设置显示范围
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
// 清屏
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
}
这时在Activity中使用setContentView(new BaseGLView(this));就可以了,Activity显示的就是一个黑色背景的布局。源码可以参考代码(https://github.com/jklwan/OpenGLProject/blob/master/sample/src/main/java/com/chends/opengl/view/BaseGLView.java)
主要参考资料:
Android官方文档(https://developer.android.com/guide/topics/graphics/opengl)
OpenGL ES文档(https://www.khronos.org/opengles/)
learnopengl(https://learnopengl-cn.github.io/)
《OpenGL ES 2 for Android: A Quick-Start Guide》(英文原版电子书)
Android的文档主要用来入门,参考learnopengl的文档来进行一步步的学习,当然learnopengl是桌面版的文档,和移动版有些区别,需要OpenGL ES文档和其他来学习。
最后是一个自定义的EGLContextFactory,当3.0可用时使用3.0,当3.0版本不可用时使用2.0
private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
private BaseListener<Integer> listener;
public ContextFactory(BaseListener<Integer> listener) {
this.listener = listener;
}
@Override
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
EGLContext context = null;
Integer version = null;
try {
context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT,
new int[]{EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE});
} catch (Exception ex) {
LogUtil.e(ex);
}
if (context == null || context == EGL10.EGL_NO_CONTEXT) {
LogUtil.d("un support OpenGL ES 3.0 ");
try {
context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT,
new int[]{EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE});
} catch (Exception ex) {
LogUtil.e(ex);
}
} else {
version = 3;
}
if (context == null || context == EGL10.EGL_NO_CONTEXT) {
LogUtil.d("un support OpenGL ES 2.0 ");
} else {
version = 2;
}
if (listener != null) {
listener.onFinish(version);
}
return context;
}
@Override
public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
if (!egl.eglDestroyContext(display, context)) {
LogUtil.d("destroyContext false");
}
}
}