Facebook Twitter LinkedIn E-mail
magnify
Home Archive for category "OpenGL ES"

Android ApiDemos示例解析(205):Graphics->OpenGL ES->Translucent GLSurfaceView

本例介绍使用透明背景绘制OpenGL 图形。步骤如下:

1. 对于Activity使用透明主题

<activity android:name=”.graphics.TranslucentGLSurfaceViewActivity”
android:label=”Graphics/OpenGL ES/Translucent GLSurfaceView”
  android:theme=”@style/Theme.Translucent”
android:configChanges=”keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.SAMPLE_CODE” />
</intent-filter>
</activity>

2. 使用8888 (RGBA) 格式,Alpha通道是显示透明图形必需的。

// We want an 8888 pixel format because that's required for
// a translucent window.
// And we want a depth buffer.
mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);

3. 为GLSurfaceView指定Alpha通道

mGLSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);

4. 为绘制的图行背景为颜色(0,0,0,0)

gl.glClearColor(0,0,0,0);

 

 

Android ApiDemos示例解析(204):Graphics->OpenGL ES->Frame Buffer Object

Frame Buffer 对象的概念可以参见前面文章Android OpenGL ES 开发教程(23):FrameBuffer

简单的和2D图像类比,Frame Buffer 如果 对应到二维图形环境中,就是一个2D的内存数组空间,缺省情况为屏幕的显存,也可以创建Offscreen 内存空间,此时Frame Buffer 可以是一个二维数组,数组每个元素代表一个像素颜色。

对于三维图形来说,除了需要代表颜色的二维数组(Color Buffer),还需要深度二维数组(Depth Buffer) 或遮罩数组(Stencil Buffer),因此在OpenGL 中的Frame Buffer为上述Color Buffer,Depth Buffer,Stencil Buffer 的集合。如果手机具有GPU,其缺省的Frame Buffer也是3D屏幕显示区域。

通过Opengl ES扩展支持,应用程序也可以创建内存中的Frame Buffer对象(不用于屏幕显示)。通过这种应用程序创建的FrameBuffer对象,OpenGL应用可以将图像显示输出重新定向到这个非屏幕显示用FrameBuffer对象中,类似于二维图形绘制中常用的Offscreen 技术。

和缺省的屏幕显示FrameBuffer一样,由应用程序创建的FrameBuffer对象也是由Color Buffer, Depth Buffer和Stencil Buffer(可选)的集合组成。这些Buffer在FrameBuffer对象中可以称为FrameBuffer-attachable 图像,FrameBuffer定义了一些接入点(Attachment Point)可以用于连接(Attach)这些Buffer数组。

OpenGL ES定义了两种FrameBuffer-attachable 图像,Texture 和 renderbuffer ,简单的可以将Texture  理解为Color buffer 或是2D图像,render buffer 对应于depth buffer。

下图表示了Texture , Renderbuffer 对象和 Frame Buffer 对象之间的关系:

但把Texture 和 Render Buffer 链接到FrameBuffer这些接入点(ATTACHMENT)之后,之后所有OpenGL绘图指令的输出结果就写入到这些内存Buffer中,而非缺省屏幕显示。

不同的Android设备支持的OpenGL ES扩展可能有所不同,因此如果需要使用应用创建FrameBuffer对象前需要检查手机是否支持Framebuffer扩展,本例使用

private boolean checkIfContextSupportsExtension(GL10 gl, String extension) {
 String extensions = " " + gl.glGetString(GL10.GL_EXTENSIONS) + " ";
 // The extensions string is padded with spaces between extensions, but not
 // necessarily at the beginning or end. For simplicity, add spaces at the
 // beginning and end of the extensions string and the extension string.
 // This means we can avoid special-case checks for the first or last
 // extension, as well as avoid special-case checks when an extension name
 // is the same as the first part of another extension name.
 return extensions.indexOf(" " + extension + " ") >= 0;
}

使用FrameBuffer的基本步骤如下:

1. 使用glGenFramebuffersOES创建FrameBuffer对象

int[] framebuffers = new int[1];
gl11ep.glGenFramebuffersOES(1, framebuffers, 0);
framebuffer = framebuffers[0];

2. 创建好FrameBuffer后,必须绑定FrameBuffer到OpenGL中,

gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, framebuffer);

第一个参数类型必须为GL_FRAMEBUFFER_OES,第二个参数为FrameBuffer的ID。如果Id为0,表示绑定到缺省的屏幕FrameBuffer。

绑定之后,后续的OpenGL绘制结果就从定向到FrameBuffer中,而不是显示到屏幕上。

3, 使用glGenRenderbuffersOES创建RenderBuffer

int depthbuffer;
int[] renderbuffers = new int[1];
gl11ep.glGenRenderbuffersOES(1, renderbuffers, 0);
depthbuffer = renderbuffers[0];

4. 和FrameBuffer类似,创建RenderBuffer对象,也需要绑定到OpengL库中

gl11ep.glBindRenderbufferOES(GL11ExtensionPack.GL_RENDERBUFFER_OES, depthbuffer);

5. 给renderBuffer 分配内存。

创建的renderBuffer 本身不含有内存空间,因此必须给它分配内存空间,这是通过glRenderbufferStorageOES来实现的。

gl11ep.glRenderbufferStorageOES(GL11ExtensionPack.GL_RENDERBUFFER_OES,
 GL11ExtensionPack.GL_DEPTH_COMPONENT16, width, height);

第二个参数为创建的RenderBuffer的内部格式类型。

6. 创建Texture对象,可以参见Android ApiDemos示例解析(200):Graphics->OpenGL ES->Textured Triangle

7. 在创建好FrameBuffer,Texture和renderBuffer对象之后,需要把Texture,RenderBuffer对象和FrameBuffer中对应的Attachment Point链接起来。

链接Texture对象

gl11ep.glFramebufferTexture2DOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES,
 GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES, GL10.GL_TEXTURE_2D,
 targetTextureId, 0);

链接renderBuffer 对象

l11ep.glFramebufferRenderbufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES,
 GL11ExtensionPack.GL_DEPTH_ATTACHMENT_OES,
 GL11ExtensionPack.GL_RENDERBUFFER_OES, depthbuffer);

来看一下本例的onDrawFrame方法

private static final boolean DEBUG_RENDER_OFFSCREEN_ONSCREEN = false;

public void onDrawFrame(GL10 gl) {
 checkGLError(gl);
 if (mContextSupportsFrameBufferObject) {
 GL11ExtensionPack gl11ep = (GL11ExtensionPack) gl;
 if (DEBUG_RENDER_OFFSCREEN_ONSCREEN) {
 drawOffscreenImage(gl, mSurfaceWidth, mSurfaceHeight);
 } else {
 gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFramebuffer);
 drawOffscreenImage(gl, mFramebufferWidth, mFramebufferHeight);
 gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0);
 drawOnscreen(gl, mSurfaceWidth, mSurfaceHeight);
 }
 } else {
 // Current context doesn't support frame buffer objects.
 // Indicate this by drawing a red background.
 gl.glClearColor(1,0,0,0);
 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
 }
}

本例在创建的FrameBuffer中绘制立方体,其绘图指令和例子Android ApiDemos示例解析(203):Graphics->OpenGL ES->GLSurfaceView一样。但其显示结果存放在mFramebuffer中(可以通过将DEBUG_RENDER_OFFSCREEN_ONSCREEN设为True看到FrameBuffer的内容)。然后将mFramebuffer显示的内存作为Triangle的材质绘制三角形

 

 

 

Android ApiDemos示例解析(203):Graphics->OpenGL ES->GLSurfaceView

这个例子是使用OpenGL ES绘图最简单的例子,说明如何使用GLSerfaceView,前面的例子Android OpenGL ES 开发教程(6):GLSurfaceView 已经详细说明了,本篇不再具体描述。几何图形顶点定义参见Android OpenGL ES 开发教程(8):基本几何图形定义,颜色定义参见Android OpenGL ES 开发教程(20):颜色Color

本例对应的类文件为Cube.java ,CubeRenderer.java ,GLSurfaceViewActivity.java 。

Cube 类定义了一个立方体,要注意的是本例使用GL_FIXED,而非GL_FLOAT, GL_FIXED 表示16.16 定点浮点数,GL_FIXED 的0x10000 相当于GL_FLOAT 的1.0

对应的代码如下:

int one = 0x10000;
int vertices[] = {
 -one, -one, -one,
 one, -one, -one,
 one, one, -one,
 -one, one, -one,
 -one, -one, one,
 one, -one, one,
 one, one, one,
 -one, one, one,
};

int colors[] = {
 0, 0, 0, one,
 one, 0, 0, one,
 one, one, 0, one,
 0, one, 0, one,
 0, 0, one, one,
 one, 0, one, one,
 one, one, one, one,
 0, one, one, one,
};

byte indices[] = {
 0, 4, 5, 0, 5, 1,
 1, 5, 6, 1, 6, 2,
 2, 6, 7, 2, 7, 3,
 3, 7, 4, 3, 4, 0,
 4, 7, 6, 4, 6, 5,
 3, 0, 1, 3, 1, 2
};

CubeRenderer 通过坐标变换的方式绘制两个立方体,

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glTranslatef(0, 0, -3.0f);
gl.glRotatef(mAngle, 0, 1, 0);
gl.glRotatef(mAngle*0.25f, 1, 0, 0);

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

mCube.draw(gl);

gl.glRotatef(mAngle*2.0f, 0, 1, 1);
gl.glTranslatef(0.5f, 0.5f, 0.5f);

mCube.draw(gl);

下图为当mAngle=45 度时显示结果:

 

Android ApiDemos示例解析(202):Graphics->OpenGL ES->Cube Map

Cube Map,中文可以翻译成立方环境映射,下面是摘自维基百科的说明:

立方反射映射是用立方映射使得物体看起来如同在反射周围环境的一项技术。通常,这通常使用户外渲染中使用的 skybox 完成。尽管由于反射物周围的物体无法在结果中看到,所以这并不是一个真正的反射,但是通常仍然可以达到所期望的效果。

通过确定观察物体的向量就可以进行立方映射反射,照相机光线在照相机向量与物体相交的位置按照曲面法线方向进行反射,这样传到立方图(cube map)取得纹素(texel)的反射光线在照相机看来好像位于物体表面,这样就得到了物体的反射效果。

简单的讲,就是你把一个具有金属反射特性的茶壶放在一个房间中,茶壶的金属表面会反射房间的场景,Cube Map就是解决如何将场景(环境)的内容显示在茶壶的表面,如下图所示:

本例使用环面(Torus)做为反射的表面,在OpenGL ES中任何3D物体,最终都是通过三角形来构造的,本例代码generateTorusGrid 和Grid对象用来构造环面的顶点坐标。具体算法有兴趣的可以自行研究(需要有立体几何的知识,这里不详细解释)。

Cube map技术说到底就是用一个虚拟的立方体(cube)包围住物体,眼睛到物体某处的向量eyevec经过反射(以该处的法线为对称轴),反射向量reflectvec射到立方体上,就在该立方体上获得一个纹素了(见下图)。明显,我们需要一个类似天空盒般的6张纹理贴在这个虚拟的立方体上。按CUBE MAPPING原意,就是一种enviroment map,因此把周围场景渲染到这6张纹理里是“正统”的。也就是每次渲染时,都作一次离线渲染,分别在每个矩形中心放置相机“拍下”场景,用FBO渲染到纹理,然后把这张纹理作为一个cube map对象的六纹理之一。这样即使是动态之物也能被映射到物体表面了(虽然缺点是不能映射物体自身的任何部分)。

本例使用的六张图为res/raw 目录下的 skycubemap0 — skycubemap5 ,如下图所示

使用Cube Map,首先要检测设备是否支持Cube Map 材质,本例使用以下代码检测设备是否支持Cube Map。

private boolean checkIfContextSupportsCubeMap(GL10 gl) {
return checkIfContextSupportsExtension(gl, "GL_OES_texture_cube_map");

}

/**
* This is not the fastest way to check for an extension, but fine if
* we are only checking for a few extensions each time a context is created.
* @param gl
* @param extension
* @return true if the extension is present in the current context.
*/
private boolean checkIfContextSupportsExtension(GL10 gl, String extension) {
String extensions = " " + gl.glGetString(GL10.GL_EXTENSIONS) + " ";
// The extensions string is padded with spaces between extensions, but not
// necessarily at the beginning or end. For simplicity, add spaces at the
// beginning and end of the extensions string and the extension string.
// This means we can avoid special-case checks for the first or last
// extension, as well as avoid special-case checks when an extension name
// is the same as the first part of another extension name.
return extensions.indexOf(" " + extension + " ") >= 0;
}

Cube Map (使用6张图),处调用设置Cube Map外,其基本使用步骤类似于普通材质的使用。本例使用资源,其设置Cube Map的基本步骤如下:

1. 调入图像资源

if (mContextSupportsCubeMap) {
int[] cubeMapResourceIds = new int[]{
R.raw.skycubemap0, R.raw.skycubemap1, R.raw.skycubemap2,
R.raw.skycubemap3, R.raw.skycubemap4, R.raw.skycubemap5};
mCubeMapTextureID = generateCubeMap(gl, cubeMapResourceIds);
}

....

private int generateCubeMap(GL10 gl, int[] resourceIds) {
checkGLError(gl);
int[] ids = new int[1];
gl.glGenTextures(1, ids, 0);
int cubeMapTextureId = ids[0];
gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, cubeMapTextureId);
gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

for (int face = 0; face < 6; face++) {
InputStream is = getResources().openRawResource(resourceIds[face]);
Bitmap bitmap;
try {
bitmap = BitmapFactory.decodeStream(is);
} finally {
try {
is.close();
} catch(IOException e) {
Log.e("CubeMap", "Could not decode texture for face " + Integer.toString(face));
}
}
GLUtils.texImage2D(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0,
bitmap, 0);
bitmap.recycle();
}
checkGLError(gl);
return cubeMapTextureId;
}

2. 绑定材质

函数generateCubeMap返回一个Texture的ID,在OpenGL ES中使用材质时,需要绑定材质

gl.glActiveTexture(GL10.GL_TEXTURE0);
checkGLError(gl);
gl.glEnable(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP);
checkGLError(gl);
gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, mCubeMapTextureID);
checkGLError(gl);
GL11ExtensionPack gl11ep = (GL11ExtensionPack) gl;
gl11ep.glTexGeni(GL11ExtensionPack.GL_TEXTURE_GEN_STR,
GL11ExtensionPack.GL_TEXTURE_GEN_MODE,
GL11ExtensionPack.GL_REFLECTION_MAP);
checkGLError(gl);
gl.glEnable(GL11ExtensionPack.GL_TEXTURE_GEN_STR);
checkGLError(gl);
gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_DECAL);
...
mGrid.draw(gl);
...
gl.glDisable(GL11ExtensionPack.GL_TEXTURE_GEN_STR);

这样就给环面物体添加了环境材质,显示结果如下:

 

Android ApiDemos示例解析(201):Graphics->OpenGL ES->Compressed Texture

本例和上例Android ApiDemos示例解析(200):Graphics->OpenGL ES->Textured Triangle 非常类似,所不同的是调用图像(Texture)的方法不同。

本例介绍如何使用ETC1 压缩格式的图像,一种方式是从资源文件中读取ETC1格式的图像作为三角形的材质(本例使用res.raw 中的androids.pkm文件),一种是通过代码动态创建ETC1格式的图像。 android.opengl 包中的 ETC1, ETCUtil, ETCUtil.ETC1Texture 用来支持ETC1格式的压缩格式图像,简单的讲,可以把android.pkm 当做android.png ,所不同的是两种图像压缩方法不同。

使用ETC1 图像作为Texture的基本方法如下:

InputStream input = getResources().openRawResource(R.raw.androids);
...
ETC1Util.loadTexture(GLES10.GL_TEXTURE_2D, 0, 0,
GLES10.GL_RGB, GLES10.GL_UNSIGNED_SHORT_5_6_5, input);

 

Android ApiDemos示例解析(200):Graphics->OpenGL ES->Textured Triangle

前面介绍Android ApiDemos 跳过了其中与OpenGL 相关的例子,而是专为OpenGL编程写了教程。 这里将接着补上Android ApiDemos中有关OpenGL ES的例子的解析。

Textured Triangle 主要介绍了如何为图形添加材质。其具体步骤可以参见Android OpenGL ES 简明开发教程七:材质渲染

本例对应的Activity为TriangleActivity,它使用了一个StaticTriangleRenderer 来绘制三角形,本例使用res.raw.robot 图像作为材质为三角形添加材质。 添加材质的一个关键步骤为UV坐标映射,将如何将一个二维图像的坐标映射到所要绘制的图形上。

一个简单的理解可以这样理解,如做风筝,用竹子做好框架(几何图形),然后如何将风筝纸(图像)粘帖到框架上,怎样放风筝纸到框架上,类同于OpenGL 如何将Texture坐标映射到几何图形的坐标上。

二维图像(材质)的坐标总是如下:UV坐标定义为左上角(0,0),右下角(1,1)(因为使用的2D Texture)

那么本例如何将这个材质的坐标映射到三角形上呢。下图为StaticTriangleRenderer例子中三角形和材质的坐标定义:

对应的代码如下:

// A unit-sided equilateral triangle centered on the origin.
 float[] coords = {
 // X, Y, Z
 -0.5f, -0.25f, 0,
 0.5f, -0.25f, 0,
 0.0f,  0.559016994f, 0
 };

 for (int i = 0; i < VERTS; i++) {
 for(int j = 0; j < 3; j++) {
 mFVertexBuffer.put(coords[i*3+j] * 2.0f);
 }
 }

 for (int i = 0; i < VERTS; i++) {
 for(int j = 0; j < 2; j++) {
 mTexBuffer.put(coords[i*3+j] * 2.0f + 0.5f);
 }
 }

 for(int i = 0; i < VERTS; i++) {
 mIndexBuffer.put((short) i);
 }

可以看到三角形顶点和Texture 坐标的对应关系,因此本例结果(在没有旋转角度的情况下)为下图。

三角形为正三角形,但由于Texture映射的关系,Android机器人图像为倒置的。