Android使用opengl来绘制图形时主要依赖于GLSurfaceView,它的绘制流程如下
获取EGLDisplay对象
初始化与EGLDisplay 之间的连接。
获取EGLConfig对象
创建EGLContext 实例
创建EGLSurface实例
连接EGLContext和EGLSurface.
使用GL指令绘制图形
断开并释放与EGLSurface关联的EGLContext对象
删除EGLSurface对象
删除EGLContext对象
终止与EGLDisplay之间的连接。
下面看一下GLSurfaceView类的继承关系
java.lang.ObjectAndroid APP如果需要使用OpenGl制图,如果在java层实现,一般是GLSurfaceView来显示出OpenGl制图,GLSurfaceView的特点:
1.管理一个平面,这个平面是一个特殊的内存块,它可以和Android视图系统混合.
2.管理一个EGL显示,它能够让OpenGL渲染到一个平面.
3.接受一个用户提供的实际显示的Renderer对象.
4.使用一个专用线程去渲染从而和UI线程解耦.
5.支持连续的渲染和丰富的动画效果.
通过继承关系我们可以发现它是继承自View的,我们都知道View的绘制原理,包括测量,创建画布和绘制。那么GLSurfaceView的原理其实是一样的,只是它的坐标系多了一个z轴,来实现3D的效果。下面是
GLSurfaceView,SurfaceView,View各自的优缺点
View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等;必须在UI主线程内更新画面,速度较慢。
SurfaceView:基于view视图进行拓展的视图类,更适合2D游戏的开发;是view的子类,类似使用双缓机制,在新的线程中更新画面所以刷新界面速度比view快。
GLSurfaceView:基于SurfaceView视图再次进行拓展的视图类,专用于3D游戏开发的视图;是SurfaceView的子类,OpenGL专用。
Android自定义控件,有了视图View、画布Canvas、画笔Paint,方能绘制炫彩多姿的各种控件。那么对于OpenGL的三维绘图来说,也同样需要具备这三种要素,分别是GLSurfaceView、GLSurfaceView.Renderer和GL10,其中GLSurfaceView继承自表面视图SurfaceView,对应于二维绘图的View;GLSurfaceView.Renderer是三维图形的渲染器,对应于二维绘图的Canvas;最后一个GL10自然相当于二维绘图的Paint了。有了GLSurfaceView、GLRender和GL10这三驾马车,Android才能实现OpenGL的三维图形渲染功能。
使用GLSurfaceView来绘制图形,主要是实现一个Renderer接口,来实现里面的方法,包括
1、onSurfaceCreated函数在GLSurfaceView创建时调用,相当于自定义控件的构造函数,一样可在此进行三维绘图的初始化操作;
2、onSurfaceChanged函数在GLSurfaceView创建、恢复与改变时调用,在这里不但要定义三维空间的大小,还要定义三维物体的方位,所以该函数相当于完成了自定义控件的onMeasure和onLayout两个函数的功能;
3、onDrawFrame顾名思义跟自定义控件的onDraw函数差不多,onDraw函数用于绘制二维图形的具体形状,而onDrawFrame函数用于绘制三维图形的具体形状;
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { //初始化 gl.glClearColor(0f, 0f, 0f, 0f); // 启动阴影平滑 gl.glShadeModel(GL10.GL_SMOOTH); }
@Override public void onSurfaceChanged(GL10 gl, int width, int height) {
// 设置输出屏幕大小
gl.glViewport(0, 0, width, height);
// 设置投影矩阵,对应gluPerspective(调整相机)、glFrustumf(调整透视投影)、glOrthof(调整正投影)
gl.glMatrixMode(GL10.GL_PROJECTION);
// 设置透视图视窗大小
GLU.gluPerspective(gl, 40, (float) width / height, 0.1f, 20.0f);
//选择模型观察矩阵
gl.glMatrixMode(GL10.GL_MODELVIEW);
// 重置投影矩阵,即去掉所有的平移、缩放、旋转操作
gl.glLoadIdentity();
}
@Override public void onDrawFrame(GL10 gl) {
//具体的绘制的过程
// 清除屏幕和深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 重置当前的模型观察矩阵 gl.glLoadIdentity(); // 设置画笔颜色 gl.glColor4f(0f, 1f, 0f, 1f); //gl.glColor4x(0,0,1,1); // 设置镜头的方位 GLU.gluLookAt(gl, 5.0f, 4.0f, 3.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
前面的三个参数是相机放置的位置在世界坐标系中的坐标,中间三个参数表示被观察的物体在世界坐标系中的位置,后面3个表示相机的
顶部的朝向,例如第一个如果为1表示相机的顶部向右偏离,类似于人的眼睛观察物体,头顶的朝向
}定义图形时定义相应的xyz轴上的坐标值(x,y,z)
在坐标系中绘制图形需要知道相关的额坐标点,三维的话应该是xyz对应的值,比如(0,1,0)就代表一个点
不过这个浮点数组并不能直接传给OpenGL处理,因为OpenGL的底层是用C语言实现的,C语言与其它语言(如Java)默认的数据存储方式在字节顺序上可能不同(如大端小端问题),所以其它语言的数据结构必须转换成C语言能够识别的形式,说白了就是翻译。这里面C语言能听懂的数据结构名叫FloatBuffer,于是问题的实质就变成了如何将浮点数组folat[]转换为浮点缓存FloatBuffer,具体的转换过程已经有了现成的模板,开发者只管套进去即可,详细的转换函数代码如下所示:
public FloatBuffer getFloatBuffer(float[] array) { //初始化字节缓冲区的大小=数组长度*数组元素大小。float类型的元素大小为Float.SIZE, //int类型的元素大小为Integer.SIZE,double类型的元素大小为Double.SIZE。 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length * Float.SIZE); //以本机字节顺序来修改字节缓冲区的字节顺序 //OpenGL在底层的实现是C语言,与Java默认的数据存储字节顺序可能不同,即大端小端问题。 //因此,为了保险起见,在将数据传递给OpenGL之前,需要指明使用本机的存储顺序 byteBuffer.order(ByteOrder.nativeOrder()); //根据设置好的参数构造浮点缓冲区 FloatBuffer floatBuffer = byteBuffer.asFloatBuffer(); //把数组数据写入缓冲区 floatBuffer.put(array); //设置浮点缓冲区的初始位置 floatBuffer.position(0); return floatBuffer; }
public class OpenTrangle extends GLSurfaceView implements GLSurfaceView.Renderer { private FloatBuffer mFloatBuffer, mNextFloatBuffer; private float mAngle=0.5f; public OpenTrangle(Context context) { this(context, null); } public OpenTrangle(Context context, AttributeSet set) { this(context, set, 0); } public OpenTrangle(Context context, AttributeSet set, int def) { super(context, set); setRenderer(this); } //opengl created @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { //the background of the canvas gl.glClearColor(1f, 1f, 1f, 0f); //set the mode of shade gl.glShadeModel(GL10.GL_SMOOTH); } //size change @Override public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL10.GL_PROJECTION); //adjust the camera GLU.gluPerspective(gl, 40, (float) width / height, 0.1f, 20.0f); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); } //draw the view @Override public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT); gl.glColor4f(0, 1, 0, 30); drawShape(gl, -2f); if (mAngle > 30) { mAngle = 0.1f; } else { mAngle += 0.1f; } } private void drawShape(GL10 gl, float transLenght) { for (int i = 0; i < 8; i++) { float[] mNextPoints = { 0f, 2f + transLenght * i, 1f, 2f, 1f + transLenght * i, 1f, 2f, 0f + transLenght * i, 1f, 0f, 1f + transLenght * i, 1f, -2f, 0f + transLenght * i, 1f, -2f, 1f + transLenght * i, 1f }; gl.glLoadIdentity(); gl.glRotatef(mAngle, -1, 0, 0); GLU.gluLookAt(gl, 0f, -15f+i, 9f-i, 0.0f, 0f, .0f, 0f, 1f, 0f);
mNextFloatBuffer = getFloatBuffer(mNextPoints);
// 启用顶点开关
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
//size: 每个顶点有几个数值描述。必须是2,3 ,4 之一。 //type: 数组中每个顶点的坐标类型。取值:GL_BYTE, GL_SHORT, GL_FIXED, GL_FLOAT。 //stride:数组中每个顶点间的间隔,步长(字节位移)。取值若为0,表示数组是连续的 //pointer:即存储顶点的Buffer
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mNextFloatBuffer);
// 用画线的方式将点连接并画出来 //GL_POINTS ————绘制独立的点 //GL_LINE_STRIP————绘制连续的线段,不封闭 //GL_LINE_LOOP————绘制连续的线段,封闭 //GL_LINES————顶点两两连接,为多条线段构成 //GL_TRIANGLES————每隔三个顶点构成一个三角形 //GL_TRIANGLE_STRIP————每相邻三个顶点组成一个三角形 //GL_TRIANGLE_FAN————以一个点为三角形公共顶点,组成一系列相邻的三角形
gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 6);
//关闭定点开关
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
}
public FloatBuffer getFloatBuffer(float[] array) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length * Float.SIZE);
byteBuffer.order(ByteOrder.nativeOrder()); FloatBuffer floatBuffer = byteBuffer.asFloatBuffer(); floatBuffer.put(array); floatBuffer.position(0); return floatBuffer; } }
7.总结相关的使用
public class OpenTrangle extends GLSurfaceView implements GLSurfaceView.Renderer { private FloatBuffer mNextFloatBuffer; private float mAngle = 0.5f; public OpenTrangle(Context context) { this(context, null); } public OpenTrangle(Context context, AttributeSet set) { this(context, set, 0); } public OpenTrangle(Context context, AttributeSet set, int def) { super(context, set); setRenderer(this); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glClearColor(1f, 1f, 1f, 0f); gl.glShadeModel(GL10.GL_SMOOTH); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL10.GL_PROJECTION); GLU.gluPerspective(gl, 40, (float) width / height, 0.1f, 20.0f); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); } @Override public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT); gl.glColor4f(0, 1, 0, 30); drawShape(gl, -2f); if (mAngle > 30) { mAngle = 0.1f; } else { mAngle += 0.1f; } } private void drawShape(GL10 gl, float transLenght) { for (int i = 0; i < 8; i++) { float[] mNextPoints = { 0f, 2f + transLenght * i, 1f, 2f, 1f + transLenght * i, 1f, 2f, 0f + transLenght * i, 1f, 0f, 1f + transLenght * i, 1f, -2f, 0f + transLenght * i, 1f, -2f, 1f + transLenght * i, 1f }; gl.glLoadIdentity(); gl.glRotatef(mAngle, 1, 0, 0); GLU.gluLookAt(gl, 0f, -15f + i, 9f - i, 0.0f, 0f, .0f, 0f, 1f, 0f); mNextFloatBuffer = getFloatBuffer(mNextPoints); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mNextFloatBuffer); gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 6); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); } } public FloatBuffer getFloatBuffer(float[] array) { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length * Float.SIZE); byteBuffer.order(ByteOrder.nativeOrder()); FloatBuffer floatBuffer = byteBuffer.asFloatBuffer(); floatBuffer.put(array); floatBuffer.position(0); return floatBuffer; } }
最后在使用的时候作为自定义的view去使用就可以了