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");
}
}
}