Android OpenGL ES 画球体
16lz
2021-01-23
最近因为兴趣所向,开始学习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方法便会按照顶点画出图形。因为我们接下来要画球体,是通过画非常多的三角形拼接而成(听起来挺有意思的)。所以先简单了解一下画三角形的三种模式:
根据顶点的顺序, GL_TRIANGLES按三个顶点为一组独自画三角形, GL_TRIANGL_STRIP总是以最后三个顶点组成三角形, GL_TRIANGLE_FAN则是以第一个顶点为中心,后续顶点分别形成三角形。 我们接下来使用 GL_TRIANGLE_STRIP这种模式画球体。
> 使用三角形构成空间球体
我们这里利用的是极限逼近的思想。想当年,祖冲之不也是用这种思想计算出圆周率π吗。当正多边形的边数够多,看起来很像一个圆!于是,我们同样认为,当正多面体的边数够多,看起来很像一个球!
好了,思想是有了,但是我们最终并不是通过画正多面体来画。因为看起来,利用正多面体来切割一个球算起来比较麻烦。 如果用经纬线的纵横切割方法,算起来要简单很多!
左右两条经线,上下两条纬线构成一个正方形(近似)。正方形可以看做是两个三角形构成。 途中土黄色的箭头,代表使用GL_TRIANGLE_STRIP模式画图时采用的顶点顺序。 这种切割方法,看起来清晰很多,纵横经纬两层循环遍历所有顶点。 关键是:怎么计算球面的顶点坐标?(x, y, z)
> 球面顶点坐标计算
首先,我们确认两个遍历方向: 第一层:从Y轴负方向开始,角度不断增加直到Y轴正方向。(时钟6点->5点->4点->3点->2点->1点->12点) 第二层:固定Y值,以Y轴为旋转轴,360度旋转。即可遍历所有顶点。 如下图,a角递增,b角做一个360度变化。如上图,任意球面上的点,三维坐标 (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.0fstep = 2.0f
关于光照效果,我们以后有空再讨论。
更多相关文章
- Android 3D 旋转的三角形(一)
- Android drawable 三角形
- Android下使用OpenGL绘制三角形
- Android svg 绘制三角形
- Android 3D 旋转的三角形(二)
- Android 3D 旋转的三角形(四)
- Android 3D 旋转的三角形(三)