最近在做一些Android OpenGL相关的工作,作为一名初学者,第一次接触OpenGL难免有点畏惧心理,踩了很多坑。写这篇文章介绍关于OpenGL的一些基础知识,帮助大家能够快速理解。
OpenGL概念:
OpenGL(open Graphics Library):开放图形库,主要用于2D、3D矢量图形的绘制。封装了一些列API用于图像绘制,OpenGL具有非常好的跨平台性,可在windows、linux、MacOS上使用,它依赖于硬件的支持,能够通过GPU高效绘制各种图形和动画。
Android OpenGL ES:
Android的OpenGL ES库中封装了大量API,对OpenGL绘制操作提供了非常好的支持。App开发者可以通过这些接口实现OpenGL的绘制,其实这些接口底层都是通过native方法调用OpenGL库的API。应用开发者只需要理解上层接口的含义、参数的意义,就可以完成简单的绘制,对于开发者来说相当友好。
EGL相关概念:
EGL是Android为OpenGL ES提供平台独立性而设计的,上面我们提到过,OpenGL其实是通过GPU进行渲染。但是我们的程序是运行在CPU上,要与GPU关联,就需要通过EGL,它相当于Android上层应用与GPU通讯的中间层。 要在Android平台实现OpenGL渲染,需要完成一系列的EGL操作,主要为下面几步:
1、获取显示设备(EGLDisplay)
获取将要用于显示的设备,有些系统具有多个显示器,会存在多个Display。在Android上通过调用EGL10的eglGetDisplay(Object native_display)方法获得EGLDisplay对象,通常传入的参数为EGL10.EGL_DEFAULT_DISPLAY。
2、初始化EGL
调用EGL10的eglInitialize(EGLDisplay display, int[] major_minor)方法完成初始化操作。display参数即为上一步获取的对象,major_minor传入的是一个int数组,通常传入的是一个大小为2的数组。
3、选择Config配置
调用EGL10的eglChooseConfig(EGLDisplay display, int[] attrib_list, EGLConfig[] configs, int config_size, int[] num_config)方法,参数1、2其意明显,参数3用于存放输出的configs,参数4指定最多输出多少个config,参数5由EGL系统写入,表明满足attributes的config一共有多少个。
4、创建EGL环境(EGLContext)
eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, int[] attrib_list);参数1即为上面获取的Display,参数2为上一步chooseConfig传入的configs,share_context,是否有context共享,共享的contxt之间亦共享所有数据,通常设置为EGL_NO_CONTEXT代表不共享。attrib_list为int数组 {EGL_CONTEXT_CLIENT_VERSION, 2,EGL10.EGL_NONE };中间的2代表的是OpenGL ES的版本。
4、创建EGLSurface
eglCreateWindowSurface(EGLDisplay display, EGLConfig config, Object native_window, int[] attrib_list);参数1、2均为上述步骤得到的结果,参数3为上层创建的用于绘制内容的surface对象,参数4常设置为null。
5、设置OpenGL的渲染环境
eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context);该方法的参数意义很明确,该方法在异步线程中被调用,该线程也会被成为GL线程,一旦设定后,所有OpenGL渲染相关的操作都必须放在该线程中执行。下面介绍GLSurfaceView的流程会描述该线程。
通过上述5步操作,就完成了EGL的初始化设置,便可以进行OpenGL的渲染操作。
GLSurfaceView绘制流程:
Android的UI绘制必须要在主线程,而OpenGL的某些操作比较耗时,放在主线程显然是不合适的。因此,Android提供了GLSurfaceView用于OpenGL的绘制,下面我们将简单介绍一下GLSurfaceView的绘制流程。
Renderer
public interface Renderer {
/**
* Called when the surface is created or recreated.
* <p>
* Called when the rendering thread
* starts and whenever the EGL context is lost. The EGL context will typically
* be lost when the Android device awakes after going to sleep.
* <p>
* Since this method is called at the beginning of rendering, as well as
* every time the EGL context is lost, this method is a convenient place to put
* code to create resources that need to be created when the rendering
* starts, and that need to be recreated when the EGL context is lost.
* Textures are an example of a resource that you might want to create
* here.
* <p>
* Note that when the EGL context is lost, all OpenGL resources associated
* with that context will be automatically deleted. You do not need to call
* the corresponding "glDelete" methods such as glDeleteTextures to
* manually delete these lost resources.
* <p>
* @param gl the GL interface. Use <code>instanceof</code> to
* test if the interface supports GL11 or higher interfaces.
* @param config the EGLConfig of the created surface. Can be used
* to create matching pbuffers.
*/
void onSurfaceCreated(GL10 gl, EGLConfig config);
/**
* Called when the surface changed size.
* <p>
* Called after the surface is created and whenever
* the OpenGL ES surface size changes.
* <p>
* Typically you will set your viewport here. If your camera
* is fixed then you could also set your projection matrix here:
* <pre class="prettyprint">
* void onSurfaceChanged(GL10 gl, int width, int height) {
* gl.glViewport(0, 0, width, height);
* // for a fixed camera, set the projection too
* float ratio = (float) width / height;
* gl.glMatrixMode(GL10.GL_PROJECTION);
* gl.glLoadIdentity();
* gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
* }
* </pre>
* @param gl the GL interface. Use <code>instanceof</code> to
* test if the interface supports GL11 or higher interfaces.
* @param width
* @param height
*/
void onSurfaceChanged(GL10 gl, int width, int height);
/**
* Called to draw the current frame.
* <p>
* This method is responsible for drawing the current frame.
* <p>
* The implementation of this method typically looks like this:
* <pre class="prettyprint">
* void onDrawFrame(GL10 gl) {
* gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
* //... other gl calls to render the scene ...
* }
* </pre>
* @param gl the GL interface. Use <code>instanceof</code> to
* test if the interface supports GL11 or higher interfaces.
*/
void onDrawFrame(GL10 gl);
}
使用GLSurfaceView时需要自定义一个渲染器,实现Render接口,并将渲染器对象设置给GLSurfaceView。Render接口中定义的方法意义非常明确。将GLSurfaceView放入布局中,自定义实现Render接口就能完成OpenGL的绘制了,使用上非常简单。
GLThread
GLThread就是上面在介绍EGL时提到的GL线程,所有的EGL相关操作都在该线程中被调用,OpenGL绘制相关操作都由该线程完成,包括Render中的方法。而该线程最主要的方法就是guardedRun(),所有的操作都在该方法中执行。该方法的代码较长,在此处不贴出。不过逻辑还是比较好理解:
1、检测渲染环境是否准备好(EGL的初始化操作);
2、循环调用Render的onDrawFrame方法;
3、 调用EGL10的eglSwapBuffers(EGLDisplay display, EGLSurface surface)方法,该方法才是真正的将surface中的内容渲染到屏幕的操作。
结束语
本篇文章主要是为了介绍Android中使用OpenGL绘图的一些基础知识,没有具体的实践代码。关于GLSurfaceView建议大家阅读一下源码,源码比较容易理解。只要理解了上述的过程,其实还是比较容易看懂的。后面的文章会通过实例介绍OpenGL的绘制。