Android OpenGL ES 2.0 的开发思路总结

背景

项目一开始,自己对着一本《OpenGL ES 2.0 游戏开发(上卷)》撸了很长一段时间,里面学习到OpenGL 的挺多知识,包括着色器语言,还有大部分GL函数,纹理,光照等等。然而书中的所有Demo都采用一种模式,GLSurfaceView + MatrixState + ShaderUtil ,这几个构成了书中开发OpenGL的基本框架。这是很完善的框架。但是在3D坐标计算的时候我遇到了大麻烦。当需要切换视野,或者操作绘制出来的模型的3D坐标的时候,书中只用了些 x ,y ,z 等进行运算。恕我愚钝,虽然我觉得这种方法可以实现效果,但是当数据量很大的时候就有很多个 x ,y,z 并不好管理,而且有时候用一些 sin() cos() tan() 函数,初学者难免会懵逼。之后我查了很久资料没有发现在Android平台下有好的库能帮我们理解。于是我找到了IOS的书本。里面的一些思路值得借鉴。

MatrixState 和 ShaderUtil 的优点

当然我并不是说Android的书中没有什么好东西。MatrixState和ShaderUtil就是很好的东西,在Android上开发OpenGL 基本上都是这样的流程。但是着两个把一些流程封装了起来。

ShaderUtil 源码:

//加载制定shader的方法
    public static int loadShader(
            int shaderType, //shader的类型  GLES20.GL_VERTEX_SHADER   GLES20.GL_FRAGMENT_SHADER
            String source   //shader的脚本字符串
    ) {
        //创建一个新shader
        int shader = GLES20.glCreateShader(shaderType);
        //若创建成功则加载shader
        if (shader != 0) {
            //加载shader的源代码
            GLES20.glShaderSource(shader, source);
            //编译shader
            GLES20.glCompileShader(shader);
            //存放编译成功shader数量的数组
            int[] compiled = new int[1];
            //获取Shader的编译情况
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == 0) {//若编译失败则显示错误日志并删除此shader
                Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
                Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }

上述方法是加载一段shader程序源码字符串,编译shader程序,然后返回一个代表该程序的id,其中shader程序一般有2个,片元着色器和顶点着色器(不会的话请自己看看一些OpenGL 书本普及一下)

 //创建shader程序的方法
    public static int createProgram(String vertexSource, String fragmentSource) {
        //加载顶点着色器
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader == 0) {
            return 0;
        }

        //加载片元着色器
        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if (pixelShader == 0) {
            return 0;
        }

        //创建程序
        int program = GLES20.glCreateProgram();
        //若程序创建成功则向程序中加入顶点着色器与片元着色器
        if (program != 0) {
            //向程序中加入顶点着色器
            GLES20.glAttachShader(program, vertexShader);
            checkGlError("glAttachShader");
            //向程序中加入片元着色器
            GLES20.glAttachShader(program, pixelShader);
            checkGlError("glAttachShader");
            //链接程序
            GLES20.glLinkProgram(program);
            //存放链接成功program数量的数组
            int[] linkStatus = new int[1];
            //获取program的链接情况
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
            //若链接失败则报错并删除程序
            if (linkStatus[0] != GLES20.GL_TRUE) {
                Log.e("ES20_ERROR", "Could not link program: ");
                Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        return program;
    }

上面的方法是创建一个程序,返回编译成功后的程序id,在绘制之前需要使用此id

 //检查每一步操作是否有错误的方法
    public static void checkGlError(String op) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e("ES20_ERROR", op + ": glError " + error);
            throw new RuntimeException(op + ": glError " + error);
        }
    }

上述代码作用是检查GL函数错误。定位出在执行那些GL函数的时候在发生了什么异常

MatrixState 源码

//存储系统矩阵状态的类
public class MatrixState {
    private static float[] mProjMatrix = new float[16];//4x4矩阵 投影用
    private static float[] mVMatrix = new float[16];//摄像机位置朝向9参数矩阵

    private static float[] currMatrix;   //当前变换矩阵
    static float[][] mStack = new float[10][16];   //用于保存变换矩阵的类
    static int stackTop = -1; //标识栈顶的索引



    /**
     * 初始化变换矩阵
     */
    public static void setInitStack() {
        currMatrix = new float[16];
        Matrix.setRotateM(currMatrix, 0, 0, 1, 0, 0);  //除初始化无变换内容的矩阵
    }

    /**
     * 把变换矩阵保存到栈中
     */
    public static void pushMatrix() {
        stackTop++;
        for (int i = 0; i < 16; i++) {
            mStack[stackTop][i] = currMatrix[i];
        }
    }

    /**
     * 从栈中读取变换矩阵
     */
    public static void popMatrix() {
        for (int i = 0; i < 16; i++) {
            currMatrix[i] = mStack[stackTop][i];
        }
        stackTop--;
    }

    /**
     * 平移变换
     */
    public static void translate(float x, float y, float z) {
        Matrix.translateM(currMatrix, 0, x, y, z);
    }

    /**
     * 旋转变换
     *
     * @param angle
     * @param x
     * @param y
     */
    public static void rotate(float angle, float x, float y, float z) {
        Matrix.rotateM(currMatrix, 0, angle, x, y, z);
    }

    /**
     * 缩放变换
     */
    public static void scale(float x, float y, float z) {
        Matrix.scaleM(currMatrix, 0, x, y, z);
    }

    //设置摄像机
    static float[] cameraLocation=new float[3];//摄像机位置
    /**
     * 设置摄像机
     *
     * @param cx  摄像机位置x
     * @param cy  摄像机位置y
     * @param cz  摄像机位置z
     * @param tx  摄像机目标点x
     * @param ty  摄像机目标点y
     * @param tz  摄像机目标点z
     * @param upx 摄像机UP向量X分量
     * @param upy 摄像机UP向量Y分量
     * @param upz 摄像机UP向量Z分量
     */
    public static void setCamera(float cx, float cy, float cz, float tx, float ty, float tz, float upx, float upy, float upz) {
        Matrix.setLookAtM(mVMatrix, 0, cx, cy, cz, tx, ty, tz, upx, upy, upz);
    }

    /**
     * 设置正交投影参数
     *
     * @param left   near面的left
     * @param right  near面的right
     * @param bottom near面的bottom
     * @param top    near面的top
     * @param near   near面距离
     * @param far    far面距离
     */
    public static void setProjectOrtho(float left, float right, float bottom, float top, float near, float far) {
        Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far);
    }

    /**
     * 设置透视投影参数
     *
     * @param left   near面的left
     * @param right  near面的right
     * @param bottom near面的bottom
     * @param top    near面的top
     * @param near   near面距离
     * @param far    far面距离
     */
    public static void setProjectFrustum(float left, float right, float bottom, float top, float near, float far) {
        Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
    }
     /**
     * 获取具体物体的变换之后的矩阵
     *
     * @return
     */
    public static float[] mMVPMatrix = new float[16];
    public static float[] getFinalMatrix() {
        Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, currMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
        return mMVPMatrix;
    }



    //获取具体物体的变换矩阵
    public static float[] getMMatrix()
    {
        return currMatrix;
    }
}

MatrixState 中有一下功能

  • 设置摄像机位置(setCamera)
  • 设置投影模式(正交投影 setProjectOrtho |透视投影 setProjectFrustum)
  • 提供了一个方法获取模型矩阵和变换矩阵相乘之后的总变换矩阵(此矩阵是需要传送到GL的最终矩阵) (getFinalMatrix)
  • 提供了一个栈用于物体做变换,栈的长度为16(mStack 根据需要自己调整长度)
  • 提供了缩放、平移、旋转的变换操作

由于MatrixState中维护的是一个static的float数组所以我们在操作的时候只需要调用方法并不需要关心内部的运行情况,在使用的时候需要注意: 在创建onSurfaceCreated中调用MatrixState.setInitStack()初始化栈,当需要调用变换的时候:顺序是 MatrixState.pushMatrix() -> 变换操作,绘制 -> MatrixState.popMatrix();

这样做的好处是,如果你需要变换一个物体,则会改变传入GL的总变换矩阵,所以我们用栈保存好变换前的矩阵,等变换完成后,在从栈中读取恢复矩阵。 如果不做此操作。GL绘制的物体都会执行同一个变换。

好了,上面说完这两个类的好处,接下来我要介绍一下IOS开发中使用的框架。这个框架是方便与我们对绘制出来的物体的3D坐标处理,对摄像机位置的放置等起着巨大的作用。IOS中的库名为GLKit,里面提供了一个概念——向量。

GLKit 框架

本人参考了GLKit的一些方法的实现方式,把代码转换成为用JAVA语言描述。

GLVector3

这是一个向量的概念,其中包含了3个坐标(x,y,z) 向量可以代表一个点,同时也可以代表一个方向和长度。我们所有的点都用向量表示。后面的运算也全部基于向量来运算。

GLKVector3Substract

两个向量相减

    /**
     * 两个向量相减
     */
    public static GLKVector3 GLKVector3Subtract(GLKVector3 vectorLeft, GLKVector3 vectorRight) {
        GLKVector3 v = new GLKVector3();
        v.x = vectorLeft.x - vectorRight.x;
        v.y = vectorLeft.y - vectorRight.y;
        v.z = vectorLeft.z - vectorRight.z;
        return v;
    }

GLKVector3Lenght

计算一个向量的长度,其实就是一个点到点(0,0,0)的距离长度

     /**
     * 计算向量的长度
     */
    public static float GLKVector3Length(GLKVector3 vector) {
        return (float) Math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
    }

GLKVector3Distance

计算两个向量之间的距离

    public static float GLKVector3Distance(GLKVector3 vectorStart, GLKVector3 vectorEnd) {
        return GLKVector3Length(GLKVector3Subtract(vectorEnd, vectorStart));
    }

GLKVector3MultiplyScalar

根据基数计算从一个向量的比例,缩放向量。

public static GLKVector3 GLKVector3MultiplyScalar(GLKVector3 vector, float value) {
        GLKVector3 v = new GLKVector3();
        v.x = vector.x * value;
        v.y = vector.y * value;
        v.z = vector.z * value;
        return v;
    }

GLKVector3Lerp

根据基数计算两个向量连成直线上的一点

    /**
     * 从向量start 向向量end 移动 value 比列
     */
    public static GLKVector3 GLKVector3Lerp(GLKVector3 vectorStart, GLKVector3 vectorEnd, float value) {
        GLKVector3 v = new GLKVector3();
        v.x = vectorStart.x + value * (vectorEnd.x - vectorStart.x);
        v.y = vectorStart.y + value * (vectorEnd.y - vectorStart.y);
        v.z = vectorStart.z + value * (vectorEnd.z - vectorStart.z);

        return v;

    }

GLKVector3Add

两个向量相加

 public static GLKVector3 GLKVector3Add(GLKVector3 x, GLKVector3 y) {
        GLKVector3 v = new GLKVector3();
        v.x = x.x + y.x;
        v.y = x.y + y.y;
        v.z = x.z + y.z;
        return v;
    }

GLKVector3CrossProduct

计算两个向量形成的矢量积,返回的是一个垂直于两个向量之间形成的平面的一个向量

 /** 计算两个向量的矢量积 */
    public static GLKVector3 GLKVector3CrossProduct(GLKVector3 vectorLeft, GLKVector3 vectorRight) {
        GLKVector3 v = new GLKVector3();
        v.x = vectorLeft.y * vectorRight.z - vectorLeft.z * vectorRight.y;
        v.y = vectorLeft.z * vectorRight.x - vectorLeft.x * vectorRight.z;
        v.z = vectorLeft.x * vectorRight.y - vectorLeft.y * vectorRight.x;
        return v;
    }

GLKVector3Normalize

矢量标准化把一个向量转换成为(1,1,1)的向量

/** 向量标准化 */
    public static GLKVector3 GLKVector3Normalize(GLKVector3 vector)
    {
        float scale = 1.0f / GLKVector3Length(vector);
        GLKVector3 v = new GLKVector3(vector.x * scale, vector.y * scale, vector.z * scale);
        return v;
    }

GLKMatrix4MultiplyAndProjectVector3

返回将一个坐标经过一个矩阵转换后的坐标

 /**
     * 将物体坐标转换成世界坐标
     * @param matrixLeft
     * @param vectorRight
     * @return
     */
    public static GLKVector3 GLKMatrix4MultiplyAndProjectVector3(float[] matrixLeft, GLKVector3 vectorRight) {
        GLKVector4 v4 = GLKMatrix4MultiplyVector4(matrixLeft, GLKVector4Make(vectorRight.x, vectorRight.y, vectorRight.z, 1.0f));
        return GLKVector3MultiplyScalar(GLKVector3Make(v4.x, v4.y, v4.z), 1.0f / v4.w);


    }

    public static GLKVector3 GLKVector3Make(float x, float y, float z) {
        GLKVector3 v = new GLKVector3();
        v.x = x;
        v.y = y;
        v.z = z;
        return v;
    }

    public static GLKVector4 GLKMatrix4MultiplyVector4(float[] matrixLeft, GLKVector4 vector) {
        GLKVector4 v4 = new GLKVector4();
        v4.x = matrixLeft[0] * vector.x + matrixLeft[4] * vector.y + matrixLeft[8] * vector.z + matrixLeft[12] * vector.w;
        v4.y = matrixLeft[1] * vector.x + matrixLeft[5] * vector.y + matrixLeft[9] * vector.z + matrixLeft[13] * vector.w;
        v4.z = matrixLeft[2] * vector.x + matrixLeft[6] * vector.y + matrixLeft[10] * vector.z + matrixLeft[14] * vector.w;
        v4.w = matrixLeft[3] * vector.x + matrixLeft[7] * vector.y + matrixLeft[11] * vector.z + matrixLeft[15] * vector.w;
        return v4;
    }

    public static GLKVector4 GLKVector4Make(float x, float y, float z, float w) {
        GLKVector4 v4 = new GLKVector4();
        v4.x = x;
        v4.y = y;
        v4.z = z;
        v4.w = w;
        return v4;
    }

还有很多很强大的函数,我这里就不一一列举了。 而有了这些函数的时候我们需要计算坐标的时候大大增加了灵活性。

下面我举一个例子来说明方便在那里。

在绘制场景中的树木的时候,我们经常用到一个技巧,就是通过标志板实现。但是使用标记板需要计算标记板的朝向以便在视野变换的时候保持数目看起来是立体的。

下面我贴出来OpenGL ES 2.0 书中的实现方法:

//根据摄像机位置计算数目面朝向
    public void calculateBillboardDirection(){
        float xspan=x-cx;
        float zspan=z-cz;

        if(zspan<=0)
        {

            yAngle=(float)Math.toDegrees(Math.atan(xspan/zspan));
        }
        else
        {
            yAngle=180+(float)Math.toDegrees(Math.atan(xspan/zspan));
        }
        Log.i("aaa","yAngle = " + yAngle);
    }

    //比较2棵树离摄像机的距离的方法
    @Override
    public int compareTo(SingleTree another) {
        float xs = x-cx;
        float zs = z-cz;
        float xo = another.x - cx;
        float zo = another.z - cz;

        float disA = (float) Math.sqrt(xs * xs + zs * zs);
        float disB = (float) Math.sqrt(xo * xo + zo * zo);

        return ((disA-disB) == 0)?0:((disA-disB)>0)?-1:1;  //远的 -1 ,近的 1
    }

其中的cx,cy,cz 是摄像机的位置坐标

上面的运算需要用到一些很直观的运行 sin cos 开平方等。实在是难以理解,一时之间难以理解为什么用sin cos。

下面是用了这些函数的方法


@Override
    public int compareTo(JTTreeInfo another) {  //进行排序,用Collections.sort 可以升序排列
        CameraPosition camera = JTGeoEngine.getInstance().getCameraPosition();
        GLKVector3 otherPosition = another.mPosition;
        float mDis = GeoUtils.GLKVector3Distance(camera.eyePosition, mPosition);
        float oDis = GeoUtils.GLKVector3Distance(camera.eyePosition, otherPosition);
        return ((mDis - oDis) == 0) ? 0 : ((mDis - oDis) > 0) ? -1 : 1;
    }


    //根据摄像机位置计算树木面朝向
    public void calculateBillboardDirection() {
        CameraPosition camera = JTGeoEngine.getInstance().getCameraPosition();
        GLKVector3 lookDirection = GeoUtils.GLKVector3Subtract(camera.lookAtPosition, camera.eyePosition);
        //得到视线方向
        lookDirection = GeoUtils.GLKVector3Normalize(lookDirection);

        GLKVector3 upUnitVector = GeoUtils.GLKVector3Make(0.0f, 0.0f, 1.0f);

        //得到一个法向量。此法向量为垂直于视线方向的向量,所以,标致版只需要按此向量定义左右底部的位置即可。
        GLKVector3 rightVector = GeoUtils.GLKVector3CrossProduct(upUnitVector, lookDirection);
        //左右底部的位置为 x = x - w * 0.5
        GLKVector3 leftBottomPosition = GeoUtils.GLKVector3Add(GeoUtils.GLKVector3MultiplyScalar(rightVector, w * -0.5f), mPosition);
        GLKVector3 rightBottomPosition = GeoUtils.GLKVector3Add(GeoUtils.GLKVector3MultiplyScalar(rightVector, w * 0.5f), mPosition);
        //左右上面的点为底部加上高度即可
        GLKVector3 leftTopPosition = GeoUtils.GLKVector3Add(leftBottomPosition, GeoUtils.GLKVector3MultiplyScalar(upUnitVector, h));
        GLKVector3 rightTopPosition = GeoUtils.GLKVector3Add(rightBottomPosition, GeoUtils.GLKVector3MultiplyScalar(upUnitVector, h));
        mVertexBuffer.clear();
        vertices[0] = rightBottomPosition.x;
        vertices[1] = rightBottomPosition.y;
        vertices[2] = rightBottomPosition.z;
        vertices[3] = rightTopPosition.x;
        vertices[4] = rightTopPosition.y;
        vertices[5] = rightTopPosition.z;
        vertices[6] = leftBottomPosition.x;
        vertices[7] = leftBottomPosition.y;
        vertices[8] = leftBottomPosition.z;
        vertices[9] = leftTopPosition.x;
        vertices[10] = leftTopPosition.y;
        vertices[11] = leftTopPosition.z;
        mVertexBuffer.put(vertices);
        mVertexBuffer.position(0);
    }

这样的代码简单明了很多,而且有很多都是有思路可循。(不要觉得我的代码多就不好,,我指的简单是说逻辑简单,不是单纯的代码量减少。)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书共分两篇,第一篇介绍了Android 3D游戏开发的基础知识,主要对OpenGL ES的相关内容进行了介绍。   章 名主 要 内 容   第1章 英雄还看今朝—Android简介本章介绍了市场上主流的手机平台,同时也分析了未来手机平台的发展趋势及Android平台的前景   第2章 数风流人物—当前流行游戏类型简介本章以分类的方式简要地介绍了当前流行的游戏的玩法,游戏的视觉效果,游戏的设计及《仙剑》等著名游戏的历史   第3章 不积跬步,无以至千里—游戏开发基础知识本章初步介绍了游戏开发的基础知识   第4章 千里之行,始于足下—3D开发基础知识本章介绍了3D开发中的基础知识,包括OpenGL ES的介绍及OpenGL ES中绘制模型的原理,并通过点、线和三角形的绘制介绍了OpenGL ES中模型的几种绘制方式。最后介绍了3D场景中常用的两种投影方式,并通过例子比较了这两种投影的区别   第5章 愿君多采撷,此物最相思—光照效果的开发本章介绍了光照的基础知识,包括环境光、散射光及镜面光   第6章 为伊消得人憔悴——纹理映射本章主要介绍了纹理的基础知识,以及纹理的不同拉伸方式和纹理过滤高级技术,从绘制三角形开始到绘制地月系,可能会经历很长时间,但是这对以后的学习是有帮助的   第7章 海阔凭鱼跃,天高任鸟飞—3D基本形状的构建在本章中介绍了圆柱体、圆锥体、圆环、抛物面、双曲面和螺旋面在OpenGL ES中的渲染方法。这些基本形状在3D世界中应用广泛,在构造一些复杂物体时,经常会运用这些基本形状来进行拼装组合   第8章 执子之手,与子偕老—坐标变换本章介绍了坐标变换的应用。绘制3D场景的过程,主要是旋转和平移操作的组合,通过合理的堆栈操作,就比较容易绘制出所需的3D场景   第9章 孤帆远影碧空尽—摄像机与雾特效在本章中,首先对摄像机及其配置做了介绍。摄像机在3D编程中至关重要,没有正确的配置,摄像机可能不能获得想要的场景效果。然后对雾特效做了具体介绍,应用雾特效可以使场景更加逼真,并且可以减少场景渲染量来提高性能   第10章 假作真时真亦假—混合本章主要为读者介绍了混合,从混合的背景知识到如何配置源因子和目标因子。在介绍源因子和目标因子的时候,向读者介绍了一些预定义常量和一些常用的组合方式,以及如何启用混合   第11章 蓦然回首,那人却在灯火阑珊处—3D高级技术本章主要为读者介绍了3D的一部分高级技术。每一项技术通过讲解其原理和案例,使读者对3D高级技术有一定的了解   第12章 心有灵犀一点通—传感器在本章中,向读者介绍了Android中传感器的相关知识。包括传感器的种类、配置,并且着重介绍了姿态传感器的应用   第13章 千锤万凿出深山—游戏中的数学与物理在本章中对3D游戏中可能会用到的数学及物理知识进行了简单的介绍,这在3D游戏开发中是相当重要的。游戏中的核心算法,基本上都要用到数学和物理知识。一款游戏的性能很大程度上取决于游戏设计的算法   第14章 山舞银蛇,原驰蜡象—AI基本理念本章主要介绍了AI、AI引擎的基本组成与设计,以及游戏AI中图的搜索和模糊逻辑,其中游戏AI中图的搜索为本章的重点。在本章中详细介绍了5种算法的原理与实现   第15章 独上高楼,望尽天涯路—开发小秘籍本章介绍了地图设计器、多键技术、虚拟键盘、查找表技术、状态机、AABB边界框、穿透效应、拾取技术,以及天空盒和天空穹在OpenGL ES中的应用 第二篇以7个比较大的案例来说明Android平台下3D游戏的开发流程,通过这7个案例的讲解,读者对3D游戏的开发将会有更深层次的理解。   章 名主 要 内 容   第16章 体育类游戏——《疯狂投篮》本章介绍了Android 3D游戏《疯狂投篮》的开发。通过该案例向读者介绍了在Android平台下进行3D游戏开发的相关知识和基本流程,并对游戏开发中的编程技巧进行了介绍,并主要介绍了篮球与地面、墙面及篮框的碰撞检测及运动动画的实现方法   第17章 益智类游戏——《旋转积木》本章介绍了Android 3D游戏《旋转积木》的开发。主要介绍了积木旋转的不同状态的实现方法和地图设计器的应用   第18章 休闲类游戏——《摩天大楼》本章介绍了Android 3D游戏《摩天大楼》的开发。主要介绍了楼层与楼层之间的衔接与碰撞及掉落后翻转动画的实现   第19章 动作类游戏——《3D空战》本章介绍了Android 3D游戏《3D空战》的开发。主要介绍了飞机的构造方法和我方战机与敌方战机的操控及动画实现   第20章 桌面类游戏——《激情台球》本章介绍了Android 3D游戏《激情台球》的开发。主要介绍了台球与台球的碰撞检测实现、台球与球桌的碰撞检测实现和进球的判定实现   第21章 射击类游戏——《抢滩登陆》本章介绍了Android 3D游戏《抢滩登陆》的开发。主要运用了灰度图生成技术并且主要介绍了坦克运动的实现方法及炮弹碰撞检测的实现   第22章 竞技类游戏——《乡村飙车》本章介绍了Android 3D游戏《乡村飙车》的开发。主要介绍了运用分层绘制和拼接绘制的策略进行场景的优化绘制,并且对场景部件进行了分类控制   本书面向的读者   本书的内容详细,且几乎涵盖了Android 3D游戏开发所有相关的技术,并向读者介绍了真实项目的开发流程,主要面向以下读者。   Android的初学者   本书详细介绍了OpenGL ES的基础知识,并对Android 3D游戏程序的开发进行了介绍。作为一名Android的初学者,通过本书的学习可以快速全面地掌握Android 3D游戏开发的相关知识,稳健地步入Android 3D游戏开发人员的行列。   有一定Android基础且希望学习Android 3D游戏开发的读者   有一定Android基础的读者通过阅读本书的前半部分便可快速掌握OpenGL ES的基础知识,然后通过7个真实案例的学习迅速掌握Android平台下应用程序的开发。   在职的开发人员

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值