• Android OpenGL ES 画球体


    近期由于兴趣所向。開始学习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;
    
    	@Override
    	public 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);
    	}
    
    	@Override
    	public 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();
    
    	}
    
    	@Override
    	public 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) {
    
    		float	angleA, angleB;
        	float	cos, sin;
        	float	r1, r2;
        	float	h1, h2;
        	float	step = 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) {
        		int	n = 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);
    	}
    
    	@Override
    	public 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;
    
    	@Override
    	protected 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


    step = 2.0f


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


  • 相关阅读:
    redis主从架构
    redis持久化
    git 首次push失败
    Java8 CompletableFuture
    Mac Item2自动远程连接服务器
    Java8 日期和时间类
    【LeetCode】31. 下一个排列
    【LeetCode】30. 串联所有单词的子串
    【LeetCode】29. 两数相除
    【LeetCode】28. 实现 strStr()
  • 原文地址:https://www.cnblogs.com/mqxnongmin/p/10634765.html
Copyright © 2020-2023  润新知