Android OpenGL ES(二)繪制簡(jiǎn)單圖形

通過(guò)上篇文章的學(xué)習(xí),現(xiàn)在已經(jīng)了解到篱昔,要想在 Android 端使用 OpenGL ES 繪制圖形乌逐,必須創(chuàng)建 OpenGL ES 環(huán)境和視圖窗口逻住,具體來(lái)說(shuō)就是構(gòu)建 EGL 環(huán)境,即 OpenGL ES 和 Android 底層平臺(tái)視窗系統(tǒng)之間的接口创肥。另外 OpenGL ES 2.0 版本為可編程管線达舒,我們就可以編寫著色器程序來(lái)確定繪制內(nèi)容,即編寫 Vertex Shader 頂點(diǎn)著色器和 Fragment Shader 片元著色器叹侄。

而這些工作可以通過(guò) GLSurfaceView 非常簡(jiǎn)單的實(shí)現(xiàn)巩搏。

在介紹 GLSurfaceView 之前先來(lái)看下 Android 系統(tǒng)提供的與 OpenGL ES 相關(guān)的包:

  • javax.microedition.khronos.opengles: 存放 GL 繪圖指令相關(guān)代碼
  • javax.microedition.khronos.egl: 存放 EGL 管理相關(guān)代碼,包括 Display趾代、surface 等
  • android.opengl: 存放 GL 輔助類塔猾,連接 OpenGL 與 Android View,Activity 等

其中 GLSurfaceView 處于 android.opengl 包中稽坤,GLSurfaceView 具有以下特性:

  • 內(nèi)置 EGL 管理丈甸,自帶 GL 上下文環(huán)境和 GLThread 繪制線程
  • 起到連接 OpenGL ES 與 Android 的 View 層次結(jié)構(gòu)之間的橋梁作用
  • 使得 OpenGL ES 庫(kù)適應(yīng)于 Activity 生命周期
  • 繼承自 SurfaceView糯俗,擁有 SurfaceView 的全部特性,繪制結(jié)果會(huì)輸出到 SurfaceView 所提供的 Surface 上
  • 提供了方便使用的調(diào)試工具來(lái)跟蹤 OpenGL ES 函數(shù)調(diào)用以幫助檢查錯(cuò)誤

通過(guò) GLSurfaceView 的 setRenderer 方法可設(shè)置要渲染的效果睦擂,即 GLSurfaceView.Renderer 渲染器接口得湘,該接口方法:

  • onSurfaceCreated:渲染線程開(kāi)啟時(shí)調(diào)用,可做初始化背景色顿仇、初始化紋理資源等工作
  • onSurfaceChanged:窗口尺寸改變時(shí)調(diào)用淘正,通常會(huì)設(shè)置視窗范圍或投影矩陣等
  • onDrawFrame:外部請(qǐng)求渲染一次就調(diào)用一次,可在此載入著色器程序臼闻、激活綁定紋理以及調(diào)用繪制

下面來(lái)看具體如何繪制一個(gè)三角形:


public class Triangle implements GLSurfaceView.Renderer {

    //頂點(diǎn)著色器
    private static final String vertexShaderResource =
            "attribute vec3 vPosition;" +
                    "void main() {" +
                    "  gl_Position = vec4(vPosition.x, vPosition.y, vPosition.z, 1.0);" +
                    "}";
    //片段著色器
    private static final String fragmentShaderResource =
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";
    //頂點(diǎn)
    private final float[] vertexCoords = new float[]{
            0.0f, 0.5f, 0.0f, // top
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f  // bottom right
    };

    private final float color[] = {1.0f, 0f, 0f, 1.0f}; //red

    // 著色器程序
    private int mProgram;
    // 頂點(diǎn)坐標(biāo)數(shù)據(jù)
    private FloatBuffer vertexFloatBuffer;


    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //設(shè)置清空屏幕后的背景色
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
        //構(gòu)建頂點(diǎn)著色器
        int vertexShader = GLES30.glCreateShader(GLES30.GL_VERTEX_SHADER);
        GLES30.glShaderSource(vertexShader, vertexShaderResource);
        GLES30.glCompileShader(vertexShader);
        //構(gòu)建片段著色器
        int fragmentShader = GLES30.glCreateShader(GLES30.GL_FRAGMENT_SHADER);
        GLES30.glShaderSource(fragmentShader, fragmentShaderResource);
        GLES30.glCompileShader(fragmentShader);
        //構(gòu)建著色器程序鸿吆,并將頂點(diǎn)著色器和片段著色器鏈接進(jìn)來(lái)
        mProgram = GLES30.glCreateProgram();
        GLES30.glAttachShader(mProgram, vertexShader);
        GLES30.glAttachShader(mProgram, fragmentShader);
        GLES30.glLinkProgram(mProgram);
        //頂點(diǎn)著色器和片段著色器鏈接到著色器程序后就無(wú)用了
        GLES30.glDeleteShader(vertexShader);
        GLES30.glDeleteShader(fragmentShader);
        //轉(zhuǎn)換為需要的頂點(diǎn)數(shù)據(jù)格式
        vertexFloatBuffer = floatToBuffer(vertexCoords);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //設(shè)置視窗
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //清空屏幕,擦除屏幕上所有的顏色述呐,用 glClearColor 定義的顏色填充
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        //在當(dāng)前 EGL 環(huán)境激活著色器程序
        GLES30.glUseProgram(mProgram);
        //獲取頂點(diǎn)著色器的 vPosition 成員句柄
        int positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");
        //啟用句柄
        GLES30.glEnableVertexAttribArray(positionHandle);
        //設(shè)置頂點(diǎn)坐標(biāo)數(shù)據(jù)
        GLES30.glVertexAttribPointer(positionHandle, 3, GLES30.GL_FLOAT,
                false, 3 * 4, vertexFloatBuffer);
        //獲取片元著色器的 vColor 成員句柄
        int colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");
        //設(shè)置顏色
        GLES30.glUniform4fv(colorHandle, 1, color, 0);
        //繪制三角形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 3);
        //禁止頂點(diǎn)數(shù)組的句柄
        GLES30.glDisableVertexAttribArray(positionHandle);
    }

    private FloatBuffer floatToBuffer(float[] a) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(a.length * 4); //float占4個(gè)字節(jié)
        buffer.order(ByteOrder.nativeOrder());
        FloatBuffer byteBuffer = buffer.asFloatBuffer();
        byteBuffer.put(a);
        byteBuffer.position(0);
        return byteBuffer;
    }
}

頂點(diǎn)輸入

要繪制一個(gè)三角形惩淳,就要確定三個(gè)頂點(diǎn)的 3D 坐標(biāo)(OpenGL 是一個(gè) 3D 圖形庫(kù),在 OpenGL 中指定的所有坐標(biāo)都需要是 3D 坐標(biāo)乓搬,即 x思犁、y 和 z)。而頂點(diǎn)坐標(biāo)起始于局部坐標(biāo)进肯,需要為標(biāo)準(zhǔn)化設(shè)備坐標(biāo)激蹲,即 x、y江掩、z 的范圍限定于 -1 到 1 之間学辱,任何落在范圍外的坐標(biāo)都會(huì)被丟棄。上面代碼中輸入的頂點(diǎn)數(shù)據(jù)為:

    private final float[] vertexCoords = new float[]{
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f
    };

這里將 z 坐標(biāo)都設(shè)置為 0环形,表示三角形每一點(diǎn)的深度都為 0(通常深度可以理解為z坐標(biāo)策泣,它代表一個(gè)像素在空間中和你的距離,如果離你遠(yuǎn)就可能被別的像素遮擋斟赚,你就看不到它了着降,它會(huì)被丟棄,以節(jié)省資源)拗军,這樣定義的頂點(diǎn)數(shù)據(jù)反應(yīng)到標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系中就是這樣的:

image

解釋頂點(diǎn)數(shù)據(jù)

可以看到調(diào)用 glVertexAttribPointer 設(shè)置頂點(diǎn)數(shù)據(jù)時(shí)并不是直接把 float[] 數(shù)組傳遞進(jìn)去任洞,而是轉(zhuǎn)換成 FloatBuffer 傳入,所謂的解釋頂點(diǎn)數(shù)據(jù)就是說(shuō)明輸入數(shù)據(jù)的哪一個(gè)部分對(duì)應(yīng)頂點(diǎn)著色器的哪一個(gè)頂點(diǎn)屬性发侵。對(duì)于 glVertexAttribPointer 函數(shù):

    public static void glVertexAttribPointer(
        int indx,
        int size, 
        int type,
        boolean normalized,
        int stride,
        java.nio.Buffer ptr
    )
  • indx:指定要配置的頂點(diǎn)屬性交掏,這里傳入頂點(diǎn)著色器的 vPosition 成員句柄
  • size:指定頂點(diǎn)屬性的大小,頂點(diǎn)屬性是一個(gè) vec3刃鳄,它由 3 個(gè)值(x盅弛、y、z)組成,所以大小傳入 3
  • type:指定數(shù)據(jù)的類型為 float 類型
  • normalized:是否希望數(shù)據(jù)被標(biāo)準(zhǔn)化
  • stride:指定連續(xù)的頂點(diǎn)數(shù)據(jù)之間的間隔挪鹏,由于一個(gè)頂點(diǎn)數(shù)據(jù)長(zhǎng)度為 3 個(gè) float见秽,所以把步長(zhǎng)設(shè)置為 3 * 4(一個(gè) float 占 4 個(gè)字節(jié))
  • ptr:頂點(diǎn)數(shù)據(jù)

下圖很好的闡釋這個(gè)邏輯:

image

頂點(diǎn)著色器

    private static final String vertexShaderResource =
        "attribute vec3 vPosition;" +
            "void main() {" +
            "  gl_Position = vec4(vPosition.x, vPosition.y, vPosition.z, 1.0);" +
            "}";

由于每個(gè)頂點(diǎn)都有一個(gè) 3D 坐標(biāo),這里就創(chuàng)建一個(gè) vec3 變量輸入頂點(diǎn)坐標(biāo)讨盒。而內(nèi)置變量 gl_Position 為 vec4 類型解取,所以需要將三維向量轉(zhuǎn)換為四維向量,最后 gl_Position 設(shè)置的值會(huì)成為該頂點(diǎn)著色器的輸出返顺。

onDrawFrame 方法中在獲取頂點(diǎn)著色器的 vPosition 成員句柄后禀苦,需要調(diào)用 glEnableVertexAttribArray、glDisableVertexAttribArray 分別啟用遂鹊、禁止頂點(diǎn)數(shù)據(jù)振乏,而片段著色器的 vColor 成員句柄就不需要。這是因?yàn)槌鲇谛阅芸紤]秉扑,所有頂點(diǎn)著色器的屬性默認(rèn)都是關(guān)閉的慧邮。
glVertexAttribPointer 只是建立 CPU 和 GPU 之間的邏輯連接實(shí)現(xiàn)將 CPU 數(shù)據(jù)上傳至 GPU,但是邻储,數(shù)據(jù)在 GPU 端是否可見(jiàn)萧福,即著色器能否讀取到數(shù)據(jù)般眉,還要取決于 glEnableVertexAttribArray 方法。

片段著色器

    private static final String fragmentShaderResource =
    "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            "  gl_FragColor = vColor;" +
            "}";

在 OpenGL 中定義一個(gè)顏色的數(shù)據(jù)格式為 RGBA 四個(gè) 0.0 到 1.0 之間強(qiáng)度的分量赦抖,片段著色器所做的是計(jì)算像素最后的顏色輸出淘钟,也只有 gl_FragColor 這一個(gè)輸出變量宦赠。

編譯著色器

      int vertexShader = GLES30.glCreateShader(GLES30.GL_VERTEX_SHADER);
      GLES30.glShaderSource(vertexShader, vertexShaderResource);
      GLES30.glCompileShader(vertexShader);

為了讓 OpenGL 能夠使用我們編寫的著色器源碼,必須在運(yùn)行時(shí)動(dòng)態(tài)編譯米母。首先通過(guò) glCreateShader 創(chuàng)建一個(gè)著色器對(duì)象勾扭,返回該著色器的 ID,然后通過(guò) glShaderSource铁瞒、glCompileShader 方法將源碼附著在著色器對(duì)象上并編譯它妙色。

編譯著色器可能失敗,一般編譯時(shí)會(huì)通過(guò)如下方法判斷是否編譯成功并輸出編譯信息:

      final int[] compileStatus = new int[1];
      GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
      Log.d(TAG, "glCompileStatus: " + compileStatus[0] 
                  + " log:" + GLES20.glGetShaderInfoLog(shaderObjectId));
      if (compileStatus[0] == 0) { //編譯失敗
          GLES20.glDeleteShader(shaderObjectId);
          return 0;
      }

著色器程序

      mProgram = GLES30.glCreateProgram();
      GLES30.glAttachShader(mProgram, vertexShader);
      GLES30.glAttachShader(mProgram, fragmentShader);
      GLES30.glLinkProgram(mProgram);

著色器程序?qū)ο笫嵌鄠€(gè)著色器合并之后并最終鏈接完成的版本慧耍,它將編譯好的頂點(diǎn)著色器和片段著色器鏈接為一個(gè)著色器程序?qū)ο笊肀妫溄雍箜旤c(diǎn)著色器和片段著色器就沒(méi)用了,可以通過(guò) glDeleteShader 刪除芍碧。就像著色器的編譯一樣煌珊,我們也可以檢測(cè)鏈接著色器程序是否失敗,并獲取相應(yīng)的日志:

      final int[] linkStatus = new int[1];
      GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
      Log.d(TAG, "glCompileStatus:" + linkStatus[0]
                  + " log:" + GLES20.glGetProgramInfoLog(mProgram));
      if (linkStatus[0] == 0) { //鏈接失敗
          GLES20.glDeleteProgram(mProgram);
          return 0;
      }

鏈接成功后泌豆,在渲染的時(shí)候通過(guò) glUseProgram 方法激活著色器程序定庵,已激活著色器程序的著色器就會(huì)在渲染時(shí)被使用,最后通過(guò) glDrawArrays 方法觸發(fā)繪制。

參考文章:

《音視頻開(kāi)發(fā)進(jìn)階指南 - 基于Android與IOS平臺(tái)的實(shí)踐》
Android GLSurfaceView詳解
GLSurfaceView
你好蔬浙,三角形
OpenGL ES 3.0 glEnableVertexAttribArray的作用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猪落,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子畴博,更是在濱河造成了極大的恐慌许布,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绎晃,死亡現(xiàn)場(chǎng)離奇詭異蜜唾,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)庶艾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門袁余,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人咱揍,你說(shuō)我怎么就攤上這事颖榜。” “怎么了煤裙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵掩完,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我硼砰,道長(zhǎng)且蓬,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任题翰,我火速辦了婚禮恶阴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘豹障。我一直安慰自己冯事,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布血公。 她就那樣靜靜地躺著昵仅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪累魔。 梳的紋絲不亂的頭發(fā)上摔笤,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音薛夜,去河邊找鬼籍茧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梯澜,可吹牛的內(nèi)容都是我干的寞冯。 我是一名探鬼主播渴析,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吮龄!你這毒婦竟也來(lái)了俭茧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤漓帚,失蹤者是張志新(化名)和其女友劉穎母债,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體尝抖,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毡们,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昧辽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衙熔。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖搅荞,靈堂內(nèi)的尸體忽然破棺而出红氯,到底是詐尸還是另有隱情,我是刑警寧澤咕痛,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布痢甘,位于F島的核電站,受9級(jí)特大地震影響茉贡,放射性物質(zhì)發(fā)生泄漏塞栅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一块仆、第九天 我趴在偏房一處隱蔽的房頂上張望构蹬。 院中可真熱鬧王暗,春花似錦悔据、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绷雏,卻和暖如春头滔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涎显。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工坤检, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人期吓。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓早歇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子箭跳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容