openGL ES 画球

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


 

本文可以随意转载,转载请注明出处


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值