Android OpenGL ES 离屏渲染(offscreen render)

一般在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下载连接

相关文章
相关标签/搜索