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