• Android OpenGL(3)


    前面介绍了Android OpenGL的开发基础,绘制了一个3D的物体,在立体空间控制一个3D对象,但如何来构建一个3D的场景呢?接下来就讲讲怎样去完成一个3D世界的场景吧。

    首先,我们应该明白的是,任何一个复杂的对象都是由一些简单的三角形构成的,所以在创建一个复杂的3D场景之前,要先定义一个场景的数据结构。三角形本质上是由一些(两个以上)顶点组成的多边形,顶点是最基本的分类单位,它包含了OpenGL真正有用的数据,我们用3D空间中的坐标值(x,y,z)以及它们的纹理坐标(u,v)来定义三角形的每个顶点。当然啦,每个对象都不只是由一个三角形构成的,因此可以通过一个List来存储这些三角形。

    数据结构代码如下:

    /*ScData.java*/
    import java.util.ArrayList;
    import java.util.List;
    //VERTEX顶点结构
    class VERTEX
    {
        float x, y, z;// 3D 坐标
        float u, v;// 纹理坐标
        public VERTEX(float x,float y,float z,float u,float v)
        {
            this.x = x;
            this.y = y;
            this.z = z;
            this.u = u;
            this.v = v;
        }
    }
    //TRIANGLE三角形结构
    class TRIANGLE
    {
        // VERTEX矢量数组,大小为3
        VERTEX[]    vertex    = new VERTEX[3];
    }
    //SECTOR区段结构
    class SECTOR
    {
        // Sector中的三角形个数
        int numtriangles;
        // 三角行的list
        List<TRIANGLE>    triangle    = new ArrayList<TRIANGLE>();
    }

    一个场景必然由很多个顶点组成,由于这些顶点的数据量过大,所以这里将这些顶点存放到一个和游戏一起打包的文件中,然后在程序中通过装载这个文件来取得数据。在这里将顶点数据存放到“assets/data/world.txt”文件中,其读取文件的代码如下:

    /*GLFile.java*/
    import java.io.IOException;
    import java.io.InputStream;
    import android.content.res.AssetManager;
    import android.content.res.Resources;
    public class GLFile
    {
        public static Resources resources;
        public GLFile(Resources resources)
        {
            GLFile.resources = resources;
        }
        public static InputStream getFile(String name){
            AssetManager am = GLFile.resources.getAssets();
            try {
                return am.open(name);
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
    }

    装载图片的代码前面也已经说过,在此就不详细说了。。代码如下:

    /*GLImage.java*/
    import android.content.res.Resources;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    public class GLImage
    {
        public static Bitmap mBitmap;
        public static void load(Resources resources)
        {
            mBitmap = BitmapFactory.decodeResource(resources, R.drawable.img);
        }
    }

    然后创建一个MainActivity.java,代码跟以前的差不多,也不详细讲解了。。不懂的可以去我前两篇帖子。。代码如下:

    /*MainActivity.java*/
    public class MainActivity  extends Activity {
            GLRender renderer = new GLRender();
            @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            GLImage.load(this.getResources());
            new GLFile(this.getResources());
            GLSurfaceView glView = new GLSurfaceView(this);
            glView.setRenderer(render);
            setContentView(glView);
        }
    }

    接下来就是整个应用的重头戏了。。先创建一个GLRender.java文件,根据http://www.apkbus.com/android-121526-1-1.html ,当然也会少不了下面的3个抽象方法的:
    public void onDrawFrame(GL10 gl){}
    public void onSurfaceChanged(GL10 gl, int width, int height){}
    public void onSurfaceCreated(GL10 gl, EGLConfig config){}

    onSurfaceCreated先实现下载纹理,然后就是一些设置,这些在前两篇中说到过了。。最后用读取资源数据,这里用了一个SetupWorld()函数。代码如下:

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config)
        {
            LoadGLTextures(gl);
            gl.glEnable(GL10.GL_TEXTURE_2D);                            
            gl.glBlendFunc(GL10.GL_SRC_ALPHA,GL10.GL_ONE);                    
            gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);                
            gl.glClearDepthf(1.0f);                                    
            gl.glDepthFunc(GL10.GL_LESS);                                
            gl.glEnable(GL10.GL_DEPTH_TEST);                            
            gl.glShadeModel(GL10.GL_SMOOTH);                            
            gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
            SetupWorld();
        }

    SetupWorld()函数就是从”assets/data/world.txt”中读取数据,每取出3个点就构成一个三角形,代码如下:

        public void SetupWorld()
        {
            BufferedReader br = new BufferedReader(new InputStreamReader(GLFile.getFile("data/world.txt")));
            TRIANGLE triangle = new TRIANGLE();
            int vertexIndex = 0;    
            try {
                String line = null;
                while((line = br.readLine()) != null){
                    if(line.trim().length() <= 0 || line.startsWith("/")){
                        continue;
                    }
                    String part[] = line.trim().split("\\s+");
                    float x = Float.valueOf(part[0]);
                    float y = Float.valueOf(part[1]);
                    float z = Float.valueOf(part[2]);
                    float u = Float.valueOf(part[3]);
                    float v = Float.valueOf(part[4]);
                    VERTEX vertex = new VERTEX(x, y, z, u, v);
                    triangle.vertex[vertexIndex] = vertex;           
                    vertexIndex ++;
                    if(vertexIndex == 3){
                        vertexIndex = 0;
                        sector1.triangle.add(triangle);
                        triangle = new TRIANGLE();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    LoadGLTextures(GL10 gl) 实现的是下载纹理,这部分在http://www.apkbus.com/android-121768-1-1.html 有较详细的讲到。。也不细说了哈。。代码如下:

        public void LoadGLTextures(GL10 gl) 
        {
            IntBuffer textureBuffer = IntBuffer.allocate(3);
            gl.glGenTextures(3, textureBuffer);
            texture[0] = textureBuffer.get();
            gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[0]);
            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, GLImage.mBitmap, 0);
            texture[1] = textureBuffer.get(2);
            gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[1]);
            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
            gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, GLImage.mBitmap, 0);
        }

    接下来就来实现onDrawFrame(GL10 gl)函数吧。首先要去掉每个三角形的顶点数据,然后就是装在纹理贴图,将这些数据绘制到屏幕上就构建了所指定的场景样式了。代码如下:

        @Override
        public void onDrawFrame(GL10 gl)
        {
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);    // Clear The Screen And The Depth Buffer
            gl.glLoadIdentity();                                        // Reset The View
            float xtrans = -xpos;
            float ztrans = -zpos;
            float ytrans = -walkbias-0.25f;
            float sceneroty = 360.0f - yrot;
            FloatBuffer vertexPointer = FloatBuffer.wrap(new float[9]);
            FloatBuffer texCoordPointer = FloatBuffer.wrap(new float[6]);
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexPointer);
            gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texCoordPointer);        
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);        
            gl.glLoadIdentity();
            gl.glRotatef(lookupdown, 1.0f, 0.0f, 0.0f);
            gl.glRotatef(sceneroty, 0.0f, 1.0f, 0.0f);
            gl.glTranslatef(xtrans, ytrans, ztrans);        
            gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[1]);
            for(TRIANGLE triangle : sector1.triangle)
            {
                vertexPointer.clear();
                texCoordPointer.clear();
                gl.glNormal3f(0.0f, 0.0f, 1.0f);
                for(int i=0; i<3; i++)
                {
                    VERTEX vt = triangle.vertex[i];
                    vertexPointer.put(vt.x);
                    vertexPointer.put(vt.y);
                    vertexPointer.put(vt.z);
                    texCoordPointer.put(vt.u);
                    texCoordPointer.put(vt.v);
                }
                gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 4);
            }
            gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
            gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);    
        }

    剩下一个就是onSurfaceChanged(GL10 gl, int width, int height)啦,这个跟前面的说的区别不大,直接看代码吧。。

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height)
        {
            float ratio = (float) width / height;
            //设置OpenGL场景的大小
            gl.glViewport(0, 0, width, height);
            //设置投影矩阵
            gl.glMatrixMode(GL10.GL_PROJECTION);
            //重置投影矩阵
            gl.glLoadIdentity();
            // 设置视口的大小
            gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
            // 选择模型观察矩阵
            gl.glMatrixMode(GL10.GL_MODELVIEW);    
            // 重置模型观察矩阵
            gl.glLoadIdentity();    
        }

    其实,到这里就可以说已经搞定了3D的场景效果啦。。不过这个静态的。。

    下面就将通过按键事件处理镜头的移动和旋转,具体代码如下:

        public boolean onKeyUp(int keyCode, KeyEvent event)
        {
            switch ( keyCode )
            {
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    yrot -= 1.5f;
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    yrot += 1.5f;
                    break;
                case KeyEvent.KEYCODE_DPAD_UP:
                    // 沿游戏者所在的X平面移动
                    xpos -= (float)Math.sin(heading*piover180) * 0.05f;    
                    // 沿游戏者所在的Z平面移动
                    zpos -= (float)Math.cos(heading*piover180) * 0.05f;            
                    if (walkbiasangle >= 359.0f)// 如果walkbiasangle大于359度
                    {
                        walkbiasangle = 0.0f;// 将 walkbiasangle 设为0
                    }
                    else                                
                    {
                         walkbiasangle+= 10;// 如果 walkbiasangle < 359 ,则增加 10
                    }
                    // 使游戏者产生跳跃感
                    walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;        
                    break;
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    // 沿游戏者所在的X平面移动
                    xpos += (float)Math.sin(heading*piover180) * 0.05f;        
                    // 沿游戏者所在的Z平面移动
                    zpos += (float)Math.cos(heading*piover180) * 0.05f;    
                    // 如果walkbiasangle小于1度
                    if (walkbiasangle <= 1.0f)                    
                    {
                        walkbiasangle = 359.0f;// 使 walkbiasangle 等于 359                    
                    }
                    else                            
                    {
                        walkbiasangle-= 10;// 如果 walkbiasangle > 1 减去 10
                    }
                    // 使游戏者产生跳跃感
                    walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;        
                    break;
            }
            return false;
        }

    因为有按键事件,所以要在MAinActivity.java 中加上如下代码:

    @Override 
    public boolean onKeyUp(int keyCode, KeyEvent event){ 
        renderer.onKeyUp(keyCode, event); 
        return super.onKeyUp(keyCode, event);
     }

    额。。结束了哦。。期待效果图了吧。。见下图吧:

    截图01

    其实,这个还有许多的地方可以加以改进的,比如可以用鼠标或者触笔来实现对场景的缩放,毕竟现在很多的手机和平板都没有按键的了。。(PS:LZ最近很忙,就不弄这个了哈。。等改天有空再来改进吧)

    还有就是考虑到程序运行的效率,可以减少处理镜头背面的三角形的绘制,也即是观察者视线不能看到的地方,这样会使程序运行得更加流畅。。(PS:还是那就是,LZ最近很忙。。)

    代码下载链接:http://download.csdn.net/detail/klcf0220/5526237

     

    参考链接:http://developer.android.com/guide/topics/graphics/opengl.html

    http://www.cnblogs.com/android100/archive/2012/06/27/2565438.html

     

    喜欢开源,乐意分享的大神们,欢迎加入QQ群:176507146,你值的拥有哦!

    作者:快乐出发0220 ;Android群:151319601 ; Linux群:96394158 ;转载请注明出处 http://klcf0220.cnblogs.com/ !!!
  • 相关阅读:
    数字音乐均衡器
    移植x264到vs2008之二
    无线连接频繁掉线,解决方法之telnet命令突破ddwrt端口最大数连接限制分析
    最新开发的消费平台开发过程 持续更新(二)
    .net 4.0 下请求验证模式变化 应对方法
    DDWRT无线参数解读
    利用 Application_Error 捕获所有异常
    location.reload() 和 location.replace()的区别和应用
    纯CSS 实现组织架构图,学习
    Syslog架设windows日志服务器
  • 原文地址:https://www.cnblogs.com/klcf0220/p/3118025.html
Copyright © 2020-2023  润新知