实现3D绘图一般采用的是GLSurfaceView类,他继承于SurfaceView并实现了SurfaceHolder.Callback接口,实现渲染需要,开启渲染进程,一般通过setRenderer(Renderer)函数获得,在设置此函数之前,可以调用以下函数实现该类的一些其他功能,如调错标志设置等等。
- setDebugFlags(int)
- setEGLConfigChooser(boolean)
- setEGLConfigChooser(EGLConfigChooser)
- setEGLConfigChooser(int, int, int, int, int, int)
- setGLWrapper(GLWrapper)
需要对画面渲染,需要实现Renderer接口,并重写里面的三个方法,如下:
private class SceneRenderer implementsGLSurfaceView.Renderer{
public SceneRenderer(){ //构造函数
}
//以下三个函数必须重写,其功能一看便知,第一个是负责重绘的;第二个是surfaceView改变时调用,但是有一点,程序第一次运行的时候,也会调用此方法;第三个是创建时调用
- @Override
- public void onDrawFrame(GL10 gl) {}
- @Override
- public void onSurfaceChanged(GL10 gl, int width,intheight) {}
- @Override
- public void onSurfaceCreated(GL10gl, EGLConfig config) {}
- }
下面以一个具体的例子,完成3D世界的第一个图形绘制----三角形。
第一步:Activity类(省略包的导入等,后同)。
- public class MyActivity extends Activity {
- /** Called when the activity is firstcreated. */
- private MySurfaceView mSurfaceView;//声明MySurfaceView对象
- @Override
- public void onCreate(BundlesavedInstanceState) {
- super.onCreate(savedInstanceState);
- mSurfaceView=new MySurfaceView(this);//创建MySurfaceView对象
- setContentView(mSurfaceView);
- }
- @Override
- protected void onPause() {
- // TODO Auto-generated methodstub
- super.onPause();
- mSurfaceView.onPause();
- }
- @Override
- protected void onResume() {
- // TODO Auto-generated methodstub
- super.onResume();
- mSurfaceView.onResume();
- }}
此类为应用程序的启动类,可以理解为应用程序的入口。按照google的官方文档,此Activity必须重写onPause()方法和onResume()方法,具体原因这里不再解释。Layout界面接受GLSurfaceView,即可完成图形界面的显示。
第二步:MySurfaceView类,画图类。
- public class MySurfaceView extends GLSurfaceView {
- private SceneRenderer myRenderer;//场景渲染器
- public MySurfaceView(Context context){
- super(context);
- // TODO Auto-generatedconstructor stub
- myRenderer=new SceneRenderer();//创建场景渲染器
- this.setRenderer(myRenderer);//设置渲染器
- //this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//设置渲染模式为主动渲染
- }
- private classSceneRenderer implementsGLSurfaceView.Renderer{//内部类,实现Renderer接口,渲染器
- Triangle tr=new Triangle();
- public SceneRenderer(){
- }
- @Override
- public void onDrawFrame(GL10gl) {
- // TODO Auto-generated methodstub
- gl.glEnable(GL10.GL_CULL_FACE);//设置为打开背面剪裁,当旋转到背面时,图像将不再显示,究竟那一面是背面,是由函数glFrontFace()决定的。
- gl.glShadeModel(GL10.GL_SMOOTH_LINE_WIDTH_RANGE);//设置着色模型为平滑着色,经测试,默认的也是这种平滑着色模式。
- gl.glFrontFace(GL10.GL_CCW);//设置自定义卷绕顺序为逆时针为正面
- gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);//清除颜色缓存和深度缓存,每次重绘的时候必须调用此函数
- gl.glMatrixMode(GL10.GL_MODELVIEW);//设置当前矩阵为模式矩阵,这个不太理解,反正调用就是了
- gl.glLoadIdentity();//设置当前矩阵为单位矩阵,功能将当前的用户坐标系的原点移到了屏幕中心:类似于一个复位操作
- gl.glTranslatef(0, 0, -2.0f);//沿着 X, Y 和 Z 轴移动。注意在glTranslatef(x, y, z)中,当您移动的时候,您并不是相对屏幕中心移动,而是相对与当前所在的屏幕位置。其作用就是将你绘点坐标的原点在当前原点的基础上平移一个(x,y,z)向量。
- tr.drawSelf(gl);//
- }
- @Override
- public voidonSurfaceChanged(GL10 gl, int width, int height) {
- // TODO Auto-generated methodstub
- 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); //近裁剪平面和远裁剪平面
- }
- @Override
- public voidonSurfaceCreated(GL10 gl, EGLConfig config) {
- }}}
如果对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类,三角形相关参数。
- public class Triangle {
- privateIntBuffer myVertexBuffer;//顶点坐标数据缓冲
- privateIntBuffer myColorBuffer;//顶点着色数据缓冲
- privateByteBuffer myIndexBuffer;//顶点构建的索引数据缓冲
- intvCount=3;//顶点数量,一个三角形,3个顶点
- intiCount=3;//索引数量
- floatyAngle=0;//绕y轴旋转的角度
- floatzAngle=0;//绕z轴旋转的角度
- publicTriangle(){
- finalint UNIT_SIZE=10000;//缩放比例
- int[]vertices=new int[]
- {//第一个三角形的三个顶点坐标
- -8*UNIT_SIZE,6*UNIT_SIZE,0,
- -8*UNIT_SIZE,-6*UNIT_SIZE,0,
- 8*UNIT_SIZE,-6*UNIT_SIZE,0,
- //第二个三角形的三个顶点坐标
- 0,6*UNIT_SIZE,0,
- 8*UNIT_SIZE,0,0,
- 8*UNIT_SIZE,6*UNIT_SIZE,0
- };
- //创建顶点坐标数据缓存,由于不同平台字节顺序不同,数据单元不是字节的(上面的事整型的缓存),一定要经过ByteBuffer转换,关键是通过ByteOrder设置nativeOrder()
- ByteBuffervbb=ByteBuffer.allocateDirect(vertices.length*4);//一个整数四个字节,根据最新分配的内存块来创建一个有向的字节缓冲
- vbb.order(ByteOrder.nativeOrder());//设置这个字节缓冲的字节顺序为本地平台的字节顺序
- myVertexBuffer=vbb.asIntBuffer();//转换为int型缓冲
- myVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据
- myVertexBuffer.position(0);//设置缓冲区的起始位置
- finalint one=65535;//支持65535色色彩通道
- int[]colors=new int[]{//顶点颜色值数组,每个顶点4个色彩值RGBA
- 0,one,0,0,
- 0,0,one,0,
- one,0,0,0,
- one,one,one,0,
- one,one,one,0,
- 0,0,one,0,
- };
- ByteBuffercbb=ByteBuffer.allocateDirect(colors.length*4);
- cbb.order(ByteOrder.nativeOrder());
- myColorBuffer=cbb.asIntBuffer();
- myColorBuffer.put(colors);
- myColorBuffer.position(0);
- //为三角形构造索引数据初始化
- byte[]indices=new byte[]{
- 0,1,2,3,4,5
- };
- //创建三角形构造索引数据缓冲
- myIndexBuffer=ByteBuffer.allocateDirect(indices.length);
- myIndexBuffer.put(indices);
- myIndexBuffer.position(0);
- }
- publicvoid drawSelf(GL10 gl){ //GL10是实现接口GL的一公共接口,包含了一系列常量和抽象方法
- gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//启用顶点坐标数组
- gl.glEnableClientState(GL10.GL_COLOR_ARRAY);//启用顶点颜色数组
- gl.glRotatef(yAngle,0,1,0);//根据yAngle的角度值,绕y轴旋转yAngle
- gl.glRotatef(zAngle,0,0,1);
- gl.glVertexPointer(//为画笔指定顶点坐标数据
- 3, //每个顶点的坐标数量为3
- GL10.GL_FIXED, //顶点坐标值的类型为GL_FIXED,整型
- 0, //连续顶点坐标数据之间的间隔
- myVertexBuffer //顶点坐标数量
- );
- gl.glColorPointer(//为画笔指定顶点 颜色数据
- 4,
- GL10.GL_FIXED,
- 0,
- myColorBuffer
- );
- gl.glDrawElements(//绘制图形
- GL10.GL_TRIANGLES, //填充模式,这里是以三角形方式填充
- 6, //顶点数量
- GL10.GL_UNSIGNED_BYTE, //索引值的类型
- myIndexBuffer //索引值数据
- );
- }}
函数drawSelf中,不考虑旋转,主要调用三个函数,glVertexPointer、glColorPointer、glDrawElements,前两个是设置三角形的三个顶点的坐标缓冲,在3D绘图中,由于使用的资源非常大,所以必须把所有的数据放入缓冲中,在图像变换的时候才能更加流畅。索引缓冲的标号顺序(三角形填充时,每三个一组取出,将该索引对应的顶点坐标作为三角形的顶点)决定了三角形怎么取点。特别的是,在建立缓冲的过程中,需要制定计算机储存模式为大端模式还是小端模式(我的计算机是小端模式),这个影响到数据在计算机中的存储,如果采用大端模式存储,而又以小端模式读取,将会产生错误的结果。函数其它部分可参考程序注释,在三角形的实现函数中还调用了旋转函数,主要是对三角形的旋转设置,初始化为不旋转。如果需要实现程序的旋转效果,在GLSurfaceView类中重写onTouchEvent方法,表示触摸响应。
- @Override//触摸事件回调方法
- publicboolean onTouchEvent(MotionEvent event) {
- //TODO Auto-generated method stub
- floaty=event.getY();//获得当前触点的Y坐标
- floatx=event.getX();//获得当前触点的X坐标
- switch(event.getAction()){
- caseMotionEvent.ACTION_MOVE:
- floatdy=y-myPreviousY;//滑动距离在y轴方向上的垂直距离
- floatdx=x-myPreviousX;//活动距离在x轴方向上的垂直距离
- myRenderer.tr.yAngle+=dx*TOUCH_SCALE_FACTOR;//设置沿y轴旋转角度
- myRenderer.tr.zAngle+=dy*TOUCH_SCALE_FACTOR;//设置沿z轴旋转角度
- requestRender();//渲染画面
- }
- myPreviousY=y;
- myPreviousX=x;
- returntrue;
- }
最终效果如下所示(有旋转),读者可以根据笛卡尔坐标,描绘出三角形的顶点,然后绘制这两个三角形,与下图一致。
说明:在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、 颜色缓存和深度缓存
此外,在openGL编程中,还经常遇到以下几种缓存:
累积缓存:与RGBA模式中的颜色缓存类似,累积缓存包含了RGBA颜色数据(在颜色索引模式中使用累积缓存将产生不可预测的结果)。它通常用于将一系列图像累加成一幅最终的合成图像。利用这种方法,可以完成类似于这样的操作:通过超量采样消除场景的锯齿现象,然后利用均匀采样生成最终绘制到颜色缓存的颜色值。像素不能直接绘制到累积缓存;累加操作常常是先在矩形块中进行的,然后再与颜色缓存进行数据交换。
4、 矩阵模式
矩阵模式指定哪一个矩阵堆栈是下一个矩阵操作的目标,可选值:GL_MODELVIEW、GL_PROJECTION、GL_TEXTURE.。glMatrixMode设置当前矩阵模式:
GL_MODELVIEW,对模型视景矩阵堆栈应用随后的矩阵操作.
GL_PROJECTION,对投影矩阵应用随后的矩阵操作.
GL_TEXTURE,对纹理矩阵堆栈应用随后的矩阵操作.
与glLoadIdentity()一同使用,该函数的功能是重置当前指定的矩阵为单位矩阵。
在glLoadIdentity()之后我们为场景设置了透视图。glMatrixMode(GL_MODELVIEW)设置当前矩阵为模型视图矩阵,模型视图矩阵储存了有关物体的信息。