Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android OpenGL ES 畫球體

Android OpenGL ES 畫球體

編輯:關於Android編程

最近因為興趣所向,開始學習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這種模式畫球體。

> 使用三角形構成空間球體

我們這裡利用的是極限逼近的思想。想當年,祖沖之不也是用這種思想計算出圓周率π嗎。當正多邊形的邊數夠多,看起來很像一個圓! \
於是,我們同樣認為,當正多面體的邊數夠多,看起來很像一個球! vcw==" alt="\" src="/uploadfile/Collfiles/20140704/20140704090113128.jpg" width="758" height="269">
好了,思想是有了,但是我們最終並不是通過畫正多面體來畫。因為看起來,利用正多面體來切割一個球算起來比較麻煩。 如果用經緯線的縱橫切割方法,算起來要簡單很多! \
左右兩條經線,上下兩條緯線構成一個正方形(近似)。正方形可以看做是兩個三角形構成。 途中土黃色的箭頭,代表使用GL_TRIANGLE_STRIP模式畫圖時采用的頂點順序。 這種切割方法,看起來清晰很多,縱橫經緯兩層循環遍歷所有頂點。 關鍵是:怎麼計算球面的頂點坐標?(x, y, z)

> 球面頂點坐標計算

首先,我們確認兩個遍歷方向: 第一層:從Y軸負方向開始,角度不斷增加直到Y軸正方向。(時鐘6點->5點->4點->3點->2點->1點->12點) 第二層:固定Y值,以Y軸為旋轉軸,360度旋轉。即可遍歷所有頂點。 如下圖,a角遞增,b角做一個360度變化。 seogKHgwLCB5MCwgejApILzGy+OjuihSzqrH8rDrvrYpCngwID0gUiAqIGNvcyhhKSAqIHNpbihiKTsKeTAgPSBSICogc2luKGEpOwp6MCA9IFIgKiBjb3MoYSkgKiBjb3MoYik7Cjxicj4KCjxoMj4+INS0wus8L2gyPgrS1M/Csr+31rLOv7y78tXfysezrdC009qjumh0dHA6Ly9ibG9nLmNzZG4ubmV0L3d1em9uZ3BvL2FydGljbGUvZGV0YWlscy83MjMwMjg1Cjxicj4KCsq508NPcGVuR0wgRVO75s28tcTSu7Djsr3W6MrHo7oKMaOsu/HIoUVHTERpc3BsYXm21M/zCjKjrLP1yry7r9PrRUdMRGlzcGxhedauvOS1xMGsvdMKM6Osu/HIoUVHTENvbmZpZ7bUz/MKNKOstLS9qEVHTENvbnRleHS21M/zCjWjrLS0vahFR0xTdXJmYWNlyrXA/Qo2o6zBrL3TRUdMQ29udGV4dNPrRUdMU3VyZmFjZQo3o6zKudPDR0zWuMHuu63NvAo4o6y2z7+qys23xUVHTENvbnRleHS21M/zCjmjrMm+s/1FR0xTdXJmYWNlCjEwo6zJvrP9RUdMQ29udGV4dAoxMaOs1tXWudPrRUdMRGlzcGxhedauvOS1xMGsvdMKPGJyPgoKQW5kcm9pZCBHTFN1cmZhY2VWaWV3IMDgo6y21E9wZW5HTCBBcGkgvfjQ0MHL0ruy47fi17Cho7Dvw6bO0sPHudzA7URpc3BsYXksQ29udGV4dCxTdXJmYWNloaPO0sPH1rvSqsq1z9ZhbmRyb2lkLm9wZW5nbC5HTFN1cmZhY2VWaWV3LlJlbmRlcmVyvdO/2ry0v8mhowo8YnI+Cgo8cHJlIGNsYXNzPQ=="brush:java;">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

關於光照效果,我們以後有空再討論。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved