GLSurfaceView
GLSurfaceView屬於View的一種, 可以透過OpenGL API呼叫它, 可對它畫圖和作其他操作, 功能上和SurfaceView類似.
GLSurfaceView管理記憶體中的一個性別區塊, 能夠被覆合成為Android 的View. 它自己會產生一個執行緒, 一直作Render的工作. 所以在Render裏, 不需實作Runnable. 依測試, GLSurfaceView的執行緒, 每秒會執行Render約60次, 速度相當的快. 如果想要有觸控的功能, 要自己延伸touch listeners
底下程式碼, 是在MainActivity.java中加入GlSurfaceView, 並設定render
public class MainActivity extends AppCompatActivity {
GLSurfaceView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view=new GLSurfaceView(this);
view.setEGLContextClientVersion(2); //設定使用OpenGL ES 2的版本
view.setPreserveEGLContextOnPause(true); //螢幕旋轉時不重複產生GL Context
view.setRenderer(new MyRenderer()); //設定Render
setContentView(view);
}
}
GLSurfaceView.Renderer
Renderer這個介面定義了用來在GLSurfaceView上繪圖的方法, 然後使用GLSurfaceView.setRenderer()方法將Renderer物件附加在GLSurfaceView上.
GLSurfaceView.Renderer介面需要實作以下幾個方法:
onSurfaceCreated() : 這個方法在創造GLSurfaceView時只會被系統呼叫一次,像是設定OpenGL的環境參數或是初始化OpenGL繪圖物件都是在這裡。
onDrawFrame() : 當系統每次要重新繪圖(redraw)時會呼叫這個方法,此為物件繪圖(重繪)的主要方法。
onSurfaceChanged() : 當GLSurfaceView在幾何上有改變時,系統會呼叫這個方法,包含GLSurfaceView的大小改變或是設備螢幕大小的改變。像是設備將顯示從直立改成橫立時,系統就會呼叫它。請這個方法來回應與處理GLSurfaceView的改變。
GLSurfaceView.Renderer具有獨立的執行緒. 此執行緒(run()方法) 一開始會先初始化OpenGL, 如同第三章節所述的initGL功能, 將本身的SurfaceTexture與OpenGL的mEglSurface進行連結, 然後一直執行onDrawFrame().
GLSurfaceView產生的執行緒, 以每秒60次的速度執行此Renderer的 onDrawFrame()方法, 所以在這裏有一個count, 計算每秒執行的次數.
public class MyRenderer implements GLSurfaceView.Renderer {
int count=0;
long t1;
Triangle triangle;
@Override
public void onSurfaceCreated(GL10 notUsed, EGLConfig eglConfig) {
triangle=new Triangle();
}
@Override
public void onSurfaceChanged(GL10 notUsed, int i, int i1) {}
@Override
public void onDrawFrame(GL10 notUsed) {
triangle.onDraw();
count++;
long now = System.currentTimeMillis();
if (now - t1 >= 1000) {
t1 = now;
Log.d("Thomas", Thread.currentThread().getName() + " : " + count);
count = 0;
}
}
}
著色器
使用OpenGL, 其實是呼叫Native的C語言, 所以都會先產生著色器, 再將數值丟給著色器. 著色器的產生, 可以使用如下制式的類別, 將產生的著色器以int 傳回
public class MyShaderFactory {
public static final String FIELD_POSITION = "vPosition";
public static final String FIELD_COLOR = "vColor";
private static final String SHADER_CODE_VERTEX =
"attribute vec4 " + FIELD_POSITION + ";" +
"void main(){" +
" gl_Position = " + FIELD_POSITION + ";" +
"}";
private static final String SHADER_CODE_FRAGMENT =
"precision mediump float;" +
"uniform vec4 " + FIELD_COLOR + ";" +
"void main(){" +
" gl_FragColor = " + FIELD_COLOR + ";" +
"}";
public static int getInstanceShader(){
MyShaderFactory factory=new MyShaderFactory();
return factory.createShaderProgram();
}
private int createShaderProgram(){
int shaderProgram= GLES20.glCreateProgram();
int vertexShader=loadShader(GLES20.GL_VERTEX_SHADER, SHADER_CODE_VERTEX);
int fragmentShader=loadShader(GLES20.GL_FRAGMENT_SHADER, SHADER_CODE_FRAGMENT);
GLES20.glAttachShader(shaderProgram, vertexShader);
GLES20.glAttachShader(shaderProgram, fragmentShader);
GLES20.glLinkProgram(shaderProgram);
return shaderProgram;
}
private int loadShader(int type, String shaderCode){
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
}
繪制三角型
如下的Triangle, 可以繪制一個三角型. 首先定義三角型的三個頂點. 再轉換成FloatBuffer才可供OpenGL繪圖.
每次繪圖時, 都需以GLES20.glClear()清除畫面. 然後使用GLES20.glEnableVertexAttribArry() 打開屬性設定. 接著就可以使用GLES20.glVertexAttribPointer()將floatBuffer傳入, GLES20.glDrawArray()進行繪圖.
繪制完後, 再使用GLES20.glDisableVertexAttribArry()關閉屬性設定. 此時己繪制完成, 但還沒投射到GLSurfaceView中. GLSurfaceView於執行onDrawFrame()後, 會自行呼叫mEgl.swap()將顯存的幀數據轉到Surface中.
public class Triangle {
private static float triangleCoords[] = {
0.0f, 0.62008459f, 0.0f,
-0.5f, -0.311004243f, 0.0f,
0.5f, -0.311004243f, 0.0f,
};
private float color[] = {1.0f, 0.0f, 0.0f, 1.0f};//RGBA
private FloatBuffer vertexBuffer;
private static final int COORDS_PER_VERTEX = 3;
private static final int BYTES_PER_FLOAT = 4;
private final int vertexStride = COORDS_PER_VERTEX *BYTES_PER_FLOAT;
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
int mProgram;
public Triangle() {
initVertexBuffer();
mProgram = MyShaderFactory.getInstanceShader();
GLES20.glClearColor(1,1,0,0);
}
public void onDraw(){
GLES20.glUseProgram(mProgram);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT| GLES20.GL_DEPTH_BUFFER_BIT);
int positionHandle = GLES20.glGetAttribLocation(mProgram, MyShaderFactory.FIELD_POSITION);
int colorHandle = GLES20.glGetUniformLocation(mProgram, MyShaderFactory.FIELD_COLOR);
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
GLES20.glUniform4fv(colorHandle, 1, color, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
GLES20.glDisableVertexAttribArray(positionHandle);
}
private void initVertexBuffer(){
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(triangleCoords.length*BYTES_PER_FLOAT);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer.put(triangleCoords);
vertexBuffer.position(0);//由第0個頂點開始畫起
}
}
完整程式碼下載 : OpenGL_GLSurfaceView.rar
