1 OpenGL ES簡介
談到OpenGL ES沸移,首先我們應(yīng)該先去了解一下Android的基本架構(gòu)痪伦,基本架構(gòu)下圖:
這里我們可以找到Libraries里面有我們目前要接觸的庫,即OpenGL ES雹锣。
根據(jù)上圖可以知道Android 目前是支持使用開放的圖形庫的网沾,特別是通過OpenGL ES API來支持高性能的2D和3D圖形。OpenGL是一個跨平臺的圖形API蕊爵。為3D圖形處理硬件指定了一個標(biāo)準(zhǔn)的軟件接口辉哥。OpenGL ES 是適用于嵌入式設(shè)備的OpenGL規(guī)范。
Android 支持OpenGL ES API版本的詳細(xì)狀態(tài)是:
OpenGL ES 1.0 和 1.1 能夠被Android 1.0及以上版本支持
OpenGL ES 2.0 能夠被Android 2.2及更高版本支持
OpenGL ES 3.0 能夠被Android 4.3及更高版本支持
OpenGL ES 3.1 能夠被Android 5.0及以上版本支持
2 OpenGL ES使用
在了解OpenGL的使用之前,我們需要了解兩個基本類別的Android框架:GLSurfaceView和GLSurfaceView.Renderer醋旦。
2.1 GLSurfaceView
GLSurfaceView從名字就可以看出恒水,它是一個SurfaceView∷瞧耄看源碼可知寇窑,GLSurfaceView繼承自SurfaceView,并增加了Renderer箩张,它的作用就是專門為OpenGL顯示渲染使用的。
2.2 GLSurfaceView.Renderer
此接口定義了在GLSurfaceView中繪制圖形所需的方法窗市。您必須將此接口的實(shí)現(xiàn)作為單獨(dú)的類提供先慷,并使用GLSurfaceView.setRenderer()將其附加到您的GLSurfaceView實(shí)例。
GLSurfaceView.Renderer要求實(shí)現(xiàn)以下方法:
- onSurfaceCreated():創(chuàng)建GLSurfaceView時咨察,系統(tǒng)調(diào)用一次該方法论熙。使用此方法執(zhí)行只需要執(zhí)行一次的操作,例如設(shè)置OpenGL環(huán)境參數(shù)或初始化OpenGL圖形對象摄狱。
- onDrawFrame():系統(tǒng)在每次重畫GLSurfaceView時調(diào)用這個方法脓诡。使用此方法作為繪制(和重新繪制)圖形對象的主要執(zhí)行方法。
- onSurfaceChanged():當(dāng)GLSurfaceView的發(fā)生變化時媒役,系統(tǒng)調(diào)用此方法祝谚,這些變化包括GLSurfaceView的大小或設(shè)備屏幕方向的變化。例如:設(shè)備從縱向變?yōu)闄M向時酣衷,系統(tǒng)調(diào)用此方法交惯。我們應(yīng)該使用此方法來響應(yīng)GLSurfaceView容器的改變。
介紹完了GlSurfaceView和GlSurfaceView.renderer之后穿仪,接下來說下如何使用GlSurfaceView:
1席爽、創(chuàng)建一個GlSurfaceView
2、為這個GlSurfaceView設(shè)置渲染
3啊片、在GlSurfaceView.renderer中繪制處理顯示數(shù)據(jù)
3 OpenGL ES繪制圖形
3.1 OpenGL ES環(huán)境搭建
為了在Android應(yīng)用程序中使用OpenGL ES繪制圖形只锻,必須要為他們創(chuàng)建一個視圖容器。其中最直接或者最常用的方式就是實(shí)現(xiàn)一個GLSurfaceView和一個GLSurfaceView.Renderer紫谷。GLSurfaceView是用OpenGL繪制圖形的視圖容器齐饮,GLSurfaceView.Renderer控制在該視圖內(nèi)繪制的內(nèi)容。
3.1.1 在Manifest中聲明OpenGL ES使用
了讓你的應(yīng)用程序能夠使用OpenGL ES 2.0的API碴里,你必須添加以下聲明到manifest:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
如果你的應(yīng)用程序需要使用紋理壓縮沈矿,你還需要聲明你的應(yīng)用程序需要支持哪種壓縮格式,以便他們安裝在兼容的設(shè)備上咬腋。
<supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" />
<supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />
3.1.2 創(chuàng)建一個Activity 用于展示OpenGL ES 圖形
使用OpenGL ES的應(yīng)用程序的Activity和其他應(yīng)用程的Activity一樣羹膳,不同的地方在于你設(shè)置的Activity的布局。在許多使用OpenGL ES的app中根竿,你可以添加TextView陵像,Button和ListView就珠,還可以添加GLSurfaceView。
下面的代碼展示了使用GLSurfaceView做為主視圖的基本實(shí)現(xiàn):
public class OpenGLES20Activity extends Activity {
private GLSurfaceView mGLView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a GLSurfaceView instance and set it
// as the ContentView for this Activity.
mGLView = new MyGLSurfaceView(this);
setContentView(mGLView);
}
}
3.1.3 創(chuàng)建GLSurfaceView對象
GLSurfaceView是一個特殊的View醒颖,通過這個View你可以繪制OpenGL圖像妻怎。但是View本身沒有做太多的事情,主要的繪制是通過設(shè)置在View里面的GLSurfaceView.Renderer 來控制的泞歉。實(shí)際上逼侦,創(chuàng)建這個對象的代碼是很少的,你能會想嘗試跳過extends的操作腰耙,只去創(chuàng)建一個沒有被修改的GLSurfaceView實(shí)例榛丢,但是不建議這樣去做。因?yàn)樵谀承┣闆r下挺庞,你需要擴(kuò)展這個類來捕獲觸摸的事件晰赞,捕獲觸摸的事件的方式會在后面的文章里面做介紹。
GLSurfaceView的基本代碼很少选侨,為了快速的實(shí)現(xiàn)掖鱼,通常會在使用它的Activity中創(chuàng)建一個內(nèi)部類來做實(shí)現(xiàn):
class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRenderer mRenderer;
public MyGLSurfaceView(Context context){
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
mRenderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
}
你可以通過設(shè)置GLSurfaceView.RENDERMODE_WHEN_DIRTY來讓你的GLSurfaceView監(jiān)聽到數(shù)據(jù)變化的時候再去刷新,即修改GLSurfaceView的渲染模式援制。這個設(shè)置可以防止重繪GLSurfaceView戏挡,直到你調(diào)用了requestRender(),這個設(shè)置在默寫層面上來說隘谣,對你的APP是更有好處的增拥。
3.1.4 創(chuàng)建一個GLSurfaceView.Renderer類
實(shí)現(xiàn)了GLSurfaceView.Renderer 類才是真正算是開始能夠在應(yīng)用中使用OpenGL ES。這個類控制著與它關(guān)聯(lián)的GLSurfaceView 繪制的內(nèi)容寻歧。在Renderer 里面有三個方法能夠被Android系統(tǒng)調(diào)用掌栅,以便知道在GLSurfaceView繪制什么以及如何繪制:
- onSurfaceCreated() - 在View的OpenGL環(huán)境被創(chuàng)建的時候調(diào)用。
- onDrawFrame() - 每一次View的重繪都會調(diào)用
- onSurfaceChanged() - 如果視圖的幾何形狀發(fā)生變化(例如码泛,當(dāng)設(shè)備的屏幕方向改變時)猾封,則調(diào)用此方法。
下面是使用OpenGL ES 渲染器的基本實(shí)現(xiàn)噪珊,僅僅做的事情就是在GLSurfaceView繪制一個黑色背景晌缘。
public class MyGLRenderer implements GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
public void onDrawFrame(GL10 unused) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
}
3.2 OpenGL ES定義形狀
我們能夠配置好基本的Android OpenGL 使用的環(huán)境。但是如果我們不了解OpenGL ES如何定義圖像的一些基本知識就使用OpenGL ES進(jìn)行繪圖還是有點(diǎn)棘手的痢站。所以能夠在OpenGL ES的View里面定義要繪制的形狀是進(jìn)行高端繪圖操作的第一步磷箕。
下面講解Android設(shè)備屏幕相關(guān)的OpenGL ES坐標(biāo)系統(tǒng),定義形狀阵难,形狀面的基礎(chǔ)知識岳枷,以及定義三角形和正方形。
3.2.1 定義三角形
OpenGL ES允許你使用三維空間坐標(biāo)系定義繪制的圖像,所以你在繪制一個三角形之前必須要先定義它的坐標(biāo)空繁。在OpenGL中殿衰,這樣做的典型方法是為坐標(biāo)定義浮點(diǎn)數(shù)的頂點(diǎn)數(shù)組。
為了獲得最大的效率盛泡,可以將這些坐標(biāo)寫入ByteBuffer闷祥,并傳遞到OpenGL ES圖形管道進(jìn)行處理。
public class Triangle {
private FloatBuffer vertexBuffer;
// // 數(shù)組中每個頂點(diǎn)的坐標(biāo)數(shù)
static final int COORDS_PER_VERTEX = 3;
static float triangleCoords[] = { // 按逆時針方向順序
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
};
// 設(shè)置顏色傲诵,分別為red, green, blue 和alpha (opacity)
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
public Triangle() {
// // 為存放形狀的坐標(biāo)凯砍,初始化頂點(diǎn)字節(jié)緩沖
ByteBuffer bb = ByteBuffer.allocateDirect(
// (坐標(biāo)數(shù) * 4)float占四字節(jié)
// 使用設(shè)備的本點(diǎn)字節(jié)序
bb.order(ByteOrder.nativeOrder());
// 從ByteBuffer創(chuàng)建一個浮點(diǎn)緩沖
vertexBuffer = bb.asFloatBuffer();
//把坐標(biāo)們加入FloatBuffer中
vertexBuffer.put(triangleCoords);
//設(shè)置buffer,從第一個坐標(biāo)開始讀
vertexBuffer.position(0);
}
}
請注意拴竹,此圖形的坐標(biāo)以逆時針順序定義果覆。 繪圖順序非常重要,因?yàn)樗x了哪一面是您通常想要繪制的圖形的正面殖熟,以及背面。
3.2.2 定義正方形
可以看到斑响,在OpenGL里面定義一個三角形很簡單菱属。但是如果你想要得到一個更復(fù)雜一點(diǎn)的東西呢?比如一個正方形舰罚?能夠找到很多辦法來作到這一點(diǎn)纽门,但是在OpenGL里面繪制這個圖形的方式是將兩個三角形畫在一起。
同樣营罢,你應(yīng)該以逆時針的順序?yàn)檫@兩個代表這個形狀的三角形定義頂點(diǎn)赏陵,并將這些值放在一個ByteBuffer中。 為避免定義每個三角形共享的兩個坐標(biāo)兩次饲漾,請使用圖紙列表告訴OpenGL ES圖形管道如何繪制這些頂點(diǎn)蝙搔。 這是這個形狀的代碼:
public class Square {
//頂點(diǎn)緩沖區(qū)
private FloatBuffer vertexBuffer;
//繪圖順序頂點(diǎn)緩沖區(qū)
private ShortBuffer drawListBuffer;
// 每個頂點(diǎn)的坐標(biāo)數(shù)
static final int COORDS_PER_VERTEX = 3;
//正方形四個頂點(diǎn)的坐標(biāo)
static float squareCoords[] = {
-0.5f, 0.5f, 0.0f, // top left
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, // bottom right
0.5f, 0.5f, 0.0f }; // top right
private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; //頂點(diǎn)的繪制順序
// 設(shè)置圖形的RGB值和透明度
public Square() {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (坐標(biāo)數(shù) * 4))
squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
//為繪制列表初始化字節(jié)緩沖
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (對應(yīng)順序的坐標(biāo)數(shù) * 2)short是2字節(jié)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
}
}
這個例子讓你了解用OpenGL創(chuàng)建更復(fù)雜的形狀的過程。 一般來說考传,您使用三角形的集合來繪制對象吃型。
3.3 OpenGL ES繪制形狀
3.3.1 初始化形狀
在你做任何繪制操作之前,你必須要初始化并加載你準(zhǔn)備繪制的形狀僚楞。除非形狀的結(jié)構(gòu)(指原始的坐標(biāo))在執(zhí)行過程中發(fā)生改變勤晚,你都應(yīng)該在你的Renderer的方法onSurfaceCreated()中進(jìn)行內(nèi)存和效率方面的初始化工作。
public class MyGLRenderer implements GLSurfaceView.Renderer {
...
private Triangle mTriangle;
private Square mSquare;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
...
// initialize a triangle
mTriangle = new Triangle();
// initialize a square
mSquare = new Square();
}
...
}
3.3.2 繪制形狀
使用OpenGLES 2.0畫一個定義好的形狀需要比較多的代碼泉褐,因?yàn)槟惚仨殲閳D形渲染管線提供一大堆信息赐写。特別的,你必須定義以下幾個東西:
- Vertex Shader - 用于渲染形狀的頂點(diǎn)的OpenGLES 圖形代碼膜赃。
- Fragment Shader - 用于渲染形狀的外觀(顏色或紋理)的OpenGLES 代碼挺邀。
- Program - 一個OpenGLES對象,包含了你想要用來繪制一個或多個形狀的shader。
你至少需要一個vertexshader來繪制一個形狀和一個fragmentshader來為形狀上色悠夯。這些形狀必須被編譯然后被添加到一個OpenGLES program中癌淮,program之后被用來繪制形狀。下面是一個展示如何定義一個可以用來繪制形狀的基本shader的例子:
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們包含了OpenGLShading Language (GLSL)代碼沦补,必須在使用前編譯乳蓄。要編譯這些代碼,在你的Renderer類中創(chuàng)建一個工具類方法:
private int loadShader(int type, String shaderCode) {
//根據(jù)type創(chuàng)建頂點(diǎn)著色器或者片元著色器
//創(chuàng)建一個vertex shader類型(GLES20.GL_VERTEX_SHADER)
//或一個fragment shader類型(GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
//將資源加入到著色器中夕膀,并編譯
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
為了繪制你的形狀虚倒,你必須編譯shader代碼,添加它們到一個OpenGLES program 對象然后鏈接這個program产舞。在renderer對象的構(gòu)造器中做這些事情魂奥,從而只需做一次即可。
注:編譯OpenGLES shader們和鏈接linkingprogram們是很耗CPU的易猫,所以你應(yīng)該避免多次做這些事耻煤。如果在運(yùn)行時你不知道shader的內(nèi)容,你應(yīng)該只創(chuàng)建一次code然后緩存它們以避免多次創(chuàng)建准颓。
public class Triangle() {
...
private final int mProgram;
public Triangle() {
...
//編譯shader代碼
int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// // 創(chuàng)建空的OpenGL ES Program
mProgram = GLES20.glCreateProgram();
// 將vertex shader(頂點(diǎn)著色器)添加到program
GLES20.glAttachShader(mProgram, vertexShader);
// 將fragment shader(片元著色器)添加到program
GLES20.glAttachShader(mProgram, fragmentShader);
// 創(chuàng)建可執(zhí)行的 OpenGL ES program
GLES20.glLinkProgram(mProgram);
}
}
此時哈蝇,你已經(jīng)準(zhǔn)備好增加真正的繪制調(diào)用了。需要為渲染管線指定很多參數(shù)來告訴它你想畫什么以及如何畫攘已。因?yàn)槔L制操作因形狀而異炮赦,讓你的形狀類包含自己的繪制邏輯是個很好主意。
創(chuàng)建一個draw()方法負(fù)責(zé)繪制形狀样勃。下面的代碼設(shè)置位置和顏色值到形狀的vertexshader和fragmentshader吠勘,然后執(zhí)行繪制功能:
private int mPositionHandle;
private int mColorHandle;
//頂點(diǎn)個數(shù)
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
//頂點(diǎn)之間的偏移量
private final int vertexStride = COORDS_PER_VERTEX * 4; // 每個頂點(diǎn)四個字節(jié)
public void draw() {
// // 添加program到OpenGL ES環(huán)境中
GLES20.glUseProgram(mProgram);
// 獲取指向vertex shader(頂點(diǎn)著色器)的成員vPosition的handle
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 啟用一個指向三角形的頂點(diǎn)數(shù)組的handle
GLES20.glEnableVertexAttribArray(mPositionHandle);
// 準(zhǔn)備三角形的坐標(biāo)數(shù)據(jù)
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 獲取指向fragment shader(片元著色器)的成員vColor的handle
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 設(shè)置繪制三角形的顏色
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// 繪制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
一旦完成了所有這些代碼,繪制該對象只需要在渲染器的onDrawFrame()方法中調(diào)用draw()方法:
public void onDrawFrame(GL10 unused) {
...
mTriangle.draw();
}
當(dāng)你運(yùn)行程序的時候峡眶,你就應(yīng)該看到以下的內(nèi)容:
此例子中的代碼還有很多問題剧防。首先,它不會打動你和你的朋友辫樱。其次诵姜,三角形會在你從豎屏變?yōu)闄M屏?xí)r被壓扁。三角形變形的原因是其頂點(diǎn)們沒有跟據(jù)屏幕的寬高比進(jìn)行修正搏熄。而且這里展示出來的三角形是靜止的棚唆,這樣的圖形是有點(diǎn)無聊的,在“添加動畫”的文章中心例,我們會使用OpenGL ES 的視圖管線來旋轉(zhuǎn)此形狀宵凌。
源碼地址:https://github.com/Xiaoben336/OpenGLES20Study
在下一篇文章,我們將使用投影和攝像頭視圖來修正顯示的問題止后。