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:
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
GL11ExtensionPackOpenGL ES 2.0 API Packages :
android.opengl.GLES20:該包為OpenGL ES 2.0的提供了接口并且從Android 2.2 (API level 8)開始可用库菲。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ì)被壓縮或者拉伸耿戚,如下圖所示:
要解決右側(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é)最開始的效果一樣。