OpenGL ES学习笔记---3D世界的第一个3D图形绘制

实现3D绘图一般采用的是GLSurfaceView类,他继承于SurfaceView并实现了SurfaceHolder.Callback接口,实现渲染需要,开启渲染进程,一般通过setRenderer(Renderer)函数获得,在设置此函数之前,可以调用以下函数实现该类的一些其他功能,如调错标志设置等等。

  1. setDebugFlags(int)
  2. setEGLConfigChooser(boolean)
  3. setEGLConfigChooser(EGLConfigChooser)
  4. setEGLConfigChooser(int, int, int, int, int, int)
  5. setGLWrapper(GLWrapper)

需要对画面渲染,需要实现Renderer接口,并重写里面的三个方法,如下:

private class SceneRenderer  implementsGLSurfaceView.Renderer{

public SceneRenderer(){ //构造函数  

}

//以下三个函数必须重写,其功能一看便知,第一个是负责重绘的;第二个是surfaceView改变时调用,但是有一点,程序第一次运行的时候,也会调用此方法;第三个是创建时调用

  1. @Override
  2. public void onDrawFrame(GL10 gl) {}
  3. @Override
  4. public void onSurfaceChanged(GL10 gl, int width,intheight) {}
  5. @Override
  6. public void onSurfaceCreated(GL10gl, EGLConfig config) {}
  7. }

下面以一个具体的例子,完成3D世界的第一个图形绘制----三角形。

第一步:Activity类(省略包的导入等,后同)。

  1. public class MyActivity extends Activity {
  2.     /** Called when the activity is firstcreated. */
  3.     private MySurfaceView mSurfaceView;//声明MySurfaceView对象
  4.     @Override
  5.     public void onCreate(BundlesavedInstanceState) {
  6.         super.onCreate(savedInstanceState);
  7.         mSurfaceView=new MySurfaceView(this);//创建MySurfaceView对象
  8.         setContentView(mSurfaceView);    
  9.     }
  10.     @Override
  11.     protected void onPause() {
  12.         // TODO Auto-generated methodstub
  13.         super.onPause();
  14.         mSurfaceView.onPause();
  15.     }
  16.     @Override
  17.     protected void onResume() {
  18.         // TODO Auto-generated methodstub
  19.         super.onResume();
  20.         mSurfaceView.onResume();
  21.     }}

此类为应用程序的启动类,可以理解为应用程序的入口。按照google的官方文档,此Activity必须重写onPause()方法和onResume()方法,具体原因这里不再解释。Layout界面接受GLSurfaceView,即可完成图形界面的显示。

第二步:MySurfaceView类,画图类。

  1. public class MySurfaceView extends GLSurfaceView {
  2.     private SceneRenderer myRenderer;//场景渲染器
  3.     public MySurfaceView(Context context){
  4.         super(context);
  5.         // TODO Auto-generatedconstructor stub
  6.         myRenderer=new SceneRenderer();//创建场景渲染器
  7.         this.setRenderer(myRenderer);//设置渲染器
  8.         //this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//设置渲染模式为主动渲染
  9.     }
  10.     private classSceneRenderer  implementsGLSurfaceView.Renderer{//内部类,实现Renderer接口,渲染器  
  11.         Triangle tr=new Triangle();
  12.         public SceneRenderer(){    
  13.         }
  14.         @Override
  15.         public void onDrawFrame(GL10gl) {
  16.             // TODO Auto-generated methodstub 
  17.             gl.glEnable(GL10.GL_CULL_FACE);//设置为打开背面剪裁,当旋转到背面时,图像将不再显示,究竟那一面是背面,是由函数glFrontFace()决定的。
  18.             gl.glShadeModel(GL10.GL_SMOOTH_LINE_WIDTH_RANGE);//设置着色模型为平滑着色,经测试,默认的也是这种平滑着色模式。
  19.             gl.glFrontFace(GL10.GL_CCW);//设置自定义卷绕顺序为逆时针为正面
  20.              gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);//清除颜色缓存和深度缓存,每次重绘的时候必须调用此函数
  21.             gl.glMatrixMode(GL10.GL_MODELVIEW);//设置当前矩阵为模式矩阵,这个不太理解,反正调用就是了
  22.             gl.glLoadIdentity();//设置当前矩阵为单位矩阵,功能将当前的用户坐标系的原点移到了屏幕中心:类似于一个复位操作
  23.             gl.glTranslatef(0, 0, -2.0f);//沿着 X, Y 和 Z 轴移动。注意在glTranslatef(x, y, z)中,当您移动的时候,您并不是相对屏幕中心移动,而是相对与当前所在的屏幕位置。其作用就是将你绘点坐标的原点在当前原点的基础上平移一个(x,y,z)向量。
  24.             tr.drawSelf(gl);//
  25.         }
  26.         @Override
  27.         public voidonSurfaceChanged(GL10 gl, int width, int height) {
  28.             // TODO Auto-generated methodstub
  29.              gl.glViewport(0, 0, width, height);//设置视界区域大小,此处为整个屏幕
  30.              gl.glMatrixMode(GL10.GL_PROJECTION); //投影模式
  31.              gl.glLoadIdentity(); //设置单位矩阵
  32.              float ratio=(float)width/height;
  33.              gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10); //近裁剪平面和远裁剪平面
  34.         }
  35.         @Override
  36.         public voidonSurfaceCreated(GL10 gl, EGLConfig config) {
  37.         }}}

如果对GLSurfaceView不了解,可以先了解一下surfaceView类,在游戏开发中,这两个是经常用于二维和三维的绘图操作类。需要说明的是,此处还有另外一个类——SceneRenderer,场景渲染类,继承与Renderer,每一个3D场景都需要渲染,在二维绘图中,我们习惯于称之为重绘,即在场景发生变化的情况下,重新绘制(渲染)。GLSurfaceView类非常简单,只做了一件事,就是实例化渲染器,并设置渲染(setRenderer()函数),当然,还可以设置渲染模式,如主动渲染等(setRenderMode()函数)。所有的工作都在渲染器中进行,也就是不停地绘图呗。

在onSurfaceCreated函数中可以添加如下代码,具体功能看注释,解释可参考具体的OpenGl书籍。

gl.glDisable(GL10.GL_DITHER);//关闭抗抖动gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,

GL10.GL_FASTEST);//设置特定Hint项目的模式,这里为设置使用快速模式。

gl.glClearColor(0,0, 0, 0);//设置屏幕背景色为黑色

gl.glEnable(GL10.GL_DEPTH_TEST);//启用深度检测

在onSurfaceChanged函数中设置视界区域大小以及投影模式等,在GLsurfaceView第一次显示的时候会调用此函数,但是,如果应用程序第一次被加载,并没有显示,此函数是不会调用的。重点解释glFrustumf(left, right, button, top, near, far)函数,此函数设置场景的剪裁区域,可以把该函数设置的区域理解成一个以矩形,由其参数控制,一般的人眼观察点都是在(0,0,0)位置,当场景旋转时,超出此矩形区域的部分将不再显示,可以将程序中该函数的far参数改为教小的值(必须大于near)测试,旋转该图像(顺时针,让顶点原理视点),发现三角形的顶点不是逐渐变小,而是被切掉一部分。如图:

第三步:Triangle类,三角形相关参数。

  1. public class Triangle {
  2.     privateIntBuffer myVertexBuffer;//顶点坐标数据缓冲
  3.     privateIntBuffer myColorBuffer;//顶点着色数据缓冲
  4.     privateByteBuffer myIndexBuffer;//顶点构建的索引数据缓冲
  5.     intvCount=3;//顶点数量,一个三角形,3个顶点
  6.     intiCount=3;//索引数量
  7.     floatyAngle=0;//绕y轴旋转的角度
  8.     floatzAngle=0;//绕z轴旋转的角度
  9.     publicTriangle(){
  10.         finalint UNIT_SIZE=10000;//缩放比例
  11.         int[]vertices=new int[]
  12.            {//第一个三角形的三个顶点坐标
  13.                 -8*UNIT_SIZE,6*UNIT_SIZE,0,
  14.                 -8*UNIT_SIZE,-6*UNIT_SIZE,0,
  15.                 8*UNIT_SIZE,-6*UNIT_SIZE,0,
  16.                 //第二个三角形的三个顶点坐标
  17.                 0,6*UNIT_SIZE,0,
  18.                 8*UNIT_SIZE,0,0,
  19.                 8*UNIT_SIZE,6*UNIT_SIZE,0
  20.            };
  21.         //创建顶点坐标数据缓存,由于不同平台字节顺序不同,数据单元不是字节的(上面的事整型的缓存),一定要经过ByteBuffer转换,关键是通过ByteOrder设置nativeOrder()
  22.         ByteBuffervbb=ByteBuffer.allocateDirect(vertices.length*4);//一个整数四个字节,根据最新分配的内存块来创建一个有向的字节缓冲
  23.         vbb.order(ByteOrder.nativeOrder());//设置这个字节缓冲的字节顺序为本地平台的字节顺序
  24.         myVertexBuffer=vbb.asIntBuffer();//转换为int型缓冲
  25.         myVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据
  26.         myVertexBuffer.position(0);//设置缓冲区的起始位置
  27.         finalint one=65535;//支持65535色色彩通道
  28.         int[]colors=new int[]{//顶点颜色值数组,每个顶点4个色彩值RGBA
  29.             0,one,0,0,
  30.             0,0,one,0,
  31.             one,0,0,0,
  32.             one,one,one,0,
  33.             one,one,one,0,
  34.             0,0,one,0,
  35.         };
  36.         ByteBuffercbb=ByteBuffer.allocateDirect(colors.length*4);
  37.         cbb.order(ByteOrder.nativeOrder());
  38.         myColorBuffer=cbb.asIntBuffer();
  39.         myColorBuffer.put(colors);
  40.         myColorBuffer.position(0);
  41.         //为三角形构造索引数据初始化
  42.         byte[]indices=new byte[]{
  43.                 0,1,2,3,4,5
  44.            };
  45.         //创建三角形构造索引数据缓冲
  46.         myIndexBuffer=ByteBuffer.allocateDirect(indices.length);
  47.         myIndexBuffer.put(indices);
  48.         myIndexBuffer.position(0);
  49.        
  50.     }
  51.     publicvoid drawSelf(GL10 gl){ //GL10是实现接口GL的一公共接口,包含了一系列常量和抽象方法
  52.         gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//启用顶点坐标数组
  53.         gl.glEnableClientState(GL10.GL_COLOR_ARRAY);//启用顶点颜色数组
  54.         gl.glRotatef(yAngle,0,1,0);//根据yAngle的角度值,绕y轴旋转yAngle
  55.         gl.glRotatef(zAngle,0,0,1);
  56.         gl.glVertexPointer(//为画笔指定顶点坐标数据
  57.                 3,                  //每个顶点的坐标数量为3
  58.                 GL10.GL_FIXED,      //顶点坐标值的类型为GL_FIXED,整型
  59.                 0,                  //连续顶点坐标数据之间的间隔
  60.                 myVertexBuffer      //顶点坐标数量
  61.         );
  62.         gl.glColorPointer(//为画笔指定顶点 颜色数据
  63.             4,
  64.             GL10.GL_FIXED,
  65.             0,
  66.             myColorBuffer
  67.         );
  68.         gl.glDrawElements(//绘制图形
  69.             GL10.GL_TRIANGLES,      //填充模式,这里是以三角形方式填充
  70.             6,                  //顶点数量
  71.             GL10.GL_UNSIGNED_BYTE,  //索引值的类型
  72.             myIndexBuffer           //索引值数据
  73.         );
  74.     }}

函数drawSelf中,不考虑旋转,主要调用三个函数,glVertexPointer、glColorPointer、glDrawElements,前两个是设置三角形的三个顶点的坐标缓冲,在3D绘图中,由于使用的资源非常大,所以必须把所有的数据放入缓冲中,在图像变换的时候才能更加流畅。索引缓冲的标号顺序(三角形填充时,每三个一组取出,将该索引对应的顶点坐标作为三角形的顶点)决定了三角形怎么取点。特别的是,在建立缓冲的过程中,需要制定计算机储存模式为大端模式还是小端模式(我的计算机是小端模式),这个影响到数据在计算机中的存储,如果采用大端模式存储,而又以小端模式读取,将会产生错误的结果。函数其它部分可参考程序注释,在三角形的实现函数中还调用了旋转函数,主要是对三角形的旋转设置,初始化为不旋转。如果需要实现程序的旋转效果,在GLSurfaceView类中重写onTouchEvent方法,表示触摸响应。

  1.     @Override//触摸事件回调方法
  2.     publicboolean onTouchEvent(MotionEvent event) {
  3.         //TODO Auto-generated method stub
  4.         floaty=event.getY();//获得当前触点的Y坐标
  5.         floatx=event.getX();//获得当前触点的X坐标
  6.         switch(event.getAction()){
  7.         caseMotionEvent.ACTION_MOVE:
  8.             floatdy=y-myPreviousY;//滑动距离在y轴方向上的垂直距离
  9.             floatdx=x-myPreviousX;//活动距离在x轴方向上的垂直距离
  10.             myRenderer.tr.yAngle+=dx*TOUCH_SCALE_FACTOR;//设置沿y轴旋转角度
  11.             myRenderer.tr.zAngle+=dy*TOUCH_SCALE_FACTOR;//设置沿z轴旋转角度
  12.             requestRender();//渲染画面
  13.         }
  14.         myPreviousY=y;
  15.         myPreviousX=x;
  16.         returntrue;
  17.     }

最终效果如下所示(有旋转),读者可以根据笛卡尔坐标,描绘出三角形的顶点,然后绘制这两个三角形,与下图一致。

说明:在google的官方文档中,关于3D开发中的具体函数介绍很少,必须参考OpenGl相关资料,下面对一些常用词汇总结:

1、  旋转

关于旋转,要深刻理解笛卡尔坐标系,Z轴是纵向的,如果一个图形绕Z轴旋转,在观察者看来(屏幕上是2D呈像),图像会顺时针或者逆时针旋转;而绕X,Y旋转时,图像总是靠近或者远离观察者。以上程序定义了在屏幕上X滑动绕Y轴旋转,在Y轴滑动绕Z轴旋转即是解释,读者可以亲自试验一下,感受坐标对图像显示的影响。TOUCH_SCALE_FACTOR规定了移动单位像素角度的变化量(增加或者减少),图像旋转一周,角度变化360度,通过函数glRotatef(Angle,X,Y,Z)函数设置图像的旋转,X,Y,Z分别表示绕哪一个坐标旋转。

2、  背面剪裁

所谓背面剪裁,顾名思义可以理解为剪掉图像的背面,不显示背面,在一个三角形中,三个点构成一个图像,总会有两面,哪一面是背面,哪一面是正面是设计者规定的,glFrontFace可以完成这样的功能。以逆时针为正面为例,观察者从某一面观察三角形,如果点以逆时针的方式构成该三角形,者为正面,否者为反面。

3、  颜色缓存和深度缓存

颜色缓存:程序员绘图所用的缓存。颜色缓存包含颜色索引或RGB颜色数据,还包括alpha值。支持立体感试图的OpenGL实现具有用于左右立体图像的左右颜色缓存。如果不支持立体感,就只用到左颜色缓存。类似地,双缓存系统具有前后两个缓存。单缓存系统只有前缓存。在每一个OpenGL实现中都必须提供左前颜色缓存。
         深度缓存:用来存储每个像素的深度值。深度通常是以眼睛的距离来度量的,因此,具有较大深度缓存值的像素要被具有较小深度缓存值的像素覆盖掉。这样深度缓存的行为就可以像深度测试中。深度缓存也称为Z缓存,可以理解为,X和Y表示了屏幕的水平和垂直位移,Z表示了垂直于屏幕的距离。
    模板缓存:其一种用法是将作图限制在屏幕的某些部分中进行,就像是油画制作中用到的纸板和喷漆罐一样。比如想绘制一幅透过挡风玻璃观察到的图像,就要在模板缓存中存储挡风玻璃形状的图像,然后再绘制整个场景。模板缓存可以使得透过挡风玻璃看不见的物体不被画出。因此,编制驾驶模拟的应用程序,就可以一次性地画出汽车内地仪表和其他部分,在汽车运动时候,只更新车外地场景即可。

此外,在openGL编程中,还经常遇到以下几种缓存:

累积缓存:与RGBA模式中的颜色缓存类似,累积缓存包含了RGBA颜色数据(在颜色索引模式中使用累积缓存将产生不可预测的结果)。它通常用于将一系列图像累加成一幅最终的合成图像。利用这种方法,可以完成类似于这样的操作:通过超量采样消除场景的锯齿现象,然后利用均匀采样生成最终绘制到颜色缓存的颜色值。像素不能直接绘制到累积缓存;累加操作常常是先在矩形块中进行的,然后再与颜色缓存进行数据交换。

4、  矩阵模式

矩阵模式指定哪一个矩阵堆栈是下一个矩阵操作的目标,可选值:GL_MODELVIEW、GL_PROJECTION、GL_TEXTURE.。glMatrixMode设置当前矩阵模式:

GL_MODELVIEW,对模型视景矩阵堆栈应用随后的矩阵操作.

GL_PROJECTION,对投影矩阵应用随后的矩阵操作.

GL_TEXTURE,对纹理矩阵堆栈应用随后的矩阵操作.

与glLoadIdentity()一同使用,该函数的功能是重置当前指定的矩阵为单位矩阵。

在glLoadIdentity()之后我们为场景设置了透视图。glMatrixMode(GL_MODELVIEW)设置当前矩阵为模型视图矩阵,模型视图矩阵储存了有关物体的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值