2012090311483675  

色子是可以觸摸轉動的,不要見怪,更多玩法還有待開發。
進入正題,先看一下類結構:

 

2012090311490948  

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 );
        }
}

 轉自安卓巴士,個人覺得不錯 

部分代碼已加上注釋,就不多說了,上附件:

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


From:CNBLOGS
arrow
arrow
    全站熱搜

    戮克 發表在 痞客邦 留言(0) 人氣()