通常在Android上使用OpenGL ES,都是希望把渲染后的结果显示在屏幕上,例如图片处理、模型显示等。这种情况下,只需要使用Android API中提供的GLSurfaceView类和Renderer类,在这两个类提供的初始化、回调函数中设置/编写相应的代码即可。不过,如果不希望把渲染结果显示在屏幕上,也就是所说的离屏渲染(offscreen render),这两个类就帮不上忙了。在此介绍一下如何在Android系统上做OpenGL ES 的离屏渲染。

1.基础知识

要想使用OpenGL ES,一般包括如下几个步骤:

  (1)EGL初始化

  (2)OpenGL ES初始化

  (3)OpenGL ES设置选项&绘制

  (4)OpenGL ES资源释放(可选)

  (5)EGL资源释放

Android提供的GLSurfaceView和Renderer自动完成了(1)(5)两个部分,这部分只需要开发者做一些简单配置即可。另外(4)这一步是可选的,因为随着EGL中上下文的销毁,openGL ES用到的资源也跟着释放了。因此只有(2)(3)是开发者必须做的。这大大简化了开发过程,但是灵活性也有所降低,利用这两个类是无法完成offscreen render的。要想完成offscreen render其实也很简单,相信大家也都猜到了,只要我们把(1)~(5)都自己完成就可以了。后续部分的代码大部分都是C/C++,少部分是Java。

2.EGL初始化

EGL的功能是将OpenGL ES API和设备当前的窗口系统粘合在一起,起到了沟通桥梁的作用。不同设备的窗口系统千变万化,但是OpenGL ES提供的API却是统一的,所以EGL需要协调当前设备的窗口系统和OpenGL ES。下面EGL初始化的代码我是用C++写的,然后通过jni调用。Android在Java层面上也提供了对应的Java接口函数。

  1. static EGLConfig eglConf;
  2. static EGLSurface eglSurface;
  3. static EGLContext eglCtx;
  4. static EGLDisplay eglDisp;
  5.  
  6. JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_init
  7. (JNIEnv*env,jobject obj)
  8. {
  9. // EGL config attributes
  10. const EGLint confAttr[] =
  11. {
  12. EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,// very important!
  13. EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,//EGL_WINDOW_BIT EGL_PBUFFER_BIT we will create a pixelbuffer surface
  14. EGL_RED_SIZE, ,
  15. EGL_GREEN_SIZE, ,
  16. EGL_BLUE_SIZE, ,
  17. EGL_ALPHA_SIZE, ,// if you need the alpha channel
  18. EGL_DEPTH_SIZE, ,// if you need the depth buffer
  19. EGL_STENCIL_SIZE,,
  20. EGL_NONE
  21. };
  22. // EGL context attributes
  23. const EGLint ctxAttr[] = {
  24. EGL_CONTEXT_CLIENT_VERSION, ,// very important!
  25. EGL_NONE
  26. };
  27. // surface attributes
  28. // the surface size is set to the input frame size
  29. const EGLint surfaceAttr[] = {
  30. EGL_WIDTH,,
  31. EGL_HEIGHT,,
  32. EGL_NONE
  33. };
  34. EGLint eglMajVers, eglMinVers;
  35. EGLint numConfigs;
  36.  
  37. eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  38. if(eglDisp == EGL_NO_DISPLAY)
  39. {
  40. //Unable to open connection to local windowing system
  41. LOGI("Unable to open connection to local windowing system");
  42. }
  43. if(!eglInitialize(eglDisp, &eglMajVers, &eglMinVers))
  44. {
  45. // Unable to initialize EGL. Handle and recover
  46. LOGI("Unable to initialize EGL");
  47. }
  48. LOGI("EGL init with version %d.%d", eglMajVers, eglMinVers);
  49. // choose the first config, i.e. best config
  50. if(!eglChooseConfig(eglDisp, confAttr, &eglConf, , &numConfigs))
  51. {
  52. LOGI("some config is wrong");
  53. }
  54. else
  55. {
  56. LOGI("all configs is OK");
  57. }
  58. // create a pixelbuffer surface
  59. eglSurface = eglCreatePbufferSurface(eglDisp, eglConf, surfaceAttr);
  60. if(eglSurface == EGL_NO_SURFACE)
  61. {
  62. switch(eglGetError())
  63. {
  64. case EGL_BAD_ALLOC:
  65. // Not enough resources available. Handle and recover
  66. LOGI("Not enough resources available");
  67. break;
  68. case EGL_BAD_CONFIG:
  69. // Verify that provided EGLConfig is valid
  70. LOGI("provided EGLConfig is invalid");
  71. break;
  72. case EGL_BAD_PARAMETER:
  73. // Verify that the EGL_WIDTH and EGL_HEIGHT are
  74. // non-negative values
  75. LOGI("provided EGL_WIDTH and EGL_HEIGHT is invalid");
  76. break;
  77. case EGL_BAD_MATCH:
  78. // Check window and EGLConfig attributes to determine
  79. // compatibility and pbuffer-texture parameters
  80. LOGI("Check window and EGLConfig attributes");
  81. break;
  82. }
  83. }
  84. eglCtx = eglCreateContext(eglDisp, eglConf, EGL_NO_CONTEXT, ctxAttr);
  85. if(eglCtx == EGL_NO_CONTEXT)
  86. {
  87. EGLint error = eglGetError();
  88. if(error == EGL_BAD_CONFIG)
  89. {
  90. // Handle error and recover
  91. LOGI("EGL_BAD_CONFIG");
  92. }
  93. }
  94. if(!eglMakeCurrent(eglDisp, eglSurface, eglSurface, eglCtx))
  95. {
  96. LOGI("MakeCurrent failed");
  97. }
  98. LOGI("initialize success!");
  99. }

代码比较长,不过大部分都是检测当前函数调用是否出错的,核心的函数只有6个,只要它们的调用没有问题即可:

eglGetDisplay(EGL_DEFAULT_DISPLAY)

eglInitialize(eglDisp, &eglMajVers, &eglMinVers)

eglChooseConfig(eglDisp, confAttr, &eglConf, 1, &numConfigs)

eglCreatePbufferSurface(eglDisp, eglConf, surfaceAttr)

eglCreateContext(eglDisp, eglConf, EGL_NO_CONTEXT, ctxAttr)

eglMakeCurrent(eglDisp, eglSurface, eglSurface, eglCtx)

这些函数中参数的具体定义可以在这个网站找到:  https://www.khronos.org/registry/egl/sdk/docs/man/

需要说明的是,eglChooseConfig(eglDisp, confAttr, &eglConf, 1, &numConfigs)中confAttr参数一定要有EGL_SURFACE_TYPE,EGL_PBUFFER_BIT这个配置,它决定了是渲染surface的类型,是屏幕还是内存。另外,还有一些选项和OpenGL ES版本有关,具体选用1.x还是2.x,这个视个人情况而定,我使用的是2.x。

3.OpenGL ES部分

为了方便说明,我把OpenGL ES部分都写在一起了,代码如下:

  1. JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_draw
  2. (JNIEnv*env,jobject obj)
  3. {
  4. const char*vertex_shader=vertex_shader_fix;
  5. const char*fragment_shader=fragment_shader_simple;
  6. glPixelStorei(GL_UNPACK_ALIGNMENT,);
  7. glClearColor(0.0,0.0,0.0,0.0);
  8. glEnable(GL_DEPTH_TEST);
  9. glDepthFunc(GL_LESS);
  10. glCullFace(GL_BACK);
  11. glViewport(,,,);
  12. GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
  13. glShaderSource(vertexShader,,&vertex_shader,NULL);
  14. glCompileShader(vertexShader);
  15. GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  16. glShaderSource(fragmentShader,,&fragment_shader,NULL);
  17. glCompileShader(fragmentShader);
  18. GLuint program = glCreateProgram();
  19. glAttachShader(program, vertexShader);
  20. glAttachShader(program, fragmentShader);
  21. glLinkProgram(program);
  22. glUseProgram(program);
  23. GLuint aPositionLocation =glGetAttribLocation(program, "a_Position");
  24. glVertexAttribPointer(aPositionLocation,,GL_FLOAT,GL_FALSE,,tableVerticesWithTriangles);
  25. glEnableVertexAttribArray(aPositionLocation);
  26. //draw something
  27. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  28. glDrawArrays(GL_TRIANGLES,,);
  29. eglSwapBuffers(eglDisp,eglSurface);
  30. }

需要说明的是,绘制完成后,需要调用eglSwapBuffers(eglDisp,eglSurface)函数,因为在初始化EGL时默认设置的是双缓冲模式,即一份缓冲用于绘制图像,一份缓冲用于显示图像,每次显示时需要交换两份缓冲,把绘制好的图像显示出来。

上面openGL绘制需要的两个shader在此不写了,可供下载的demo里会有。

4.EGL资源释放

最后还差一个函数,用于EGL资源释放,代码如下:

  1. JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_release
  2. (JNIEnv*env,jobject obj)
  3. {
  4. eglMakeCurrent(eglDisp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
  5. eglDestroyContext(eglDisp, eglCtx);
  6. eglDestroySurface(eglDisp, eglSurface);
  7. eglTerminate(eglDisp);
  8.  
  9. eglDisp = EGL_NO_DISPLAY;
  10. eglSurface = EGL_NO_SURFACE;
  11. eglCtx = EGL_NO_CONTEXT;
  12. }

5.总结

大功告成,其实吃透了openGL ES的原理后,整个过程还是很简单的。为了测试是否真的做到了offscreen render,我们把framebuffer中的图片保存成图片,来检测结果。代码如下:

  1. RGBABuffer = IntBuffer.allocate(512 * 512);
  2. MyGles.release();
  3. MyGles.init();
  4. MyGles.draw();
  5. RGBABuffer.position(0);
  6. GLES20.glReadPixels(0, 0, 512, 512,GLES20.GL_RGBA,GLES20.GL_UNSIGNED_BYTE,RGBABuffer);
  7. int[] modelData=RGBABuffer.array();
  8. int[] ArData=new int[modelData.length];
  9. int offset1, offset2;
  10. for (int i = 0; i < 512; i++) {
  11. offset1 = i * 512;
  12. offset2 = (512 - i - 1) * 512;
  13. for (int j = 0; j < 512; j++) {
  14. int texturePixel = modelData[offset1 + j];
  15. int blue = (texturePixel >> 16) & 0xff;
  16. int red = (texturePixel << 16) & 0x00ff0000;
  17. int pixel = (texturePixel & 0xff00ff00) | red | blue;
  18. ArData[offset2 + j] = pixel;
  19. }
  20. }
  21. Bitmap modelBitmap = Bitmap.createBitmap(ArData,512,512,Bitmap.Config.ARGB_8888);
  22. saveBitmap(modelBitmap);

要注意的是,因为openGL ES framebuffer和图像通道的存储顺序不同,需要做一次ABGR到ARGB的转换。

一般来说,offscreen render的用处主要是做GPU加速,如果你的算法是计算密集型的,可以通过一些方法将其转化成位图形式,作为纹理(texture)载入到GPU显存中,再利用GPU的并行计算能力,对其进行处理,最后利用glReadPixels将计算结果读取到内存中。就说这么多吧,更多的用法还需要大家的发掘。

这里是demo下载链接

Android OpenGL ES 离屏渲染(offscreen render)的更多相关文章

  1. Android OpenGL ES(八)绘制点Point ..

    上一篇介绍了OpenGL ES能够绘制的几种基本几何图形:点,线,三角形.将分别介绍这几种基本几何图形的例子.为方便起见,暂时在同一平面上绘制这些几何图形,在后面介绍完OpenGL ES的坐标系统和坐 ...

  2. Android OpenGL ES(五)GLSurfaceView .

    Android OpenGL ES 相关的包主要定义在 javax.microedition.khronos.opengles    GL 绘图指令 javax.microedition.khrono ...

  3. Android OpenGL ES 开发教程 从入门到精通

    感谢,摘自:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ...

  4. Android OpenGL ES .介绍

    引自:http://blog.csdn.net/hgl868/article/details/6971624 1.    OpenGL ES 简介 Android 3D引擎采用的是OpenGL ES. ...

  5. Android OpenGL ES(七)基本几何图形定义 .

    在前面Android OpenGL ES(六):创建实例应用OpenGLDemos程序框架 我们创建了示例程序的基本框架,并提供了一个“Hello World”示例,将屏幕显示为红色. 本例介绍Ope ...

  6. [工作记录] Android OpenGL ES: non-square texture - continue

    previous: [工作记录] Android OpenGL ES 2.0: square texture not supported on some device recently I found ...

  7. Android OpenGL ES(十三)通用的矩阵变换指令 .

    Android OpenGL ES 对于不同坐标系下坐标变换,大都使用矩阵运算的方法来定义和实现的.这里介绍对应指定的坐标系(比如viewmodel, projection或是viewport) An ...

  8. Android OpenGL ES(十二):三维坐标系及坐标变换初步 .

    OpenGL ES图形库最终的结果是在二维平面上显示3D物体(常称作模型Model)这是因为目前的打部分显示器还只能显示二维图形.但我们在构造3D模型时必须要有空间现象能力,所有对模型的描述还是使用三 ...

  9. Android OpenGL ES(六)创建实例应用OpenGLDemos程序框架 .

    有了前面关于Android OpenGL ES的介绍,可以开始创建示例程序OpenGLDemos. 使用Eclipse 创建一个Android项目 Project Name: OpenGLDemos ...

随机推荐

  1. Logistic回归分析简介

    Logistic回归:实际上属于判别分析,因拥有很差的判别效率而不常用. 1. 应用范围: ①     适用于流行病学资料的危险因素分析 ②     实验室中药物的剂量-反应关系 ③     临床试验 ...

  2. myeclipse上传git的问题

    unstaged changes 建好仓库,连接到git之后,在上传代码的时候发现有一些代码是unstaged changes状态.这样的文件是没法上传到git上去的.解决方法是给这些文件增加inde ...

  3. 设计模式之装饰模式(iOS开发,代码用Objective-C展示)

    在面向对象编程中有个重要的原则,里氏代换原则:一个软件实体如果使用的是一个父类的话,那么一定适用其子类,而且它察觉不出父类对象与子类对象的区别.也就是说,在软件设计里面,把父类替换成它的子类,程序的行 ...

  4. django 返回json数据

    from django.core import serializers @login_required def ajax_get_data(request): json_data = serializ ...

  5. postgresql相关命令

    1,打开命令窗口: 2,查看数据库用户:\du 3,列出所有数据库名:\l或者SELECT datname FROM pg_database; 4,切换某个数据库下面的某个用户下面:\c 数据库名 用 ...

  6. ADO对Excel对象进行连接时的 两种方法区别

    在通过ADO对Excel对象进行连接时(此时Excel则认为是一个数据源),需要配置对Excel数据源对应的连接串,这个连接串中包括了Provider信息(其实类似对数据库进行连接操作时,都需要指定连 ...

  7. C#中的索引器

    在Java中,一般会这样使用get,set方法: class Person{ private String name; public void setName(String name){ this.n ...

  8. Windows 管理

    1. Outlook 2010 更改显示语言. 在"选项"里选择"语言"这一项,在"显示语言"这一项,如果你没有英文选项,则需要安装Engl ...

  9. 解决android studio上“com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65935”问题

    我是在更换应用的一个jar包时发生的这个错误,网上查到说是因为同时在工程中引入了多个第三方jar包,导致调用的方法数超过了android设定的65935个(DEX 64K problem),进而导致d ...

  10. 关闭R语言载入包时候的警告

    options(warn =-1)