從這周開始學習Android的3D繪圖。網絡上已經有大量有關OpenGL的好教程和書籍。但是,卻沒有多少是關於OpenGLES,更加沒有多少是專門針對學習android上3D編程的。為了養成良好的學習習慣,也算是給自己的學習過程做一個總結、筆記,我決定按照自己的學習規矩,撰寫一個針對android3D初學者的博文系列。這是此系列的第一篇文章。
相關閱讀:
Android 3D 系列之光效篇 http://www.linuxidc.com/Linux/2011-09/43749.htm
Android 3D系列之入門實踐篇 http://www.linuxidc.com/Linux/2011-09/43751.htm
Android 3D 系列之紋理篇 http://www.linuxidc.com/Linux/2011-09/43753.htm
為了方便後邊的編程,我們第一篇文章主要是介紹一些關於OpenGLES基本的概念。
點
3D圖像的最小單位稱為點(point)或者頂點vertex。它們代表三維空間中的一個點並用來建造更復雜的物體。多邊形就是由點構成,而物體是由多個多邊形組成。盡管通常OpenGL支持多種多邊形,但OpenGLEs只支持三邊形(即三角形)所以即使我們要繪制一個正方形也要把它拆分為兩個三角形繪制。先說說坐標系的問題。
默認情況下,以屏幕中心為坐標軸原點。原點左方x為負值,右邊為正值。原點上方y為正,原點下方為負。垂直屏幕向外為z正,垂直屏幕向裡為z負。默認情況下,從原點到屏幕邊緣為1.0f,沿各軸增加或減小的數值是以任意刻度進行的–它們不代表任何真實單位,如英尺,像素或米等。你可以選擇任何對你的程序有意義的刻度(全局必須保持單位一致,不能一部分使用米,一部分使用像素)。OpenGL只是將它作為一個參照單位處理,保證它們具有相同的距離。如圖:
了解了坐標軸,我們來看看怎麼在坐標系中表示一個點,通常用一組浮點數來表示點。例如一個正方形的4個頂點可表示為:
float vertices[] = {
-1.0f, 1.0f, 0.0f, //左上
-1.0f, -1.0f, 0.0f, //左下
1.0f, -1.0f, 0.0f, //右下
1.0f, 1.0f, 0.0f, //右上
};
為了提高性能,通常還需要將浮點數組存入一個字節緩沖中。所以有了下面的操作:
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); //申請內存
vbb.order(ByteOrder.nativeOrder()); //設置字節順序,其中ByteOrder.nativeOrder()是獲取本機字節順序
FloatBuffer vertexBuffer = vbb.asFloatBuffer(); //轉換為float型
vertexBuffer.put(vertices); //添加數據
vertexBuffer.position(0); //設置緩沖區起始位置
OpenGLES的很多函數功能的使用狀態是處於關閉的。啟用和關閉這些函數可以用glEnableClientState、glDisableClientState來完成。
// 指定需要啟用定點數組
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 說明啟用數組的類型和字節緩沖,類型為GL_FLOAT
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
// 不再需要時,關閉頂點數組
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
多邊形
多邊形是由點和邊構成的單閉合環。 繪制多邊形時需要特別注意頂點的繪制順序,可以分為順時針和逆時針。因為方向決定了多邊形的朝向, 即正面和背面。避免渲染那些被遮擋的部分可以了有效提高程序性能。默認以逆時針次序繪制頂點的構成的面是正面。
可以通過glFrontFace函數來交換“正面”和“反面”的概念。
glFrontFace(GL_CCW); // 設置CCW方向為“正面”,CCW即CounterClockWise,逆時針
glFrontFace(GL_CW); // 設置CW方向為“正面”,CW即ClockWise,順時針
渲染
有了以上的概念講解後,現在要進行最主要的工作—渲染。渲染是把物體坐標所指定的圖元轉化成幀緩沖區中的圖像。圖像和頂點坐標有著密切的關系。這個關系通過繪制模式給出。常用到得繪制模式有GL_POINTS、GL_LINE_STRIP、GL_LINE_LOOP、GL_LINES、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。下面分別介紹:
? GL_POINTS:把每一個頂點作為一個點進行處理,頂點n即定義了點n,共繪制n個點。
? GL_LINES:把每一個頂點作為一個獨立的線段,頂點2n-1和2n之間共定義了n個線段,總共繪制N/2條線段。,如果N為奇數,則忽略最後一個頂點。
? GL_LINE_STRIP:繪制從第一個頂點到最後一個頂點依次相連的一組線段,第n和n+1個頂點定義了線段n,總共繪制N-1條線段。
? GL_LINE_LOOP:繪制從定義第一個頂點到最後一個頂點依次相連的一組線段,然後最後一個頂點與第一個頂點相連。第n和n+1個頂點定義了線段n,然後最後一個線段是由頂點N和1之間定義,總共繪制N條線段。
? GL_TRIANGLES:把每三個頂點作為一個獨立的三角形。頂點3n-2,3n-1和3n定義了第n個三角形,總共繪制N/3個三角形。
? GL_TRIANGLE_STRIP:繪制一組相連的三角形。對於奇數點n,頂點n,n+1和n+2定義了第n個三角形;對於偶數n,頂點n+1,n和n+2定義了第n個三角形,總共繪制N-2個三角形。這是最常使用的渲染方式,第一個三角形條是由前三個頂點構成(索引0,1, 2)。第二個三角形條是由前一個三角形的兩個頂點加上數組中的下一個頂點構成,繼續直到整個數組結束。
? GL_TRIANGLE_FAN:繪制一組相連的三角形。三角形是由第一個頂點及其後給定的頂點所確定。頂點1,n+1和n+2定義了第n個三角形,總共繪制N-2個三角形。
繪制圖形步驟:
1.定義頂點並且轉換存儲在字節緩沖中;
2.我們使用頂點數組繪制圖形,而opengles是默認關閉這個開關的,所以我們要啟用它。gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
3.設置繪制的顏色。以下為設置紅色
gl.glColor4f(1.0, 0.0, 0.0, 1.0);? //R,G,B,A 4.由於我們使用頂點數組,我們必須通知OpenGL頂點的數組在什麼地方。需使用函數:
gl.glVertexPointer(
3,//每個頂點的坐標的維數,這裡為3xyz
GL10.GL_FIXED,//頂點坐標值的類型為GL_FIXED
0,//數組中數據的偏移值
mVertexBuffer//頂點坐標數據數組
);
5.開始繪圖
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,0, 9);
函數原型 voidglDrawArrays(int mode, int first, int count)
mode為繪制式,有GL_POINTS、GL_LINES、GL_TRIANGLES、GL_TRIANGLE_STRIP等。first為數據在數組中的起始位置,讀取數據的個數
視圖與透視
我們生活在一個三維的世界——如果要觀察一個物體,我們可以:
1、從不同的位置去觀察它。(視圖變換)
2、移動或者旋轉它,當然了,如果它只是計算機裡面的物體,我們還可以放大或縮小它。(模型變換)
3、如果把物體畫下來,我們可以選擇:是否需要一種“近大遠小”的透視效果。另外,我們可能只希望看到物體的一部分,而不是全部(剪裁)。(投影變換)
4、我們可能希望把整個看到的圖形畫下來,但它只占據紙張的一部分,而不是全部。(視口變換)
這些,都可以在OpenGL中實現。
OpenGL變換實際上是通過矩陣乘法來實現。無論是移動、旋轉還是縮放大小,都是通過在當前矩陣的基礎上乘以一個新的矩陣來達到目的。
1、模型變換和視圖變換
即設置3D模型的位移,旋轉等屬性。由於模型和視圖的變換都通過矩陣運算來實現,在進行變換前,應先設置當前操作的矩陣為“模型視圖矩陣”。設置的方法是以GL_MODELVIEW為參數調用glMatrixMode函數,像這樣:
glMatrixMode(GL_MODELVIEW);
通常,我們需要在進行變換前把當前矩陣設置為單位矩陣。這也只需要一行代碼:
glLoadIdentity();
然後,就可以進行模型變換和視圖變換了。進行模型和視圖變換,主要涉及到三個函數:
glTranslate*,(*表示這個函數分為float型的glTranslatef和int型的glTranslatex)把當前矩陣和一個表示移動物體的矩陣相乘。三個參數分別表示了在三個坐標上的位移值。
glRotate*,把當前矩陣和一個表示旋轉物體的矩陣相乘。物體將繞著(0,0,0)到(x,y,z)的直線以逆時針旋轉,參數angle表示旋轉的角度。
glScale*,把當前矩陣和一個表示縮放物體的矩陣相乘。x,y,z分別表示在該方向上的縮放比例。
2、投影變換
投影變換就是定義一個可視空間,可視空間以外的物體不會被繪制到屏幕上。(注意,從現在起,坐標可以不再是-1.0到1.0了!)
OpenGL支持兩種類型的投影變換,即透視投影和正投影。投影也是使用矩陣來實現的。如果需要操作投影矩陣,需要以GL_PROJECTION為參數調用glMatrixMode函數。
glMatrixMode(GL_PROJECTION);
通常,我們需要在進行變換前把當前矩陣設置為單位矩陣。
glLoadIdentity();
透視投影所產生的結果類似於照片,有近大遠小的效果,比如在火車頭內向前照一個鐵軌的照片,兩條鐵軌似乎在遠處相交了。
使用glFrustum函數可以將當前的可視空間設置為透視投影空間。其參數的意義如下圖:
聲明:該圖片來自www.opengl.org,該圖片是《OpenGL編程指南》一書的附圖,由於該書的舊版(第一版,1994年)已經流傳於網絡,我希望沒有觸及到版權問題。
也可以使用更常用的gluPerspective函數。其參數的意義如下圖:
聲明:該圖片來自www.opengl.org,該圖片是《OpenGL編程指南》一書的附圖,由於該書的舊版(第一版,1994年)已經流傳於網絡,我希望沒有觸及到版權問題。
正投影相當於在無限遠處觀察得到的結果,它只是一種理想狀態。但對於計算機來說,使用正投影有可能獲得更好的運行速度。
使用glOrtho函數可以將當前的可視空間設置為正投影空間。其參數的意義如下圖:
聲明:該圖片是《OpenGL編程指南》一書的附圖,由於該書的舊版(第一版,1994年)已經流傳於網絡,我希望沒有觸及到版權問題。
如果繪制的圖形空間本身就是二維的,可以使用gluOrtho2D。他的使用類似於glOrgho。
3、視口變換
當一切工作已經就緒,只需要把像素繪制到屏幕上了。這時候還剩最後一個問題:應該把像素繪制到窗口的哪個區域呢?通常情況下,默認是完整的填充整個窗口,但我們完全可以只填充一半。(即:把整個圖象填充到一半的窗口內)
聲明:該圖片來自www.opengl.org,該圖片是《OpenGL編程指南》一書的附圖,由於該書的舊版(第一版,1994年)已經流傳於網絡,我希望沒有觸及到版權問題。
使用glViewport來定義視口。其中前兩個參數定義了視口的左下腳(0,0表示最左下方),後兩個參數分別是寬度和高度。