博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android OpenGL(3)
阅读量:7268 次
发布时间:2019-06-29

本文共 10125 字,大约阅读时间需要 33 分钟。

前面介绍了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 = new ArrayList
();}

一个场景必然由很多个顶点组成,由于这些顶点的数据量过大,所以这里将这些顶点存放到一个和游戏一起打包的文件中,然后在程序中通过装载这个文件来取得数据。在这里将顶点数据存放到“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文件,根据 ,当然也会少不了下面的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) 实现的是下载纹理,这部分在 有较详细的讲到。。也不细说了哈。。代码如下:

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); }

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

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

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

 

代码下载链接:

 

参考链接:

 

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

转载地址:http://wsgdm.baihongyu.com/

你可能感兴趣的文章
设计模式-单例模式
查看>>
package.json和package-lock.json
查看>>
pattern
查看>>
MyBatis中井号与百分号的区别
查看>>
新建一个xib
查看>>
初识HTTP
查看>>
Python安装(一)
查看>>
thinkphp 输出变量使用函数处理
查看>>
MySQL学习笔记 - 1 - 基本架构与日志两阶段提交
查看>>
编程语言对比手册(横向版)[-PHP-]
查看>>
Vue 中状态管理VueX的使用方法?
查看>>
thinkphp控制器获取参数
查看>>
Netty源码分析(七):初识ChannelPipeline
查看>>
Semaphore-信号量的实现分析
查看>>
iOS逆向之旅(进阶篇) — 工具(class-dump)
查看>>
排序算法-归并排序
查看>>
今日学习笔记:hash 以及 nodejs基本服务
查看>>
kafka broker configs list
查看>>
Socket.io开发注意点
查看>>
RoundProgressBar 的实现
查看>>