Andriod OpenGL 教程 10 - 3D世界

来源:互联网 发布:nba2k17球员数据 编辑:程序博客网 时间:2024/04/29 11:53

关键字: android OpenGL 移动开发 教程

 

        到目前为止,我们已经学会了如何创建3D物体,给物体着色,在空间中打开灯光,给物体贴上纹理等基本功能。并有能力创建一个旋转的立方体或者一群闪烁的星星了,对3D编程也有了一定的了解。

         这一课,我们将演示如何加载3D世界,并在3D世界中遨游。为此我们采用一个文本文件来定义我们的3D世界。这样可以方便地加载不同的3D世界定义文件,来得到不同的3D世界。

        数据文件中每个三角形都以如下形式声明:
        X1 Y1 Z1 U1 V1
        X2 Y2 Z2 U2 V2
        X3 Y3 Z3 U3 V3

      保存在world .txt文件中。

       类MyWorld方法loadWorld加载这个文件。

MyWorld.java

package wintop.gllesson10;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.FloatBuffer;import java.util.ArrayList;import java.util.List;import java.util.StringTokenizer;import javax.microedition.khronos.opengles.GL10;import javax.microedition.khronos.opengles.GL11;import android.view.KeyEvent;import android.view.MotionEvent;import android.view.View;import android.view.View.OnKeyListener;import android.view.View.OnTouchListener;import android.content.Context;import android.util.Log;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.opengl.GLUtils;public class MyWorld implements OnKeyListener, OnTouchListener {// 纹理指针private int[] textures = new int[3];// 上下文句柄private Context context;// 3D 世界private Sector sector1;// 下面这些变量用于在3D世界中导航和头像的导航private final float piover180 = 0.0174532925f;private float heading;private float xpos;private float zpos;private float yrot; //Y 旋转private float walkbias = 0;private float walkbiasangle = 0;private float lookupdown = 0.0f;// 用于输入控制的变量和因子private float oldX;    private float oldY;private final float TOUCH_SCALE = 0.2f;// // 顶点缓冲区private FloatBuffer vertexBuffer;// 纹理坐标缓冲区private FloatBuffer textureBuffer;// 最初的顶点定义private float[] vertices;// 纹理坐标(u, v)private float[] texture;// 构造函数public MyWorld(Context context) {this.context = context;}// 加载我们的3D世界// 参数fileName 从assets目录中加载的文件名public void loadWorld(String fileName) {try {// 一些临时变量int numtriangles = 0;int counter = 0;sector1 = new Sector();List<String> lines = null;StringTokenizer tokenizer;//用于输入文件的快速阅读器InputStream in_stream = this.context.getAssets().open(fileName);BufferedReader reader = new BufferedReader(new InputStreamReader(in_stream));//迭代读取所有的行String line = null;while((line = reader.readLine()) != null) {//跳过注释和空行if(line.startsWith("//") || line.trim().equals("")) {continue;}//读取这个文件包含多少个三角形if(line.startsWith("NUMPOLLIES")) {numtriangles = Integer.valueOf(line.split(" ")[1]);sector1.num_triangles = numtriangles;sector1.triangle = new Triangle[sector1.num_triangles];//读所有的其他行} else {if(lines == null) {lines = new ArrayList<String>();}lines.add(line);}}//清理reader.close();//现在迭代分析所有的行for(int loop = 0; loop < numtriangles; loop++) {//...定义三角形...Triangle triangle = new Triangle();//...然后yoga读取的五个点值赋给三角形的顶点 for(int vert = 0; vert < 3; vert++) {//line = lines.get(loop * 3 + vert);tokenizer = new StringTokenizer(line);//triangle.vertex[vert] = new Vertex();//triangle.vertex[vert].x = Float.valueOf(tokenizer.nextToken());triangle.vertex[vert].y = Float.valueOf(tokenizer.nextToken());triangle.vertex[vert].z = Float.valueOf(tokenizer.nextToken());triangle.vertex[vert].u = Float.valueOf(tokenizer.nextToken());triangle.vertex[vert].v = Float.valueOf(tokenizer.nextToken());}//最后,添加这些三角形到sectorsector1.triangle[counter++] = triangle;}//如果什么都没发生, 则写一个日志然后返回} catch(Exception e) {Log.e("World", "Could not load the World file!", e);return;}// 现在将顶点和纹理坐标分离出来vertices = new float[sector1.num_triangles * 3 * 3];texture = new float[sector1.num_triangles * 3 * 2];int vertCounter = 0;int texCounter = 0;for(Triangle triangle : sector1.triangle) {//for(Vertex vertex : triangle.vertex) {//vertices[vertCounter++] = vertex.x;vertices[vertCounter++] = vertex.y;vertices[vertCounter++] = vertex.z;//texture[texCounter++] = vertex.u;texture[texCounter++] = vertex.v;}}// 建立缓冲区ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);byteBuf.order(ByteOrder.nativeOrder());vertexBuffer = byteBuf.asFloatBuffer();vertexBuffer.put(vertices);vertexBuffer.position(0);byteBuf = ByteBuffer.allocateDirect(texture.length * 4);byteBuf.order(ByteOrder.nativeOrder());textureBuffer = byteBuf.asFloatBuffer();textureBuffer.put(texture);textureBuffer.position(0);}// 绘图public void draw(GL10 gl, int filter) {// 根据给定的滤波方式绑定纹理gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[filter]);float xtrans = -xpos;//用于游戏者沿X轴平移时的大小float ztrans = -zpos;//用于游戏者沿Z轴平移时的大小float ytrans = -walkbias - 0.25f;//用于头部的上下摆动float sceneroty = 360.0f - yrot;//位于游戏者方向的360度角//视图gl.glRotatef(lookupdown, 1.0f, 0, 0);//上下旋转gl.glRotatef(sceneroty, 0, 1.0f, 0);//根据游戏者正面所对方向所作的旋转gl.glTranslatef(xtrans, ytrans, ztrans);//以游戏者为中心的平移场景//使能顶点,纹理和法向状态gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);// 设置缓冲区数据指针gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);// 绘制所有的三角形gl.glDrawArrays(GL10.GL_TRIANGLES, 0, vertices.length / 3);// 返回前恢复原来的状态gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);}// 加载纹理public void loadGLTexture(GL10 gl, Context context) {// 从Android的资源目录获取纹理InputStream is = context.getResources().openRawResource(R.drawable.mud);Bitmap bitmap = null;try {//BitmapFactory 是 Android 图形库中处理图像的工具 bitmap = BitmapFactory.decodeStream(is);} finally {//总是要清理和关闭try {is.close();is = null;} catch (IOException e) {}}// 生成纹理指针gl.glGenTextures(3, textures, 0);// 生成Nearest方式的滤波纹理并绑定到纹理0gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);// 生成Linear方式的滤波纹理并绑定到纹理1gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[1]);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);// 生成mipmapped方式的滤波纹理并绑定到纹理2gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[2]);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);// 在Android系统SDK中,1.0版本没有buildMipMap类似的函数,因此这里// 检查OpenGL实例的版本,如果为1.1。则使用系统提供的方法,否在使用自定义的方法if(gl instanceof GL11) {gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE);GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);//} else {buildMipmap(gl, bitmap);}//清理bitmap.recycle();}// 建立自己的mipmap工具,总是按1/2的比例来缩小原图作为我们新的mipmap纹理层次。private void buildMipmap(GL10 gl, Bitmap bitmap) {//int level = 0;//int height = bitmap.getHeight();int width = bitmap.getWidth();//while(height >= 1 || width >= 1) {//首先, 根据位图生成纹理并设置器对应的层次GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bitmap, 0);if(height == 1 || width == 1) {break;}//增加 mipmap 层次level++;height /= 2;width /= 2;Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true);//清理bitmap.recycle();bitmap = bitmap2;}}///////// 用于3D世界的一些结构 //////////////// 一个包含纹理坐标的经典的Vertex类public class Vertex {public float x,y,z;public float u,v;}// Triangle类,包含三角形的所有顶点public class Triangle {public Vertex[] vertex = new Vertex[3];}// Sector类,保存我们的3D世界的所有三角形的个数和所有三角形public class Sector {public int num_triangles;public Triangle[] triangle;}// ===== 处理监听事件 ===== //// 重载键盘监听器以监听键盘事件@Overridepublic boolean onKey(View v, int keyCode, KeyEvent event) {// 处理键盘按下的事件if(event.getAction() == KeyEvent.ACTION_DOWN){return onKeyDown(keyCode, event);}return false;}/** * 检查 DPad 按下 左, 右, 上  和  下按钮. * 然后按相应的方向行走或者旋转头部. *  * @参数 keyCode - 键值 * @参数 event - 键事件 * @返回值 事件处理了则返回true */public boolean onKeyDown(int keyCode, KeyEvent event) {if(keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {heading += 1.0f;yrot = heading;// 向左旋转场景} else if(keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {heading -= 1.0f;yrot = heading;//向右侧旋转场景} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {xpos -= (float)Math.sin(heading * piover180) * 0.05f;//沿游戏者所在的X平面移动zpos -= (float)Math.cos(heading * piover180) * 0.05f;//沿游戏者所在的Z平面移动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;//使游戏者产生跳跃感} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {xpos += (float)Math.sin(heading * piover180) * 0.05f;//沿游戏者所在的X平面移动zpos += (float)Math.cos(heading * piover180) * 0.05f;//沿游戏者所在的Z平面移动if(walkbiasangle <= 1.0f) {//如果walkbiasangle小于1度walkbiasangle = 359.0f;//使 walkbiasangle 等于 359} else {walkbiasangle -= 10;//如果 walkbiasangle > 1 减去 10}walkbias = (float)Math.sin(walkbiasangle * piover180) / 20.0f;//使游戏者产生跳跃感}//事件处理完成后return true;}/** * 触屏方式处理. */@Overridepublic boolean onTouch(View v, MotionEvent event) {boolean handled = false;float x = event.getX();        float y = event.getY();                //如果在屏幕上移动        if(event.getAction() == MotionEvent.ACTION_MOVE) {        //计算改变的值        float dx = x - oldX;        float dy = y - oldY;                        //通过触摸上下移动        lookupdown += dy * TOUCH_SCALE;        //通过触摸左右观看        heading += dx * TOUCH_SCALE;        yrot = heading;        //处理完事件后            handled = true;        }                //保存当前的值        oldX = x;        oldY = y;          return handled;}}


 最终运行结果:

 

代码下载地址:http://download.csdn.net/detail/seniorwizard/4470542