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

posted @ 2012-09-03 11:54  vincy  阅读(2435)  评论(0编辑  收藏  举报