天空盒

OPhone 3D开发之天空盒

UI设计, 2010-09-25 14:44:08

标签 : OPhone 3d

在3D世界中,我们可以通过精细的模型来表现近处场景的细节,但对于远距离场景,比如天空、高山、日月星辰等这些“遥不可及”的对象来说,我们可以使用高质量贴图环绕组合成一个封闭的场景,给玩家以始终处在内部的感觉。在这种以贴图来渲染远景的方式中,最常用的技术是天空盒。

天空盒设计思想

天空盒的思想非常简单,将远景渲染成6种纹理贴图,每个纹理应用到立方体的一面,同时确保摄像机始终位于立方体的中心。渲染时,映射到天空盒的各个面上的图像将拼接在一起,从而在视觉上形成一个完整的天空。

组成天空盒的6张纹理,分别对应立方体的前、后、左、右、顶、底这6个面,如图1所示:

图1 组成天空盒的6张纹理

使用时,将这6张纹理以下图2的形式拼接起来,组合成一个立方体,并在渲染时将观察相机放置于立方体内部中心位置,这样无论相机怎样旋转观察,都会感觉到始终是处在这个盒子当中,从而营造出一种视觉上无限远的感觉。

图2 天空盒拼接方式

创建天空盒渲染数据

对于天空盒立方体来说,每个面分别对应于一个四边形,每个四边形由4个顶点,两个三角形组成。渲染时,指定每一个面的渲染数据及对应纹理,循环渲染所有面即可。

首先我们需要指定立方体每个面的顶点位置数据和纹理顶点数据,这里我们对操作顶点缓存对象进行了传统的glVertex3f()和glTexCoord2f()封装,以便于大家更好的理解传统的OpenGL绑定顶点数据与OPhone 3D中的OpenGL ES使用顶点数组缓存对象的使用方法差异。相关代码如下:

 

  1. //总共六个面,前后左右顶底  
  2.     private static final int VALID_FACE = 6;  
  3.     //每个面由2个三角形,4个顶点构成  
  4.     private static final int VERTEX_PER_FACE = 4;  
  5.     //存储天空盒渲染位置数据  
  6.     private FloatBuffer mBufPosition;  
  7.     //存储天空盒纹理数据  
  8. private FloatBuffer mBufUV;  
  9. /** 
  10.      * 创建天空盒渲染数据 
  11.      */  
  12.     private void CreateSkyBoxRenderBuffer() {  
  13.         //创建天空盒立方体顶点缓存  
  14.         mBufPosition = IBufferFactory.newFloatBuffer(VALID_FACE  
  15.                 * VERTEX_PER_FACE * 3);  
  16.         //创建天空盒立方体纹理缓存  
  17.         mBufUV = IBufferFactory  
  18.                 .newFloatBuffer(VALID_FACE * VERTEX_PER_FACE * 2);  
  19.   
  20.         mBufPosition.position(0);  
  21.         mBufUV.position(0);  
  22.   
  23.         //背面  
  24.         glTexCoord2f(1.0f, 0.0f);  
  25.         glVertex3f(101010);  
  26.         glTexCoord2f(1.0f, 1.0f);  
  27.         glVertex3f(10, -1010);  
  28.         glTexCoord2f(0.0f, 1.0f);  
  29.         glVertex3f(-10, -1010);  
  30.         glTexCoord2f(0.0f, 0.0f);  
  31.         glVertex3f(-101010);  
  32.   
  33.         //正面  
  34.         glTexCoord2f(1.0f, 0.0f);  
  35.         glVertex3f(-1010, -10);  
  36.         glTexCoord2f(1.0f, 1.0f);  
  37.         glVertex3f(-10, -10, -10);  
  38.         glTexCoord2f(0.0f, 1.0f);  
  39.         glVertex3f(10, -10, -10);  
  40.         glTexCoord2f(0.0f, 0.0f);  
  41.         glVertex3f(1010, -10);  
  42.   
  43.         //右面  
  44.         glTexCoord2f(1.0f, 0.0f);  
  45.         glVertex3f(1010, -10);  
  46.         glTexCoord2f(1.0f, 1.0f);  
  47.         glVertex3f(10, -10, -10);  
  48.         glTexCoord2f(0.0f, 1.0f);  
  49.         glVertex3f(10, -1010);  
  50.         glTexCoord2f(0.0f, 0.0f);  
  51.         glVertex3f(101010);  
  52.   
  53.         //左面  
  54.         glTexCoord2f(1.0f, 0.0f);  
  55.         glVertex3f(-101010);  
  56.         glTexCoord2f(1.0f, 1.0f);  
  57.         glVertex3f(-10, -1010);  
  58.         glTexCoord2f(0.0f, 1.0f);  
  59.         glVertex3f(-10, -10, -10);  
  60.         glTexCoord2f(0.0f, 0.0f);  
  61.         glVertex3f(-1010, -10);  
  62.   
  63.         //顶部  
  64.         glTexCoord2f(01);  
  65.         glVertex3f(-101010);  
  66.         glTexCoord2f(00);  
  67.         glVertex3f(-1010, -10);  
  68.         glTexCoord2f(10);  
  69.         glVertex3f(1010, -10);  
  70.         glTexCoord2f(11);  
  71.         glVertex3f(101010);  
  72.           
  73.         //底部  
  74.         glTexCoord2f(01);  
  75.         glVertex3f(-10, -1010);  
  76.         glTexCoord2f(00);  
  77.         glVertex3f(-10, -10, -10);  
  78.         glTexCoord2f(10);  
  79.         glVertex3f(10, -10, -10);  
  80.         glTexCoord2f(11);  
  81.         glVertex3f(10, -1010);  
  82.   
  83.         mBufPosition.position(0);  
  84.         mBufUV.position(0);  
  85.     }  
  86. /** 
  87.      * 将纹理坐标写入渲染缓存 
  88.      * @param u 
  89.      * @param v 
  90.      */  
  91.     private void glTexCoord2f(float u, float v) {  
  92.         mBufUV.put(u);  
  93.         mBufUV.put(v);  
  94.     }  
  95.       
  96.     /** 
  97.      * 将位置坐标写入缓存 
  98.      * @param x 
  99.      * @param y 
  100.      * @param z 
  101.      */  
  102.     private void glVertex3f(float x, float y, float z) {  
  103.         mBufPosition.put(x);  
  104.         mBufPosition.put(y);  
  105.         mBufPosition.put(z);  
  106.     }  

在上面的代码中,我们将天空盒的六个面的渲染数据都统一放置在一起,因此使用时,只需要绑定一次渲染数据,提交渲染时,指定当前四边面的在渲染缓存中的偏移即可,这样可以避免频繁切换渲染数据而造成的性能损失。

创建天空盒纹理

天空盒所用到的六张纹理,每一张均已独立的PNG图像的形式放置于程序资源文件中。每一张纹理的尺寸必须均为2的N次方,而且若无特殊要求,纹理宽高应该相等。在这里,我们使用的是256x256的PNG图像作为纹理对象。在创建天空盒对象时,将纹理贴图从外部资源文件中分别载入,并申请绑定成为OPhone 3D中的纹理对象。相关操作如下:

  1. // 定义的天空盒纹理外部资源索引  
  2.     private int[] mpStrTexPath = { R.drawable.sk_0back, R.drawable.sk_1front,  
  3.             R.drawable.sk_2right, R.drawable.sk_3left, R.drawable.sk_4top,  
  4.             R.drawable.sk_5bottom };  
  5.     // 载入后的纹理对象句柄  
  6.     private int[] mpTextures;  
  7. /** 
  8.      * 载入天空盒纹理 
  9.      * @param context 
  10.      * @param gl 
  11.      */  
  12.     public void loadSkyBoxTextures(Context context, GL10 gl) {  
  13.         mpTextures = new int[mpStrTexPath.length];  
  14.         //循环创建所有天空盒纹理  
  15.         for (int i = 0; i < mpTextures.length; i++) {  
  16.             try {  
  17.                 mpTextures[i] = TextureFactory.getTexture(context, gl,  
  18.                         mpStrTexPath[i]);  
  19. gl.glBindTexture(GL10.GL_TEXTURE_2D, mpTextures[i]);  
  20.                 // 设置环绕模式  
  21.                 gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);  
  22.                 gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);  
  23.             } catch (GLException ex) {  
  24.                 ex.printStackTrace();  
  25.             }  
  26.         }  
  27.     }  

这里需要注意的是,在创建天空盒纹理时,必须要确保纹理的环绕模式为GL_CLAMP_TO_EDGE,否则会造成天空盒立方体四边面之间存在明显的接缝。

渲染天空盒

在渲染天空盒之前,需要将观察相机放置于天空盒立方体内部中心位置,这样无论观察者如何渲染,所看到的视野均处于天空盒的内部,从而营造出一种视觉上的无限连续状态。

  1. /** 
  2.      * 渲染天空盒之前将观察相机放置于天空盒中心 
  3.      * @param gl -  
  4.      * @param camera - 当前相机 
  5.      */  
  6.     public void renderSkyBox(GL10 gl, ICamera camera) {  
  7.         gl.glPushMatrix();  
  8.         {  
  9.             //将相机放置于天空盒中心位置  
  10.             gl.glTranslatef(camera.getCamPosition().x, camera  
  11.                     .getCamPosition().y, camera.getCamPosition().z);  
  12.             //渲染天空盒  
  13.             mSkybox.renderSkyBox(gl);  
  14.         }  
  15.         gl.glPopMatrix();  
  16.     }  

由于天空盒始终处于无限远处,场景中的物体都不会与天空盒相交,因此在渲染时,需要禁止深度测试以及禁用深度写入,同时也要禁用光照、雾化等其他类似效果。下面的代码展示了渲染天空盒之前的必要设置:

  1. //禁用光照  
  2.         gl.glDisable(GL10.GL_LIGHTING);  
  3.         //禁用混合  
  4.         gl.glDisable(GL10.GL_BLEND);  
  5.         //禁用深度测试  
  6.         gl.glDisable(GL10.GL_DEPTH_TEST);  
  7. //禁止深度写入  
  8.         gl.glDepthMask(false);  

实际渲染时,绑定好统一的位置缓存和纹理坐标缓存后,针对每个面绑定对应的纹理,之后采用glDrawArrays()渲染GL_TRIANGLE_FAN的形式,指定好当前四边面在整体渲染数据中的起始偏移,来最终渲染天空盒的每个面。相关代码如下:

 

  1. /** 
  2.      * 渲染天空盒对象 
  3.      * @param gl 
  4.      */  
  5.     public void renderSkyBox(GL10 gl) {  
  6.         // 禁用光照  
  7.         gl.glDisable(GL10.GL_LIGHTING);  
  8.         // 禁用混合  
  9.         gl.glDisable(GL10.GL_BLEND);  
  10.         // 禁用深度测试  
  11.         gl.glDisable(GL10.GL_DEPTH_TEST);  
  12.           
  13.         gl.glDisable(GL10.GL_CULL_FACE);  
  14.         // 禁止深度写入  
  15.         gl.glDepthMask(false);  
  16.           
  17.         //启用纹理贴图  
  18.         gl.glEnable(GL10.GL_TEXTURE_2D);  
  19.   
  20.         // 启用必要渲染状态  
  21.         gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);  
  22.         gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);  
  23.         // 绑定渲染数据  
  24.         gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufPosition);  
  25.         gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mBufUV);  
  26.         // 记录当前四边面在整体渲染数据缓存中的起始偏移  
  27.         int offset = 0;  
  28.   
  29.         // 后  
  30.         gl.glBindTexture(GL10.GL_TEXTURE_2D, mpTextures[0]);  
  31.           
  32.         gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, offset, VERTEX_PER_FACE);  
  33.         offset += VERTEX_PER_FACE;  
  34.   
  35.         // 前  
  36.         gl.glBindTexture(GL10.GL_TEXTURE_2D, mpTextures[1]);  
  37.   
  38.         gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, offset, VERTEX_PER_FACE);  
  39.         offset += VERTEX_PER_FACE;  
  40.   
  41.         // 右  
  42.         gl.glBindTexture(GL10.GL_TEXTURE_2D, mpTextures[2]);  
  43.   
  44.         gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, offset, VERTEX_PER_FACE);  
  45.         offset += VERTEX_PER_FACE;  
  46.   
  47.         // 左  
  48.         gl.glBindTexture(GL10.GL_TEXTURE_2D, mpTextures[3]);  
  49.   
  50.         gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, offset, VERTEX_PER_FACE);  
  51.         offset += VERTEX_PER_FACE;  
  52.   
  53.         // 顶  
  54.         gl.glBindTexture(GL10.GL_TEXTURE_2D, mpTextures[4]);  
  55.   
  56.         gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, offset, VERTEX_PER_FACE);  
  57.         offset += VERTEX_PER_FACE;  
  58.   
  59.         // 底部  
  60.         gl.glBindTexture(GL10.GL_TEXTURE_2D, mpTextures[5]);  
  61.   
  62.         gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, offset, VERTEX_PER_FACE);  
  63.   
  64.         //重新启用必要状态  
  65.         gl.glEnable(GL10.GL_DEPTH_TEST);  
  66.         gl.glDepthMask(true);  
  67.         gl.glEnable(GL10.GL_CULL_FACE);  
  68.     }  

总结

随着OPhone 2.0平台的发布和推广,移动设备的3D能力越来越强大。天空盒技术是3D开发中常用的一个组件,本文通过介绍如何使用OPhone中提供的3D API来实现天空盒,希望能引领更多开发者步入神奇的3D世界。

文章所附源码可以在http://www.ophonesdn.com/forum/viewthread.jsp?tid=832 中下载。

作者介绍

薛永,专注于移动3D技术开发,目前正在完善跨PC/Iphone/Android NDK的统一3D引擎。


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值