OpenGL ES2.0入門之Android篇(一)——繪製三角形
OpenGL ES簡介
- OpenGL ES是一個為行動式或嵌入式裝置例如:行動電話、監視器等發展的3D繪圖API。
- 在Android框架中有兩個基礎類用於使用OpenGL ES建立和處理圖形
- GLSurfaceView類是OpenGL ES繪製圖形的view容器
- GLSurfaceView.Renderer類是用於控制上述view容器中顯示什麼內容
Android使用OpenGL ES繪製三角形
- 整個過程比較繁瑣,程式碼也挺多的。因為 OpenGL ES 2.0 是以可程式設計著色器為基礎的,這意味著你繪製任何圖形都必須有一
個合適的著色器裝載和繫結,比使用固定管線的桌面版本有更多程式碼。
1. 在manifest中宣告使用OpenGL ES 2.0 API
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
0x00020000代表OpenGL ES 2.0版本,3.0版本是0x00030000,3.1版本是0x00030001
若應用需要使用紋理壓縮(紋理壓縮通過減少記憶體需求和更有效的使用記憶體頻寬使OpenGL ES應用的效能顯著增加)功能,還需在manifest宣告所支援的壓縮格式,如下:
2. 將Activity的ContentView設為GLSurfaceView
public class OpenGLES20Activity extends Activity { private GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mGLView = new MyGLSurfaceView(this); setContentView(mGLView); } }
3. 構造GLSurfaceView物件
class MyGLSurfaceView extends GLSurfaceView { private final MyGLRenderer mRenderer; public MyGLSurfaceView(Context context){ super(context); // 建立OpenGL ES 2.0的上下文 setEGLContextClientVersion(2); mRenderer = new MyGLRenderer(); //設定Renderer用於繪圖 setRenderer(mRenderer); //只有在繪製資料改變時才繪製view,可以防止GLSurfaceView幀重繪 setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); } }
4. 構造GLSurfaceView.Renderer物件
Renderer類提供三個回撥方法供Android系統呼叫,用來計算在GLSurfaceView中繪製什麼以及如何繪製。
- onSurfaceCreated():僅呼叫一次,用於設定view的OpenGL ES環境
- onDrawFrame():每次重繪view時呼叫
- onSurfaceChanged():當view的幾何形狀發生變化時呼叫,比如裝置螢幕方向改變時
public class MyGLRenderer implements GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
//設定背景色(r,g,b,a)
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);//白色不透明
}public void onDrawFrame(GL10 unused) {
//重繪背景色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}public void onSurfaceChanged(GL10 unused, int width, int height) {
//繪製視窗
GLES20.glViewport(0, 0, width, height);
}
}
5. 在定義三角形之前需瞭解Android和OpenGL ES的座標系
- Android螢幕左上角為原點,往右為X正方向,往下為Y正方向
- Android螢幕的中心點座標為OpenGL的原點,方向如下圖,並且最大和最小值為1和-1
- 也就是說OpenGL的(-1,1,0)為螢幕的左上角,
6. 定義一個三角形
public class Triangle {
private FloatBuffer vertexBuffer;
//設定每個頂點的座標數
static final int COORDS_PER_VERTEX = 3;
//設定三角形頂點陣列
static float triangleCoords[] = { //預設按逆時針方向繪製
0.0f, 1.0f, 0.0f, // 頂點
-1.0f, -0.0f, 0.0f, // 左下角
1.0f, -0.0f, 0.0f // 右下角
};
// 設定三角形顏色和透明度(r,g,b,a)
float color[] = {0.0f, 1.0f, 0f, 1.0f};//綠色不透明
public Triangle() {
// 初始化頂點位元組緩衝區,用於存放形狀的座標
ByteBuffer bb = ByteBuffer.allocateDirect(
//(每個浮點數佔用4個位元組
triangleCoords.length * 4);
//設定使用裝置硬體的原生位元組序
bb.order(ByteOrder.nativeOrder());
//從ByteBuffer中建立一個浮點緩衝區
vertexBuffer = bb.asFloatBuffer();
// 把座標都新增到FloatBuffer中
vertexBuffer.put(triangleCoords);
//設定buffer從第一個座標開始讀
vertexBuffer.position(0);
}
}
7. 在MyGLRenderer中初始化三角形形狀
private Triangle mTriangle;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
...
//初始化三角形
mTriangle = new Triangle();
}
8. 繪製三角形形狀
使用OpenGL ES 2.0繪製一個定義好的形狀需要大量的程式碼,因為必須提供給圖形渲染管道很多細節資訊
- VertexShader:用於渲染形狀的頂點的OpenGL ES圖形程式碼
- FragmentShader:用於渲染形狀的外觀(顏色或紋理)的OpenGL ES程式碼
- Program:一個OpenGL ES物件,包含了你想要用來繪製一個或多個形狀的shader
至少需要一個vertex shader來繪製一個形狀和一個fragment 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包含OpenGL Shading Language(GLSL)程式碼,必須在OpenGL ES環境下先編譯再使用。想要編譯這些程式碼,需要在你的Renderer類中建立一個工具類方法:
public static int loadShader(int type, String shaderCode){
//建立一個vertex shader型別(GLES20.GL_VERTEX_SHADER)
//或一個fragment shader型別(GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
// 將原始碼新增到shader並編譯它
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
在三角形類中編譯shader程式碼,並將它們新增到一個OpenGL ES program 物件中,然後連結這個program
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);
// 建立空的OpenGL ES Program
mProgram = GLES20.glCreateProgram();
// 將vertex shader新增到program
GLES20.glAttachShader(mProgram, vertexShader);
// 將fragment shader新增到program
GLES20.glAttachShader(mProgram, fragmentShader);
// 建立可執行的 OpenGL ES program
GLES20.glLinkProgram(mProgram);
}
}
在三角形類中建立一個draw()方法負責繪製形狀
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);
// 獲取指向vertex shader的成員vPosition的handle
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 啟用一個指向三角形的頂點陣列的handle
GLES20.glEnableVertexAttribArray(mPositionHandle);
//準備三角形的座標資料
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 獲取指向fragment shader的成員vColor的handle
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 繪製三角形
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用指向三角形的頂點陣列
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
9. 在Render類中的onDrawFrame()方法中呼叫draw()方法
public void onDrawFrame(GL10 unused) {
...
mTriangle.draw();
}
此時執行程式顯示如下圖:
到這裡,三角形已繪製完畢,下一篇介紹新增動作及觸控事件。