上图:(转自安卓巴士,个人觉得不错)
色子是可以触摸转动的,不要见怪,更多玩法还有待开发。
进入正题,先看一下类结构:
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