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);
- }
- }
- 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>