OpenGL ES 學習筆記(1)-基本使用

1. OpenGL ES 基礎

1.1. OpenGL 坐標

屏幕坐標系中橫軸是x軸,縱軸是y軸,分別向右是正方向,向下是正方向.原點在左上角
數(shù)學坐標系中原點在左下角,向上是y軸正方向.
在OpenGL3D坐標系中,稍有不同,使用的是笛卡爾坐標系,表示空間坐標位置.和數(shù)學坐標系相似,左下角是原點,向右和向上是正值.還要添加第三個量就是z軸.z軸方向指向你.
下圖是OpenGL ES笛卡爾坐標系.

OpenGL ES笛卡爾坐標系

實際上我們有幾種坐標系和空間,在OpenGL中,每個空間位置會按照如下變換:

  • 對象空間,相對于每個對象自身
  • 相機,眼睛,空間是針對視點
  • 投影,剪裁,空間是平面的屏幕或者是圖像上的視口.

1.2. 視景體

也叫平截頭體(frustum),是一個棱臺,表示的是照相機能夠捕捉到畫面的一個區(qū)域.
下圖為視景體


視景體

視景體數(shù)學模型

定義視景體非常簡單通過定義一個幾何體就可以了,空間中,任何平截頭體內(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()方法

  1. 設置viewport(視口) 輸出畫面的區(qū)域(從左下角開始)
    gl.glViewport(0, 0, width, height);
  2. 操縱投影矩陣,設置平截頭體(比例通常和視口比例相同,否則輸出畫面會走樣)
    //矩陣模式,投影矩陣,openGL基于狀態(tài)機
    gl.glMatrixMode(GL10.GL_PROJECTION);
    //加載單位矩陣
    gl.glLoadIdentity();
    //平截頭體 zNear:近平面 zFar:遠平面
    gl.glFrustumf(-ratio, ratio, -1f, 1f, 3, 7);

2.4.3. onDrawFrame()方法:

  1. 清除顏色緩沖區(qū)
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
  2. 操縱模型視圖矩陣,設置眼球的參數(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);
  3. 定義圖形頂點坐標值數(shù)組
    float[] coords = {
    0f,0.5f,0f,
    -0.5f,-0.5f,0f,
    0.5f,-0.5f,0f
    };
  4. 將頂點坐標轉換成緩沖區(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);
  5. 設置繪圖顏色
    gl.glColor4f(1f, 0f, 0f, 1f);
  6. 指定頂點緩沖區(qū)指針
    //3:3維點,使用三個坐標值表示一個點
    //type:每個點的數(shù)據(jù)類型
    //stride:0,跨度.
    //ibb:指定頂點緩沖區(qū)
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ibb);
  7. 繪圖
    //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);
}
preview

3.2. 線

畫線分為三類:

  • lines: 線集(線段集合)
  • line_strip: 線帶(首尾相接不閉環(huán))
  • line_loop: 線環(huán)(首尾相接閉環(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);
preview

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ū)中的點進行對應.

顏色緩沖區(qū)

頂點的著色模式:

  1. smmoth: 平滑模式,也稱漸變(默認)
  2. 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);

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖檩赢,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡梅掠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門店归,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阎抒,“玉大人,你說我怎么就攤上這事消痛∏胰” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵秩伞,是天一觀的道長逞带。 經(jīng)常有香客問我,道長纱新,這世上最難降的妖魔是什么展氓? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮脸爱,結果婚禮上遇汞,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好空入,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布教寂。 她就那樣靜靜地躺著,像睡著了一般执庐。 火紅的嫁衣襯著肌膚如雪酪耕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天轨淌,我揣著相機與錄音迂烁,去河邊找鬼。 笑死递鹉,一個胖子當著我的面吹牛盟步,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播躏结,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼却盘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了媳拴?” 一聲冷哼從身側響起黄橘,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屈溉,沒想到半個月后塞关,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡子巾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年帆赢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片线梗。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡椰于,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仪搔,到底是詐尸還是另有隱情瘾婿,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布僻造,位于F島的核電站憋他,受9級特大地震影響孩饼,放射性物質發(fā)生泄漏髓削。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一镀娶、第九天 我趴在偏房一處隱蔽的房頂上張望立膛。 院中可真熱鬧,春花似錦、人聲如沸宝泵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽儿奶。三九已至框往,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闯捎,已是汗流浹背椰弊。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓤鼻,地道東北人秉版。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像茬祷,于是被迫代替她去往敵國和親清焕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容