要用OpenGl ES在應用程式中繪制圖像,首先你要建立與之對應的視圖容器。
當中最直接的方法就是使用GLSurfaceView和GLSurfaceView.Renderer接口。
GLSurfaceView是OpenGL所繪制的圖形的視圖容器,而GLSurfaceView.Renderer控制在視圖上所繪制的圖像。想擷取更多關于這兩個類的資訊,請參考OpenGl ES開發指南。
GLSurfaceView隻是把OpenGL ES圖像組合到應用程式的其中一種方法,但對于繪制全屏或接近全屏的圖形來說,它是一個很好的選擇。開發者如果想把OpenGL ES圖像組合成布局中的一小部分,可以檢視TextureView。事實上,獨立開發者也可以在SurfaceView上建立OpenGL ES視圖,但這需要寫更多額外的代碼。
在Manifest聲明OpenGL ES的使用
要在應用程式中使用 OpenGL ES 2.0 API,你必須在Manifest添加以下的聲明:
- <uses-feature android:glEsVersion="0x00020000" android:required="true" />
如果你的應用程式中使用到了紋理壓縮,你必須聲明應用程式所支援的壓縮格式,以便提醒不支援這些格式的裝置不會嘗試運作你的應用程式:
- <supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" />
- <supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />
想擷取更多關于紋理壓縮格式的資訊,請參考OpenGl ES開發指南。
建立OpenGL ES的Activity
Android使用到OpenGL ES的應用程式和其他的應用程式的activities一樣,都有一個使用者界面。當中不同就是,在activity的布局中你使用了什麼控件,在很多應用中,你可能會使用TextView,Button和ListView,在使用了OpenGL ES的應用程式,你還可以添加GLSurfaceView。
package com.example.opengldemo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class OpenGLES20Activity extends AppCompatActivity {
private GLSurfaceView glView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glView = new MyGLSurfaceView(this);
setContentView(glView);
}
@Override
protected void onPause() {
super.onPause();
glView.onPause();
}
@Override
protected void onResume() {
super.onResume();
glView.onResume();
}
}
自定義GLSurfaceView:
package com.example.opengldemo;
import android.content.Context;
import android.opengl.GLSurfaceView;
/**
* GLSurfaceView 是一個可以繪制OpenGLES圖像的專門視圖,它本身沒有處理功能,
*
* 所繪制的圖像都是由你所設定的GLSurfaceView.Render來控制的。
*
*/
public class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRenderer renderer;
public MyGLSurfaceView(Context context){
super(context);
// 聲明你使用的是OpenGlES2.0的API
setEGLContextClientVersion(2);
renderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(renderer);
}
}
GLSurfaceView.Renderer渲染器介紹
GLSurfaceView是一個可以繪制OpenGL ES圖像的專門視圖,它本身沒有處理功能,所繪制的圖像都是由你所設定的GLSurfaceView.Renderer來控制的。Render就是整個程式的重點:
package com.example.opengldemo;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
public class MyGLRenderer implements GLSurfaceView.Renderer {
private Triangle mTriangle;
/**
* shader語言跟C語言很像,它有一個主函數,也叫void main(){}。
* gl_Position是一個内置變量,用于指定頂點,它是一個點,三維空間的點,是以用一個四維向量來指派。vec4是四維向量的類型,vec4()是它的構造方法。等等,三維空間,
* 不是(x, y, z)三個嗎?咋用vec4呢?四維是叫做齊次坐标,它的幾何意義仍是三維,先了解這麼多,記得對于2D的話,第四位永遠傳1.0就可以了。這裡,是指定原點
* (0, 0, 0)作為頂點,就是說想在原點(正中心)位置畫一個點。gl_PointSize是另外一個内置變量,用于指定點的大小。這個shader就是想在原點畫一個尺寸為20的點
*
*/
private String VERTEX_SHADER =
"void main() {\n" +
"gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" +
"gl_PointSize = 200.0;\n" +
"}\n";
private String FRAGMENT_SHADER =
"void main() {\n" +
"gl_FragColor = vec4(1., 0., 0.0, 1.0);\n" +
"}\n";
private int mGLProgram;
//設定視圖的OpenGL ES環境,隻需調用一次
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(1.0f, 1.0f, 0.0f, 1.0f); //設定清空螢幕用的顔色,設定背景底色
/**
* shader
* GL ES 2.0與1.0版本最大的差別在于,把渲染相關的操作用一個專門的叫作着色語言的程式來表達,全名叫作OpenGL ES Shading language,它是一個程式設計語言,與C語言非常類似,
* 能夠直接操作矩陣和向量,運作在GPU之上專門用于圖形渲染。它又分為兩種,一個叫做頂點着色器(vertex shader),另一個叫做片元着色器(fragment shader)。
*前者用來指定幾何形狀的頂點;後者用于指定每個頂點的着色。每個GL程式必須要有一個vertex shader和一個fragment shader,且它們是互相對應的。(互相對應,
* 意思是vertex shader必須要有一個fragment shader,反之亦然,但并不一定是一一對應)。當然,也是可以複用的,比如同一個vertex shader,可能會多個fragment shader
* 來表達不同的着色方案。
*
*/
**
*
* 建立一個着色器分三步:
*
* 1) 建立Shader對象
*
* 2) 裝載Shader源碼
*
* 3) 編譯Shader
*/
/**
* 1)glCreateShader
* 它建立一個空的shader對象,它用于維護用來定義shader的源碼字元串。支援以下兩種shader:
* (1) GL_VERTEX_SHADER: 它運作在可程式設計的“頂點處理器”上,用于代替固定功能的頂點處理;
* (2) GL_FRAGMENT_SHADER: 它運作在可程式設計的“片斷處理器”上,用于代替固定功能的片段處理;
*/
int vertextShaderId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
/**
* 2)glShaderSource裝載源碼:
* shader對象中原來的源碼全部被新的源碼所代替。
*/
GLES20.glShaderSource(vertextShaderId,VERTEX_SHADER);
/**
* 3)glCompileShader
* 編譯存儲在shader對象中的源碼字元串,編譯結果被當作shader對象狀态的一部分被儲存起來,可通過
* glGetShaderiv函數擷取編譯狀态。
*/
GLES20.glCompileShader(vertextShaderId);
// 建立一個片着色器,三個步驟介紹同上
int fragmentShaderId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
GLES20.glShaderSource(fragmentShaderId,FRAGMENT_SHADER);
GLES20.glCompileShader(fragmentShaderId);
//建立一個空的program對象,shader對象可以被連接配接到program對像
mGLProgram = GLES20.glCreateProgram();
// 把vertex shader添加到program
GLES20.glAttachShader(mGLProgram,vertextShaderId);
// 把fragment shader添加到program
GLES20.glAttachShader(mGLProgram,fragmentShaderId);
// 做連結,可以了解為把兩種shader進行融合,做好投入使用的最後準備工作
GLES20.glLinkProgram(mGLProgram);
}
// 每次重繪都會重新調用一次
public void onDrawFrame(GL10 unused) {
// //擦除螢幕上的所有顔色,并用 glClearColor 中的顔色填充背景,因為我們要開始新一幀的繪制了,是以先清理,以免有髒資料。
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 告訴OpenGL,使用我們在onSurfaceCreated裡面準備好了的shader program來渲染
GLES20.glUseProgram(mGLProgram);
// 開始渲染,發送渲染點的指令, 第二個參數是offset,第三個參數是點的個數。目前隻有一個點,是以是1。
GLES20.glDrawArrays(GLES20.GL_POINTS,0,1);
}
//視圖的幾何發現變化時調用,例如,裝置的螢幕的方向發生變化時才會調用
public void onSurfaceChanged(GL10 unused, int width, int height) {
//設定了視口尺寸,告訴 OpenGL 可以用來渲染的 surface 的大小。
//(0, 0)是左上角,然後是width和heigh
GLES20.glViewport(0, 0, width, height);
}
}
在使用OpenGL ES 2.0時,你必須在你的GLSurfaceView構造器中添加多一句代碼,以聲明你是使用OpenGL ES 2.0的API:
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
備注:如果你使用的是OpenGL ES 2.0的API,請确認你已經在應用程式的manifest進行了聲明。
現在我們分析上面的Render渲染器。渲染器中提供了三個方法給Android系統調用,以控制什麼可以,以及怎樣繪制在GLSurfaceView上。它們分别是:
* onSurfaceCreated()) - 設定視圖的OpenGL ES環境,隻需調用一次
* onDrawFrame()) - 重新繪制每個視圖時調用
* onSurfaceChanged()) - 視圖的幾何發現變化時調用,例如,裝置的螢幕的方向發生變化
最後一個概念:着色器 shader
GL ES 2.0與1.0版本最大的差別在于,把渲染相關的操作用一個專門的叫作着色語言的程式來表達,全名叫作OpenGL ESShading language,它是一個程式設計語言,與C語言非常類似,能夠直接操作矩陣和向量,運作在GPU之上專門用于圖形渲染。它又分為兩種,一個叫做頂點着色器(vertex shader),另一個叫做片元着色器(fragment shader)。前者用來指定幾何形狀的頂點;後者用于指定每個頂點的着色。每個GL程式必須要有一個vertex shader和一個fragment shader,且它們是互相對應的。(互相對應,意思是vertex shader必須要有一個fragment shader,反之亦然,但并不一定是一一對應)。當然,也是可以複用的,比如同一個vertex shader,可能會多個fragment shader來表達不同的着色方案。 下面就是一段着色器片段:
//點着色器,定義點的位置和大小
private String VERTEX_SHADER =
"void main() {\n" +
"gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" +
"gl_PointSize = 200.0;\n" +
"}\n";
//片着色器,定義由點組成的點或線或面的屬性,比如填充顔色
private String FRAGMENT_SHADER =
"void main() {\n" +
"gl_FragColor = vec4(1.0, 0., 0.0, 1.0);\n" +
"}\n";
shader語言跟C語言很像,它有一個主函數,也叫void main(){}
gl_Position是一個内置變量,用于指定頂點,它是一個點,三維空間的點,是以用一個四維向量來指派。
vec4是四維向量的類型,vec4()是它的構造方法。等等,三維空間,不是(x, y, z)三個嗎?咋用vec4呢?四維是叫做齊次坐标,它的幾何意義仍是三維,先了解這麼多,記得對于2D的話,第四位永遠傳1.0就可以了。
這裡,是指定原點(0, 0, 0)作為頂點,就是說想在原點(正中心)位置畫一個點。由于這裡隻定義了一個點。并且指定填充顔色為紅色,是以它的運作效果就是在手機(0,0,0)正中心畫一個紅色的大小為200像素的點,效果如圖:

好了,我們的第一堂實踐課就到這裡了。