天天看點

OpenGLES應用開發實踐指南——OpenGLES2.0實戰第一課

要用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添加以下的聲明:

  1. <uses-feature android:glEsVersion="0x00020000" android:required="true" /> 

如果你的應用程式中使用到了紋理壓縮,你必須聲明應用程式所支援的壓縮格式,以便提醒不支援這些格式的裝置不會嘗試運作你的應用程式:

  1. <supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" /> 
  2. <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像素的點,效果如圖:

OpenGLES應用開發實踐指南——OpenGLES2.0實戰第一課

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

繼續閱讀