Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5542912
  • 博文数量: 763
  • 博客积分: 12108
  • 博客等级: 上将
  • 技术积分: 15717
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-28 21:21
个人简介

业精于勤,荒于嬉

文章分类

全部博文(763)

文章存档

2018年(6)

2017年(15)

2016年(2)

2015年(31)

2014年(14)

2013年(87)

2012年(75)

2011年(94)

2010年(190)

2009年(38)

2008年(183)

2007年(28)

分类: C/C++

2010-02-02 18:26:26

  1. public class MyGLSurfaceView extends GLSurfaceView {   
  2.     private final float TOUCH_SCALE_FACTOR = 180.0f / 320;   
  3.     /**  
  4.      * 具体实现的渲染器  
  5.      */  
  6.     private OPhoneOglesDevRenderer mRenderer;   
  7.     /**  
  8.      * 记录上次触屏位置的坐标  
  9.      */  
  10.     private float mPreviousX, mPreviousY;   
  11.   
  12.     public MyGLSurfaceView(Context context) {   
  13.         super(context);   
  14.         // 设置渲染器   
  15.         mRenderer = new OPhoneOglesDevRenderer(context);   
  16.         setRenderer(mRenderer);   
  17.         // 设置渲染模式为主动渲染   
  18.         setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);   
  19.     }   
  20.   
  21.     public void onPause() {   
  22.         super.onPause();   
  23.     }   
  24.        
  25.     public void onResume() {   
  26.         super.onResume();   
  27.     }      
  28.   
  29.     /**  
  30.      * 响应触屏事件  
  31.      */  
  32.     @Override  
  33.     public boolean onTouchEvent(MotionEvent e) {   
  34.         float x = e.getX();   
  35.         float y = e.getY();   
  36.         switch (e.getAction()) {   
  37.         case MotionEvent.ACTION_MOVE:   
  38.             float dx = x - mPreviousX;   
  39.             float dy = y - mPreviousY;   
  40.             mRenderer.mAngleX += dx * TOUCH_SCALE_FACTOR;   
  41.             mRenderer.mAngleY += dy * TOUCH_SCALE_FACTOR;   
  42.             requestRender();   
  43.         }   
  44.         mPreviousX = x;   
  45.         mPreviousY = y;   
  46.         return true;   
  47.     }   
  48. }  


OpenGL ES开发简要框架
       开发OpenGL ES程序,首要做的就是设置视口,设置投影矩阵,设置模型视图矩阵等。对于设置模型视图矩阵,我们通常会分别设置相机矩阵和模型矩阵。对于一些全局性的设置,我们通常只需要执行一次;而对于那些需要动态改变的属性,则应该在相应事件发生时或者逐帧进行动态更新。GLSurfaceView.Renderer接口提供了监视绘图表面创建、改变以及逐帧更新的方法,分别是: 

  1. /**  
  2.      * 创建绘图表面时调用  
  3.      */  
  4.     @Override  
  5.     public void onSurfaceCreated(GL10 gl, EGLConfig config)   
  6. /**  
  7.      * 当绘图表面尺寸发生改变时调用  
  8.      */  
  9.     @Override  
  10.     public void onSurfaceChanged(GL10 gl, int width, int height)   
  11. /**  
  12.      * 逐帧渲染  
  13.      */  
  14.     @Override  
  15.     public void onDrawFrame(GL10 gl)   

/** * 创建绘图表面时调用 */ @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) /** * 当绘图表面尺寸发生改变时调用 */ @Override public void onSurfaceChanged(GL10 gl, int width, int height) /** * 逐帧渲染 */ @Override public void onDrawFrame(GL10 gl)

       通常,我们在onSurfaceCreated()中通过调用glHint()函数来设置渲染质量与速度的平衡,设置清屏颜色,着色模型,启用背面剪裁和深度测试,以及禁用光照和混合等全局性设置。相关代码如下:

  1. public void onSurfaceCreated(GL10 gl, EGLConfig config) {   
  2.         //全局性设置   
  3.         gl.glDisable(GL10.GL_DITHER);   
  4.            
  5.         gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);   
  6.         //设置清屏背景颜色   
  7.         gl.glClearColor(0.5f, 0.5f, 0.5f, 1);   
  8.         //设置着色模型为平滑着色   
  9.         gl.glShadeModel(GL10.GL_SMOOTH);   
  10.            
  11.         //启用背面剪裁   
  12.         gl.glEnable(GL10.GL_CULL_FACE);   
  13.         gl.glCullFace(GL10.GL_BACK);   
  14.         //启用深度测试   
  15.         gl.glEnable(GL10.GL_DEPTH_TEST);   
  16.         //禁用光照和混合   
  17.         gl.glDisable(GL10.GL_LIGHTING);   
  18.         gl.glDisable(GL10.GL_BLEND);   
  19.     }  

       在onSurfaceChanged中,我们会根据绘图表面尺寸的改变,来即时改变视口大小,以及重新设置投影矩阵。相关代码如下:

  1. public void onSurfaceChanged(GL10 gl, int width, int height) {   
  2.         //设置视口   
  3.         gl.glViewport(00, width, height);   
  4.            
  5.         //设置投影矩阵   
  6.         float ratio = (float) width / height;//屏幕宽高比   
  7.         gl.glMatrixMode(GL10.GL_PROJECTION);   
  8.         gl.glLoadIdentity();   
  9.         GLU.gluPerspective(gl, 45.0f, ratio, 15000);   
  10.         //每次修改完GL_PROJECTION后,最好将当前矩阵模型设置回GL_MODELVIEW   
  11.         gl.glMatrixMode(GL10.GL_MODELVIEW);   
  12.     }  

}

      在onDrawFrame中,需要编写的是每帧实际渲染的代码,包括清屏,设置模型视图矩阵,渲染模型,以及相应的update函数。相关代码如下:

  1. public void onDrawFrame(GL10 gl) {   
  2.         //一般的opengl程序,首先要做的就是清屏   
  3.         gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);   
  4.            
  5.         //紧接着设置模型视图矩阵   
  6.         setUpCamera(gl);   
  7.            
  8.         //渲染物体   
  9.         drawModel(gl);   
  10.            
  11.         //更新时间   
  12.         updateTime();    
  13.     }  

        设置模型视图矩阵(即GL_MODELVIEW矩阵)时,我们通常分别设置相机和物体矩阵。在设置相机矩阵时,我们可以通过调用
GLU.gluLookAt (GL10 gl, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) 传入视点位置(eyeX, eyeY, eyeZ)、被观察体的中心位置(centerX, centerY, centerZ)以及相机向上方向的向量(upX, upY, upZ)。相关代码如下:
 

  1. /**  
  2.      * 设置相机矩阵  
  3.      * @param gl  
  4.      */  
  5.     private void setUpCamera(GL10 gl) {   
  6.         gl.glMatrixMode(GL10.GL_MODELVIEW);   
  7.         gl.glLoadIdentity();   
  8.         GLU.gluLookAt(gl, mfEyeX, mfEyeY, mfEyeZ, mfCenterX, mfCenterY, mfCenterZ, 010);   
  9.     }  

          OpenGL ES中采用的是矩阵堆栈体系。对于模型视图矩阵,堆栈深度至少为16;对于投影矩阵或者纹理矩阵,则至少为2。由于OpenGL ES中的矩阵操作,都是针对当前栈顶的矩阵,因此很多时候需要配对使用glPushMatrix()和glPopMatrix()来进行保存和恢复矩阵现场。在本例中,渲染模型之前,我们首先使用glPushMatrix()来复制当前模型视图矩阵,并将其推入到栈顶,之后所有的矩阵操作均针对该矩阵。然后我们通过调用glRotate()函数,进行适当的旋转,在渲染模型完毕之后,通过调用glPopMatrix()将当前矩阵弹出,恢复之前的矩阵现场。相关代码如下:

  1. /**  
  2.      * 渲染模型  
  3.      * @param gl  
  4.      */  
  5.     private void drawModel(GL10 gl) {   
  6.         gl.glPushMatrix();   
  7.         {      
  8.             //首先对模型进行旋转   
  9.             gl.glRotatef(mfAngleX, 100);//绕X轴旋转   
  10.             gl.glRotatef(mfAngleY, 010);//绕Y轴旋转   
  11.             if(mModel.containsAnimation()) {   
  12.                 //如果模型有动画,那么按时间就更新动画   
  13.                 if (mMsPerFrame > 0) {   
  14.                     mModel.animate(mMsPerFrame * 0.001f);//将毫秒数转化为秒, /1000   
  15.                 }   
  16.                 mModel.fillRenderBuffer();//更新顶点缓存   
  17.             }   
  18.             mModel.render(gl);//渲染模型   
  19.             mModel.renderJoints(gl);//渲染关节,骨骼   
  20.         }   
  21.         gl.glPopMatrix();   
  22.     }  

       OpenGL ES中支持三种渲染图元:点(GL_POINTS)、线(GL_LINES)和三角形(GL_TRIANGLES)。在本例子中,模型实体采用三角形渲染(对应函数mModel.render(gl)),而对于有骨骼信息的模型,会使用点和线来渲染骨骼辅助信息(对应函数mModel.renderJoints(gl))。OpenGL ES抛弃了OpenGL中传统但低效的glBegin()、glEnd()的渲染方式,采用了更为高效的批量渲染模式,使用java.nio.Buffer对象来存储渲染数据,之后通过调用glVertexPointer()、glNormalPointer()、glColorPointer()以及glTextureCoordPointer()传入Buffer对象来分别设置顶点位置、法线、颜色和纹理坐标渲染数据。在设置渲染数据的同时,需要通过调用glEnableClientState()函数,分别传入GL_VERTEX_ARRAY、GL_NORMAL_ARRAY、GL_COLOR_ARRAY和GL_TEXTURE_COORD_ARRAY来通知底层引擎启用相应渲染属性数据。这四个渲染属性并非要全部设置,而是可以根据需要只是启用其中的某几个。在本例中,渲染模型实体时,仅启用了顶点位置数据和纹理坐标数据;在渲染点线的骨骼辅助信息时,则仅仅启用了顶点位置数据。对于那些没有被启用的渲染属性,必须要确保其当前处于为非活动状态(即调用glDisableClientState()),否则就可能会对渲染结果造成一定影响,或者白白加重底层管线运算负担。
 

        另外需要注意的是OPhone中要传入gl*Pointer()函数的Buffer对象必须要为direct模式申请的,这样可以确保缓存对象放置在Native的堆中,以免受到Java端的垃圾回收机制的影响。对于FloatBuffer、ShortBuffer和IntBuffer等多字节的缓存对象,它们的字节顺序必须设置为nativeOrder,否则会极大降低程序执行效率。
 

       在设置好各个渲染属性的数据之后,就要通过调用glDrawArrays()或者glDrawElements()来进行数据的最终提交渲染。前者表示传入的数据是最终要渲染的数据,可以直接渲染,而后者会根据传入的索引,由底层重组最终要真正渲染的数据。相比之下,后者可以节省更多的内存。下面的代码展示了以三角形来渲染模型实体,启用顶点位置数据和纹理坐标数据,未启用法线和颜色数据:
 

  1. /**  
  2.      * 渲染实体模型  
  3.      * @param gl  
  4.      */  
  5.     public void render(GL10 gl) {   
  6.         gl.glPushMatrix();   
  7.         {   
  8.             //设置默认颜色   
  9.             gl.glColor4f(1.0f, 0.5f, 0.5f, 1.0f);   
  10.                
  11.             //启用客户端状态   
  12.             gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);   
  13.                
  14.             //遍历所有的MS3D Group,渲染每一个Group   
  15.             for (int i = 0; i < mpGroups.length; i++) {   
  16.                 if (mpGroups[i].getTriangleCount() == 0) {   
  17.                     //如果该Group包含的三角形个数为零,则直接跳过   
  18.                     continue;   
  19.                 }   
  20.                 //得到相应纹理   
  21.                 TextureInfo tex = mpTexInfo[i % mpTexInfo.length];   
  22.                    
  23.                 if (tex != null) {   
  24.                     //如果纹理不为空,则绑定相应纹理   
  25.                     gl.glBindTexture(GL10.GL_TEXTURE_2D, tex.mTexID);   
  26.                     //启用纹理贴图   
  27.                     gl.glEnable(GL10.GL_TEXTURE_2D);   
  28.                     //绑定纹理坐标数据   
  29.                     gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);   
  30.                     gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0,   
  31.                             mpBufTextureCoords[i]);   
  32.   
  33.                 } else {   
  34.                     //如果纹理为空,禁用纹理贴图   
  35.                     //禁用纹理客户端状态   
  36.                     gl.glDisable(GL10.GL_TEXTURE_2D);   
  37.                     gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);   
  38.                 }   
  39.                 //绑定顶点数据   
  40.                 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mpBufVertices[i]);   
  41.                 //提交渲染   
  42.                 gl.glDrawArrays(GL10.GL_TRIANGLES, 0, mpGroups[i]   
  43.                         .getTriangleCount() * 3);   
  44.             }   
  45.             //渲染完毕,重置客户端状态   
  46.             gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);   
  47.             gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);   
  48.             gl.glDisable(GL10.GL_TEXTURE_2D);   
  49.         }   
  50.         gl.glPopMatrix();   
  51.     }  


         程序中渲染骨骼关节辅助信息的部分,就是以点和线的模型进行渲染,相关代码如下

 

 

  1. /**  
  2.      * 渲染骨骼帮助信息  
  3.      * @param gl  
  4.      */  
  5.     public void renderJoints(GL10 gl) {   
  6.         if(!containsJoint()) {   
  7.             return;   
  8.         }   
  9.         //为保证骨骼始终可见,暂时禁用深度测试   
  10.         gl.glDisable(GL10.GL_DEPTH_TEST);   
  11.         //设置点和线的宽度   
  12.         gl.glPointSize(4.0f);   
  13.         gl.glLineWidth(2.0f);   
  14.         //仅仅启用顶点数据   
  15.         gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);   
  16.            
  17.         //渲染骨骼连线   
  18.         gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);//设置颜色   
  19.         gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufJointLinePosition);   
  20.         //提交渲染   
  21.         gl.glDrawArrays(GL10.GL_LINES, 0, mJointLineCount);   
  22.            
  23.         //渲染关节点   
  24.         gl.glColor4f(1.0f, 1.0f, 0.0f, 1.0f);//设置颜色   
  25.         gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufJointPointPosition);   
  26.         //提交渲染   
  27.         gl.glDrawArrays(GL10.GL_POINTS, 0, mJointPointCount);   
  28.            
  29.         //重置回默认状态   
  30.         gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);   
  31.         gl.glPointSize(1.0f);   
  32.         gl.glLineWidth(1.0f);   
  33.         gl.glEnable(GL10.GL_DEPTH_TEST);   
  34.     }  

纹理操作
        在前面的代码中,我们看到了启用、绑定纹理等操作。纹理映射是3D中非常重要的一块,如果没有纹理,整个3D世界就会只是一些单纯的色块。OPhone中目前支持2D纹理映射(贴图尺寸必须要为2的N次方),并支持2个以上的纹理贴图单元。由于纹理数据存储在OpenGL ES服务器端(可以理解为GPU端,即Graphics Process Unit,图形处理单元),因此需要我们从客户端(即外部的应用程序端)将像素数据传入,由底层将这些像素转换成更为高效的、对硬件更为友好的纹素格式。OpenGL ES中的每一个纹理都被当作一个纹理对象,它除了包括纹理像素数据之外,还包括该纹理的其他属性,比如名字、过滤模式、混合模式等。开发者需要首先向底层申请一个纹理名称,之后上传纹理像素数据,以及设置其他属性。下面的代码向我们展示了如何在OPhone中创建一个纹理对象:
 

  1. /**  
  2.      * 创建一个纹理对象  
  3.      * @param context - 应用程序环境  
  4.      * @param gl - opengl es对象  
  5.      * @param resID - R.java中的资源ID  
  6.      * @param wrap_s_mode - 纹理环绕S模式  
  7.      * @param wrap_t_mode - 纹理环绕T模式  
  8.      * @return 申请好的纹理ID  
  9.      */  
  10.     public static int getTexture(Context context, GL10 gl, int resID,   
  11.             int wrap_s_mode, int wrap_t_mode) {   
  12.         //申请一个纹理对象ID   
  13.         int[] textures = new int[1];   
  14.         gl.glGenTextures(1, textures, 0);   
  15.         //绑定这个申请来的ID为当前纹理操作对象   
  16.         int textureID = textures[0];   
  17.         gl.glBindTexture(GL10.GL_TEXTURE_2D, textureID);   
  18.         //设置当前纹理对象的过滤模式   
  19.         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,   
  20.                 GL10.GL_NEAREST);   
  21.         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,   
  22.                 GL10.GL_LINEAR);   
  23.         //设置环绕模式   
  24.         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,   
  25.                 wrap_s_mode);   
  26.         gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,   
  27.                 wrap_t_mode);   
  28.         //设置混合模式   
  29.         gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,   
  30.                 GL10.GL_REPLACE);   
  31.            
  32.         //开始载入纹理   
  33.         InputStream is = context.getResources().openRawResource(resID);   
  34.         Bitmap bitmap;   
  35.         try {   
  36.             bitmap = BitmapFactory.decodeStream(is);   
  37.         } finally {   
  38.             try {   
  39.                 is.close();   
  40.             } catch (IOException e) {   
  41.                 // Ignore.   
  42.             }   
  43.         }   
  44.            
  45.         //绑定像素数据到纹理对象   
  46.         GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);   
  47.         bitmap.recycle();   
  48.   
  49.         return textureID;   
  50.     }  


      创建好纹理对象之后,在使用时,需要首先通过调用gl.glEnable(GL10.GL_TEXTURE_2D)来通知底层开启纹理贴图操作,之后绑定相应的纹理ID到当前纹理贴图单元,同时通过调用glTexCoordPointer()来设置好相应的纹理坐标信息,最终提交渲染时,底层就会自动进行纹理映射操作。当纹理不再被使用时,可以通过调用glDeleteTextures()来将其删除。
 

输入事件响应
       我们可以重载GLSurfaceView的onTouchEvent()方法,从而监测用户对屏幕的触摸事件。本例中,我们根据触摸位置的改变,来对模型进行绕Y轴和X轴的旋转。如果有需要,开发者还可以重载键盘按键onKeyDown()方法。值得注意的是,由于这些事件和渲染线程是分别独立的线程,因此有些操作如果需要确保在渲染线程内部执行的话,可以调用queueEvent (Runnable)来将该操作附加到渲染线程操作队列中。相关代码如下:
 

  1. /**  
  2.      * 响应触屏事件  
  3.      */  
  4.     @Override  
  5.     public boolean onTouchEvent(MotionEvent e) {   
  6.         float x = e.getX();   
  7.         float y = e.getY();   
  8.         switch (e.getAction()) {   
  9.         case MotionEvent.ACTION_MOVE:   
  10.             float dx = x - mPreviousX;   
  11.             float dy = y - mPreviousY;   
  12.             mRenderer.mfAngleY += dx * TOUCH_SCALE_FACTOR;   
  13.             mRenderer.mfAngleX += dy * TOUCH_SCALE_FACTOR;   
  14.             requestRender();   
  15.         }   
  16.         mPreviousX = x;   
  17.         mPreviousY = y;   
  18.         return true;   
  19.     }  
阅读(2322) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~