OpenGL ES基礎(chǔ)篇

1. 概述

Android通過使用Open Graphics Library(OpenGL?)提供了對(duì)高性能2D和3D圖形的支持宾濒。 OpenGL是一個(gè)跨平臺(tái)圖形API湾笛,用于指定3D圖形處理硬件的標(biāo)準(zhǔn)軟件界面喧伞。 OpenGL ES是面向嵌入式設(shè)備的OpenGL規(guī)范。 Android支持多種版本的OpenGL ES API 規(guī)范:

OpenGL ES 1.0和1.1 - 此API規(guī)范由Android 1.0及更高版本支持歼秽。
OpenGL ES 2.0 - 此API規(guī)范由Android 2.2(API級(jí)別8)及更高版本支持折砸。
OpenGL ES 3.0 - 此API規(guī)范由Android 4.3(API級(jí)別18)及更高版本支持。
OpenGL ES 3.1 - 此API規(guī)范由Android 5.0(API級(jí)別21)及更高版本支持诊胞。

注意:在設(shè)備上支持OpenGL ES 3.0 API需要設(shè)備的制造商提供此圖形管道的實(shí)現(xiàn)暖夭。 運(yùn)行Android 4.3
或更高版本的設(shè)備可能不支持OpenGL ES 3.0 API。 開發(fā)時(shí)如何配置OpenGL ES的版本可以參考下面的6.3節(jié)。

2. OpenGL ES API簡(jiǎn)介

Android通過其framework API和Native Development Kit(NDK)來支持OpenGL迈着。 本文側(cè)重于Android framework 層面竭望。 關(guān)于NDK可以參考 Android NDK

Android framework中提供了兩個(gè)基礎(chǔ)類:GLSurfaceView和GLSurfaceView.Renderer裕菠。為了在Android應(yīng)用程序中使用OpenGL ES API咬清,那么在一個(gè)Activity中使用這些類是很關(guān)鍵的。

1. GLSurfaceView : 用來顯示OpenGL ES API繪制和操作的圖像對(duì)象的View奴潘,并且在功能上與
SurfaceView類似旧烧。 通過創(chuàng)建GLSurfaceView的實(shí)例并且將Renderer添加到該實(shí)例中來使用此類。
如果想要捕獲觸touch event画髓,則應(yīng)該繼承GLSurfaceView類并且實(shí)現(xiàn)onTouchEvent方法粪滤,
具體參考https://developer.android.google.cn/training/graphics/opengl/touch.html。

2. GLSurfaceView.Renderer :
該接口定義了在GLSurfaceView中繪制圖形對(duì)象所需的方法雀扶。

GLSurfaceView.Renderer接口中的方法:
1> onSurfaceCreated():創(chuàng)建GLSurfaceView時(shí)杖小,系統(tǒng)會(huì)調(diào)用此方法一次。 使用此方法完成只需要
執(zhí)行一次的操作愚墓,例如設(shè)置OpenGL環(huán)境參數(shù)或初始化OpenGL圖形對(duì)象予权。
2> onDrawFrame():系統(tǒng)在每次繪制GLSurfaceView時(shí)調(diào)用此方法。 使用此方法作為繪制圖形對(duì)象的主要執(zhí)行點(diǎn)浪册。
3> onSurfaceChanged():當(dāng)GLSurfaceView geometry更改時(shí)扫腺,系統(tǒng)會(huì)調(diào)用此方法,包括GLSurfaceView
的大小或設(shè)備屏幕方向的更改村象。 使用此方法來響應(yīng)GLSurfaceView容器中的變化笆环。

一旦建立了GLSurfaceView和GLSurfaceView.Renderer的關(guān)聯(lián),就可以使用以下OpenGL ES API:

  1. OpenGL ES 1.0/1.1 API Packages :
    1> android.opengl :該包為OpenGL ES 1.0 / 1.1類提供了一個(gè)靜態(tài)接口厚者,并且具有比javax.microedition.khronos包接口更好的性能躁劣。
    GLES10
    GLES10Ext
    GLES11
    GLES11Ext
    2> javax.microedition.khronos.opengles : 該包提供了OpenGL ES 1.0 / 1.1的標(biāo)準(zhǔn)實(shí)現(xiàn)。
    GL10
    GL10Ext
    GL11
    GL11Ext
    GL11ExtensionPack

  2. OpenGL ES 2.0 API Packages :
    android.opengl.GLES20:該包為OpenGL ES 2.0的提供了接口并且從Android 2.2 (API level 8)開始可用库菲。

  3. OpenGL ES 3.0/3.1 API Packages:
    android.opengl:該包為OpenGL ES 3.0 / 3.1提供了接口账忘。 版本3.0可從Android 4.3 (API level 18)開始。 版本3.1可從Android 5.0 (API level 21)開始熙宇。
    GLES30
    GLES31
    GLES31Ext (Android Extension Pack)

3. 聲明OpenGL

如果您的應(yīng)用程序使用了OpenGL ES鳖擒,則必須在清單文件中作出聲明。 以下是最常見的OpenGL 清單聲明:
1> OpenGL ES版本 - 如果應(yīng)用程序需要特定版本的OpenGL ES烫止,則必須通過將以下設(shè)置添加到清單中來聲明該需求蒋荚,如下所示。
For OpenGL ES 2.0:

<!-- Tell the system this app requires OpenGL ES 2.0. -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

添加此聲明將導(dǎo)致Google Play將限制應(yīng)用程序只能安裝在支持OpenGL ES 2.0的設(shè)備上馆蠕。 如果您的應(yīng)用程序只能安裝在支持OpenGL ES 3.0的設(shè)備期升,則可以在清單中作出如下聲明:
For OpenGL ES 3.0:

<!-- Tell the system this app requires OpenGL ES 3.0. -->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />

For OpenGL ES 3.1:

<!-- Tell the system this app requires OpenGL ES 3.1. -->
<uses-feature android:glEsVersion="0x00030001" android:required="true" />

注意:OpenGL ES 3.x API向后兼容2.0 API广鳍,因此當(dāng)聲明的版本是2.0時(shí),在設(shè)備支持的情況下也可以使用3.0版本中的功能吓妆,具體參考6.3。

2> Texture compression 聲明 - 如果應(yīng)用程序使用了texture compression格式吨铸,則必須在清單文件中使用<supports-gl-texture>聲明應(yīng)用程序支持的texture compression格式行拢。 有關(guān)Texture compression格式的更多信息,參考下面的6.1節(jié)诞吱。

如果設(shè)備不支持清單中texture compression聲明的所用格式舟奠,則該應(yīng)用無法安裝到該設(shè)備中, 關(guān)于Google Play過濾texture compression的詳細(xì)信息房维,請(qǐng)參閱<supports-gl-texture>文檔的Google Play and texture compression filtering部分沼瘫。

4. 坐標(biāo)系映射

在Android設(shè)備上顯示圖形的一個(gè)基本問題是屏幕的尺寸和形狀是不同的。 OpenGL假設(shè)屏幕坐標(biāo)系時(shí)一個(gè)正方形并且分布均勻的坐標(biāo)系咙俩,因此將圖形繪制到非正方形屏幕上時(shí)圖形會(huì)被壓縮或者拉伸耿戚,如下圖所示:


圖1 默認(rèn)OpenGL坐標(biāo)系(左)映射到矩形的Android設(shè)備屏幕(右)。

要解決右側(cè)圖形變形的問題阿趁,就需要用到OpenGL 的projection 和 camera views來轉(zhuǎn)換坐標(biāo)系膜蛔,以使圖形對(duì)象在任何顯示屏上都具有正確的比例。

通過創(chuàng)建projection matrix 和 camera view matrix并且將其應(yīng)用于OpenGL渲染環(huán)境來應(yīng)用projection 和 camera views脖阵。 projection matrix重新計(jì)算圖形坐標(biāo)皂股,以使圖形以正確的比例繪制到Android設(shè)備屏幕上。 camera view matrix創(chuàng)建一個(gè)從特定視角渲染圖形對(duì)象的轉(zhuǎn)換命黔。

4.1 Projection and camera view in OpenGL ES 1.0

在ES 1.0 API中呜呐,通過創(chuàng)建projection matrix 和 camera view matrix并且將其應(yīng)用于OpenGL渲染環(huán)境來應(yīng)用projection 和 camera views。

1> Projection matrix - 使用設(shè)備屏幕的geometry創(chuàng)建projection matrix悍募,然后利用
projection matrix重新計(jì)算圖形坐標(biāo)蘑辑,以使圖形以正確的比例繪制到Android設(shè)備屏幕上。 
下面onSurfaceChanged方法中演示了根據(jù)屏幕的寬高比創(chuàng)建projection matrix 坠宴,
并將其應(yīng)用于OpenGL渲染環(huán)境以躯。

public void onSurfaceChanged(GL10 gl, int width, int height) {
    gl.glViewport(0, 0, width, height);

    // make adjustments for screen ratio
    float ratio = (float) width / height;
    gl.glMatrixMode(GL10.GL_PROJECTION);        // set matrix to projection mode
    gl.glLoadIdentity();                        // reset the matrix to its default state
    gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);  // apply the projection matrix
}

2> Camera transformation matrix - 一旦使用projection matrix調(diào)整坐標(biāo)系,就必須使用
camera view啄踊。 下面onDrawFrame()方法中演示了使用model view并且使用GLU.gluLookAt() utility
創(chuàng)建模擬攝像頭位置的視角轉(zhuǎn)換忧设。
public void onDrawFrame(GL10 gl) {
    ...
    // Set GL_MODELVIEW transformation mode
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();                      // reset the matrix to its default state

    // When using GL_MODELVIEW, you must set the camera view
    GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
    ...
}
4.2 Projection and camera view in OpenGL ES 2.0 and higher

在ES 2.0和3.0 API中,為了應(yīng)用projection 和 camera view颠通,首先將matrix成員添加到圖形對(duì)象的vertex shaders中址晕,然后生成和應(yīng)用projection matrix 和 camera view matrix到圖形對(duì)象。

1> 將matrix成員添加到vertex shaders中
private final String vertexShaderCode =

    // This matrix member variable provides a hook to manipulate
    // the coordinates of objects that use this vertex shader.
    "uniform mat4 uMVPMatrix;   \n" +

    "attribute vec4 vPosition;  \n" +
    "void main(){               \n" +
    // The matrix must be included as part of gl_Position
    // Note that the uMVPMatrix factor *must be first* in order
    // for the matrix multiplication product to be correct.
    " gl_Position = uMVPMatrix * vPosition; \n" +

    "}  \n";
注意:上面的示例定義了應(yīng)用單個(gè)matrix成員的vertex shader顿锰,該matrix成員可以是projection matrix 和
camera view matrix的組合形式谨垃。

2> 訪問 shader matrix - 在vertex shaders中添加matrix成員后启搂,就可以通過訪問該matrix成員
以應(yīng)用projection 和 camera view matrices。下面onSurfaceCreated()方法中演示了訪問上面
vertex shaders中定義的matrix成員刘陶。
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    ...
    muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
    ...
}

3> 創(chuàng)建 projection 和 camera view matrices - 生成要應(yīng)用到圖形對(duì)象的projection 和 camera view matrix胳赌。
下面onSurfaceCreated()和onSurfaceChanged()方法中演示了創(chuàng)建camera view matrix 和 
基于設(shè)備屏幕寬高比的projection matrix。
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    ...
    // Create a camera view matrix
    Matrix.setLookAtM(mVMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
}

public void onSurfaceChanged(GL10 unused, int width, int height) {
    GLES20.glViewport(0, 0, width, height);

    float ratio = (float) width / height;

    // create a projection matrix from device screen geometry
    Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}

4> 應(yīng)用 projection 和 camera view matrices - 為了應(yīng)用projection和camera viewing matrices匙隔,
先將projection和camera viewing matrices相乘然后將結(jié)果設(shè)置給vertex shader疑苫。 
下面onDrawFrame()方法中演示了組合上述代碼中創(chuàng)建的projection matrix 和camera view matrix,
然后將結(jié)果應(yīng)用于由OpenGL渲染的圖形對(duì)象纷责。
public void onDrawFrame(GL10 unused) {
    ...
    // Combine the projection and camera view matrices
    Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);

    // Apply the combined projection and camera view transformations
    GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);

    // Draw objects
    ...
}

5. Shape 的面和Winding

在OpenGL中捍掺,Shape的面是由三維空間中的三個(gè)或更多個(gè)點(diǎn)定義的表面。 一組三個(gè)或更多的三維點(diǎn)(在OpenGL中稱為vertices)具有正面和背面再膳。 你怎么知道哪個(gè)面是正面的挺勿,哪個(gè)是背面? 這和繪制Shape坐標(biāo)點(diǎn)的順序有關(guān)喂柒。



在該示例中不瓶, 繪制這些坐標(biāo)的順序定義了shape的winding方向。 默認(rèn)情況下灾杰,在OpenGL中湃番,逆時(shí)針方向繪制的面是正面。

為什么要知道shape的哪一面是正面吭露? 這與OpenGL的常用功能有關(guān)吠撮,稱為face culling。 face culling是OpenGL環(huán)境的一個(gè)選擇讲竿,使渲染管道忽略(不計(jì)算或繪制)shape的背面泥兰,以節(jié)省時(shí)間、內(nèi)存和處理周期:

// enable face culling feature
gl.glEnable(GL10.GL_CULL_FACE);
// specify which faces to not draw
gl.glCullFace(GL10.GL_BACK);

在不知道shape的哪一面是正面的情況下使用face culling功能题禀,shape可能看起來有點(diǎn)薄或者根本不顯示鞋诗,因此建議以逆時(shí)針方向定義shape的坐標(biāo)。

注意:可以通過配置OpenGL環(huán)境來將順時(shí)針繪制的面視為正面迈嘹,但這樣做需要更多的代碼削彬,因此沒有必要這樣做。

6. OpenGL版本和設(shè)備兼容性

OpenGL ES 1.0和1.1 API規(guī)范自Android 1.0以來一直得到支持秀仲。 從Android 2.2(API 8級(jí))開始融痛,開始支持OpenGL ES 2.0 API規(guī)范。 大多數(shù)Android設(shè)備都支持OpenGL ES 2.0 API規(guī)范神僵,并且在開發(fā)具有OpenGL功能的應(yīng)用時(shí)推薦使用OpenGL ES 2.0雁刷。 在提供OpenGL ES 3.0 API實(shí)現(xiàn)的設(shè)備上,Android 4.3(API級(jí)別18)及更高版本支持OpenGL ES 3.0保礼。 有關(guān)支持給定版本OpenGL ES的Android設(shè)備相對(duì)數(shù)量的信息沛励,請(qǐng)參閱OpenGL ES Version Dashboard责语。

使用OpenGL ES 1.0 / 1.1 API的圖形編程與使用2.0及更高版本顯著不同。 API的1.x版本具有更多的便利方法和固定的圖形管道目派,而OpenGL ES 2.0和3.x API通過使用OpenGL shaders提供對(duì)管道更直接的控制坤候。 您應(yīng)該仔細(xì)考慮圖形要求,并選擇最適合應(yīng)用程序的API版本企蹭。

OpenGL ES 3.x API提供了比2.0 API更多的功能和更好的性能白筹,并且還向后兼容2.0版本。 這意味著可以在編寫面向OpenGL ES 2.0的應(yīng)用程序時(shí)练对,有條件地包含OpenGL ES 3.x的圖形功能(如果可用)。 有關(guān)檢查3.x API可用性的更多信息吹害,請(qǐng)參閱下面的6.3節(jié)螟凭。

6.1 Texture compression support

Texture compression可以通過減少內(nèi)存需求和更有效地利用內(nèi)存帶寬來顯著提高OpenGL應(yīng)用程序的性能。 Android framework提供了對(duì)的ETC1 texture compression格式的支持它呀,作為標(biāo)準(zhǔn)功能螺男,包括ETC1Util工具類和etc1tool compression tool(位于Android SDK中的<sdk> / platform-tools /目錄中)。 有關(guān)使用texture compression的Android應(yīng)用程序的示例纵穿,請(qǐng)參閱Android SDK(<sdk> / samples / <version> / ApiDemos / src / com / example / android / apis / graphics /)中的CompressedTextureActivity代碼示例下隧。

注意:大多數(shù)Android設(shè)備都支持ETC1格式,但不能保證可用谓媒。 要檢查設(shè)備是否支持ETC1格式淆院,請(qǐng)調(diào)用ETC1Util.isETC1Supported()方法。

注意:ETC1 texture compression格式不支持具有透明度(Alpha通道)的紋理句惯。 如果您的應(yīng)用程序需要具有透明度的紋理土辩,則可以看看目標(biāo)設(shè)備上其他的可用的texture compression格式是否支持。

當(dāng)使用OpenGL ES 3.0 API時(shí)抢野,ETC2 / EAC texture compression格式一定可用拷淘。 這種texture compression格式提供出色的壓縮比,具有高視覺質(zhì)量指孤,同時(shí)也支持透明度(Alpha通道)启涯。

除了ETC格式,Android設(shè)備還可以根據(jù)GPU chipsets和OpenGL的實(shí)現(xiàn)對(duì)texture compression有不同的支持恃轩。 首先看看目標(biāo)設(shè)備上支持的texture compression格式结洼,繼而確定應(yīng)用程序應(yīng)支持哪些texture compression格式。 為了確定給定設(shè)備上支持什么texture compression格式叉跛,您必須查詢?cè)O(shè)備的OpenGL擴(kuò)展名稱(如下面的確定OpenGL擴(kuò)展)补君,這些名稱標(biāo)識(shí)了設(shè)備支持的texture compression格式(以及其他OpenGL特性)。 一些通常支持的texture compression格式如下:

1> ATITC (ATC) - ATI texture compression(ATITC或ATC)可用于各種設(shè)備昧互,并支持具有或者不具有
Alpha通道的RGB紋理的固定速率壓縮挽铁。 此格式可以由以下幾個(gè)OpenGL擴(kuò)展名稱表示:
GL_AMD_compressed_ATC_texture
GL_ATI_texture_compression_atitc

2> PVRTC - PowerVR texture compression(PVRTC)可用于各種設(shè)備伟桅,并支持具有或不具有Alpha通道的
每像素2位或者4位的紋理。 此格式由以下OpenGL擴(kuò)展名稱表示:
GL_IMG_texture_compression_pvrtc

3> S3TC (DXTn/DXTC) - S3texture compression(S3TC)具有多種格式變體(DXT1至DXT5)叽掘,
并且是一種不太廣泛的可用格式楣铁。 該格式支持具有4位alpha或者8位alpha通道的RGB紋理。 
這些格式由以下OpenGL擴(kuò)展名稱表示:
GL_EXT_texture_compression_s3tc

一些設(shè)備只支持DXT1格式變體; 有此限制的OpenGL擴(kuò)展名稱為:
GL_EXT_texture_compression_dxt1

4> 3DC - 3DC texture compression(3DC)是一種不太廣泛的可用格式更扁,支持具有Alpha通道的RGB紋理盖腕。 
此格式由以下OpenGL擴(kuò)展名稱表示:
GL_AMD_compressed_3DC_texture

注意:對(duì)這些格式的支持可能因制造商和設(shè)備而異。 有關(guān)如何確定特定設(shè)備上的texture compression格式的信息浓镜,請(qǐng)參閱下一節(jié)溃列。

一旦您決定應(yīng)用程序?qū)⒅С帜姆N紋理壓縮格式,請(qǐng)確保使用<supports-gl-texture>在清單中聲明它們膛薛。 
使用此聲明可以通過外部服務(wù)(如Google Play)進(jìn)行過濾听隐,以便您的應(yīng)用程序僅安裝在支持應(yīng)用程序所需格式的設(shè)備上。
6.2 確定OpenGL擴(kuò)展

由于Android設(shè)備支持的OpenGL ES API擴(kuò)展方面的不同哄啄,因此OpenGL的實(shí)現(xiàn)因Android設(shè)備而異雅任。 這些擴(kuò)展包括texture compressions,通常還包括其他的OpenGL擴(kuò)展咨跌。

要確定特定設(shè)備支持什么texture compressions格式和其他OpenGL擴(kuò)展:
1> 在目標(biāo)設(shè)備上運(yùn)行以下代碼沪么,以確定支持哪種texture compressions格式:

String extensions = javax.microedition.khronos.opengles.GL10.glGetString(
        GL10.GL_EXTENSIONS);

注意:此調(diào)用的結(jié)果因設(shè)備型號(hào)而異! 您必須在多個(gè)目標(biāo)設(shè)備上運(yùn)行此調(diào)用锌半,以確定通常支持哪些texture compressions格式禽车。

2> 查看此方法的輸出以確定設(shè)備上支持哪些OpenGL擴(kuò)展。

Android Extension Pack (AEP)

AEP確保應(yīng)用程序支持OpenGL 3.1規(guī)范中描述的核心集以外的標(biāo)準(zhǔn)化的OpenGL擴(kuò)展刊殉。 將這些擴(kuò)展打包在一起促進(jìn)跨設(shè)備的一整套功能哭当,同時(shí)允許開發(fā)人員充分利用最新的移動(dòng)GPU設(shè)備。

AEP還改進(jìn)了對(duì)fragment shaders中的images冗澈,shader存儲(chǔ)緩沖區(qū)和atomic計(jì)數(shù)器的支持钦勘。

為了使您的應(yīng)用程序能夠使用AEP,必須在應(yīng)用程序的清單聲明AEP亚亲。 此外彻采, Android版本必須支持它。在清單中聲明AEP:

<uses feature android:name="android.hardware.opengles.aep"
              android:required="true" />

要驗(yàn)證Android平臺(tái)版本是否支持AEP捌归,請(qǐng)使用hasSystemFeature(String)方法肛响,傳入FEATURE_OPENGLES_EXTENSION_PACK作為參數(shù)。 以下代碼片段顯示了如何執(zhí)行此操作:

boolean deviceSupportsAEP = getPackageManager().hasSystemFeature
     (PackageManager.FEATURE_OPENGLES_EXTENSION_PACK);

如果該方法返回true惜索,則支持AEP特笋。
有關(guān)AEP的更多信息,請(qǐng)?jiān)L問 Khronos OpenGL ES Registry頁面巾兆。

6.3 配置OpenGL ES的版本

通常而言猎物,現(xiàn)在的Android設(shè)備沒有低于API 8版本的虎囚,因此在應(yīng)用的清單文件中指定OpenGL ES最低版本是2.0,由于OpenGL ES 3.x向后兼容2.0版本蔫磨,因此在支持3.x的設(shè)備上是可以使用3.x版本中的功能淘讥,為了使用3.x版本中的功能,首先應(yīng)該檢查當(dāng)前設(shè)備是否支持3.x版本堤如,如果當(dāng)前設(shè)備支持3.x版本蒲列,則通過創(chuàng)建和應(yīng)用3.x版本EGLContext的方式來使用3.x版本中的功能。

檢查設(shè)備支持的OpenGL ES版本有如下兩種方式:

1. 創(chuàng)建更高版本的OpenGL ES context(EGLContext)并檢查結(jié)果的方式搀罢。
2. 創(chuàng)建一個(gè)最低版本OpenGL ES context并檢查版本的方式蝗岖。

1> 通過第一種方式檢查設(shè)備是否支持的OpenGL ES 3.x版本:

private static double glVersion = 3.x; //3.0或者3.1

private static class ContextFactory implements GLSurfaceView.EGLContextFactory {

  private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;

  public EGLContext createContext(
          EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {

      Log.w(TAG, "creating OpenGL ES " + glVersion + " context");
      int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, (int) glVersion,
              EGL10.EGL_NONE };
      // attempt to create a OpenGL ES 3.0 context
      EGLContext context = egl.eglCreateContext(
              display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
      return context; // returns null if 3.0 is not supported;
  }
}

2> 通過第二種方式來檢查設(shè)備支持的OpenGL ES版本:

// Create a minimum supported OpenGL ES context, then check:
String version = javax.microedition.khronos.opengles.GL10.glGetString(
        GL10.GL_VERSION);
Log.w(TAG, "Version: " + version );
// The version format is displayed as: "OpenGL ES <major>.<minor>"
// followed by optional content provided by the implementation.

使用這種方法,如果您發(fā)現(xiàn)設(shè)備支持更高級(jí)的API版本榔至,則必須銷毀最低的OpenGL ES context抵赢,并使用更高的可用API版本創(chuàng)建新的context。

通過上面兩種方式檢查設(shè)備是否支持3.x版本的EGLContext的后洛退,如果支持瓣俯,就創(chuàng)建3.x版本的EGLContext并且通過GLSurfaceView的setEGLContextFactory方法應(yīng)用3.x版本的EGLContext杰标。

7. 舉個(gè)例子

先看一下效果:


上面的效果很簡(jiǎn)單兵怯,三角形會(huì)隨著手指的滑動(dòng)而轉(zhuǎn)動(dòng),下面來講解一下具體實(shí)現(xiàn):
1> 配置OpenGL ES版本

在清單文件中指定所需要的版本號(hào)為2.0
<uses-feature
    android:glEsVersion="0x00020000"
    android:required="true" />

如果設(shè)備支持3.0版本腔剂,則使用3.0中的功能:
public class MyContextFactory implements GLSurfaceView.EGLContextFactory {

    private static final String TAG = "MyContextFactory";

    private static double glVersion = 3.0;
    private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;

    @Override
    public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
        Log.w(TAG, "creating OpenGL ES " + glVersion + " context");
        int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, (int) glVersion,
                EGL10.EGL_NONE};
        // attempt to create a OpenGL ES 3.0 context
        EGLContext context = egl.eglCreateContext(
                display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
        if (context == null || context == EGL10.EGL_NO_CONTEXT) {
            attrib_list = new int[]{EGL_CONTEXT_CLIENT_VERSION, 2,
                    EGL10.EGL_NONE};
            context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
        }
        return context;
    }

    @Override
    public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
        egl.eglDestroyContext(display, context);
    }
}
在createContext方法中媒区,會(huì)嘗試創(chuàng)建3.0版本的EGLContext,如果創(chuàng)建失敗掸犬,即該設(shè)備不支持3.0版本袜漩,
那么就會(huì)去創(chuàng)建2.0版本的EGLContext。上面的代碼大部分是參考GLSurfaceView的內(nèi)部類
DefaultContextFactory(該類是被默認(rèn)使用的)湾碎。

接下來就是將MyContextFactory應(yīng)用到GLSurfaceView實(shí)例中宙攻,代碼如下:
MyContextFactory contextFactory = new MyContextFactory();
setEGLContextFactory(contextFactory);
注意setEGLContextFactory方法必須在setRenderer方法執(zhí)行之前被調(diào)用(具體原因可以參考setRenderer的源碼)。

2> 定義三角形
最常用的方法是創(chuàng)建一個(gè)保存三角形坐標(biāo)的浮點(diǎn)型數(shù)值介褥,如下所示:

// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
static float triangleCoords[] = {
        // in counterclockwise order:
        0.0f,  0.622008459f, 0.0f,   // top
        -0.5f, -0.311004243f, 0.0f,   // bottom left
        0.5f, -0.311004243f, 0.0f    // bottom right
};
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

// Set color with red, green, blue and alpha (opacity) values
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

/**
 * Sets up the drawing object data for use in an OpenGL ES context.
 */
public Triangle() {
    // initialize vertex byte buffer for shape coordinates
    ByteBuffer bb = ByteBuffer.allocateDirect(
            // (number of coordinate values * 4 bytes per float)
            triangleCoords.length * 4);
    // use the device hardware's native byte order
    bb.order(ByteOrder.nativeOrder());

    // create a floating point buffer from the ByteBuffer
    vertexBuffer = bb.asFloatBuffer();
    // add the coordinates to the FloatBuffer
    vertexBuffer.put(triangleCoords);
    // set the buffer to read the first coordinate
    vertexBuffer.position(0);
}

3> 繪制三角形
為了在設(shè)備屏幕上繪制這個(gè)三角形座掘,就要用到GLSurfaceView和GLSurfaceView.Renderer:

public class MyGLSurfaceView extends GLSurfaceView {

    private MyGLRenderer mRenderer;

    public MyGLSurfaceView(Context context) {
        super(context);
        initView();
    }

    public MyGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    private void initView() {
        MyContextFactory contextFactory = new MyContextFactory();
        setEGLContextFactory(contextFactory);

        // Set the Renderer for drawing on the GLSurfaceView
        mRenderer = new MyGLRenderer();
        setRenderer(mRenderer);
    }
}

public class MyGLRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "MyGLRenderer";

    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // Set the background frame color
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }

    @Override
    public void onDrawFrame(GL10 unused) {
        // Draw background color
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    }

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        // Adjust the viewport based on geometry changes,
        // such as screen rotation
        GLES20.glViewport(0, 0, width, height);
    }
}

上面的代碼完成了GLSurfaceView.Renderer與GLSurfaceView的關(guān)聯(lián),接下來就是在MyGLRenderer的onSurfaceCreated方法中初始化三角形對(duì)象:

public class MyGLRenderer implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        ...

        // initialize a triangle
        mTriangle = new Triangle();
    }
    ...
}

接下來就是在MyGLRenderer的onDrawFrame方法中繪制上面初始化的三角形:

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

Triangle的draw方法是用來完成三角形的繪制操作柔滔,使用OpenGL ES 2.0繪制定義的形狀需要大量的代碼溢陪,因?yàn)楸仨毾驁D形渲染管道提供大量細(xì)節(jié),必須提供以下內(nèi)容:

1. Vertex Shader - OpenGL ES graphics code for rendering the vertices of a shape.
2. Fragment Shader - OpenGL ES code for rendering the face of a shape with colors or textures.
3. Program - An OpenGL ES object that contains the shaders you want to use for drawing one or more shapes.

以下是定義用于繪制三角形的shaders的代碼:

public class Triangle {

    private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
        "void main() {" +
        "  gl_Position = vPosition;" +
        "}";

    private final String fragmentShaderCode =
        "precision mediump float;" +
        "uniform vec4 vColor;" +
        "void main() {" +
        "  gl_FragColor = vColor;" +
        "}";

    ...
}

上面的Shader是一段執(zhí)行在GPU上的程序睛廊,此程序使用OpenGL Shading Language (GLSL) 語言編寫的形真。接下來在MyGLRenderer創(chuàng)建工具方法loadShader用于編譯Shader:

public static int loadShader(int type, String shaderCode){

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // add the source code to the shader and compile it
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}

編譯完Shader后,需要將Shader添加到Program對(duì)象中超全,然后鏈接該P(yáng)rogram對(duì)象咆霜,由于編譯Shader和鏈接Program對(duì)象是非常耗時(shí)的邓馒,因此將 編譯Shader和鏈接Program對(duì)象操作 放在Triangle的構(gòu)造方法中,只會(huì)被執(zhí)行一次:

public class Triangle() {
    ...

    private final int mProgram;

    public Triangle() {
        ...

        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                                        vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                                        fragmentShaderCode);

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }
}

接下來在Triangle的draw方法中裕便,首先將三角形的坐標(biāo)和顏色傳遞給Vertex Shader和Fragment Shader绒净,然后執(zhí)行繪制操作:

private int mPositionHandle;
private int mColorHandle;

private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

public void draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // get handle to vertex shader's vPosition member
    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // get handle to fragment shader's vColor member
    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // Set color for drawing the triangle
    GLES20.glUniform4fv(mColorHandle, 1, color, 0);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}

接下來運(yùn)行應(yīng)用,結(jié)果如下:



可以看到三角形被橫向壓扁了偿衰,這是因?yàn)闆]有應(yīng)用Projection 和 Camera Views挂疆。

4> 應(yīng)用Projection 和 Camera Views
在MyGLRenderer的onSurfaceChanged方法中創(chuàng)建Projection matrix:

// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];

@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
    GLES20.glViewport(0, 0, width, height);

    float ratio = (float) width / height;

    // this projection matrix is applied to object coordinates
    // in the onDrawFrame() method
    Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}

在MyGLRenderer的onSurfaceCreated方法中創(chuàng)建Camera Views matrix,然后在onSurfaceChanged方法中兩個(gè)matrix相乘:

@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    // Set the background frame color
    GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    // Set the camera position (View matrix)
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    mTriangle = new Triangle();
    mSquare   = new Square();
}

@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
    ...

    // Calculate the projection and view transformation
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
}

接下來首先修改一下vertex shader的代碼下翎,在其中添建uMVPMatrix成員缤言,然后在draw方法中將上面Projection matrix和 Camera Views matrix相乘的結(jié)果設(shè)置給vertex shader的uMVPMatrix成員:

public class Triangle {

    private final String vertexShaderCode =
        // This matrix member variable provides a hook to manipulate
        // the coordinates of the objects that use this vertex shader
        "uniform mat4 uMVPMatrix;" +
        "attribute vec4 vPosition;" +
        "void main() {" +
        // the matrix must be included as a modifier of gl_Position
        // Note that the uMVPMatrix factor *must be first* in order
        // for the matrix multiplication product to be correct.
        "  gl_Position = uMVPMatrix * vPosition;" +
        "}";

    // Use to access and set the view transformation
    private int mMVPMatrixHandle;

    ...
    public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix
        ...

        // get handle to shape's transformation matrix
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

        // Pass the projection and view transformation to the shader
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

        // Draw the triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}

接下來在運(yùn)行應(yīng)用,三角形就會(huì)以正確的比例顯示视事。

5> 響應(yīng)Touch Event
GLSurfaceView響應(yīng)Touch Event和普通View類似胆萧,重寫onTouchEvent方法:

public class MyGLSurfaceView extends GLSurfaceView {
    ...

    private void initView() {
        ...

        // Render the view only when there is a change in the drawing data
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
    private float mPreviousX;
    private float mPreviousY;

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        // MotionEvent reports input details from the touch screen
        // and other input controls. In this case, you are only
        // interested in events where the touch position changed.

        float x = e.getX();
        float y = e.getY();

        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:

                float dx = x - mPreviousX;
                float dy = y - mPreviousY;

                mRenderer.setAngle(
                        mRenderer.getAngle() +
                                ((dx + dy) * TOUCH_SCALE_FACTOR));  // = 180.0f / 320
                requestRender();
        }

        mPreviousX = x;
        mPreviousY = y;
        return true;
    }

}

上面的代碼根據(jù)手指滑動(dòng)的偏移量設(shè)置三角形旋轉(zhuǎn)的角度,然后調(diào)用requestRender方法請(qǐng)求重寫渲染三角形俐东,既而實(shí)現(xiàn)了三角形旋轉(zhuǎn)的效果跌穗。由于只有在角度發(fā)生改變的情況下重新渲染三角形才有意義,因此在initView方法中設(shè)置渲染模式為GLSurfaceView.RENDERMODE_WHEN_DIRTY以提高效率虏辫。

接下來就來看看MyGLRenderer中是如何旋轉(zhuǎn)圖形的:

private final float[] mRotationMatrix = new float[16];
private volatile float mAngle;

public void onDrawFrame(GL10 gl) {
    ...
    float[] scratch = new float[16];

    // Create a rotation for the triangle
    // long time = SystemClock.uptimeMillis() % 4000L;
    // float angle = 0.090f * ((int) time);
    Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);

    // Combine the rotation matrix with the projection and camera view
    // Note that the mMVPMatrix factor *must be first* in order
    // for the matrix multiplication product to be correct.
    Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);

    // Draw triangle
    mTriangle.draw(scratch);
}
/**
 * Returns the rotation angle of the triangle shape (mTriangle).
 *
 * @return - A float representing the rotation angle.
 */
public float getAngle() {
    return mAngle;
}

/**
 * Sets the rotation angle of the triangle shape (mTriangle).
 */
public void setAngle(float angle) {
    mAngle = angle;
}

上面代碼中的保存當(dāng)前旋轉(zhuǎn)角度的成員mAngle是volatile類型的蚌吸,這是因?yàn)镺penGL 的渲染代碼(比如onDrawFrame方法)運(yùn)行在獨(dú)立的線程(GLThread)中。
然后運(yùn)行應(yīng)用砌庄,滑動(dòng)屏幕時(shí)三角形就會(huì)發(fā)生旋轉(zhuǎn)羹唠,與本節(jié)最開始的效果一樣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末娄昆,一起剝皮案震驚了整個(gè)濱河市佩微,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萌焰,老刑警劉巖哺眯,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扒俯,居然都是意外死亡奶卓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門陵珍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寝杖,“玉大人,你說我怎么就攤上這事互纯∩唬” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)只盹。 經(jīng)常有香客問我辣往,道長(zhǎng),這世上最難降的妖魔是什么殖卑? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任站削,我火速辦了婚禮,結(jié)果婚禮上孵稽,老公的妹妹穿的比我還像新娘许起。我一直安慰自己,他們只是感情好菩鲜,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布园细。 她就那樣靜靜地躺著,像睡著了一般接校。 火紅的嫁衣襯著肌膚如雪猛频。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天蛛勉,我揣著相機(jī)與錄音鹿寻,去河邊找鬼。 笑死诽凌,一個(gè)胖子當(dāng)著我的面吹牛毡熏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播皿淋,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼招刹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼恬试!你這毒婦竟也來了窝趣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤训柴,失蹤者是張志新(化名)和其女友劉穎哑舒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幻馁,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洗鸵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仗嗦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膘滨。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖稀拐,靈堂內(nèi)的尸體忽然破棺而出火邓,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布铲咨,位于F島的核電站躲胳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纤勒。R本人自食惡果不足惜坯苹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摇天。 院中可真熱鬧粹湃,春花似錦、人聲如沸泉坐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坚冀。三九已至济赎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間记某,已是汗流浹背司训。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留液南,地道東北人壳猜。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像滑凉,于是被迫代替她去往敵國和親统扳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,327評(píng)論 25 707
  • 版本記錄 前言 OpenGL ES是一個(gè)強(qiáng)大的圖形庫畅姊,是跨平臺(tái)的圖形API咒钟,屬于OpenGL的一個(gè)簡(jiǎn)化版本。iOS...
    刀客傳奇閱讀 5,371評(píng)論 1 7
  • 昨天和T見了面若未,她看見我如今安穩(wěn)的生活和即將出世的寶寶朱嘴,羨慕之情溢于言表。她說她也渴望萬家燈火里有一盞燈為自己守候...
    舒心來嘮嘮嗑閱讀 460評(píng)論 0 1
  • 很多年前粗合,你許過一個(gè)心愿 我悄悄在石巷背后傾聽 冰涼的青石板覆蓋了我的腳印 銀鈴般的笑聲越來越近 我卻再也找不回那...
    風(fēng)一樣的女子1102閱讀 320評(píng)論 0 0
  • https://zhuanlan.zhihu.com/p/22564240 Google 新推出的 Allo 聊天...
    靖蘭亭閱讀 511評(píng)論 0 50