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/ !!!
原文地址:https://www.cnblogs.com/klcf0220/p/3118025.html