上图:(转自安卓巴士,个人觉得不错)

色子是可以触摸转动的,不要见怪,更多玩法还有待开发。
进入正题,先看一下类结构:
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
深圳群 260134856;成都群 252743807;西安群252746034;武汉群121592153;杭州群253603803;大连群253672904;青岛群 257925319