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);
这样就给环面物体添加了环境材质,显示结果如下: