android开发讨论群:199831230
openGL ES很强大,使用它可以很简单我画出我们想要的3D图形,今天从画球体入手。
这个示例中包含灯光效果,如果不想看灯光效果可以去掉 initLight()/ initMaterialWhite()这两个方法
第一个类:Ball
public class Ball {
private IntBuffer vertexBuffer; //顶点坐标数据缓冲
private IntBuffer nomalBuffer; //顶点法向量数据缓冲
private ByteBuffer indexBuffer; //顶点构建索引数据缓冲
public float angleX; //沿x轴旋转角度
int vCount=0;
int iCount=0;
public Ball(int scale){
//顶点坐标初始化数据
final int UNIT_SIZE=10000;
ArrayList<Integer> alVertex=new ArrayList<Integer>();
final int angleSpan=18; //将小球进行单位切分的角度
for (int vAngle = -90; vAngle <= 90; vAngle=vAngle+angleSpan) { //垂直方向angleSpan度一份
for (int hAngle = 0; hAngle <360; hAngle=hAngle+angleSpan ) { //水平方向angleSpan度一份
//纵向横向各到一个角度后计算对应的此点在球面上的坐标
double xozLength=scale*UNIT_SIZE*Math.cos(Math.toRadians(vAngle));
int x=(int) (xozLength*Math.cos(Math.toRadians(hAngle)));
int y=(int) (xozLength*Math.sin(Math.toRadians(hAngle))) ;
int z=(int) (scale*UNIT_SIZE*Math.sin(Math.toRadians(vAngle)));
alVertex.add(x);
alVertex.add(y);
alVertex.add(z);
}
}
vCount=alVertex.size()/3; //顶点数量为坐标值数量的三分之一,因为一个顶点有三个坐标
//将alVertix中的坐标值转存到一个int数组中
int vertices []=new int[alVertex.size()];
for (int i = 0; i < alVertex.size(); i++) {
vertices[i]=alVertex.get(i);
}
//创建顶点坐标数据缓冲
ByteBuffer vbb=ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder()); //设置字节顺序
vertexBuffer=vbb.asIntBuffer(); //转换成int型缓冲
vertexBuffer.put(vertices); //向缓冲区放入顶点坐标数据
vertexBuffer.position(0); //设置缓冲区起始位置
//创建顶点坐标数据缓冲
ByteBuffer nbb=ByteBuffer.allocateDirect(vertices.length*4); //一个整型是4个字节
nbb.order(ByteOrder.nativeOrder()); //设置字节顺序 由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer
nomalBuffer=nbb.asIntBuffer(); //转换成int型缓冲
nomalBuffer.put(vertices); //想缓冲区放入顶点坐标数据
nomalBuffer.position(0); //设置缓冲区起始位置
ArrayList<Integer> alIndex=new ArrayList<Integer>();
int row=(180/angleSpan)+1; //球面切分的行数
int col=360/angleSpan; //球面切分的列数
for (int i = 0; i < row; i++) { //对每一行循环
if(i>0 && i<row-1){
//中间行
for (int j = -1; j < col; j++) {
//中间行的两个相邻点与下一行的对应点构成三角形
int k=i*col+j;
alIndex.add(k+col);
alIndex.add(k+1);
alIndex.add(k);
}
for (int j = 0; j < col+1; j++) {
//中间行的两个相邻点与上一行的对应点构成三角形
int k=i*col+j;
alIndex.add(k-col);
alIndex.add(k-1);
alIndex.add(k);
}
}
}
iCount=alIndex.size();
byte indices []=new byte[iCount];
for (int i = 0; i < iCount; i++) {
indices[i]=alIndex.get(i).byteValue();
}
//三角形构造数据索引缓冲
indexBuffer=ByteBuffer.allocateDirect(iCount); //由于indices是byte型的,索引不用乘以4
indexBuffer.put(indices);
indexBuffer.position(0);
}
public void drawSelf(GL10 gl){
gl.glRotatef(angleX, 1, 0, 0); //沿x轴旋转
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); //启用顶点坐标数组
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); //启用顶点向量数组
//为画笔指定顶点坐标数据
gl.glVertexPointer(
3 , //顶点坐标数量,三个坐标一个顶点
GL10.GL_FIXED , //顶点坐标数据类型
0, //连续顶点之间的数据间隔
vertexBuffer //顶点坐标数据
);
//为画笔指定顶点向量数据
gl.glNormalPointer(GL10.GL_FIXED, 0, nomalBuffer);
//绘制图形
gl.glDrawElements(
GL10.GL_TRIANGLES, //以三角形的方式填充
iCount, GL10.GL_UNSIGNED_BYTE, indexBuffer);
}
}
第二个类:MySurfaceView
public class MySurfaceView extends GLSurfaceView{
//private final float TOUCH_SCALE_FACTOR=180.0f/360; //角度缩放比例
private final float TOUCH_SCALE_FACTOR=0.5f;
private SceneRenderer myRenderer; //场景渲染器
public boolean openLightFlag=false; //开灯标记,false为关灯,true为开灯
private float previousX,previousY; //上次触控的横纵坐标
public MySurfaceView(Context context) {
super(context);
myRenderer=new SceneRenderer(); //创建场景渲染器
this.setRenderer(myRenderer); //设置渲染器
this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); //渲染模式为主动渲染
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x=event.getX();
//float y=event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
//float dy=y-previousY; //计算触控笔移动Y位移
float dx=x-previousX; //计算触控笔移动X位移
myRenderer.ball.angleX +=dx*TOUCH_SCALE_FACTOR; //设置沿x轴旋转角度
requestRender(); //渲染画面
break;
}
previousX=x; //前一次触控位置x坐标
return true; //事件成功返回true
}
private class SceneRenderer implements GLSurfaceView.Renderer{
Ball ball=new Ball(4); //创建圆
public SceneRenderer(){}
public void onDrawFrame(GL10 gl) {
gl.glEnable(GL10.GL_CULL_FACE) ; //打开背面剪裁
gl.glShadeModel(GL10.GL_SMOOTH); //开始平滑着色
if(openLightFlag){
gl.glEnable(GL10.GL_LIGHTING);
initLight(gl, GL10.GL_LIGHT0);
initMaterialWhite(gl);
//设置light0光源位置
float [] positionParamsGreen={2,1,0,1};
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, positionParamsGreen,0);
}else{
gl.glDisable(GL10.GL_LIGHTING);
}
gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT); //清除缓存
gl.glMatrixMode(GL10.GL_MODELVIEW); //设置当前矩形为模式矩阵
gl.glLoadIdentity(); //设置矩阵为单位矩阵
gl.glTranslatef(0, 0, -1.8f); //把坐标系往z轴负方向平移2.0f个单位
ball.drawSelf(gl);
gl.glLoadIdentity();
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height); //设置视口大小和位置
gl.glMatrixMode(GL10.GL_PROJECTION); //设置矩阵为投影矩阵
gl.glLoadIdentity(); //设置矩阵为单位矩阵
float ratio=(float)width/height; //比例大小
gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10); //设置投影模式
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glDisable(GL10.GL_DITHER); //关闭抗抖动
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
//设置模式
gl.glClearColor(0, 0, 0, 0); //设置屏幕颜色为黑色
gl.glEnable(GL10.GL_DEPTH_TEST); //启用深度检测
}
/**
* 初始化灯
* @param gl
* @param LIGTH 0-7代表八盏灯
*/
private void initLight(GL10 gl,final int LIGHT){
gl.glEnable(LIGHT); //打开LIGTH+1号灯
//设置环境光
float [] ambientParams ={0.1f,0.1f,0.1f,1.0f}; //光参数RGBA
gl.glLightfv(LIGHT, GL10.GL_AMBIENT, ambientParams,0);
//设置散射光
float [] diffuseParams={0.5f,0.5f,0.5f,1.0f}; //光参数RGBA
gl.glLightfv(LIGHT, GL10.GL_DIFFUSE, diffuseParams, 0);
//设置放射光
float [] specularParams={1.0f,1.0f,1.0f,1.0f};
gl.glLightfv(LIGHT, GL10.GL_SPECULAR, specularParams,0);
}
private void initMaterialWhite(GL10 gl){
//材质为白色时,什么颜色的光照在上面就将体现出什么颜色
//设置环境光,为白色材质
float [] ambientMaterial={0.4f,0.4f,0.4f,1.0f};
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, ambientMaterial,0);
//设置散射光白色
float [] diffuseMaterial={0.8f,0.8f,0.8f,1.0f};
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, diffuseMaterial, 0);
//高光材质为白色 //建立镜面光float,镜面光一般设置较高
float [] specularMaterial={1.0f,1.0f,1.0f,1.0f};
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, specularMaterial, 0);
//高光反色区,数越大,高亮区域越小、越暗 //高光反射区域数越大,高亮区域越小
float [] shininessMaterial={1.5f};
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, shininessMaterial,0);
}
}
}
第三个类:MainActivity
public class MainActivity extends Activity {
private MySurfaceView mySurfaceView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mySurfaceView=new MySurfaceView(this); //创建MysurfaceView对象
mySurfaceView.requestFocus(); //获取焦点
mySurfaceView.setFocusableInTouchMode(true); //设置为可触控
LinearLayout ll=(LinearLayout) this.findViewById(R.id.main_liner); //获得线性布局的引用
ll.addView(mySurfaceView);
ToggleButton tb1=(ToggleButton) findViewById(R.id.toggleButton1);
tb1.setOnCheckedChangeListener(new ButtonListener());
}
class ButtonListener implements OnCheckedChangeListener{
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
switch (buttonView.getId()) {
case R.id.toggleButton1:
mySurfaceView.openLightFlag=!mySurfaceView.openLightFlag;
break;
}
}
}
@Override
protected void onPause() {
super.onPause();
mySurfaceView.onPause(); //调用MySurfaceView的onPause()方法
}
@Override
protected void onRestart() {
super.onRestart();
mySurfaceView.onResume();
}
}
第四个:main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/main_liner"
android:orientation="vertical" >
<ToggleButton
android:textOff="关闭灯光效果"
android:textOn="打开灯光效果"
android:checked="true"
android:id="@+id/toggleButton1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
本博文有吴宗坡撰写,代码参考于由吴亚峰 苏亚光著 电子工业出版社出版的Android 3D游戏开发
博客地址:http://blog.csdn.net/wuzongpo/article/details/7230285
android开发讨论群:199831230
本文可以随意转载,转载请注明出处