什么是 OpenGL?
OpenGL 是个专业的3D程序接口,是一个功能强大,调用方便的底层3D图形库。
android.opengl包提供了 OpenGL 系统和 Android GUI 系统之间的联系。
最基本的几个类:
1. android.opengl.GLSurfaceView
使用Activity来显示GLSurfaceView组件。通过setContentView(GLSurfaceView);
2. android.opengl.GLSurfaceView.Renderer
渲染图层的通过GLSurfaceView.setRendere(GLSurfaceView.Renderer);
abstract void onDrawFrame(GL10 gl) //绘制GLSurfaceView的当前帧 abstract void onSurfaceCreated(GL10 gl, EGLConfig config) //当GLSurfaceView被创建时回调该方法。
abstract void onSurfaceChanged(GL10 gl, int width, int height) //当GLSurfaceView的大小改变的时回调该方法。
3. javax.microedition.khronos.opengles.GL10
Graphics Library后面的数字,很简单GLES20是2.0用的,GL10是1.0用的。
4. java.nio.ByteBuffer
byte 数据缓冲区,Java 是大端字节序(BigEdian),而 OpenGL 所需要的数据是小端字节序(LittleEdian)。
所以,我们在将 Java 的缓冲区转化为 OpenGL 可用的缓冲区时需要作一些工作。
不管我们的数据是整型的还是浮点型的,为了完成 BigEdian 到 LittleEdian 的转换,我们都首先需要一个 ByteBuffer
onSurfaceCreated:
public void onSurfaceCreated(GL10 gl, EGLConfig config) { // TODO Auto-generated method stub // 启用阴影平滑 gl.glShadeModel(GL10.GL_SMOOTH); // 黑色背景 gl.glClearColor(0, 0, 0, 0); // 设置深度缓存 gl.glClearDepthf(122.0f); // 启用深度测试 gl.glEnable(GL10.GL_DEPTH_TEST); // 所作深度测试的类型 gl.glDepthFunc(GL10.GL_LEQUAL); // 告诉系统对透视进行修正 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); }
glHint :用于告诉 OpenGL 我们希望进行最好的透视修正,这会轻微地影响性能,但会使得透视图更好看。
glClearColor:设置清除屏幕时所用的颜色,色彩值的范围从 0.0f~1.0f 大小从暗到这的过程。也可以用一下代码。
*gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT); // 清楚屏幕和深度缓存
glShadeModel:用于启用阴影平滑度。阴影平滑通过多边形精细地混合色彩,并对外部光进行平滑。
glDepthFunc: 为将深度缓存设想为屏幕后面的层,它不断地对物体进入屏幕内部的深度进行跟踪。
glEnable: 启用深度测试。
onSurfaceChanged:
public void onSurfaceChanged(GL10 gl, int width, int height) { // TODO Auto-generated method stub float ratio = (float) width / height; // 设置OpenGL场景的大小 gl.glViewport(0, 0, width, height); // 设置投影矩阵 gl.glMatrixMode(GL10.GL_PROJECTION); // 重置投影矩阵 gl.glLoadIdentity(); // 设置视口的大小 gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10); // 选择模型观察矩阵 gl.glMatrixMode(GL10.GL_MODELVIEW); // 重置模型观察矩阵 gl.glLoadIdentity(); }
添加透视图:
我们需要将一个坐标系和我们的GLSurfaceView里的Surface做一个映射关系。
glMatrixMode(GL10.GL_PROJECTION); 是说我们现在改变的是坐标系与Surface的映射关系(投影矩阵)。
gl.glLoadIdentity(); 是将以前的改变都清掉(之前对投影矩阵的任何改变)。
glFrustumf (float left, float right, float bottom, float top, float zNear, float zFar) 它实现了Surface和坐标系之间的映射关系。它是以透视投影的方式来进行映射的。
透视投影的意思见下图:
glMatrixMode函数的选项(参数)有后面三种:
顺便说下,OpenGL里面的操作,很多是基于对矩阵的操作的,比如位移,旋转,缩放,所以,
这里其实说的规范一点就是glMatrixMode是用来指定哪一个矩阵是当前矩阵,而它的参数代表要操作的目标:
onDrawFrame:
public void onDrawFrame(GL10 gl) { // TODO Auto-generated method stub // 清除屏幕和深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 重置当前的模型观察矩阵 gl.glLoadIdentity(); // 左移 1.5 单位,并移入屏幕 6.0 gl.glTranslatef(-1.5f, 0.0f, -6.0f); // 设置旋转 gl.glRotatef(rotateTri, 0.0f, 1.0f, 0.0f); // 设置定点数组 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // 设置颜色数组 gl.glEnableClientState(GL10.GL_COLOR_ARRAY); ByteBuffer vbb = ByteBuffer.allocateDirect(colors.length * 4); vbb.order(ByteOrder.nativeOrder()); colorBuffer = vbb.asIntBuffer(); colorBuffer.put(colors); colorBuffer.position(0); gl.glColorPointer(4, GL10.GL_FIXED, 0, colorBuffer); vbb.clear(); vbb = ByteBuffer.allocateDirect(trigger.length * 4); vbb.order(ByteOrder.nativeOrder()); triggerBuffer = vbb.asIntBuffer(); triggerBuffer.put(trigger); triggerBuffer.position(0); // 设置三角形顶点 gl.glVertexPointer(3, GL10.GL_FIXED, 0, triggerBuffer); // 绘制三角形 gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
//取消颜色数组 gl.glDisableClientState(GL10.GL_COLOR_ARRAY); // 绘制三角形结束 gl.glFinish(); // 重置当前的模型观察矩阵 gl.glLoadIdentity(); // 左移 1.5 单位,并移入屏幕 6.0 gl.glTranslatef(1.5f, 0.0f, -6.0f); // 设置当前色为蓝色 gl.glColor4f(0.5f, 0.5f, 1.0f, 1.0f); // 设置旋转 gl.glRotatef(rotateQuad, 1.0f, 0.0f, 0.0f); vbb.clear(); vbb = ByteBuffer.allocateDirect(quates.length * 4); vbb.order(ByteOrder.nativeOrder()); quateBuffer = vbb.asIntBuffer(); quateBuffer.put(quates); quateBuffer.position(0); // 设置和绘制正方形 gl.glVertexPointer(3, GL10.GL_FIXED, 0, quateBuffer); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4); // 绘制正方形结束 gl.glFinish(); // 取消顶点数组 gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); // 改变旋转的角度 rotateTri += 0.5f; rotateQuad -= 0.5f; }
先清空缓存:
// 清除屏幕和深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 重置当前的模型观察矩阵
gl.glLoadIdentity();
构图:
有关位置,旋转相关就不说明了重要的是为什么使用ByteBuffer:
不管我们的数据是整型的还是浮点型的,为了完成 BigEdian 到 LittleEdian 的转换,我们都首先需要一个 ByteBuffer。我们通过它来得到一个可以改变字节序的缓冲区。
glVertexPointer 设置顶点位置数据时,需要ByteBuffer等,必须是native Buffer 。
对于FloatBuffer不可以直接用FloatBuffer.wrap将float[]数组转为FloatBuffer,会报如下错误
“ Must use a native order direct Buffer”
可以使用如下函数进行转化:
private FloatBuffer floatBufferUtil(float[] arr) { FloatBuffer mBuffer; // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节 ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4); // 数组排列用nativeOrder qbb.order(ByteOrder.nativeOrder()); mBuffer = qbb.asFloatBuffer(); mBuffer.put(arr); mBuffer.position(0); return mBuffer; }
在下一个图形绘制之前需要把颜色数组清空了。
//取消颜色数组 gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
对应的是
// 设置颜色数组 gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
不取消颜色数组那么下一个图与第一个图的颜色是一样。
但是不要忘了不要把顶点数组给清空了,那么第一个图就不显示了。相关代码如下:
// 取消顶点数组 gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); 对应的 // 设置定点数组 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
设置顶点坐标与取消顶点数组都仅仅是通知GL,颜色也一样。
加载数据:
那么具体的数据当然是数组的形式
gl.glVertexPointer(3, GL10.GL_FIXED, 0, VertexBuffer);
gl.glColorPointer(4, GL10.GL_FIXED, 0, colorBuffer);
这两句话分别绑定了顶点数据数组和颜色数据数组。
其中第一个参数表示的是每个点有几个坐标。例如顶点,有 x、y、z值,所以是 3;
而颜色是 r、g、b、a 值,所以是 4。
绘图:
数据有了那么需要画出来。
// 设置和绘制正方形 gl.glVertexPointer(3, GL10.GL_FIXED, 0, quateBuffer); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
第一个参数指明了画图的类型——三角形(android 似乎只支持画三角形、点、线,不支持画多边形)。
后面两个参数指明,从哪个顶点开始画,画多少个顶点。
还有第二个图开始画的时候必要的时候重置一下
// 重置当前的模型观察矩阵 gl.glLoadIdentity();
加载surfaceView:
public class AndroidGLDemo extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GLSurfaceView glView = new GLSurfaceView(this); AndroidGLDemoRenderer renderer = new AndroidGLDemoRenderer(); glView.setRenderer(renderer); setContentView(glView); } }