音視頻開發(fā)之旅(九) OpenGL ES 繪制平面圖形

目錄

  1. 寫著色器代碼
  2. 通過(guò)GLSurfaceview加載Shader并運(yùn)行
  3. 遇到的問(wèn)題
  4. 參考
  5. 收獲

我們前兩篇介紹了OpenGL ES 基本概念GLSL及Shader的渲染流程瑞眼,這篇我們開始實(shí)戰(zhàn),通過(guò)GLSurfaceView加載著色器帆啃,來(lái)繪制三角形勋又、正方形和直線這些平面圖形生蚁。在實(shí)踐過(guò)程中遇到的問(wèn)題有時(shí)候讓人沒(méi)有頭緒肋拔,檢查了一遍又一遍代碼氛魁,發(fā)現(xiàn)流程沒(méi)有問(wèn)題,但屏幕就是一片漆黑乖篷。响驴。通過(guò)近一個(gè)小時(shí)的排查,發(fā)現(xiàn)問(wèn)題出在了這里撕蔼。豁鲤。秽誊。下面開始我們今天的學(xué)習(xí)時(shí)間之旅,希望對(duì)你也有幫助琳骡。

GLSL著色器的編寫

如果對(duì)OpenGL的基本概念以及渲染流程不清晰的锅论,建議看下前兩篇文章,這些基本概念和流程要了解或者理解楣号,否則后面實(shí)踐之旅就是跳坑之旅最易。

我們先通過(guò)下面重要的二張圖,快速回顧下


工欲善其事炫狱,必先利其器耘纱,如何方便的編寫GLSL代碼吶? AndoridStudio提供了“Support for GLSL”插件毕荐。VS Code也有比較強(qiáng)大的插件比如“Shader Toy
”和“Shader languages support for VS Code”。但是都不足之處艳馒,就是沒(méi)有自動(dòng)提示和補(bǔ)全的功能憎亚。所以編寫GLSL代碼是要細(xì)心。

1.1 著色器代碼的編寫

首先我們來(lái)編寫下頂點(diǎn)著色器和片元著色器

//vertex_shader.glsl 頂點(diǎn)著色器

attribute vec4 a_Position;
attribute vec4 a_Color;

varying vec4 v_Color;

void main() {
    v_Color = a_Color;
    gl_Position = a_Position;
}

上述代碼簡(jiǎn)單語(yǔ)法回顧

attribute是修飾符 只能用于頂點(diǎn)著色器弄慰,用于修飾可變的參數(shù)
vec4: 浮點(diǎn)型向量

gl_Position:內(nèi)置變量

varying:也是一個(gè)修飾符第美,用于頂點(diǎn)著色器和片元著色器的值傳遞。
注意:必須要在頂點(diǎn)著色器和片元著色器都定義同名同類型同varying修飾符的變量陆爽,才能正常傳遞什往。
//fragment_shader.glsl 片元著色器

precision mediump float;

varying vec4 v_Color;

void main() {
    gl_FragColor = v_Color;
}

這里對(duì)精度precision mediump 做下說(shuō)明
用于修飾浮點(diǎn)型和整形,有三個(gè)等級(jí) highp\mediump慌闭、lowp


1.2 著色器代碼的讀取

著色器代碼通常放在assets目錄下或者raw目錄下别威,當(dāng)然也見(jiàn)到過(guò)直接寫在代碼里。我們?yōu)榱朔奖懵刻蓿捎帽容^通用的方式:把glsl代碼文件放在了assets下省古,再加載前需要先把他們讀到內(nèi)存中,常規(guī)的文件讀寫

public static String loadAsset(Resources res, String path) {
     StringBuilder stringBuilder = new StringBuilder();
     try {
         InputStream is = res.getAssets().open(path);

         byte[] buffer = new byte[1024];
         int count;
         while (-1 != (count = is.read(buffer))) {
             stringBuilder.append(new String(buffer, 0, count));
         }
         String result = stringBuilder.toString().replaceAll("\\r\\n", "\n");
        return result;
     } catch (IOException e) {
         e.printStackTrace();
     }
     return "";
 }

1.3 著色器的創(chuàng)建丧失、設(shè)置源碼豺妓、編譯

private static int loadShader(int type, String codeStr) {
        //1. 根據(jù)類型(頂點(diǎn)著色器、片元著色器)創(chuàng)建著色器布讹,拿到著色器句柄
        int shader = GLES20.glCreateShader(type);
        Log.i(TAG, "compileShaderCode: type=" + type + " shaderId=" + shader);

        if (shader > 0) {
            //2. 設(shè)置著色器代碼 琳拭,shader句柄和code進(jìn)行綁定
            GLES20.glShaderSource(shader, codeStr);
            //3. 編譯著色器,
            GLES20.glCompileShader(shader);

            //4. 查詢編譯狀態(tài)
            int[] status = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0);
            Log.i(TAG, "loadShader: status[0]=" + status[0]);
            //如果失敗描验,釋放資源
            if (status[0] == 0) {
                GLES20.glDeleteShader(shader);
                return 0;
            }
        }
        return shader;
    }

1.4 程序的創(chuàng)建白嘁、attach著色器、鏈接挠乳、使用

public static int loadProgram(String verCode, String fragmentCode) {
        //1. 創(chuàng)建Shader程序权薯,獲取到program句柄
        int programId = GLES20.glCreateProgram();
        if(programId == 0){
            Log.e(TAG, "loadProgram: glCreateProgram error" );
            return 0;
        }
        //2. 根據(jù)著色器語(yǔ)言類型和代碼姑躲,attach著色器
        GLES20.glAttachShader(programId, loadShader(GLES20.GL_VERTEX_SHADER, verCode));
        GLES20.glAttachShader(programId, loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode));
        //3. 鏈接
        GLES20.glLinkProgram(programId);
        //4. 使用
        GLES20.glUseProgram(programId);
        return programId;
    }

1.5 狀態(tài)查詢

  1. 著色器Shader和Program創(chuàng)建后會(huì)拿到對(duì)應(yīng)的句柄,通過(guò)檢查是否大于0驗(yàn)證是否可用

  2. GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0);著色器編譯后檢查編譯的狀態(tài)是否大于0判斷可用性盟蚣。

  3. glGetProgramiv(programObjectId, GL_VALIDATE_STATUS,
    validateStatus, 0);//程序鏈接后黍析,檢查程序的可用性

1.6 輸入與輸出

輸入:給著色器賦值
輸出:在屏幕上顯示

前面5個(gè)步驟把準(zhǔn)備工作都做好了,那邊現(xiàn)在面臨兩個(gè)問(wèn)題.

1. 我們看到頂點(diǎn)著色器中有兩個(gè)attribute修飾的變量屎开,如何給它們賦值?
2. 如何把著色器再屏幕上繪制出來(lái)阐枣?

這個(gè)環(huán)節(jié)我們就來(lái)解決這兩個(gè)問(wèn)題

首先定義好頂點(diǎn)坐標(biāo)和顏色的位數(shù)和著色器的數(shù)據(jù)

    //每個(gè)頂點(diǎn)坐標(biāo)的個(gè)數(shù)
    private final static int COORDS_PER_VERTEX = 2;

    //每個(gè)頂點(diǎn)顏色的個(gè)數(shù)
    private final static int COLOR_PER_VERTEX = 3;
    
    // 浮點(diǎn)類型占用的字節(jié)數(shù)
    private final static int BYTES_PER_FLOAT = 4;
    
    //下面兩個(gè)字符串常量就是GLSL頂點(diǎn)著色器的輸入

    private static final String A_POSITION = "a_Position";
    private static final String A_COLOR = "a_Color";

    //STRIDE是一個(gè)頂點(diǎn)的字節(jié)偏移(頂點(diǎn)坐標(biāo)xy+顏色rgb)
    private final int STRIDE = (COORDS_PER_VERTEX+ COLOR_PER_VERTEX )* BYTES_PER_FLOAT;

    private FloatBuffer mVertexData;

    public MyRender2() {
        //頂點(diǎn)數(shù)組
        float[] TRIANGLE_COORDS = {
                0.5f, 0.5f,1f, 0.5f,0.5f,
                -0.5f, -0.5f,0.5f, 1f,0.5f,
                0.5f, -0.5f,0.5f, 0.5f,1f

        };

       //通過(guò)nio ByteBuffer把設(shè)置的頂點(diǎn)數(shù)據(jù)加載到內(nèi)存
        mVertexData = ByteBuffer
                .allocateDirect(TRIANGLE_COORDS.length * BYTES_PER_FLOAT) //需要多少字節(jié)內(nèi)存
                .order(ByteOrder.nativeOrder())//大小端排序
                .asFloatBuffer()
                .put(TRIANGLE_COORDS);//設(shè)置數(shù)據(jù)
    }

然后給頂點(diǎn)著色器的變量賦值

 String vertexCode = ShaderHelper.loadAsset(MyApplication.getContext().getResources(), "vertex_shader.glsl");
        String fragmentCode = ShaderHelper.loadAsset(MyApplication.getContext().getResources(), "fragment_shader.glsl");
        //創(chuàng)建著色器程序
        programId = ShaderHelper.loadProgram(vertexCode, fragmentCode);


        int aPosition = GLES20.glGetAttribLocation(programId, A_POSITION);
        Log.i(TAG, "drawFrame: aposition="+aPosition);
        mVertexData.position(0);
        GLES20.glVertexAttribPointer(aPosition,
                COORDS_PER_VERTEX,//用幾個(gè)偏移描述一個(gè)頂點(diǎn)
                GLES20.GL_FLOAT,//頂點(diǎn)數(shù)據(jù)類型
                false,
                STRIDE,//一個(gè)頂點(diǎn)需要多少個(gè)字節(jié)偏移
                mVertexData//分配的buffer
        );

        //開啟頂點(diǎn)著色器的attribute
        GLES20.glEnableVertexAttribArray(aPosition);

        int aColor = GLES20.glGetAttribLocation(programId, A_COLOR);
        mVertexData.position(COORDS_PER_VERTEX);
        GLES20.glVertexAttribPointer(aColor,COLOR_PER_VERTEX,GL_FLOAT,false,STRIDE,mVertexData);
        GLES20.glEnableVertexAttribArray(aColor);

關(guān)鍵API說(shuō)明

  1. 獲取著色器attribute一個(gè)屬性:int aPosition = GLES20.glGetAttribLocation(programId, A_POSITION);_

  2. 數(shù)據(jù)的偏移:mVertexData.position(0); 因?yàn)橛凶鴺?biāo)和顏色兩個(gè)變量的值,數(shù)據(jù)又是根據(jù)頂點(diǎn)一一設(shè)定的奄抽。

  3. 給attribute賦值:GLES20. glVertexAttribPointer(
    int indx,//attribute的句柄
    int size,//在數(shù)組中占用的位數(shù)
    int type,//數(shù)據(jù)的類型
    boolean normalized,
    int stride,//步幅 單位字節(jié)數(shù)
    java.nio.Buffer ptr // 元數(shù)據(jù)
    )

  4. 使能對(duì)應(yīng)的attribute屬性:GLES20.glEnableVertexAttribArray(aPosition);

通過(guò)上面幾個(gè)環(huán)節(jié)我們可以看到蔼两,我們可以看到,是如何給著色器語(yǔ)言中的變量賦值的逞度。下面我們看來(lái)下如何渲染额划。

我們采用GlSurfaceView,通過(guò)Render來(lái)實(shí)現(xiàn)档泽,具體如下:

    //1. 設(shè)置OpenGL ES的版本
    glSView.setEGLContextClientVersion(3);

     //2. 給glSurfaceView設(shè)置render
    glSView.setRenderer(new MyRender2());

  
public class MyRender2 implements GLSurfaceView.Renderer {

@Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
      //著色器的加載俊戳、賦值
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //清屏
        GLES20.glClear(GL_COLOR_BUFFER_BIT);
        
        //繪制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN,0,3);
    }
}

是的,在onDrawFrame中進(jìn)行不斷的 glDrawArrays來(lái)繪制刷新   
 public static native void glDrawArrays(
        int mode, //點(diǎn)馆匿、線抑胎、三角形
        int first,//頂點(diǎn)的第一個(gè)數(shù)據(jù)的index
        int count//頂點(diǎn)的總數(shù)
    );

二、實(shí)踐:用GLSurfaceView加載GLSL繪制屏幕圖形

2.1 三角形

上面的代碼中定義的就是三角形渐北,對(duì)應(yīng)頂點(diǎn)數(shù)據(jù)如下

float[] TRIANGLE_COORDS = {0.5f, 0.5f,1f, 0.5f,0.5f,
                -0.5f, -0.5f,0.5f, 1f,0.5f,
                0.5f, -0.5f,0.5f, 0.5f,1f

        };

兩個(gè)坐標(biāo)位x&y,和三個(gè)顏色位rgb

效果如下


2.2 正方形

只需要修改 頂點(diǎn)數(shù)組和glDrawArrays的count參數(shù)即可

float[] TRIANGLE_COORDS = {
               0.5f, 0.5f,1f, 0.5f,0.5f,
               -0.5f, -0.5f,0.5f, 1f,0.5f,
               0.5f, -0.5f,0.5f, 0.5f,1f,
               0.5f, 0.5f,1f, 0.5f,0.5f,
               -0.5f, 0.5f,0.5f, 0.5f,1f,
               -0.5f, -0.5f,0.5f, 1f,0.5f,
       };


  public void onDrawFrame(GL10 gl) {
       GLES20.glClear(GL_COLOR_BUFFER_BIT);
       GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,6);
   }

2.3 直線

     float[] TRIANGLE_COORDS = {
            -0.5f, 0f, 1f, 0f, 0f,
             0.5f, 0f, 0f, 0f, 1f,
        };

    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GL_COLOR_BUFFER_BIT);
        GLES20.glDrawArrays(GLES20.GL_LINES,0,2);
    }

通過(guò)log分析阿逃,我們發(fā)現(xiàn)GLSurfaceView.Renderer是運(yùn)行中一個(gè)叫GLThread的線程中,它的作用和意義是什么赃蛛,下一篇我們通過(guò)對(duì)GLSurfaceView的源碼分析來(lái)理解EGL和GLThread恃锉,了解完整的流程,然后不使用Render焊虏,采用自建管理EGL和創(chuàng)建GLThread淡喜,通過(guò)TextureView實(shí)現(xiàn)圖形的繪制。做到知其然诵闭,也知其所以然炼团。

三、遇到的問(wèn)題

1. A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 9940 (GLThread 6703)_

發(fā)現(xiàn)是GLES20.glCreateProgram()時(shí)出現(xiàn)的上述崩潰
原因是因?yàn)闆](méi)有g(shù)lSView.setEGLContextClientVersion(3);
2. GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0)
Shader編譯后獲取狀態(tài)為0(失敗了)

原因是因?yàn)間lsl語(yǔ)言注釋不對(duì)用成了 # 而不是 //
3.  無(wú)法正常的看到期望的圖片
這個(gè)就是開頭說(shuō)的說(shuō)的那個(gè)折騰了一個(gè)小時(shí)的問(wèn)題疏尿,一個(gè)“逗號(hào)”引發(fā)的問(wèn)題

具體如下:

float[] TRIANGLE_COORDS = {0.5f, 0.5f,1f, 0.5f,0.5f ///注意這里沒(méi)有加逗號(hào)瘟芝,就是這個(gè)導(dǎo)致的,glsl認(rèn)為到這里就結(jié)束了褥琐。锌俱。但是代碼上寫的是3個(gè)頂點(diǎn),找不到就無(wú)法正常繪制敌呈。多么痛的領(lǐng)悟贸宏。
                -0.5f, -0.5f,0.5f, 1f,0.5f,
                0.5f, -0.5f,0.5f, 0.5f,1f

        };

四造寝、參考

《OpenGL ES應(yīng)用開發(fā)實(shí)踐指南》
《音視頻開發(fā)進(jìn)階指南》
[搭建OpenGL ES環(huán)境的兩種方式]
[Android OpenGL ES(一)-開始描繪一個(gè)平面三角形]
[Android OpenGL ES(三)-平面圖形]
[EGL 環(huán)境搭建流程]

五、收獲

  1. 通過(guò)代碼實(shí)踐加深了對(duì)GLSL語(yǔ)法吭练,OpenGL基本概念和繪制流程的熟悉诫龙。
  2. glsl程序編寫androidstudio等IDE插件的了解
  3. 理解實(shí)現(xiàn)了如何給著色器輸入數(shù)據(jù),又如何在屏幕上繪制鲫咽。
  4. 繪制三角形签赃、正方形、直線等平面圖形
  5. 遇到的問(wèn)題分析解決分尸,彌補(bǔ)認(rèn)知不足锦聊。

下一篇我們來(lái)解析GLSurfaceView源碼&自己實(shí)現(xiàn)EGL管理與GLThread。歡迎關(guān)注公眾號(hào)“音視頻開發(fā)之旅”箩绍,一起學(xué)習(xí)成長(zhǎng)孔庭。

歡迎交流

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市材蛛,隨后出現(xiàn)的幾起案子史飞,更是在濱河造成了極大的恐慌,老刑警劉巖仰税,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異抽诉,居然都是意外死亡陨簇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門迹淌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)河绽,“玉大人,你說(shuō)我怎么就攤上這事唉窃“沂危” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵纹份,是天一觀的道長(zhǎng)苟跪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蔓涧,這世上最難降的妖魔是什么件已? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮元暴,結(jié)果婚禮上篷扩,老公的妹妹穿的比我還像新娘。我一直安慰自己茉盏,他們只是感情好鉴未,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布枢冤。 她就那樣靜靜地躺著,像睡著了一般铜秆。 火紅的嫁衣襯著肌膚如雪淹真。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天羽峰,我揣著相機(jī)與錄音趟咆,去河邊找鬼。 笑死梅屉,一個(gè)胖子當(dāng)著我的面吹牛值纱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坯汤,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼虐唠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了惰聂?” 一聲冷哼從身側(cè)響起疆偿,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搓幌,沒(méi)想到半個(gè)月后杆故,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溉愁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年处铛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拐揭。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撤蟆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出堂污,到底是詐尸還是另有隱情家肯,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布盟猖,位于F島的核電站讨衣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏式镐。R本人自食惡果不足惜值依,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碟案。 院中可真熱鬧愿险,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至扮叨,卻和暖如春缤弦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彻磁。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工碍沐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衷蜓。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓累提,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親磁浇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斋陪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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