Android OpenGL ES 离屏渲染(offscreen render)
通常在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接口函数。
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, ,
EGL_GREEN_SIZE, ,
EGL_BLUE_SIZE, ,
EGL_ALPHA_SIZE, ,// if you need the alpha channel
EGL_DEPTH_SIZE, ,// if you need the depth buffer
EGL_STENCIL_SIZE,,
EGL_NONE
};
// EGL context attributes
const EGLint ctxAttr[] = {
EGL_CONTEXT_CLIENT_VERSION, ,// very important!
EGL_NONE
};
// surface attributes
// the surface size is set to the input frame size
const EGLint surfaceAttr[] = {
EGL_WIDTH,,
EGL_HEIGHT,,
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, , &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,);
glClearColor(0.0,0.0,0.0,0.0);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glCullFace(GL_BACK);
glViewport(,,,);
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader,,&vertex_shader,NULL);
glCompileShader(vertexShader);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader,,&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,,GL_FLOAT,GL_FALSE,,tableVerticesWithTriangles);
glEnableVertexAttribArray(aPositionLocation);
//draw something
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES,,);
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下载链接
Android OpenGL ES 离屏渲染(offscreen render)的更多相关文章
- Android OpenGL ES(八)绘制点Point ..
上一篇介绍了OpenGL ES能够绘制的几种基本几何图形:点,线,三角形.将分别介绍这几种基本几何图形的例子.为方便起见,暂时在同一平面上绘制这些几何图形,在后面介绍完OpenGL ES的坐标系统和坐 ...
- Android OpenGL ES(五)GLSurfaceView .
Android OpenGL ES 相关的包主要定义在 javax.microedition.khronos.opengles GL 绘图指令 javax.microedition.khrono ...
- Android OpenGL ES 开发教程 从入门到精通
感谢,摘自:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ...
- Android OpenGL ES .介绍
引自:http://blog.csdn.net/hgl868/article/details/6971624 1. OpenGL ES 简介 Android 3D引擎采用的是OpenGL ES. ...
- Android OpenGL ES(七)基本几何图形定义 .
在前面Android OpenGL ES(六):创建实例应用OpenGLDemos程序框架 我们创建了示例程序的基本框架,并提供了一个“Hello World”示例,将屏幕显示为红色. 本例介绍Ope ...
- [工作记录] Android OpenGL ES: non-square texture - continue
previous: [工作记录] Android OpenGL ES 2.0: square texture not supported on some device recently I found ...
- Android OpenGL ES(十三)通用的矩阵变换指令 .
Android OpenGL ES 对于不同坐标系下坐标变换,大都使用矩阵运算的方法来定义和实现的.这里介绍对应指定的坐标系(比如viewmodel, projection或是viewport) An ...
- Android OpenGL ES(十二):三维坐标系及坐标变换初步 .
OpenGL ES图形库最终的结果是在二维平面上显示3D物体(常称作模型Model)这是因为目前的打部分显示器还只能显示二维图形.但我们在构造3D模型时必须要有空间现象能力,所有对模型的描述还是使用三 ...
- Android OpenGL ES(六)创建实例应用OpenGLDemos程序框架 .
有了前面关于Android OpenGL ES的介绍,可以开始创建示例程序OpenGLDemos. 使用Eclipse 创建一个Android项目 Project Name: OpenGLDemos ...
随机推荐
- 分类算法----k近邻算法
K最近邻(k-Nearest Neighbor,KNN)分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一.该方法的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的 ...
- Android开发(三)——Android布局中实现圆角边框
设置corners_bg.xml(设置边框圆角可以在drawable-mdpi目录里定义一个xml): <?xml version="1.0" encoding=" ...
- 如何在wpf实现进度条
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; u ...
- Thinkphp在Tpl中调用common.php中的公共函数
Common/common.php //产生随机验证码 function random($length, $chars = '0123456789') { //随机生成的逻辑 return $hash ...
- 建立window SVN服务器
在windows下搭建SVN服务器: 首先从http://www.visualsvn.com/server/download/ 下载最新的VisualSVN-Server-x.x.x.msi,然后本机 ...
- maprduce 中reduce数量
@Override public int getPartition(Text key, FlowBean value, int numPartitions) { String prefix = key ...
- C++11中如何输出enum class的值
Unlike an unscoped enumeration, a scoped enumeration is not implicitly convertible to its integer va ...
- Android—— Animation动画(很详细)
链接: http://www.360doc.com/content/13/0102/22/6541311_257754535.shtml http://www.cnblogs.com/aimeng/a ...
- os.walk函数
import os# g = os.walk("D:\aaa") for i in os.walk(R"D:\aaa"): print(i)#执行结果如下('D ...
- SimpleDraweeView 设置圆角不生效问题
采用的是xml配置 roundedCornerRadius 参数方法 adverPic.setImageURI("http://xx.xx.xx.xx/123.jpg"); 设置c ...