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

  • 相关阅读:
    Running APP 使用说明
    Android 控件八 WebView 控件
    Android 控件七 ImageView 控件
    Android 控件六 CheckBox 控件
    Android 控件五 RadioButton 控件
    Android 控件四 EditText 控件
    Android 控件三 TextView 控件实现 Button
    Android 控件二 Button
    Android 基础控件演示实例
    Android 控件一 TextView
  • 原文地址:https://www.cnblogs.com/feifei1010/p/2668527.html
Copyright © 2011-2022 走看看