Android OpenGL ES 畫球體

來源:互聯網
上載者:User

標籤: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這種模式畫球體。
> 使用三角形構成空間球體我們這裡利用的是極限逼近的思想。想當年,祖沖之不也是用這種思想計算出圓周率π嗎。當正多邊形的邊數夠多,看起來很像一個圓!
於是,我們同樣認為,當正多面體的邊數夠多,看起來很像一個球!
好了,思想是有了,但是我們最終並不是通過畫正多面體來畫。因為看起來,利用正多面體來切割一個球算起來比較麻煩。如果用經緯線的縱橫切割方法,算起來要簡單很多!
左右兩條經線,上下兩條緯線構成一個正方形(近似)。正方形可以看做是兩個三角形構成。途中土黃色的箭頭,代表使用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與EGLSurface7,使用GL指令畫圖8,斷開釋放EGLContext對象9,刪除EGLSurface10,刪除EGLContext11,終止與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

step = 2.0f

關於光照效果,我們以後有空再討論。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.