最近因为兴趣所向,开始学习OpenGL绘图。本文以“画球体”为点,小结一下最近所学。


> 初识OpenGL ES

接触OpenGL是从Android开始的。众所周知,Android View 是线程不安全的,于是只允许在主线程中对View进行操作。然而假如我们需要实现复杂的界面,特别是开发游戏,在主线程中画大量图像,会耗费比较长的时间,使得主线程没能及时响应用户输入,甚至出现ANR。于是Android提供了一个 SurfaceView类,通过双缓冲机制(两块画布?三块画布?),允许用户非主线程操作Canvas,实现View的“异步”刷新。 Canvas类提供了很多画图方法: drawPoint(...) drawCircle(...) drawBitmap(...) drawRect(...) drawText(...) drawOval(...)

然后,如果想要实现比较复杂的效果(比如3D),Canvas就很难胜任了。了解了一下,目前大部分Android游戏都是用OpenGL来实现。

OpenGL是何方神圣?实际上,最终图像(不管是2D还是3D)都是显示在显示屏上,所以最终操作肯定是对一个2D的显示内存进行操作的。而OpenGL就是提供了很多方法,帮助我们定义空间立体模型,然后通过我们输入的各种参数,计算出映射矩阵,最终在显示屏幕上体现出效果。

OpenGL ES (OpenGL for Embedded Systems)是专门OpenGL的API子集,专门用于手机等嵌入式平台。简单理解就是,专门开发给“低端”的环境。删减了很多不必要的方法,留下了最基本的。


> 使用OpenGL ES画图

OpenGL ES提供了两个方法去绘制空间几何图形。 1. glDrawArrays (int mode, int first, int count); 2. glDrawElements (int mode, int count, int type, Buffer indices); 参数mode有以下取值:     GL_POINTS,     GL_LINE_STRIP,     GL_LINE_LOOP,     GL_LINES,     GL_TRIANGLES,     GL_TRIANGLE_STRIP,     GL_TRIANGLE_FAN. 画点,画线,画三角形!就这么多了!我们认为,任何空间图形都可以由点,线,或者三角形来表示。 3. glVertexPointer( ... ) 定义几何图形的所有顶点方法。调用此方法后,glDrawArrays,glDrawElements方法便会按照顶点画出图形。
因为我们接下来要画球体,是通过画非常多的三角形拼接而成(听起来挺有意思的)。所以先简单了解一下画三角形的三种模式: Android OpenGL ES 画球体_第1张图片 Android OpenGL ES 画球体_第2张图片 Android OpenGL ES 画球体_第3张图片
根据顶点的顺序, GL_TRIANGLES按三个顶点为一组独自画三角形, GL_TRIANGL_STRIP总是以最后三个顶点组成三角形, GL_TRIANGLE_FAN则是以第一个顶点为中心,后续顶点分别形成三角形。 我们接下来使用 GL_TRIANGLE_STRIP这种模式画球体。

> 使用三角形构成空间球体

我们这里利用的是极限逼近的思想。想当年,祖冲之不也是用这种思想计算出圆周率π吗。当正多边形的边数够多,看起来很像一个圆! Android OpenGL ES 画球体_第4张图片
于是,我们同样认为,当正多面体的边数够多,看起来很像一个球!
好了,思想是有了,但是我们最终并不是通过画正多面体来画。因为看起来,利用正多面体来切割一个球算起来比较麻烦。 如果用经纬线的纵横切割方法,算起来要简单很多! Android OpenGL ES 画球体_第5张图片
左右两条经线,上下两条纬线构成一个正方形(近似)。正方形可以看做是两个三角形构成。 途中土黄色的箭头,代表使用GL_TRIANGLE_STRIP模式画图时采用的顶点顺序。 这种切割方法,看起来清晰很多,纵横经纬两层循环遍历所有顶点。 关键是:怎么计算球面的顶点坐标?(x, y, z)

> 球面顶点坐标计算

首先,我们确认两个遍历方向: 第一层:从Y轴负方向开始,角度不断增加直到Y轴正方向。(时钟6点->5点->4点->3点->2点->1点->12点) 第二层:固定Y值,以Y轴为旋转轴,360度旋转。即可遍历所有顶点。 如下图,a角递增,b角做一个360度变化。 Android OpenGL ES 画球体_第6张图片
如上图,任意球面上的点,三维坐标 (x0, y0, z0) 计算:(R为球半径) x0 = R * cos(a) * sin(b); y0 = R * sin(a); z0 = R * cos(a) * cos(b);

> 源码

以下部分参考或者是抄写于:http://blog.csdn.net/wuzongpo/article/details/7230285
使用OpenGL ES绘图的一般步骤是: 1,获取EGLDisplay对象 2,初始化与EGLDisplay之间的连接 3,获取EGLConfig对象 4,创建EGLContext对象 5,创建EGLSurface实例 6,连接EGLContext与EGLSurface 7,使用GL指令画图 8,断开释放EGLContext对象 9,删除EGLSurface 10,删除EGLContext 11,终止与EGLDisplay之间的连接
Android GLSurfaceView 类,对OpenGL Api 进行了一层封装。帮忙我们管理Display,Context,Surface。我们只要实现android.opengl.GLSurfaceView.Renderer接口即可。
import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.FloatBuffer;import javax.microedition.khronos.egl.EGLConfig;import javax.microedition.khronos.opengles.GL10;import android.opengl.GLU;import android.opengl.GLSurfaceView.Renderer;public class OpenGLRenderer4 implements Renderer {// 环境光private final float[] mat_ambient = { 0.2f, 0.3f, 0.4f, 1.0f };private FloatBuffer mat_ambient_buf;// 平行入射光private final float[] mat_diffuse = { 0.4f, 0.6f, 0.8f, 1.0f };private FloatBuffer mat_diffuse_buf;// 高亮区域private final float[] mat_specular = { 0.2f * 0.4f, 0.2f * 0.6f, 0.2f * 0.8f, 1.0f };private FloatBuffer mat_specular_buf;private Sphere mSphere = new Sphere();public volatile float mLightX = 10f;public volatile float mLightY = 10f;public volatile float mLightZ = 10f;@Overridepublic void onDrawFrame(GL10 gl) {// 清楚屏幕和深度缓存gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);// 重置当前的模型观察矩阵gl.glLoadIdentity();gl.glEnable(GL10.GL_LIGHTING);gl.glEnable(GL10.GL_LIGHT0);    // 材质    gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, mat_ambient_buf);    gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, mat_diffuse_buf);    gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, mat_specular_buf);    // 镜面指数 0~128 越小越粗糙    gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 96.0f);        //光源位置    float[] light_position = {mLightX, mLightY, mLightZ, 0.0f};ByteBuffer mpbb = ByteBuffer.allocateDirect(light_position.length*4);mpbb.order(ByteOrder.nativeOrder());FloatBuffer mat_posiBuf = mpbb.asFloatBuffer();mat_posiBuf.put(light_position);mat_posiBuf.position(0);    gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, mat_posiBuf);        gl.glTranslatef(0.0f, 0.0f, -3.0f);    mSphere.draw(gl);}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {// 设置输出屏幕大小gl.glViewport(0, 0, width, height);// 设置投影矩阵gl.glMatrixMode(GL10.GL_PROJECTION);// 重置投影矩阵gl.glLoadIdentity();// 设置视口大小// gl.glFrustumf(0, width, 0, height, 0.1f, 100.0f);GLU.gluPerspective(gl, 90.0f, (float) width / height, 0.1f, 50.0f);// 选择模型观察矩阵gl.glMatrixMode(GL10.GL_MODELVIEW);// 重置模型观察矩阵gl.glLoadIdentity();}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig arg1) {// 对透视进行修正gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);// 背景:黑色gl.glClearColor(0, 0.0f, 0.0f, 0.0f);// 启动阴影平滑gl.glShadeModel(GL10.GL_SMOOTH);// 复位深度缓存gl.glClearDepthf(1.0f);// 启动深度测试gl.glEnable(GL10.GL_DEPTH_TEST);// 所做深度测试的类型gl.glDepthFunc(GL10.GL_LEQUAL);initBuffers();}private void initBuffers() {ByteBuffer bufTemp = ByteBuffer.allocateDirect(mat_ambient.length * 4);bufTemp.order(ByteOrder.nativeOrder());mat_ambient_buf = bufTemp.asFloatBuffer();mat_ambient_buf.put(mat_ambient);mat_ambient_buf.position(0);bufTemp = ByteBuffer.allocateDirect(mat_diffuse.length * 4);bufTemp.order(ByteOrder.nativeOrder());mat_diffuse_buf = bufTemp.asFloatBuffer();mat_diffuse_buf.put(mat_diffuse);mat_diffuse_buf.position(0);bufTemp = ByteBuffer.allocateDirect(mat_specular.length * 4);bufTemp.order(ByteOrder.nativeOrder());mat_specular_buf = bufTemp.asFloatBuffer();mat_specular_buf.put(mat_specular);mat_specular_buf.position(0);}}

import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.FloatBuffer;import javax.microedition.khronos.opengles.GL10;// 计算球面顶点public class Sphere {public void draw(GL10 gl) {floatangleA, angleB;    floatcos, sin;    floatr1, r2;    floath1, h2;    floatstep = 30.0f;    float[][] v = new float[32][3];    ByteBuffer vbb;    FloatBuffer vBuf;    vbb = ByteBuffer.allocateDirect(v.length * v[0].length * 4);        vbb.order(ByteOrder.nativeOrder());        vBuf = vbb.asFloatBuffer();    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);    gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);        for (angleA = -90.0f; angleA < 90.0f; angleA += step) {    intn = 0;            r1 = (float)Math.cos(angleA * Math.PI / 180.0);    r2 = (float)Math.cos((angleA + step) * Math.PI / 180.0);    h1 = (float)Math.sin(angleA * Math.PI / 180.0);    h2 = (float)Math.sin((angleA + step) * Math.PI / 180.0);    // 固定纬度, 360 度旋转遍历一条纬线    for (angleB = 0.0f; angleB <= 360.0f; angleB += step) {       cos = (float)Math.cos(angleB * Math.PI / 180.0);    sin = -(float)Math.sin(angleB * Math.PI / 180.0);    v[n][0] = (r2 * cos);    v[n][1] = (h2);    v[n][2] = (r2 * sin);    v[n + 1][0] = (r1 * cos);    v[n + 1][1] = (h1);    v[n + 1][2] = (r1 * sin);    vBuf.put(v[n]);    vBuf.put(v[n + 1]);    n += 2;          if(n>31){    vBuf.position(0);        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vBuf);        gl.glNormalPointer(GL10.GL_FLOAT, 0, vBuf);    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, n);        n = 0;    angleB -= step;    }        }vBuf.position(0);    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vBuf);    gl.glNormalPointer(GL10.GL_FLOAT, 0, vBuf);gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, n);    }        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);    gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);}}

import android.content.Context;import android.opengl.GLSurfaceView;import android.view.MotionEvent;public class OpenGLView extends GLSurfaceView {private OpenGLRenderer4 mRenderer;private float mDownX = 0.0f;private float mDownY = 0.0f;public OpenGLView(Context context) {super(context);mRenderer = new OpenGLRenderer4();this.setRenderer(mRenderer);}@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = event.getActionMasked();switch (action) {case MotionEvent.ACTION_DOWN:mDownX = event.getX();mDownY = event.getY();return true;case MotionEvent.ACTION_UP:return true;case MotionEvent.ACTION_MOVE:float mX = event.getX();float mY = event.getY();mRenderer.mLightX += (mX-mDownX)/10;mRenderer.mLightY -= (mY-mDownY)/10;mDownX = mX;mDownY = mY;return true;default:return super.onTouchEvent(event);}}}

import android.os.Bundle;import android.app.Activity;import android.view.Window;import android.view.WindowManager;public class MainActivity extends Activity {private OpenGLView mOpenGLView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 去标题栏requestWindowFeature(Window.FEATURE_NO_TITLE);//设置全屏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);mOpenGLView = new OpenGLView(this);setContentView(mOpenGLView);}}

> 效果图

step = 30.0f Android OpenGL ES 画球体_第7张图片

step = 2.0f Android OpenGL ES 画球体_第8张图片

关于光照效果,我们以后有空再讨论。

更多相关文章

  1. Android 3D 旋转的三角形(一)
  2. Android drawable 三角形
  3. Android下使用OpenGL绘制三角形
  4. Android svg 绘制三角形
  5. Android 3D 旋转的三角形(二)
  6. Android 3D 旋转的三角形(四)
  7. Android 3D 旋转的三角形(三)

随机推荐

  1. android移除数据
  2. android的欢迎界面splash screen
  3. Android(安卓)OpenGL ES从白痴到入门(二)
  4. 浏览Butter Knife源码收获
  5. android webView调用js函数的几种方法
  6. Android(安卓)游戏开发 SurfaceView框架
  7. android pdf
  8. Android(安卓)studio 的安装
  9. Android(安卓)实战之UI线程和Worker线程
  10. Android(安卓)Studio迁移到AndroidX详细