OpenGL ES 學習筆記(1)-基本使用
1. OpenGL ES 基礎
1.1. OpenGL 座標
螢幕座標系中橫軸是x軸,縱軸是y軸,分別向右是正方向,向下是正方向.原點在左上角
數學座標系中原點在左下角,向上是y軸正方向.
在OpenGL3D座標系中,稍有不同,使用的是笛卡爾座標系,表示空間座標位置.和數學座標系相似,左下角是原點,向右和向上是正值.還要新增第三個量就是z軸.z軸方向指向你.
下圖是OpenGL ES笛卡爾座標系.

OpenGL ES笛卡爾座標系
實際上我們有幾種座標系和空間,在OpenGL中,每個空間位置會按照如下變換:
- 物件空間,相對於每個物件自身
- 相機,眼睛,空間是針對視點
- 投影,剪裁,空間是平面的螢幕或者是影象上的視口.
1.2. 視景體
也叫平截頭體(frustum),是一個稜臺,表示的是照相機能夠捕捉到畫面的一個區域.
下圖為視景體

視景體

視景體數學模型
定義視景體非常簡單通過定義一個幾何體就可以了,空間中,任何平截頭體內的物體都能夠在螢幕上找到相應的位置,只要沒有被其他物體覆蓋.
Frustum也用於指定你的field-of-view(FOV),比如相機的廣角與長焦鏡頭.角度越大看到的景象越多,成像就越小.
可以使用gl.glMatrixMode(GL_MODELVIEW)方法對模型檢視矩陣進行平移和旋轉操作.還需要定義操縱投影矩陣(projection matrix).
透視投影是我們觀察世界的正常形勢,物體越遠越小,如果對frustum內的這種效果移除,我們就會得到正交投影,就是不論物體遠近,他們的大小不發生改變,這主要用於機械製圖等.
經常需要控制操縱的是哪個矩陣,可通過gl.glMatrixMode()實現,如果不知道操縱的是那個矩陣很容易出錯.
投影型別:
- 透視投影:有深度,越遠越小.
- 正投影:沒有深度,相同大小.
矩陣模式,openGL是基於狀態的,操縱很多矩陣,通過該函式指定使用哪個矩陣.
常用的矩陣有: - 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. 雙緩衝
所有圖形程式最重要的特性之一就是雙緩衝.他允許在一個螢幕之外的緩衝區中執行繪製程式碼,然後使用一條交換命令把繪製完成的圖形立即顯示在螢幕上.
雙緩衝用途兩個:
一是一些複雜繪圖時間較長,不希望在螢幕上看到每個步驟.雙緩衝可以合成一幅影象完成後再顯示.
使用者不會看到不完整的影象.
二是用於動畫.每個幀都在螢幕之外的緩衝區繪製,完成後快速交換到螢幕上.
1.6. OpenGL狀態機
繪製3D圖形是一項複雜任務.狀態機是一個抽象的模型,表示一組狀態變數的集合.每個狀態變數可以有各種不同的值,開啟關閉等.OpenGL繪圖時,每次指定這些變數明顯不合適.因此OpenGL給出了一中狀態模型(狀態機)來跟蹤所有的OpenGL狀態變數.一個狀態變數被設定後,以後一直保持這個狀態,直到下次對他進行修改.使用如下命令可進行狀態變
量的切換.
gl.glEnable(xx);//gl.glDisable(xx);
//例如光照
gl.glEnable(GL_LIGHGTING);
2. 使用OpenGL編碼步驟
2.1. 建立GLSurfaceView/">SurfaceView物件
寫一個繼承自GLSurfaceView的類.
2.2. 建立GLSurfaceView.renderer實現類.
實現GLSurfaceView.Renderer介面,建立渲染器.
2.3. 設定activity的contentView,以及設定view的render物件.
將2.1中的GLSurfaceView設定2.2建立的渲染器,並設定到ContentView中.
2.4. 實現Renderer類的過程
2.4.1. onSurfaceCreate()方法
設定清屏的顏色和啟用頂點緩衝區
//設定清屏色
gl.glClearColor(0, 0, 0, 1);
//啟用頂點緩衝區.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
2.4.2. onSurfaceChanged()方法
- 設定viewport(視口) 輸出畫面的區域(從左下角開始)
gl.glViewport(0, 0, width, height); - 操縱投影矩陣,設定平截頭體(比例通常和視口比例相同,否則輸出畫面會走樣)
//矩陣模式,投影矩陣,openGL基於狀態機
gl.glMatrixMode(GL10.GL_PROJECTION);
//載入單位矩陣
gl.glLoadIdentity();
//平截頭體 zNear:近平面 zFar:遠平面
gl.glFrustumf(-ratio, ratio, -1f, 1f, 3, 7);
2.4.3. onDrawFrame()方法:
- 清除顏色緩衝區
gl.glClear(GL10.GL_COLOR_BUFFER_BIT); - 操縱模型檢視矩陣,設定眼球的引數
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); - 定義圖形頂點座標值陣列
float[] coords = {
0f,0.5f,0f,
-0.5f,-0.5f,0f,
0.5f,-0.5f,0f
}; - 將頂點座標轉換成緩衝區資料
//分配位元組快取區空間,存放頂點座標資料(浮點數4個位元組)
ByteBuffer ibb = ByteBuffer.allocateDirect(coords.length * 4);
//設定的順序(本地順序, 跟作業系統有關)
ibb.order(ByteOrder.nativeOrder());
//放置頂點座標陣列
FloatBuffer fbb = ibb.asFloatBuffer();
fbb.put(coords);
//定位指標的位置,從該位置開始讀取頂點資料
ibb.position(0); - 設定繪圖顏色
gl.glColor4f(1f, 0f, 0f, 1f); - 指定頂點緩衝區指標
//3:3維點,使用三個座標值表示一個點
//type:每個點的資料型別
//stride:0,跨度.
//ibb:指定頂點緩衝區
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ibb); - 繪圖
//mode: 繪製型別
//first: 起始點
//count: 點的個數
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
2.5. 封裝通用的位元組緩衝區轉換方法
(持續更新該Utils類)
public class BufferUtil { /** * 將浮點陣列轉換成位元組緩衝區 * @param arr 輸入待轉換的浮點陣列 * @return 位元組緩衝區 */ public static ByteBuffer arr2ByteBuffer(float[] arr) { //分配位元組快取區空間, 存放定點座標資料 // 一個位元組 8位浮點數4個位元組 ByteBuffer ibb = ByteBuffer.allocateDirect(arr.length * 4); //設定順序(本地順序,跟作業系統有關) ibb.order(ByteOrder.nativeOrder()); //放置頂點座標資料 FloatBuffer fbb = ibb.asFloatBuffer(); fbb.put(arr); //定位指標的位置, 從該位置開始讀取頂點資料 ibb.position(0); return ibb; } /** * 將List集合轉換成位元組緩衝區 * @param list 輸入待轉換的浮點陣列 * @return 位元組緩衝區 */ public static ByteBuffer list2ByteBuffer(List<Float> list) { //分配位元組快取區空間, 存放定點座標資料 // 一個位元組 8位浮點數4個位元組 ByteBuffer ibb = ByteBuffer.allocateDirect(list.size() * 4); //設定順序(本地順序,跟作業系統有關) ibb.order(ByteOrder.nativeOrder()); //放置頂點座標資料 FloatBuffer fbb = ibb.asFloatBuffer(); for (Float aFloat : list) { fbb.put(aFloat); } //定位指標的位置, 從該位置開始讀取頂點資料 ibb.position(0); return ibb; } }
2.6. 渲染模式
OpenGL ES有兩種渲染模式:
GLSurfaceView.RENDERMODE_CONTINUOUSLY: 持續渲染(預設)
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. 緩衝區
緩衝區分為:
- 頂點緩衝區(VertexBuffer)
- 顏色緩衝區(ColorBuffer)
使用方式:
gl.glColorPointer(xx);//指定顏色緩衝區
gl.glVertexPointer(xx);//指定頂點指標
注意:
不要忘記在onSurfaceCreated() 方法中啟用兩個緩衝區
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//啟用頂點緩衝區
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);//啟動顏色緩衝區
3. 空間繪圖
OpenGL ES支援三種繪圖模式:點, 線和三角形.
3.1. 點
畫素是計算機監視器上最小的元素.在彩色系統中,畫素可以是許多可用顏色的一種.螢幕上的一個位置對應一個點,併為之指定顏色.然後,在此基礎上,用最擅長的計算機語言生成直線,多邊形,圓形以及其他圖形,甚至整個GUI.
下圖把可視區域看成是三維畫布,可使用OpenGL命令和函式進行繪圖.

畫點:
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;//點的步長 //迴圈繪製 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})); //畫陣列 gl.glDrawArrays(GL10.GL_POINTS, 0, 1); }

preview
3.2. 線
畫線分為三類:
- lines: 線集(線段集合)
- line_strip: 線帶(首尾相接不閉環)
- line_loop: 線環(首尾相接閉環)

線帶

線環
畫線:
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)); //畫陣列 gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, coordsList.size() / 3);

preview
3.3. 三角形
使用畫線方式繪製的圖形的線框樣式的,即都是直線無法進行
顏色填充,多邊形是閉合形狀.可以選擇顏色進行填充.
有三種模式:
3.3.1. 環繞(GL_TRIANGLES)
注意連線頂點的箭頭方向,第一個三角形的順序是v0->v1->v2->v0,該順序是有定點的指定次序決定的.該順序為順時針方向.第二個三角形也是一樣.頂點的指定次序以及方向的組合成為環繞(winding).左圖看成是順時針環繞,如果將v4和v5兩點互換,得到逆時針環繞.如右圖:


3.3.2. 三角形帶(GL_TRIANGLES_STRIP)
GL_TRIANLTE_STRIP,可繪製一串相鄰的三角形,這些頂點不是按照他們指定的順興進行遍歷的,這是為了保持每個三角形的環繞方向(逆時針).他的繪製模式是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)); //畫陣列 gl.glDrawArrays(GL10.GL_TRIANGLES, 0, coords.length/ 3);
3.4 實心物體
3.4.1. 設定顏色
我們知道畫點畫線或者三角形都要指定頂點緩衝區,同樣的也會顏色緩衝區.
需要注意的是, 頂點緩衝區中的點與顏色緩衝區中的點進行對應.

顏色緩衝區
頂點的著色模式:
- smmoth: 平滑模式,也稱漸變(預設)
- flat: 單調模式以三角形最後一個點的顏色值為準
設定著色模式:
gl.glShadeMode(GL_FLAT|GL_SMOOTH);//單調|平滑
注意: 三角形面設定的顏色, 跟最後一個頂點的顏色相同.
3.4.2. 深度測試
深度測試是一種有效地的用於隱藏表面消除的技巧,當一個畫素繪製時,他將被設定一個值(稱為z值),來表示他和觀察者之間的距離.以後,當該位置需要繪製另一個畫素時,新畫素的z值和原先的值進行比較.如果z值越高,距離觀察者越近,就位於以前畫素的前面.反之位於後面.內部是通過深度緩衝區實現的.他儲存了螢幕上每個畫素的深度值.

使用深度測試

不使用深度測試
啟用深度測試:( 不要忘記在繪圖開始時清除深度緩衝區
)
gl.glClear(GL10.GL_DEPTH_BUFFER_BIT);//清除深度緩衝區
gl.glEnable(GL10.GL_DEPTH_TEST);//啟用深度測試
3.4.3. 剔除
使用深度測試,效能上需要有開銷,因為每次繪製畫素都需要比較.如果知道那個面肯定不能繪製,就可以使用剔除這種技巧了.消除已經知道肯定不會繪製的幾何圖形.也不會向OpenGL驅動程式傳送這個幾何圖形,效能能夠顯著提升.
剔除技巧之一就是背面剔除,消除一個表面的背面.
//啟用表面剔除
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);