最近不是很忙,,所以打算從這周開始學(xué)習(xí)android的3D繪圖,。網(wǎng)絡(luò)上已經(jīng)有大量有關(guān)OpenGL的好教程和書籍。但是,,卻沒有多少是關(guān)于OpenGLES,更加沒有多少是專門針對學(xué)習(xí)android上3D編程的,。為了養(yǎng)成良好的學(xué)習(xí)習(xí)慣,,也算是給自己的學(xué)習(xí)過程做一個總結(jié)、筆記,,我決定按照自己的學(xué)習(xí)規(guī)矩,,撰寫一個針對android3D初學(xué)者的博文系列。這是此系列的第一篇文章,。
基本概念
為了方便后邊的編程,,我們第一篇文章主要是介紹一些關(guān)于OpenGLES基本的概念。
點(diǎn)
3D圖像的最小單位稱為點(diǎn)(point)或者頂點(diǎn)vertex,。它們代表三維空間中的一個點(diǎn)并用來建造更復(fù)雜的物體,。多邊形就是由點(diǎn)構(gòu)成,而物體是由多個多邊形組成,。盡管通常OpenGL支持多種多邊形,,但OpenGLEs只支持三邊形(即三角形)所以即使我們要繪制一個正方形也要把它拆分為兩個三角形繪制。先說說坐標(biāo)系的問題,。
默認(rèn)情況下,,以屏幕中心為坐標(biāo)軸原點(diǎn)。原點(diǎn)左方x為負(fù)值,,右邊為正值,。原點(diǎn)上方y為正,原點(diǎn)下方為負(fù),。垂直屏幕向外為z正,,垂直屏幕向里為z負(fù)。默認(rèn)情況下,,從原點(diǎn)到屏幕邊緣為1.0f,沿各軸增加或減小的數(shù)值是以任意刻度進(jìn)行的–它們不代表任何真實單位,,如英尺,像素或米等,。你可以選擇任何對你的程序有意義的刻度(全局必須保持單位一致,,不能一部分使用米,一部分使用像素),。OpenGL只是將它作為一個參照單位處理,,保證它們具有相同的距離。如圖:
了解了坐標(biāo)軸,我們來看看怎么在坐標(biāo)系中表示一個點(diǎn),,通常用一組浮點(diǎn)數(shù)來表示點(diǎn),。例如一個正方形的4個頂點(diǎn)可表示為:
-
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, //右上
-
};
為了提高性能,通常還需要將浮點(diǎn)數(shù)組存入一個字節(jié)緩沖中,。所以有了下面的操作:
-
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); //申請內(nèi)存
-
vbb.order(ByteOrder.nativeOrder()); //設(shè)置字節(jié)順序,,其中ByteOrder.nativeOrder()是獲取本機(jī)字節(jié)順序
-
FloatBuffer vertexBuffer = vbb.asFloatBuffer(); //轉(zhuǎn)換為float型
-
vertexBuffer.put(vertices); //添加數(shù)據(jù)
-
vertexBuffer.position(0); //設(shè)置緩沖區(qū)起始位置
OpenGLES的很多函數(shù)功能的使用狀態(tài)是處于關(guān)閉的。啟用和關(guān)閉這些函數(shù)可以用glEnableClientState,、glDisableClientState來完成,。
-
// 指定需要啟用定點(diǎn)數(shù)組
-
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
-
// 說明啟用數(shù)組的類型和字節(jié)緩沖,類型為GL_FLOAT
-
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
-
// 不再需要時,,關(guān)閉頂點(diǎn)數(shù)組
-
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
多邊形
多邊形是由點(diǎn)和邊構(gòu)成的單閉合環(huán),。 繪制多邊形時需要特別注意頂點(diǎn)的繪制順序,可以分為順時針和逆時針,。因為方向決定了多邊形的朝向,,
即正面和背面。避免渲染那些被遮擋的部分可以了有效提高程序性能,。默認(rèn)以逆時針次序繪制頂點(diǎn)的構(gòu)成的面是正面,。
可以通過glFrontFace函數(shù)來交換“正面”和“反面”的概念。
glFrontFace(GL_CCW); // 設(shè)置CCW方向為“正面”,,CCW即CounterClockWise,,逆時針
glFrontFace(GL_CW); // 設(shè)置CW方向為“正面”,CW即ClockWise,,順時針
渲染
有了以上的概念講解后,,現(xiàn)在要進(jìn)行最主要的工作—渲染。渲染是把物體坐標(biāo)所指定的圖元轉(zhuǎn)化成幀緩沖區(qū)中的圖像,。圖像和頂點(diǎn)坐標(biāo)有著密切的關(guān)系,。這個關(guān)系通過繪制模式給出。常用到得繪制模式有GL_POINTS,、GL_LINE_STRIP,、GL_LINE_LOOP、GL_LINES,、GL_TRIANGLES,、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN,。下面分別介紹:
- GL_POINTS:把每一個頂點(diǎn)作為一個點(diǎn)進(jìn)行處理,,頂點(diǎn)n即定義了點(diǎn)n,共繪制n個點(diǎn),。
- GL_LINES:把每一個頂點(diǎn)作為一個獨(dú)立的線段,,頂點(diǎn)2n-1和2n之間共定義了n個線段,,總共繪制N/2條線段。,,如果N為奇數(shù),,則忽略最后一個頂點(diǎn)。
- GL_LINE_STRIP:繪制從第一個頂點(diǎn)到最后一個頂點(diǎn)依次相連的一組線段,,第n和n+1個頂點(diǎn)定義了線段n,,總共繪制N-1條線段。
- GL_LINE_LOOP:繪制從定義第一個頂點(diǎn)到最后一個頂點(diǎn)依次相連的一組線段,,然后最后一個頂點(diǎn)與第一個頂點(diǎn)相連,。第n和n+1個頂點(diǎn)定義了線段n,然后最后一個線段是由頂點(diǎn)N和1之間定義,,總共繪制N條線段。
- GL_TRIANGLES:把每三個頂點(diǎn)作為一個獨(dú)立的三角形,。頂點(diǎn)3n-2,,3n-1和3n定義了第n個三角形,總共繪制N/3個三角形,。
- GL_TRIANGLE_STRIP:繪制一組相連的三角形,。對于奇數(shù)點(diǎn)n,頂點(diǎn)n,,n+1和n+2定義了第n個三角形,;對于偶數(shù)n,頂點(diǎn)n+1,,n和n+2定義了第n個三角形,,總共繪制N-2個三角形。這是最常使用的渲染方式,,第一個三角形條是由前三個頂點(diǎn)構(gòu)成(索引0,1,
2),。第二個三角形條是由前一個三角形的兩個頂點(diǎn)加上數(shù)組中的下一個頂點(diǎn)構(gòu)成,繼續(xù)直到整個數(shù)組結(jié)束,。
- GL_TRIANGLE_FAN:繪制一組相連的三角形,。三角形是由第一個頂點(diǎn)及其后給定的頂點(diǎn)所確定。頂點(diǎn)1,,n+1和n+2定義了第n個三角形,,總共繪制N-2個三角形。
繪制圖形步驟:
1.定義頂點(diǎn)并且轉(zhuǎn)換存儲在字節(jié)緩沖中;
2.我們使用頂點(diǎn)數(shù)組繪制圖形,,而opengles是默認(rèn)關(guān)閉這個開關(guān)的,,所以我們要啟用它。gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
3.設(shè)置繪制的顏色,。以下為設(shè)置紅色
gl.glColor4f(1.0, 0.0, 0.0, 1.0);? //R,,G,B,A
4.由于我們使用頂點(diǎn)數(shù)組,,我們必須通知OpenGL頂點(diǎn)的數(shù)組在什么地方,。需使用函數(shù):
gl.glVertexPointer(
3,//每個頂點(diǎn)的坐標(biāo)的維數(shù),這里為3xyz
GL10.GL_FIXED,//頂點(diǎn)坐標(biāo)值的類型為GL_FIXED
0,//數(shù)組中數(shù)據(jù)的偏移值
mVertexBuffer//頂點(diǎn)坐標(biāo)數(shù)據(jù)數(shù)組
);
5.開始繪圖
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,0,
9);
函數(shù)原型 voidglDrawArrays(int mode, int
first, int count)
mode為繪制式,,有GL_POINTS,、GL_LINES、GL_TRIANGLES,、GL_TRIANGLE_STRIP等,。first為數(shù)據(jù)在數(shù)組中的起始位置,讀取數(shù)據(jù)的個數(shù)
視圖與透視
我們生活在一個三維的世界——如果要觀察一個物體,,我們可以:
1,、從不同的位置去觀察它。(視圖變換)
2,、移動或者旋轉(zhuǎn)它,,當(dāng)然了,如果它只是計算機(jī)里面的物體,,我們還可以放大或縮小它,。(模型變換)
3、如果把物體畫下來,,我們可以選擇:是否需要一種“近大遠(yuǎn)小”的透視效果,。另外,我們可能只希望看到物體的一部分,,而不是全部(剪裁),。(投影變換)
4、我們可能希望把整個看到的圖形畫下來,,但它只占據(jù)紙張的一部分,,而不是全部。(視口變換)
這些,,都可以在OpenGL中實現(xiàn),。
OpenGL變換實際上是通過矩陣乘法來實現(xiàn)。無論是移動,、旋轉(zhuǎn)還是縮放大小,,都是通過在當(dāng)前矩陣的基礎(chǔ)上乘以一個新的矩陣來達(dá)到目的。
1,、模型變換和視圖變換
即設(shè)置3D模型的位移,,旋轉(zhuǎn)等屬性。由于模型和視圖的變換都通過矩陣運(yùn)算來實現(xiàn),,在進(jìn)行變換前,,應(yīng)先設(shè)置當(dāng)前操作的矩陣為“模型視圖矩陣”,。設(shè)置的方法是以GL_MODELVIEW為參數(shù)調(diào)用glMatrixMode函數(shù),像這樣:
glMatrixMode(GL_MODELVIEW);
通常,,我們需要在進(jìn)行變換前把當(dāng)前矩陣設(shè)置為單位矩陣,。這也只需要一行代碼:
glLoadIdentity();
然后,就可以進(jìn)行模型變換和視圖變換了,。進(jìn)行模型和視圖變換,,主要涉及到三個函數(shù):
glTranslate*,(*表示這個函數(shù)分為float型的glTranslatef和int型的glTranslatex)把當(dāng)前矩陣和一個表示移動物體的矩陣相乘,。三個參數(shù)分別表示了在三個坐標(biāo)上的位移值,。
glRotate*,把當(dāng)前矩陣和一個表示旋轉(zhuǎn)物體的矩陣相乘,。物體將繞著(0,0,0)到(x,y,z)的直線以逆時針旋轉(zhuǎn),,參數(shù)angle表示旋轉(zhuǎn)的角度。
glScale*,,把當(dāng)前矩陣和一個表示縮放物體的矩陣相乘,。x,y,z分別表示在該方向上的縮放比例。
2,、投影變換
投影變換就是定義一個可視空間,可視空間以外的物體不會被繪制到屏幕上,。(注意,,從現(xiàn)在起,坐標(biāo)可以不再是-1.0到1.0了?。?br>
OpenGL支持兩種類型的投影變換,,即透視投影和正投影。投影也是使用矩陣來實現(xiàn)的,。如果需要操作投影矩陣,,需要以GL_PROJECTION為參數(shù)調(diào)用glMatrixMode函數(shù)。
glMatrixMode(GL_PROJECTION);
通常,,我們需要在進(jìn)行變換前把當(dāng)前矩陣設(shè)置為單位矩陣,。
glLoadIdentity();
透視投影所產(chǎn)生的結(jié)果類似于照片,有近大遠(yuǎn)小的效果,,比如在火車頭內(nèi)向前照一個鐵軌的照片,,兩條鐵軌似乎在遠(yuǎn)處相交了。
使用glFrustum函數(shù)可以將當(dāng)前的可視空間設(shè)置為透視投影空間,。其參數(shù)的意義如下圖:
聲明:該圖片來自www.opengl.org,,該圖片是《OpenGL編程指南》一書的附圖,由于該書的舊版(第一版,,1994年)已經(jīng)流傳于網(wǎng)絡(luò),,我希望沒有觸及到版權(quán)問題,。
也可以使用更常用的gluPerspective函數(shù)。其參數(shù)的意義如下圖:
聲明:該圖片來自www.opengl.org,,該圖片是《OpenGL編程指南》一書的附圖,,由于該書的舊版(第一版,1994年)已經(jīng)流傳于網(wǎng)絡(luò),,我希望沒有觸及到版權(quán)問題,。
正投影相當(dāng)于在無限遠(yuǎn)處觀察得到的結(jié)果,它只是一種理想狀態(tài),。但對于計算機(jī)來說,,使用正投影有可能獲得更好的運(yùn)行速度。
使用glOrtho函數(shù)可以將當(dāng)前的可視空間設(shè)置為正投影空間,。其參數(shù)的意義如下圖:
聲明:該圖片來自www.opengl.org,,該圖片是《OpenGL編程指南》一書的附圖,由于該書的舊版(第一版,,1994年)已經(jīng)流傳于網(wǎng)絡(luò),,我希望沒有觸及到版權(quán)問題。
如果繪制的圖形空間本身就是二維的,,可以使用gluOrtho2D,。他的使用類似于glOrgho。
3,、視口變換
當(dāng)一切工作已經(jīng)就緒,,只需要把像素繪制到屏幕上了。這時候還剩最后一個問題:應(yīng)該把像素繪制到窗口的哪個區(qū)域呢,?通常情況下,,默認(rèn)是完整的填充整個窗口,但我們完全可以只填充一半,。(即:把整個圖象填充到一半的窗口內(nèi))
聲明:該圖片來自www.opengl.org,,該圖片是《OpenGL編程指南》一書的附圖,由于該書的舊版(第一版,,1994年)已經(jīng)流傳于網(wǎng)絡(luò),,我希望沒有觸及到版權(quán)問題。
使用glViewport來定義視口,。其中前兩個參數(shù)定義了視口的左下腳(0,0表示最左下方),,后兩個參數(shù)分別是寬度和高度。
追加:
想象一下你站在一個3d世界中,。你的頭頂上有一個朝上的箭頭在跟隨,。然后你抬起頭,看了下掛在碧天上的驕陽,;低下頭,,看到自己的腳踏在褐色的土地上,。然后轉(zhuǎn)動你的頭傾,看到一片片幽幽的麥地直到地平線的盡頭?,F(xiàn)在,,你的眼睛就充當(dāng)了攝像機(jī)。
在opengl中你總是工作在3d世界中,,即使opengles也是如此,。秘密是在2d引擎下,z坐標(biāo)被隱藏了,。所以暫時忘記2d和3d的不同之處,。如下圖,用你的眼睛掃過的地方是一個金字塔形狀的物體(frustum),。所有處在這個梯形中的物體都將是可視的,。
此梯狀物體具有6個變量:near, far, left, right, top and bottom。
-
near:最近的距離,,小于此距離的物體將不可見,。就像是你把一張紙緊貼在眼睛上,是看不到紙上的內(nèi)容一樣
-
far: 最遠(yuǎn)距離,,大于此距離的物體也將不可見,。
那么我們把3D世界轉(zhuǎn)換成2D圖像的這個過程,就叫做投影(projection),。典型的,,有兩種投影方式:正交投影(orthographic projection)和透視投影(perspective projection)
-
正交投影(orthographic projection)主要用在2D世界中,它不管物體離我們多遠(yuǎn),,都一致處理,比較簡單,。
-
透視投影是3D固定流水線的重要組成部分,,用在真實的世界中。是將相機(jī)空間中的點(diǎn)從視錐體(frustum)變換到規(guī)則觀察體(Canonical View Volume)中,,待裁剪完畢后進(jìn)行透視除法的行為,。在算法中它是通過透視矩陣乘法和透視除法兩步完成的。透視投影符合人們心理習(xí)慣,,即離視點(diǎn)近的物體大,,離視點(diǎn)遠(yuǎn)的物體小,遠(yuǎn)到極點(diǎn)即為消失,,成為滅點(diǎn),。它的視景體類似于一個頂部和底部都被切除掉的棱椎,也就是棱臺,。
這兩種視圖看起來就像這樣:
一本不錯的電子教程,,OpenGL的,,雖然是使用c++為編程語言,但是函數(shù)都是一樣的,,Nehe的OpenGl入門教程http://download.csdn.net/source/3566454
|