色子是可以觸摸轉動的,不要見怪,更多玩法還有待開發。
進入正題,先看一下類結構:
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
留言列表