1. OpenGL ES 基礎
1.1. OpenGL 坐標
屏幕坐標系中橫軸是x軸,縱軸是y軸,分別向右是正方向,向下是正方向.原點在左上角
數(shù)學坐標系中原點在左下角,向上是y軸正方向.
在OpenGL3D坐標系中,稍有不同,使用的是笛卡爾坐標系,表示空間坐標位置.和數(shù)學坐標系相似,左下角是原點,向右和向上是正值.還要添加第三個量就是z軸.z軸方向指向你.
下圖是OpenGL ES笛卡爾坐標系.
實際上我們有幾種坐標系和空間,在OpenGL中,每個空間位置會按照如下變換:
- 對象空間,相對于每個對象自身
- 相機,眼睛,空間是針對視點
- 投影,剪裁,空間是平面的屏幕或者是圖像上的視口.
1.2. 視景體
也叫平截頭體(frustum),是一個棱臺,表示的是照相機能夠捕捉到畫面的一個區(qū)域.
下圖為視景體
定義視景體非常簡單通過定義一個幾何體就可以了,空間中,任何平截頭體內(nèi)的物體都能夠在屏幕上找到相應的位置,只要沒有被其他物體覆蓋.
Frustum也用于指定你的field-of-view(FOV),比如相機的廣角與長焦鏡頭.角度越大看到的景象越多,成像就越小.
可以使用gl.glMatrixMode(GL_MODELVIEW)方法對模型視圖矩陣進行平移和旋轉操作.還需要定義操縱投影矩陣(projection matrix).
透視投影是我們觀察世界的正常形勢,物體越遠越小,如果對frustum內(nèi)的這種效果移除,我們就會得到正交投影,就是不論物體遠近,他們的大小不發(fā)生改變,這主要用于機械制圖等.
經(jīng)常需要控制操縱的是哪個矩陣,可通過gl.glMatrixMode()實現(xiàn),如果不知道操縱的是那個矩陣很容易出錯.
投影類型:
- 透視投影:有深度,越遠越小.
- 正投影:沒有深度,相同大小.
矩陣模式,openGL是基于狀態(tài)的,操縱很多矩陣,通過該函數(shù)指定使用哪個矩陣.
常用的矩陣有: - GL10.GL_PROJECTION:投影矩陣.
- GL10.GL_MODELVIEW:模型視圖矩陣.
指定使用哪個矩陣之后,需要先加載單位矩陣(使用gl.loadIdentity()方法,類似于矩陣歸零).
1.3. 定義視口
gl.glViewPort(...);需要注意的是,坐標從左下角開始.
1.4. 使用正交投影
gl.glOrtho(..)
//使用正交矩陣棧,需要重新設置坐標系,告訴OpenGL,以后所有變換都會影響該矩陣.
gl.glMatrixMode(GL_PROJECTION);
gl.glLoadIdentity();
1.5. 雙緩沖
所有圖形程序最重要的特性之一就是雙緩沖.他允許在一個屏幕之外的緩沖區(qū)中執(zhí)行繪制代碼,然后使用一條交換命令把繪制完成的圖形立即顯示在屏幕上.
雙緩沖用途兩個:
一是一些復雜繪圖時間較長,不希望在屏幕上看到每個步驟.雙緩沖可以合成一幅圖像完成后再顯示.
用戶不會看到不完整的圖像.
二是用于動畫.每個幀都在屏幕之外的緩沖區(qū)繪制,完成后快速交換到屏幕上.
1.6. OpenGL狀態(tài)機
繪制3D圖形是一項復雜任務.狀態(tài)機是一個抽象的模型,表示一組狀態(tài)變量的集合.每個狀態(tài)變量可以有各種不同的值,打開關閉等.OpenGL繪圖時,每次指定這些變量明顯不合適.因此OpenGL給出了一中狀態(tài)模型(狀態(tài)機)來跟蹤所有的OpenGL狀態(tài)變量.一個狀態(tài)變量被設置后,以后一直保持這個狀態(tài),直到下次對他進行修改.使用如下命令可進行狀態(tài)變
量的切換.
gl.glEnable(xx);//gl.glDisable(xx);
//例如光照
gl.glEnable(GL_LIGHGTING);
2. 使用OpenGL編碼步驟
2.1. 創(chuàng)建GLSurfaceView對象
寫一個繼承自GLSurfaceView的類.
2.2. 創(chuàng)建GLSurfaceView.renderer實現(xiàn)類.
實現(xiàn)GLSurfaceView.Renderer接口,創(chuàng)建渲染器.
2.3. 設置activity的contentView,以及設置view的render對象.
將2.1中的GLSurfaceView設置2.2創(chuàng)建的渲染器,并設置到ContentView中.
2.4. 實現(xiàn)Renderer類的過程
2.4.1. onSurfaceCreate()方法
設置清屏的顏色和啟用頂點緩沖區(qū)
//設置清屏色
gl.glClearColor(0, 0, 0, 1);
//啟用頂點緩沖區(qū).
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
2.4.2. onSurfaceChanged()方法
- 設置viewport(視口) 輸出畫面的區(qū)域(從左下角開始)
gl.glViewport(0, 0, width, height); - 操縱投影矩陣,設置平截頭體(比例通常和視口比例相同,否則輸出畫面會走樣)
//矩陣模式,投影矩陣,openGL基于狀態(tài)機
gl.glMatrixMode(GL10.GL_PROJECTION);
//加載單位矩陣
gl.glLoadIdentity();
//平截頭體 zNear:近平面 zFar:遠平面
gl.glFrustumf(-ratio, ratio, -1f, 1f, 3, 7);
2.4.3. onDrawFrame()方法:
- 清除顏色緩沖區(qū)
gl.glClear(GL10.GL_COLOR_BUFFER_BIT); - 操縱模型視圖矩陣,設置眼球的參數(shù)
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();//加載單位矩陣
//eyeX, eyeY, eyeZ :放置眼球的坐標
//centerX, centerY, centerZ : 眼球的觀察點
//upx, upy, upz : 指定眼球向上的向量(此處比較危險)
GLU.gluLookAt(gl, 0, 0, 5, 0, 0, 0, 0, 1, 0); - 定義圖形頂點坐標值數(shù)組
float[] coords = {
0f,0.5f,0f,
-0.5f,-0.5f,0f,
0.5f,-0.5f,0f
}; - 將頂點坐標轉換成緩沖區(qū)數(shù)據(jù)
//分配字節(jié)緩存區(qū)空間,存放頂點坐標數(shù)據(jù)(浮點數(shù)4個字節(jié))
ByteBuffer ibb = ByteBuffer.allocateDirect(coords.length * 4);
//設置的順序(本地順序, 跟操作系統(tǒng)有關)
ibb.order(ByteOrder.nativeOrder());
//放置頂點坐標數(shù)組
FloatBuffer fbb = ibb.asFloatBuffer();
fbb.put(coords);
//定位指針的位置,從該位置開始讀取頂點數(shù)據(jù)
ibb.position(0); - 設置繪圖顏色
gl.glColor4f(1f, 0f, 0f, 1f); - 指定頂點緩沖區(qū)指針
//3:3維點,使用三個坐標值表示一個點
//type:每個點的數(shù)據(jù)類型
//stride:0,跨度.
//ibb:指定頂點緩沖區(qū)
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ibb); - 繪圖
//mode: 繪制類型
//first: 起始點
//count: 點的個數(shù)
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
2.5. 封裝通用的字節(jié)緩沖區(qū)轉換方法
(持續(xù)更新該Utils類)
public class BufferUtil {
/**
* 將浮點數(shù)組轉換成字節(jié)緩沖區(qū)
* @param arr 輸入待轉換的浮點數(shù)組
* @return 字節(jié)緩沖區(qū)
*/
public static ByteBuffer arr2ByteBuffer(float[] arr) {
//分配字節(jié)緩存區(qū)空間, 存放定點坐標數(shù)據(jù)
// 一個字節(jié) 8位 浮點數(shù)4個字節(jié)
ByteBuffer ibb = ByteBuffer.allocateDirect(arr.length * 4);
//設置順序(本地順序,跟操作系統(tǒng)有關)
ibb.order(ByteOrder.nativeOrder());
//放置頂點坐標數(shù)據(jù)
FloatBuffer fbb = ibb.asFloatBuffer();
fbb.put(arr);
//定位指針的位置, 從該位置開始讀取頂點數(shù)據(jù)
ibb.position(0);
return ibb;
}
/**
* 將List集合轉換成字節(jié)緩沖區(qū)
* @param list 輸入待轉換的浮點數(shù)組
* @return 字節(jié)緩沖區(qū)
*/
public static ByteBuffer list2ByteBuffer(List<Float> list) {
//分配字節(jié)緩存區(qū)空間, 存放定點坐標數(shù)據(jù)
// 一個字節(jié) 8位 浮點數(shù)4個字節(jié)
ByteBuffer ibb = ByteBuffer.allocateDirect(list.size() * 4);
//設置順序(本地順序,跟操作系統(tǒng)有關)
ibb.order(ByteOrder.nativeOrder());
//放置頂點坐標數(shù)據(jù)
FloatBuffer fbb = ibb.asFloatBuffer();
for (Float aFloat : list) {
fbb.put(aFloat);
}
//定位指針的位置, 從該位置開始讀取頂點數(shù)據(jù)
ibb.position(0);
return ibb;
}
}
2.6. 渲染模式
OpenGL ES有兩種渲染模式:
GLSurfaceView.RENDERMODE_CONTINUOUSLY: 持續(xù)渲染(默認)
GLSurfaceView.RENDERMODE_WHEN_DIRTY: 臟渲染, 命令渲染(需要使用myGLSurfaceView.requestRender();方法主動請求渲染)
2.7. 坐標系旋轉
//angle: 旋轉角度
//x,y,z: 沿著哪個軸旋轉(向量) 迎面軸正方向看去, 順時針: 負值 逆時針: 正值
gl.glRotatef(xrotate, 1, 0, 0);//繞x軸旋轉
gl.glRotatef(yrotate, 0, 1, 0);//繞y軸旋轉
2.8. 緩沖區(qū)
緩沖區(qū)分為:
- 頂點緩沖區(qū)(VertexBuffer)
- 顏色緩沖區(qū)(ColorBuffer)
使用方式:
gl.glColorPointer(xx);//指定顏色緩沖區(qū)
gl.glVertexPointer(xx);//指定頂點指針
注意:
不要忘記在onSurfaceCreated() 方法中啟用兩個緩沖區(qū)
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//啟用頂點緩沖區(qū)
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);//啟動顏色緩沖區(qū)
3. 空間繪圖
OpenGL ES支持三種繪圖模式:點, 線和三角形.
3.1. 點
像素是計算機監(jiān)視器上最小的元素.在彩色系統(tǒng)中,像素可以是許多可用顏色的一種.屏幕上的一個位置對應一個點,并為之指定顏色.然后,在此基礎上,用最擅長的計算機語言生成直線,多邊形,圓形以及其他圖形,甚至整個GUI.
下圖把可視區(qū)域看成是三維畫布,可使用OpenGL命令和函數(shù)進行繪圖.
畫點:
gl.glDrawArray(GL_POINTS,..)
設置點大小:
gl.glPiontSize(xx);
示例(點變化大小的螺旋):
//計算點坐標
float r = 0.5f;//半徑
// 旋轉3圈
float x = 0f, y = 0f, z = 1f;
float zstep = 0.01f;//z的步長
float psize = 1.0f;//點的大小
float pstep = 0.5f;//點的步長
//循環(huán)繪制
for (float alpha = 0f; alpha < Math.PI * 6; alpha = (float) (alpha + Math.PI/32)) {
x = (float) (r * Math.cos(alpha));
y = (float) (r * Math.sin(alpha));
z = z - zstep;
//設置點的大小
gl.glPointSize(psize = psize + pstep);
//指定頂點指針
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, BufferUtil.arr2ByteBuffer(new float[]{x, y, z}));
//畫數(shù)組
gl.glDrawArrays(GL10.GL_POINTS, 0, 1);
}
3.2. 線
畫線分為三類:
- lines: 線集(線段集合)
- line_strip: 線帶(首尾相接不閉環(huán))
- line_loop: 線環(huán)(首尾相接閉環(huán))
畫線:
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fbb);
gl.glDrawArrays(GL10.GL_LINES, 0, 4);
設置直線寬度:
gl.glLinewidth(xx);
示例(使用線帶畫螺旋線):
float r = 0.5f;//半徑
List<Float> coordsList = new ArrayList<>();//坐標點集合
// 旋轉3圈
float x = 0f, y = 0f, z = 1f;
float zstep = 0.01f;//步長
for (float alpha = 0f; alpha < Math.PI * 6; alpha = (float) (alpha + Math.PI/32)) {
x = (float) (r * Math.cos(alpha));
y = (float) (r * Math.sin(alpha));
z = z - zstep;
coordsList.add(x);
coordsList.add(y);
coordsList.add(z);
}
//指定頂點指針
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, BufferUtil.list2ByteBuffer(coordsList));
//畫數(shù)組
gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, coordsList.size() / 3);
3.3. 三角形
使用畫線方式繪制的圖形的線框樣式的,即都是直線無法進行
顏色填充,多邊形是閉合形狀.可以選擇顏色進行填充.
有三種模式:
3.3.1. 環(huán)繞(GL_TRIANGLES)
注意連接頂點的箭頭方向,第一個三角形的順序是v0->v1->v2->v0,該順序是有定點的指定次序決定的.該順序為順時針方向.第二個三角形也是一樣.頂點的指定次序以及方向的組合成為環(huán)繞(winding).左圖看成是順時針環(huán)繞,如果將v4和v5兩點互換,得到逆時針環(huán)繞.如右圖:
3.3.2. 三角形帶(GL_TRIANGLES_STRIP)
GL_TRIANLTE_STRIP,可繪制一串相鄰的三角形,這些頂點不是按照他們指定的順興進行遍歷的,這是為了保持每個三角形的環(huán)繞方向(逆時針).他的繪制模式是v0->v1->v2,接著v2->v1->v3
,然后是v2->v3->v4,依次類推.
3.3.3. 三角形扇(GL_TRIANGLES_FAN)
圍繞一個中心點的相連三角形.
3.3.4. 示例(正方形)
float r = 0.5f;//半徑
float[] coords = {
-r, r, 0,
-r, -r, 0,
r, r, 0,
r, -r, 0,
};
//指定頂點指針
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, BufferUtil.arr2ByteBuffer(coords));
//畫數(shù)組
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, coords.length/ 3);
3.4 實心物體
3.4.1. 設置顏色
我們知道畫點畫線或者三角形都要指定頂點緩沖區(qū),同樣的也會顏色緩沖區(qū).
需要注意的是, 頂點緩沖區(qū)中的點與顏色緩沖區(qū)中的點進行對應.
頂點的著色模式:
- smmoth: 平滑模式,也稱漸變(默認)
- flat: 單調(diào)模式以三角形最后一個點的顏色值為準
設置著色模式:
gl.glShadeMode(GL_FLAT|GL_SMOOTH);//單調(diào)|平滑
注意: 三角形面設置的顏色, 跟最后一個頂點的顏色相同.
3.4.2. 深度測試
深度測試是一種有效地的用于隱藏表面消除的技巧,當一個像素繪制時,他將被設置一個值(稱為z值),來表示他和觀察者之間的距離.以后,當該位置需要繪制另一個像素時,新像素的z值和原先的值進行比較.如果z值越高,距離觀察者越近,就位于以前像素的前面.反之位于后面.內(nèi)部是通過深度緩沖區(qū)實現(xiàn)的.他存儲了屏幕上每個像素的深度值.
啟用深度測試:(
不要忘記在繪圖開始時清除深度緩沖區(qū)
)gl.glClear(GL10.GL_DEPTH_BUFFER_BIT);//清除深度緩沖區(qū)
gl.glEnable(GL10.GL_DEPTH_TEST);//啟用深度測試
3.4.3. 剔除
使用深度測試,性能上需要有開銷,因為每次繪制像素都需要比較.如果知道那個面肯定不能繪制,就可以使用剔除這種技巧了.消除已經(jīng)知道肯定不會繪制的幾何圖形.也不會向OpenGL驅動程序發(fā)送這個幾何圖形,性能能夠顯著提升.
剔除技巧之一就是背面剔除,消除一個表面的背面.
//啟用表面剔除
gl.glEnable(GL10.GL_CULL_FACE);
//指定正面
//ccw: counter clock wise -> 逆時針(默認)
//cw: clock wise -> 順時針
gl.glFrontFace(GL10.GL_CCW);
//剔除背面
//GL10.GL_FRONT:正面
//GL10.GL_BACK背面
gl.glCullFace(GL10.GL_BACK);
3.4.4. 使用剪刀進行裁剪
不在ViewPort整個視口進行渲染.
使用方式:
//啟用剪裁測試
gl.glEnable(GL_SCISSOR_TEST);
//剪裁: x, y: 剪裁距離 width,height:屏幕寬高
gl.glScissor(160, 240, 160, 50);