第二章 GLSurfaceView

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

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *