一般在Android上使用OpenGL ES,都是但願把渲染後的結果顯示在屏幕上,例如圖片處理、模型顯示等。這種狀況下,只須要使用Android API中提供的GLSurfaceView類和Renderer類,在這兩個類提供的初始化、回調函數中設置/編寫相應的代碼便可。不過,若是不但願把渲染結果顯示在屏幕上,也就是所說的離屏渲染(offscreen render),這兩個類就幫不上忙了。在此介紹一下如何在Android系統上作OpenGL ES 的離屏渲染。算法
1.基礎知識ide
要想使用OpenGL ES,通常包括以下幾個步驟:函數
(1)EGL初始化測試
(2)OpenGL ES初始化網站
(3)OpenGL ES設置選項&繪製ui
(4)OpenGL ES資源釋放(可選)spa
(5)EGL資源釋放code
Android提供的GLSurfaceView和Renderer自動完成了(1)(5)兩個部分,這部分只須要開發者作一些簡單配置便可。另外(4)這一步是可選的,由於隨着EGL中上下文的銷燬,openGL ES用到的資源也跟着釋放了。所以只有(2)(3)是開發者必須作的。這大大簡化了開發過程,可是靈活性也有所下降,利用這兩個類是沒法完成offscreen render的。要想完成offscreen render其實也很簡單,相信你們也都猜到了,只要咱們把(1)~(5)都本身完成就能夠了。後續部分的代碼大部分都是C/C++,少部分是Java。blog
2.EGL初始化接口
EGL的功能是將OpenGL ES API和設備當前的窗口系統粘合在一塊兒,起到了溝通橋樑的做用。不一樣設備的窗口系統變幻無窮,可是OpenGL ES提供的API倒是統一的,因此EGL須要協調當前設備的窗口系統和OpenGL ES。下面EGL初始化的代碼我是用C++寫的,而後經過jni調用。Android在Java層面上也提供了對應的Java接口函數。
static EGLConfig eglConf; static EGLSurface eglSurface; static EGLContext eglCtx; static EGLDisplay eglDisp; JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_init (JNIEnv*env,jobject obj) { // EGL config attributes const EGLint confAttr[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,// very important! EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,//EGL_WINDOW_BIT EGL_PBUFFER_BIT we will create a pixelbuffer surface EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,// if you need the alpha channel EGL_DEPTH_SIZE, 8,// if you need the depth buffer EGL_STENCIL_SIZE,8, EGL_NONE }; // EGL context attributes const EGLint ctxAttr[] = { EGL_CONTEXT_CLIENT_VERSION, 2,// very important! EGL_NONE }; // surface attributes // the surface size is set to the input frame size const EGLint surfaceAttr[] = { EGL_WIDTH,512, EGL_HEIGHT,512, EGL_NONE }; EGLint eglMajVers, eglMinVers; EGLint numConfigs; eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY); if(eglDisp == EGL_NO_DISPLAY) { //Unable to open connection to local windowing system LOGI("Unable to open connection to local windowing system"); } if(!eglInitialize(eglDisp, &eglMajVers, &eglMinVers)) { // Unable to initialize EGL. Handle and recover LOGI("Unable to initialize EGL"); } LOGI("EGL init with version %d.%d", eglMajVers, eglMinVers); // choose the first config, i.e. best config if(!eglChooseConfig(eglDisp, confAttr, &eglConf, 1, &numConfigs)) { LOGI("some config is wrong"); } else { LOGI("all configs is OK"); } // create a pixelbuffer surface eglSurface = eglCreatePbufferSurface(eglDisp, eglConf, surfaceAttr); if(eglSurface == EGL_NO_SURFACE) { switch(eglGetError()) { case EGL_BAD_ALLOC: // Not enough resources available. Handle and recover LOGI("Not enough resources available"); break; case EGL_BAD_CONFIG: // Verify that provided EGLConfig is valid LOGI("provided EGLConfig is invalid"); break; case EGL_BAD_PARAMETER: // Verify that the EGL_WIDTH and EGL_HEIGHT are // non-negative values LOGI("provided EGL_WIDTH and EGL_HEIGHT is invalid"); break; case EGL_BAD_MATCH: // Check window and EGLConfig attributes to determine // compatibility and pbuffer-texture parameters LOGI("Check window and EGLConfig attributes"); break; } } eglCtx = eglCreateContext(eglDisp, eglConf, EGL_NO_CONTEXT, ctxAttr); if(eglCtx == EGL_NO_CONTEXT) { EGLint error = eglGetError(); if(error == EGL_BAD_CONFIG) { // Handle error and recover LOGI("EGL_BAD_CONFIG"); } } if(!eglMakeCurrent(eglDisp, eglSurface, eglSurface, eglCtx)) { LOGI("MakeCurrent failed"); } LOGI("initialize success!"); }
代碼比較長,不過大部分都是檢測當前函數調用是否出錯的,核心的函數只有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部分都寫在一塊兒了,代碼以下:
JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_draw (JNIEnv*env,jobject obj) { const char*vertex_shader=vertex_shader_fix; const char*fragment_shader=fragment_shader_simple; glPixelStorei(GL_UNPACK_ALIGNMENT,1); glClearColor(0.0,0.0,0.0,0.0); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glCullFace(GL_BACK); glViewport(0,0,512,512); GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader,1,&vertex_shader,NULL); glCompileShader(vertexShader); GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader,1,&fragment_shader,NULL); glCompileShader(fragmentShader); GLuint program = glCreateProgram(); glAttachShader(program, vertexShader); glAttachShader(program, fragmentShader); glLinkProgram(program); glUseProgram(program); GLuint aPositionLocation =glGetAttribLocation(program, "a_Position"); glVertexAttribPointer(aPositionLocation,2,GL_FLOAT,GL_FALSE,0,tableVerticesWithTriangles); glEnableVertexAttribArray(aPositionLocation); //draw something glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDrawArrays(GL_TRIANGLES,0,6); eglSwapBuffers(eglDisp,eglSurface); }
須要說明的是,繪製完成後,須要調用eglSwapBuffers(eglDisp,eglSurface)函數,由於在初始化EGL時默認設置的是雙緩衝模式,即一份緩衝用於繪製圖像,一份緩衝用於顯示圖像,每次顯示時須要交換兩份緩衝,把繪製好的圖像顯示出來。
上面openGL繪製須要的兩個shader在此不寫了,可供下載的demo裏會有。
4.EGL資源釋放
最後還差一個函數,用於EGL資源釋放,代碼以下:
JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_release (JNIEnv*env,jobject obj) { eglMakeCurrent(eglDisp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(eglDisp, eglCtx); eglDestroySurface(eglDisp, eglSurface); eglTerminate(eglDisp); eglDisp = EGL_NO_DISPLAY; eglSurface = EGL_NO_SURFACE; eglCtx = EGL_NO_CONTEXT; }
5.總結
大功告成,其實吃透了openGL ES的原理後,整個過程仍是很簡單的。爲了測試是否真的作到了offscreen render,咱們把framebuffer中的圖片保存成圖片,來檢測結果。代碼以下:
RGBABuffer = IntBuffer.allocate(512 * 512); MyGles.release(); MyGles.init(); MyGles.draw(); RGBABuffer.position(0); GLES20.glReadPixels(0, 0, 512, 512,GLES20.GL_RGBA,GLES20.GL_UNSIGNED_BYTE,RGBABuffer); int[] modelData=RGBABuffer.array(); int[] ArData=new int[modelData.length]; int offset1, offset2; for (int i = 0; i < 512; i++) { offset1 = i * 512; offset2 = (512 - i - 1) * 512; for (int j = 0; j < 512; j++) { int texturePixel = modelData[offset1 + j]; int blue = (texturePixel >> 16) & 0xff; int red = (texturePixel << 16) & 0x00ff0000; int pixel = (texturePixel & 0xff00ff00) | red | blue; ArData[offset2 + j] = pixel; } } Bitmap modelBitmap = Bitmap.createBitmap(ArData,512,512,Bitmap.Config.ARGB_8888); saveBitmap(modelBitmap);
要注意的是,由於openGL ES framebuffer和圖像通道的存儲順序不一樣,須要作一次ABGR到ARGB的轉換。
通常來講,offscreen render的用處主要是作GPU加速,若是你的算法是計算密集型的,能夠經過一些方法將其轉化成位圖形式,做爲紋理(texture)載入到GPU顯存中,再利用GPU的並行計算能力,對其進行處理,最後利用glReadPixels將計算結果讀取到內存中。就說這麼多吧,更多的用法還須要你們的發掘。
這裏是demo下載連接