OpenGL ES 則是專門為行動裝置而推出的
要在 android app 裡面必須透過 view container 才能畫出 OpenGL ES 圖形
一個比較直接的方法是使用 GLSurfaceView 跟 GLSurfaceView.Renderer
GLSurfaceView - 是一個供 OpenGL 繪製圖形的全螢幕 view container
GLSurfaceView.Renderer - 控制甚麼要被劃在 view 裡面
(如果不需要全螢幕的 OpenGL ES 圖形而只需要螢幕中的一部分的話
應該考慮 TextureView )
而對於全部靠自己刻出來的開發者類型
也可以透過 SurfaceView 來做
但是這需要非常多的程式碼去達成
以下介紹使用 GLSurfaceView 跟 GLSurfaceView.Renderer 的方式
* 在 Manifest 中宣告使用 OpenGL ES
使用 OpenGL ES 2.0 API 的宣告方式
<uses-feature android:glEsVersion="0x00020000" android:required="true" />如果是 3.0 的話 android:glEsVersion 就是 0x00030000 (Android 4.3 (API level 18)後支援)
3.1 則是 0x00030001 (Android 5.0 (API level 21)後支援)
由於他們都向前相容(3.x版本可以相容2.0)
如果使用了材質壓縮 (texture compression) 的話也要宣告
<supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" /> <supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />
* 建立使用 OpenGL ES 的 Activity
將 GLSurfaceView instance 做為 Activity 的 ContentView
public class OpenGLES20Activity extends Activity { private GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mGLView = new MyGLSurfaceView(this); setContentView(mGLView); } }
* GLSurfaceView 與 GLSurfaceView.Renderer
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); // Render the view only when there is a change in the drawing data setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); } }
Renderer 有三個方法會被 Android 使用到
onSurfaceCreated() - 會被呼叫一次去設定 view 的 OpenGL ES 環境
onDrawFrame() - view 的每次重劃都會被呼叫
onSurfaceChanged() - 如果 view 的幾何 (geometry) 改變時會被呼叫 (例如螢幕方向改變)
這裡畫了一個黑背景在螢幕上
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); } }
* 定義形狀
繪製物體使用的是座標
例如畫三角形,我們需要將它的頂點定義在 array 裡
而為了獲得最大效率,我們將座標寫入 ByteBuffer 傳給 OpenGL ES 處理
OpenGL ES 預設會把 [0,0,0](X,Y,Z) 放置在 GLSurfaceView 的中心
[1,1,0] 是右上方、[-1,-1,0] 是左下方
注意繪製形狀時須依照逆時針順序,順序決定了形狀的正反面所以很重要
public class Triangle { private FloatBuffer vertexBuffer; // 每個頂點的座標數 static final int COORDS_PER_VERTEX = 3; static float triangleCoords[] = { // 按逆時針順序: 0.0f, 0.622008459f, 0.0f, // 頂點 -0.5f, -0.311004243f, 0.0f, // 左下 0.5f, -0.311004243f, 0.0f // 右下 }; //顏色值array:紅, 綠, 藍, alpha float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f }; public Triangle() { // 初始化 ByteBuffer ByteBuffer bb = ByteBuffer.allocateDirect( // (座標值數量 * 每個 float 有 4 bytes) triangleCoords.length * 4); // 使用裝置原生的 byte 順序 bb.order(ByteOrder.nativeOrder()); // 從 ByteBuffer 建立 FloatBuffer vertexBuffer = bb.asFloatBuffer(); // 在 FloatBuffer 中添加我們的三角形座標 vertexBuffer.put(triangleCoords); // FloatBuffer 讀取位置歸零 vertexBuffer.position(0); } }
而若要畫一個矩形則要用兩個畫在一起的三角形
public class Square { private FloatBuffer vertexBuffer; private ShortBuffer drawListBuffer; // 每個頂點的座標數 static final int COORDS_PER_VERTEX = 3; static float squareCoords[] = { -0.5f, 0.5f, 0.0f, // 左上 -0.5f, -0.5f, 0.0f, // 左下 0.5f, -0.5f, 0.0f, // 右下 0.5f, 0.5f, 0.0f }; // 右上 private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // 繪製頂點的順序 public Square() { ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4); bb.order(ByteOrder.nativeOrder()); vertexBuffer = bb.asFloatBuffer(); vertexBuffer.put(squareCoords); vertexBuffer.position(0); // 初始化 drawListBuffer 要用的 ByteBuffer ByteBuffer dlb = ByteBuffer.allocateDirect( // drawOrder的長度 * 每個 short 有 2 bytes) drawOrder.length * 2); dlb.order(ByteOrder.nativeOrder()); drawListBuffer = dlb.asShortBuffer(); drawListBuffer.put(drawOrder); drawListBuffer.position(0); } }
* 繪製形狀
在繪圖前必須先將我們的形狀類別載入及初始化
除非在你執行的過程中該形狀會改變,否則我們應該在 renderer 裡面的 onSurfaceCreated() 初始化
這樣對記憶體及處理效能較好
public class MyGLRenderer implements GLSurfaceView.Renderer { ... private Triangle mTriangle; private Square mSquare; public void onSurfaceCreated(GL10 unused, EGLConfig config) { ... // 初始化 mTriangle = new Triangle(); mSquare = new Square(); } ... }
繪製形狀需要提供很多細節
具體來說有:
Vertex Shader - 頂點著色器 負責渲染形狀的頂點
Fragment Shader - 片段著色器 對形狀的面進行材質或顏色的渲染
Program - 一個包含著色器的 OpenGL ES 物件
Vertex Shader 跟 Fragment Shader 最少各需要一個
編譯後加入 Program 負責畫出形狀
著色器裡面的 OpenGL Shading Language (GLSL) 必續先在 OpenGL ES 環境下編譯
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;" + "}"; ... }
編譯並加入 program 物件的動作要在形狀的建構子裡面做,因為只須執行一次
(而由於這動作非常耗費 CPU 與時間,所以應該避免執行超過一次)
public static int loadShader(int type, String shaderCode){ //依類型創建一個頂點著色器(GLES20.GL_VERTEX_SHADER) //或片段渲染器(GLES20.GL_FRAGMENT_SHADER) int shader = GLES20.glCreateShader(type); // 加入著色器原始碼及編譯 GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); return shader; }
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); // 建立 OpenGL ES 的 program mProgram = GLES20.glCreateProgram(); // 加入 vertex shader 與 fragment shader 到 program GLES20.glAttachShader(mProgram, vertexShader); GLES20.glAttachShader(mProgram, fragmentShader); // 建立 OpenGL ES 的可執行 program GLES20.glLinkProgram(mProgram); } }這時我們就可以把形狀給畫出來
我們必須給 OpenGL ES 一些參數去告訴 rendering pipeline 怎麼畫
因為這些參數因為不同形狀而有所相異,所以我們可以把它放在形狀的 class 裡面
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() { // 將 program 加入 OpenGL ES 環境 GLES20.glUseProgram(mProgram); // 取得頂點著色器的 vPosition 的 handle mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); // 啟用頂點的 handle GLES20.glEnableVertexAttribArray(mPositionHandle); // 準備三角形座標資料 GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); // 取得片段著色器 vColor 的 handle mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor"); // 設定顏色 GLES20.glUniform4fv(mColorHandle, 1, color, 0); // 畫出三角形 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount); // 停用頂點 array GLES20.glDisableVertexAttribArray(mPositionHandle); }
public void onDrawFrame(GL10 unused) { ... mTriangle.draw(); }
相關資料:
android developer-Displaying Graphics with OpenGL ES
android developer-OpenGL ES
android developer-Building an OpenGL ES Environment
android developer-Defining Shapes
android developer-Drawing Shapes
沒有留言:
張貼留言