zoukankan      html  css  js  c++  java
  • android opengl 渲染的3D色子

    上图:(转自安卓巴士,个人觉得不错)

    色子是可以触摸转动的,不要见怪,更多玩法还有待开发。
    进入正题,先看一下类结构:

    DiceActivity.java是主Activity,主要代码:

     
    mGLView = new DiceSurfaceView(this);
                    setContentView(mGLView);
    

      就是将DiceSurfaceView的实例设置为Activity的内容视图,菜单操作只是为了使这个小项目看起来还像个东西才加上的,可以忽略。
    DiceSurfaceView.java继承了android.opengl.GLSurfaceView,在DiceSurfaceView的构造方法里为他设置一个DiceRenderer渲染器实例,负责视图的渲染。这里解释一下:在Android平台中提供了一个android.opengl包,类GLSurfaceView提供了对Display(实际显示设备的抽象),Suface(存储图像的内存区域FrameBuffer的抽象),Context(存储OpenGL ES绘图的一些状态信息)的管理,大大简化了OpenGL ES的程序框架,开发OpenGL ES应用时只需为GLSurfaceView 设置渲染器实例(调用setRenderer(mRenderer))即可。关于Display,Suface,Context,附件有份AndroidOpenGL小结(不同地方拼一起的,还请原作者见谅),里面有介绍。看DiceSurfaceView.java源码:

    class DiceSurfaceView extends GLSurfaceView {
    
            private DiceRenderer mRenderer = null;
            private float mPreviousX = 0;
            private float mPreviousY = 0;
    
            public DiceSurfaceView(Context context) {
                    super(context);
                    // 设置渲染器,
                    mRenderer = new DiceRenderer(this);
                    setRenderer(mRenderer);
                    // 设置描绘方式,
                    setAutoRender(false);
                    this.requestRender();
            }
    
            @Override
            public boolean onTouchEvent(MotionEvent e) {
                    float x = e.getX();
                    float y = e.getY();
                    //转换坐标方向;
                    y = -y;
    
                    switch (e.getAction()) {
                    case MotionEvent.ACTION_MOVE:
                            float dx = x - mPreviousX;
                            float dy = y - mPreviousY;
                            mRenderer.onTouchMove(dx, dy);
                    case MotionEvent.ACTION_DOWN:
    //                        Log.i("tg","touch down/" + x + "/" + y);
                            this.mPreviousX = x;
                            this.mPreviousY = y;
                            break;
                    case MotionEvent.ACTION_UP:
    //                        Log.i("tg","touch up/" + x + "/" + y);
                            this.mPreviousX = 0;
                            this.mPreviousY = 0;
                            setAutoRender(true);
                            this.mRenderer.startRotate();
                            break;
                    }
                    this.requestRender();
                    return true;
            }
            /**
             * 设置是否自动连续渲染
             * @param auto
             */
            public void setAutoRender(boolean auto){
                    // RENDERMODE_WHEN_DIRTY-有改变时重绘-需调用requestRender()
                    // RENDERMODE_CONTINUOUSLY -自动连续重绘(默认)
                    if(auto){
                            setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
                    }else{
                            setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
                    }
            }
    
            //重置背景画
            public void resetBackground(int optionalBg){
                    TextureManager.bgIndex = optionalBg;
                    this.requestRender();
            }
    }
    

      接受一个渲染器DiceRenderer实例,并对触摸事件作出处理。
    DiceRenderer.java 继承自android.opengl.GLSurfaceView.Renderer,做具体的渲染操作,源码:

    public class DiceRenderer implements Renderer {
    
            //90度角的正余弦
            private static final float NORMALS_COS = (float) Math.cos(Math.PI/2);
            private static final float NORMALS_SIN = (float)Math.sin(Math.PI/2);
            private static final int MSG_ROTATE_STOP = 1;
            
            private DiceSurfaceView surface = null;
            private Handler handler = null;
            private Dice dice = null;
            private BackWall back = null;
            //转动时速度矢量
            private float rotateV = 0;
            //已旋转角度
            private float rotated = 0;
            //当前旋转轴
            private float axisX = 0;
            private float axisY = 0;
            private RotateTask rTask = null;
            
            /**渲染器*/
            public DiceRenderer(DiceSurfaceView surface){
    //                Log.i("tg","Renderer 构造。");
                    this.surface = surface;
                    // 初始化数据
                    dice = new Dice();
                    back = new BackWall();
                    handler = new Handler(){
                            @Override
                            public void handleMessage(Message msg){
                                    super.handleMessage(msg);
                                    if(msg.what == MSG_ROTATE_STOP){
                                            DiceRenderer.this.surface.setAutoRender(false);//设置非自动连续渲染
                                    }
                            }
                    };
            }
    
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    //                Log.i("tg","Surface created.config/" + config);
                    
                    // Set the background frame color
                    gl.glClearColor(0.3f, 0.3f, 0.4f, 0.7f);
                    // 启用深度测试, 不启用时,不管远近,后画的会覆盖之前画的,
                    gl.glEnable(GL10.GL_DEPTH_TEST);
                    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);// 启用顶点坐标数组
                    gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);// 打开法线数组
                    //初始化纹理
                    TextureManager.initTexture(gl, this.surface.getResources());
                    initLight(gl);
                    initMaterial(gl);
            }
    
            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {
    //                Log.i("tg","Surface changed.。");
                    //设置视窗
                    gl.glViewport(0, 0, width, height);
            // 适应屏幕比例
            float ratio = (float) width / height;
            //设置矩阵为投射模式
            gl.glMatrixMode(GL10.GL_PROJECTION);        // set matrix to projection mode
            //重置矩阵
            gl.glLoadIdentity();                        // reset the matrix to its default state
            //设置投射椎体 // apply the projection matrix
            if(ratio < 1 ){
                    gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7); 
            }else{
                    gl.glFrustumf(-ratio, ratio, -1, 1, 4, 8); 
    //                gl.glFrustumf(-ratio*1.5f, ratio*1.5f, -1*1.5f, 1*1.5f, 4, 8); 
            }
            
            }
    
            @Override
            public void onDrawFrame(GL10 gl) {
    //                Log.i("tg","draw a frame..");
                    // 重画背景,  刷屏
                    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    
                    // 设置 GL_MODELVIEW(模型观察) 转换模式
                    gl.glMatrixMode(GL10.GL_MODELVIEW);
                    // 重置矩阵,设置当前矩阵为单位矩阵,相当于渲染之前清屏
                    gl.glLoadIdentity();
    
                    // 使用GL_MODELVIEW 模式时, 必须设置视点
    //                GLU.gluLookAt(gl, 3,3,3, 1f, 1f, 1f, 0f, 1.0f, 0f);
                    GLU.gluLookAt(gl, 0, 0, 5, 0f, 0f, -1f, 0f, 1.0f, 0.0f);
                    
                    // 绘制背景墙
                    gl.glPushMatrix();
                    back.drawSelf(gl);
                    gl.glPopMatrix();
    
                    // 绘制色子
                    gl.glPushMatrix();
    
                    if(rotated != 0){
                            RotateOnTouch(gl);
                    }
                    gl.glRotatef(45, 1, 1, 0);
                    dice.drawSelf(gl);
                    gl.glPopMatrix();
    
            }
            /**触摸后转动*/
            private void RotateOnTouch(GL10 gl){
                    this.rotated += rotateV;
                    gl.glRotatef(rotated, axisX, axisY, 0);
                    if(rotateV>0){
    //                        Log.i("tg","GL rotateV/" + rotateV);
    //                        Log.i("tg","GL rotated/" + rotated + "/" + rotateV);
                    }
            }
            /**
             * 响应触摸移动
             * @param dx
             * @param dy
             */
            public void onTouchMove(float dx,float dy){
                    rotateV = Math.abs(dx) + Math.abs(dy);
    //                Log.i("tg","GL rotateV/" + rotateV);
                    rotated += rotateV;
                    setAxisLine(dx,dy);
            }
            /**设置转轴线*/
            public void setAxisLine(float dx ,float dy){
                    //x1 = x0 * cosB - y0 * sinB                y1 = x0 * sinB + y0 * cosB
                    this.axisX = dx*NORMALS_COS - dy*NORMALS_SIN;
                    this.axisY= dx*NORMALS_SIN + dy*NORMALS_COS;
            }
            /**启动旋转线程*/
            public void startRotate(){
                    if(rTask != null){
                            rTask.running = false;
                    }
                    rTask = new RotateTask();
                    rTask.start();
            }
            /**
             * 旋转线程类
             *
             */
            class RotateTask extends Thread{
                    boolean running = true;
                    @Override
                    public void run() {
                            while(running && rotateV > 0){
                                    if(rotateV>50){
                                            rotateV -= 7;
                                    }else if(rotateV>20){
                                            rotateV -= 3;
                                    }else{
                                            rotateV --;
                                    }
                                    try {
                                            Thread.sleep(200);
                                    } catch (InterruptedException e) {
                                            e.printStackTrace();
                                    }
                            }
                            if(rotateV<=0){
                                    handler.sendEmptyMessage(MSG_ROTATE_STOP);
                            }
                    }
            }
    
            /** 初始化灯光
             * 定义各种类型光的光谱
             * */
            private void initLight(GL10 gl) {
                    gl.glEnable(GL10.GL_LIGHTING);                //打开照明总开关
                    gl.glEnable(GL10.GL_LIGHT1);                // 打开1号灯
    
                    // 环境光设置
                    float[] ambientParams = { 0.7f, 0.7f, 0.7f, 1.0f };// 光参数 RGBA
                    gl.glLightfv(GL10.GL_LIGHT1,                //光源序号
                                    GL10.GL_AMBIENT,                         //光照参数名-环境光
                                    ambientParams,                                 //参数值
                                    0                                                        //偏移
                                    );
                    // 散射光设置
                    float[] diffuseParams = { 0.7f, 0.7f, 0.7f, 1.0f };// 光参数 RGBA
                    gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_DIFFUSE, diffuseParams, 0);
                    // 反射光设置
                    float[] specularParams = { 1f, 1f, 1f, 1.0f };// 光参数 RGBA
                    gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_SPECULAR, specularParams, 0);
                    //光源位置
                    float[] positionParams = { 0,0,9,1 };
                    gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_POSITION, positionParams, 0);
                    //聚光灯方向
                    float[] directionParams = {0,0,-1};
                    gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_SPOT_DIRECTION , directionParams, 0);
                    //聚光角度(0-90)度
                    gl.glLightf(GL10.GL_LIGHT1, GL10.GL_SPOT_CUTOFF , 30);
                    //聚光程度(0-128)实现聚焦
                    gl.glLightf(GL10.GL_LIGHT1, GL10.GL_SPOT_EXPONENT  , 10);
            }
    
            /** 初始化材质 
             * 定义平面对各种类型光的反射光谱
             * */
            private void initMaterial(GL10 gl) {
                    //控制环境光在平面上的反射光光谱                                        
                    float ambientMaterial[] = { 0.4f, 0.5f, 0.6f, 0.3f };
                    gl.glMaterialfv(
                                    GL10.GL_FRONT_AND_BACK, //反射面,正面,反面,或两面(android)只支持两面
                                    GL10.GL_AMBIENT,                //反射光类型,环境光
                                    ambientMaterial,                 //反射参数值
                                    0                                                //偏移
                                    );
                    //控制反射散射光
                    float diffuseMaterial[] = { 0.7f, 0.6f, 0.7f, 0.8f };
                    gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE,
                                    diffuseMaterial, 0);
                    //控制反射光
                    float specularMaterial[] = { 0.9f, 0.9f, 0.9f, 0.8f };
                    gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR,
                                    specularMaterial, 0);
                    //对高光的反射指数(0-128)值越大光的散射越小
                    gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 120f);
            }
            
    }
    

      这里包括了灯光,材质的设置,旋转逻辑,最终的渲染画屏,可参看注释理解。
    以上是主要类,他们之间的联系看参考Android开发文档Resources>Tutorials>OpenGL ES 1.0 或巴士里相关帖,这里不罗嗦了。
    TextureManager.java是上线后重构出来整个结构更清晰,是管理纹理的类,由它生成纹理ID,绑定图片资源,供渲染所用,看源码:

    public class TextureManager {
    
            //纹理索引号
            public static final int TEXTURE_INDEX_DICE = 0;
            public static final int TEXTURE_INDEX_BG00 = 1;
            public static final int TEXTURE_INDEX_BG01 = 2;
            public static final int TEXTURE_INDEX_BG02 = 3;
            //纹理资源id
            private static int[] textureSrcs = {R.drawable.dice_map,R.drawable.bg00,R.drawable.bg01,R.drawable.bg02};
            //纹理id存储
            private static int[] textureIds = new int[textureSrcs.length];
            
            private static GL10 gl = null;
            private static Resources res = null;
    
            //背景画索引 0-2;
            public static int bgIndex = 0;
            
            /**
             * 取得指定索引的纹理id
             * @param index
             * @return
             */
            public static int getTextureId(int index){
    //                Log.i("tg","TextureManager/getTextureId/" + textureIds[index]);
                    if(textureIds[index] <= 0){
                            Log.i("tg","TextureManager/getTextureId/" + textureIds[index]);
                            gl.glGenTextures(1, textureIds, index);
                            bindTexture(gl,res,index);
                    }
                    return textureIds[index];
            }
            /**初始化纹理*/
            public static void initTexture( GL10 gl, Resources res) {
    
                    TextureManager.gl = gl;
                    TextureManager.res = res;
                    //获取未使用的纹理对象ID
                    gl.glGenTextures(1, textureIds, TEXTURE_INDEX_DICE);
                    bindTexture(gl,res,TEXTURE_INDEX_DICE);
                    //获取未使用的纹理对象ID
                    gl.glGenTextures(1, textureIds, bgIndex + 1);
                    bindTexture(gl,res,bgIndex + 1);
    
                    
    //                for(int i=0;i<textureIds.length;i++){
    //                        bindTexture(gl,res,i);
    //                }
    
            }
            /**
             * 为纹理id绑定纹理。
             * @param gl
             * @param res
             * @param index
             */
            private static void bindTexture(GL10 gl,Resources res,int index){
    //                Log.i("tg","TextureManager/initTexture/" + textureIds[i]);
                    //绑定纹理对象
                    gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIds[index]);
                    //设置纹理控制,指定使用纹理时的处理方式
                    //缩小过滤:一个像素代表多个纹素。
                    gl.glTexParameterf(GL10.GL_TEXTURE_2D,         //纹理目标
                                    GL10.GL_TEXTURE_MIN_FILTER,                        //纹理缩小过滤
                                    GL10.GL_NEAREST                                                                //使用距离当前渲染像素中心最近的纹素
                                    );
                    //放大过滤:一个像素是一个纹素的一部分。
                    //放大过滤时,使用距离当前渲染像素中心,最近的4个纹素加权平均值,也叫双线性过滤。
                    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
                                    GL10.GL_LINEAR);                //
                    //设置纹理贴图方式,指定对超出【0,1】的纹理坐标的处理方式
                    //左下角是【0,0】,右上角是【1,1】,横向是S维,纵向是T维。android以左上角为原点
                    //S维贴图方式:重复平铺
                    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
                                    GL10.GL_REPEAT);
                    //T维贴图方式:重复平铺
                    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
                                    GL10.GL_REPEAT);
                    bindBitmap(index,res);
            }
            /**
             * 为纹理绑定位图
             * @param index
             * @param res
             */
            private static void bindBitmap(int index,Resources res){
                    Bitmap bitmap = null;
                    InputStream is = res.openRawResource(textureSrcs[index]);
                    try {
                            bitmap = BitmapFactory.decodeStream(is);
                    } finally {
                            if(is != null){
                                    try {
                                            is.close();
                                            is = null;
                                    } catch (IOException e) {
                                            e.printStackTrace();
                                    }
                            }
                    }
                    //为纹理对象指定位图
                    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
                    //释放bitmap对象内存,像素数据仍存在,不影响使用。
                    bitmap.recycle();
            }
    }
    

      还有Dice.java和BackWall.java是渲染物件类色子和背景,各自保存自己的顶点,法向量,贴图坐标数据,并提供一个自我渲染的方法,看Dice.java源码:

    public class Dice {
            private int vertexCount = 36;
            /** 顶点坐标数据缓冲 */
            private FloatBuffer mVertexBuffer;
            /** 顶点法向量数据缓冲 */
            private FloatBuffer mNormalBuffer;
            /** 顶点纹理数据缓冲,存储每个顶点在位图中的坐标 */
            private FloatBuffer mTextureBuffer;
    
            /**色子类*/
            public Dice() {
                    initDataBuffer();
            }
            /**初始化定点数据缓冲区*/
            private void initDataBuffer(){
                    float[] vertices = Constant.VERTEX_COORD;
                    float[] normals = Constant.NORMALS_COORD;
                    float[] texST = Constant.TEXTURE_COORD;                //new float[cpTexST.length];        //常量数组的内容可变,这里要拷贝
    
                    // vertices.length*4是因为一个Float四个字节
                    ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
                    vbb.order(ByteOrder.nativeOrder());// 设置字节顺序
                    mVertexBuffer = vbb.asFloatBuffer();// 转换为float型缓冲
                    mVertexBuffer.put(vertices);// 向缓冲区中放入顶点坐标数据
                    mVertexBuffer.position(0);// 设置缓冲区起始位置
                    
                    ByteBuffer nbb = ByteBuffer.allocateDirect(normals.length * 4);
                    nbb.order(ByteOrder.nativeOrder());// 设置字节顺序
                    mNormalBuffer = nbb.asFloatBuffer();// 转换为int型缓冲
                    mNormalBuffer.put(normals);// 向缓冲区中放入顶点着色数据
                    mNormalBuffer.position(0);// 设置缓冲区起始位置
                    
                    ByteBuffer tbb = ByteBuffer.allocateDirect(texST.length * 4);
                    tbb.order(ByteOrder.nativeOrder());// 设置字节顺序
                    mTextureBuffer = tbb.asFloatBuffer();// 转换为int型缓冲
                    mTextureBuffer.put(texST);// 向缓冲区中放入顶点着色数据
                    mTextureBuffer.position(0);// 设置缓冲区起始位置
            }
            /**绘制色子*/
            public void drawSelf(GL10 gl) {
    //                Log.i("tg","to draw dice..");
    
                    // 为画笔指定顶点坐标数据
                    gl.glVertexPointer(3,                                 // 每个顶点的坐标数量为3 xyz
                                    GL10.GL_FLOAT,                                 // 顶点坐标值的类型为 GL_FIXED
                                    0,                                                                                 // 连续顶点坐标数据之间的间隔
                                    mVertexBuffer                                         // 顶点坐标数据
                    );
    
                    // 为画笔指定顶点法向量数据
                    gl.glNormalPointer(GL10.GL_FLOAT, 0, mNormalBuffer);
    
                    // 开启纹理贴图
                    gl.glEnable(GL10.GL_TEXTURE_2D);
                    // 允许使用纹理ST坐标缓冲
                    gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
                    // 指定纹理ST坐标缓冲
                    gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer);
                    // 绑定当前纹理
                    gl.glBindTexture(GL10.GL_TEXTURE_2D, TextureManager.getTextureId(TextureManager.TEXTURE_INDEX_DICE));
    
                    // 绘制图形 , 以三角形方式填充
                    gl.glDrawArrays(GL10.GL_TRIANGLES,         0,         vertexCount );
            }
    }
    

     转自安卓巴士,个人觉得不错 

    部分代码已加上注释,就不多说了,上附件:

    https://files.cnblogs.com/feifei1010/Dice-1.0.zip

    深圳群 260134856;成都群 252743807;西安群252746034;武汉群121592153;杭州群253603803;大连群253672904;青岛群 257925319

  • 相关阅读:
    bootstrap初识
    司徒正美居中方式
    css中的浮动(float)
    fio
    简易接口测试脚本
    字符加密
    Python 汉诺塔
    Python 找零问题
    Python 二分法
    Python 冒泡排序
  • 原文地址:https://www.cnblogs.com/feifei1010/p/2668527.html
Copyright © 2011-2022 走看看