Android OpenGL ES(一)-開始描繪一個平面三角形

image.png

關(guān)于OpenGL ES Android的介紹這里略過

OpenGL ES世界的基本元素

  1. 著色器
  2. 坐標(biāo)系内贮。矩陣
  3. 紋理
    ...

本文主要涉及的部分是著色器的使用。

直接開始


創(chuàng)建GLSurfaceView

今天的目標(biāo)是做一個OpenGL ES學(xué)習(xí)的開端伊佃。就是畫一個簡單的三角形情龄。暫時不考慮坐標(biāo)系的矩陣變換和紋理等灭忠。只需要用頂點(diǎn)著色器簡單的來進(jìn)行描述简卧。
這一節(jié)需要使用和認(rèn)識的關(guān)鍵類是
GLSurfaceViewGLSurfaceView.Render
一句話來描述就是,我們會在GLSurfaceView.Render上進(jìn)行描繪族跛,在GLSurfaceView中顯示出來闰挡。

判斷是否支持OpenGL Es2

 /**
     * 判斷是否支持es2.0
     *
     * @param context
     * @return
     */
    public static boolean isSupportEs2(Context context) {
        //檢查是否支持2.0
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        if (activityManager != null) {
            ConfigurationInfo deviceConfigurationInfo = activityManager.getDeviceConfigurationInfo();
            int reqGlEsVersion = deviceConfigurationInfo.reqGlEsVersion;
            return reqGlEsVersion >= GLES_VERSION_2 || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
                    && (Build.FINGERPRINT.startsWith("generic")
                    || Build.FINGERPRINT.startsWith("unknown")
                    || Build.MODEL.contains("google_sdk")
                    || Build.MODEL.contains("Emulator")
                    || Build.MODEL.contains("Android SDK build for x86")));
        } else {
            return false;
        }

    }

創(chuàng)建GLSurfaceView

接著創(chuàng)建一個GLSurfaceView。并且設(shè)置其版本為2礁哄。同時設(shè)置我們自己的Render

           //創(chuàng)建一個GLSurfaceView
            glSurfaceView = new GLSurfaceView(this);
            glSurfaceView.setEGLContextClientVersion(2);
            //設(shè)置自己的Render.Render 內(nèi)進(jìn)行圖形的繪制
            glSurfaceView.setRenderer(new TriangleShapeRender(this));
            isRenderSet = true;
            setContentView(glSurfaceView);

還需要在Activity對應(yīng)的生命周期內(nèi)长酗,來調(diào)用我們的GLSurfaceView的方法

 @Override
    protected void onPause() {
        super.onPause();
        if (isRenderSet) {
            glSurfaceView.onPause();
        }

    }

    @Override
    protected void onResume() {
        super.onResume();
        if (isRenderSet) {
            glSurfaceView.onResume();
        }

    }

繪制三角形-Render的實(shí)現(xiàn)類

繪制的基礎(chǔ)知識


GLThread

因?yàn)锳ndroid中的GLSurfaceView的操作,其實(shí)都是在GLThread中進(jìn)行桐绒。所以生命周期方法的回調(diào)也都在GLThread線程中夺脾。所有OpenGL的操作也都需要在該線程中。

基礎(chǔ)的生命周期方法

接下來轉(zhuǎn)到Render的實(shí)現(xiàn)類里面來茉继。先關(guān)注需要實(shí)現(xiàn)的生命周期方法咧叭。

/**
 * 注意:
 * 因?yàn)槲覀兪鞘褂胓20來進(jìn)行編程,需要要注意導(dǎo)包
 *
 * 主要在Render類內(nèi)烁竭,完成對應(yīng)的繪制.
 * 對應(yīng)的生命周期的回調(diào)
 * -> onSurfaceCreated
 * -> onSurfaceChanged
 * -> onDrawFrame
 *
 * Created by a2957 on 2018/5/3.
 */
public class ViewGLRender implements GLSurfaceView.Renderer {
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //0.簡單的給窗口填充一種顏色
        GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);

        //在創(chuàng)建的時候佳簸,去創(chuàng)建這些著色器

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //在窗口改變的時候調(diào)用
        GLES20.glViewport(0,0,width,height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //0.glClear()的唯一參數(shù)表示需要被清除的緩沖區(qū)。當(dāng)前可寫的顏色緩沖
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }
}

如上面代碼所示颖变,對應(yīng)的生命周期方法,對應(yīng)了各個狀態(tài)

  • onSurfaceCreated
    GLSurfaceView創(chuàng)建的時機(jī)听想。按照慣用思維腥刹,在這里進(jìn)行一些初始化的操作。
  • onSurfaceChanged
    GLSurfaceView改變的時機(jī)汉买。如代碼所示衔峰,初始化GL的ViewPort
  • onDrawFrame
    這個生命周期方法會不斷的回調(diào)。不斷的繪制。

開始繪制三角形


著色器代碼的套路

我們需要熟悉編寫著色器代碼的套路垫卤。
之所以說是套路威彰,因?yàn)檫@些步驟都是類似的。

1. 編寫著色器的glsl

我們先簡單的寫一下頂點(diǎn)著色器和片元著色器的代碼穴肘。這些代碼具體是為什么這樣寫歇盼。我們在這里先不關(guān)注。我們先熟悉一下流程评抚。
assets中創(chuàng)建對應(yīng)的文件豹缀。代碼內(nèi)容如下

  • 頂點(diǎn)著色器

    //定義一個attribute aPosition ,類型為vec4慨代。4個方向的向量
    attribute vec4 aPosition;
    
    void main() {
    //這里的gl_Position是OpenGL內(nèi)置的變量邢笙。
    gl_Position = aPosition;
    }
    

    簡單的描述一下,就是我們自己定義一個屬性aPosition來描述位置侍匙。(ps:像是廢話)

  • 片元著色器

    //設(shè)置片元著色器的精度氮惯。這里值要兼容性能和效率。通常都是選擇mediump
    precision mediump float;
    
    //定義個常量 uColor
    uniform vec4 uColor;
    void main(){
    //同樣想暗。這里的gl_FragColor是內(nèi)置的變量
    gl_FragColor = uColor;
    }
    

    廢話同上妇汗。

2. 在onSurfaceCreated方法內(nèi)初始化

注意OpenGL的操作,都必須在GLThread中進(jìn)行江滨。生命周期方法的回調(diào)也都在這個線程中

  • 編譯著色器代碼铛纬,得到代表著色的Id(類似于指針的感覺)

     /**
     * 對ShaderCode進(jìn)行編譯
     *
     * @param type       shader的type
     * @param shaderCode 進(jìn)行編譯的Shader代碼
     * @return shaderObjectId
     */
    public static int compileShaderCode(int type, String shaderCode) {
        //得到一個著色器的ID。主要是對ID進(jìn)行操作
        int shaderObjectId = GLES20.glCreateShader(type);
    
        //如果著色器的id不為0唬滑,則表示是可以用的
        if (shaderObjectId != 0) {
            //0.上傳代碼
            GLES20.glShaderSource(shaderObjectId, shaderCode);
            //1.編譯代碼.根據(jù)剛剛和代碼綁定的ShaderObjectId進(jìn)行編譯
            GLES20.glCompileShader(shaderObjectId);
    
            //2.查詢編譯的狀態(tài)
            int[] status = new int[1];
            //調(diào)用getShaderIv 告唆,傳入GL_COMPILE_STATUS進(jìn)行查詢
            GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, status, 0);
    
            if (status[0] == 0) { //等于0。則表示失敗
                //失敗的話晶密,需要釋放資源擒悬,就是刪除這個引用
                GLES20.glDeleteShader(shaderObjectId);
                Log.w("OpenGL Utils", "compile failed!");
                return 0;
            }
        }
        //最后都會去返回這個shader的引用id
        return shaderObjectId;
    }
    

    這段基本的流程就是

    根據(jù)著色器的類型,創(chuàng)建一個shaderObjectId=>
    使用GLES20將我們的代碼和ID進(jìn)行綁定=>
    編譯我們綁定的代碼=>
    查詢編譯的狀態(tài)稻艰。如果失敗的話懂牧,就需要釋放資源。delete=>
    成功返回我們綁定好編譯后代碼的shaderObjectId
    
  • 創(chuàng)建program,將得到的id綁定上尊勿,并鏈接program

      @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        super.onSurfaceCreated(gl, config);
        //0.先從Asset中得到著色器的代碼
        String vertexShaderCode = GLESUtils.readAssetShaderCode(context, VERTEX_SHADER_FILE);
        String fragmentShaderCode = GLESUtils.readAssetShaderCode(context, FRAGMENT_SHADER_FILE);
        //1.得到之后僧凤,進(jìn)行編譯。得到id
        int vertexShaderObjectId = GLESUtils.compileShaderCode(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShaderObjectId = GLESUtils.compileShaderCode(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
    
        //3.繼續(xù)套路元扔。取得到program
        mProgramObjectId = GLES20.glCreateProgram();
        //將shaderId綁定到program當(dāng)中
        GLES20.glAttachShader(mProgramObjectId, vertexShaderObjectId);
        GLES20.glAttachShader(mProgramObjectId, fragmentShaderObjectId);
        //4.最后躯保,啟動GL link program
        GLES20.glLinkProgram(mProgramObjectId);
    }
    

    整體的流程就如上面代碼注釋的一樣。

    創(chuàng)建program=>
    將shaderId綁定到program當(dāng)中=>
    最后澎语,啟動GL link program
    

這樣著色器的套路就基本確定下來了途事。

三角形的形狀

上面編寫的頂點(diǎn)著色器中验懊,我們定義了aPosition的屬性。就相當(dāng)于我們將在OpenGL中定義了一個存儲的點(diǎn)尸变。接下來,我們就會將這個點(diǎn)來存儲我們定義的形狀信息义图。來顯示出形狀。

  • 三角形的坐標(biāo)系

OpenGL中的坐標(biāo)系是從[-1召烂,1]碱工。
我們先用一組數(shù)組的坐標(biāo)系,來描述我們的三角形

  //頂點(diǎn)的坐標(biāo)系
  private static float TRIANGLE_COORDS[] = {
          //Order of coordinates: X, Y, Z
          0.5f, 0.5f, 0.0f, // top
          -0.5f, -0.5f, 0.0f, // bottom left
          0.5f, -0.5f, 0.0f   // bottom right
  };
三角形的歸一化坐標(biāo).png

我們還會定義一些常量骑晶,來幫助我們操作痛垛。常量就如注釋說明

 //在數(shù)組中曼氛,一個頂點(diǎn)需要3個來描述其位置惑折,需要3個偏移量
  private static final int COORDS_PER_VERTEX = 3;
  private static final int COORDS_PER_COLOR = 0;

  //在數(shù)組中宛琅,描述一個頂點(diǎn)彤钟,總共的頂點(diǎn)需要的偏移量煮仇。這里因?yàn)橹挥形恢庙旤c(diǎn)水醋,所以和上面的值一樣
  private static final int TOTAL_COMPONENT_COUNT = COORDS_PER_VERTEX+COORDS_PER_COLOR;
  //一個點(diǎn)需要的byte偏移量渐白。
  private static final int STRIDE = TOTAL_COMPONENT_COUNT * Constant.BYTES_PER_FLOAT;
  • 給三角形分配內(nèi)存空間
    需要注意的是借杰,
    調(diào)用GLES20的包的方法時碟婆,其實(shí)就是調(diào)用JNI的方法电抚。
    所以分配本地的內(nèi)存塊,將java數(shù)據(jù)復(fù)制到本地內(nèi)存中竖共,而本地內(nèi)存可以不受垃圾回收的控制蝙叛。可以使用nio中的ByteBuffer來創(chuàng)建內(nèi)存區(qū)域公给。

     /*
          1. 使用nio中的ByteBuffer來創(chuàng)建內(nèi)存區(qū)域借帘。
          2. ByteOrder.nativeOrder()來保證,同一個平臺使用相同的順序
          3. 然后可以通過put方法淌铐,將內(nèi)存復(fù)制過去肺然。
    
          因?yàn)檫@里是Float,所以就使用floatBuffer
           */
          mVertexFloatBuffer = ByteBuffer
                  .allocateDirect(TRIANGLE_COORDS.length * Constant.BYTES_PER_FLOAT)
                  .order(ByteOrder.nativeOrder())
                  .asFloatBuffer()
                  .put(TRIANGLE_COORDS);
        //因?yàn)槭菑牡谝粋€點(diǎn)開始腿准,就表示三角形的际起,所以將position移動到0
          mVertexFloatBuffer.position(0);
    
  • 整體上

    1. 通過數(shù)組來描述三角形的坐標(biāo)系。因?yàn)槲覀儧]有考慮空間轉(zhuǎn)換吐葱,所以就不需要進(jìn)行矩陣變化街望,暫時就直接使用三角形在OpenGl中的坐標(biāo)系就可以。
    2. 給定義的數(shù)組弟跑,分配對應(yīng)的本地內(nèi)存的空間它匕。ByteBuffer .allocateDirect方法
onDrawFrame中調(diào)用繪制
@Override
    public void onDrawFrame(GL10 gl) {
        super.onDrawFrame(gl);

        //0.先使用這個program?這一步應(yīng)該可以放到onCreate中進(jìn)行
        GLES20.glUseProgram(mProgramObjectId);

        //1.根據(jù)我們定義的取出定義的位置
        int vPosition = GLES20.glGetAttribLocation(mProgramObjectId, A_POSITION);
        //2.開始啟用我們的position
        GLES20.glEnableVertexAttribArray(vPosition);
        //3.將坐標(biāo)數(shù)據(jù)放入
        GLES20.glVertexAttribPointer(
                vPosition,  //上面得到的id
                COORDS_PER_VERTEX, //告訴他用幾個偏移量來描述一個頂點(diǎn)
                GLES20.GL_FLOAT, false,
                STRIDE, //一個頂點(diǎn)需要多少個字節(jié)的偏移量
                mVertexFloatBuffer);

        //取出顏色
        int uColor = GLES20.glGetUniformLocation(mProgramObjectId, U_COLOR);

        //開始繪制
        //設(shè)置繪制三角形的顏色
        GLES20.glUniform4fv(
                uColor,
                1,
                TRIANGLE_COLOR,
                0
        );

        //繪制三角形.
        //draw arrays的幾種方式 GL_TRIANGLES三角形 
        //GL_TRIANGLE_STRIP三角形帶的方式(開始的3個點(diǎn)描述一個三角形,后面每多一個點(diǎn)窖认,多一個三角形) 
        //GL_TRIANGLE_FAN扇形(可以描述圓形)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_COUNT);
        //禁止頂點(diǎn)數(shù)組的句柄
        GLES20.glDisableVertexAttribArray(vPosition);

    }
  • 整體上
    1. use我們之前得到的programObjectId
    2. 啟用我們定義的屬性和變量豫柬,并設(shè)置數(shù)據(jù)
    3. 用繪制的命令開始對應(yīng)的繪制

最后的效果

image.png

總結(jié)一下,我們從這第一章節(jié)的內(nèi)容了解到了下面這些使用的知識點(diǎn):

  1. 運(yùn)行在GLThread中
  2. 著色器編譯和使用的套路
  3. 使用數(shù)組的方式來描述圖形扑浸,使用本地內(nèi)存分配的方式來分配對應(yīng)的內(nèi)存烧给。
  4. 繪制圖形的過程中,啟用我們設(shè)置的屬性和變量喝噪,并且繪制的套路

未知道的:
坐標(biāo)矩陣的變化础嫡。和紋理等。

整體的代碼位置:https://github.com/deepsadness/OpenGLDemo5

系列文章地址
Android OpenGL ES(一)-開始描繪一個平面三角形
Android OpenGL ES(二)-正交投影
Android OpenGL ES(三)-平面圖形
Android OpenGL ES(四)-為平面圖添加濾鏡
Android OpenGL ES(五)-結(jié)合相機(jī)進(jìn)行預(yù)覽/錄制及添加濾鏡
Android OpenGL ES(六) - 將輸入源換成視頻
Android OpenGL ES(七) - 生成抖音照片電影

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酝惧,一起剝皮案震驚了整個濱河市榴鼎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晚唇,老刑警劉巖巫财,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異哩陕,居然都是意外死亡平项,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門悍及,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闽瓢,“玉大人,你說我怎么就攤上這事心赶】鬯希” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵缨叫,是天一觀的道長椭符。 經(jīng)常有香客問我,道長弯汰,這世上最難降的妖魔是什么艰山? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮咏闪,結(jié)果婚禮上曙搬,老公的妹妹穿的比我還像新娘。我一直安慰自己鸽嫂,他們只是感情好纵装,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著据某,像睡著了一般橡娄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上癣籽,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天挽唉,我揣著相機(jī)與錄音滤祖,去河邊找鬼。 笑死瓶籽,一個胖子當(dāng)著我的面吹牛匠童,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播塑顺,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼汤求,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了严拒?” 一聲冷哼從身側(cè)響起扬绪,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裤唠,沒想到半個月后挤牛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巧骚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年赊颠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劈彪。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡竣蹦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沧奴,到底是詐尸還是另有隱情痘括,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布滔吠,位于F島的核電站纲菌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疮绷。R本人自食惡果不足惜翰舌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冬骚。 院中可真熱鬧椅贱,春花似錦、人聲如沸只冻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喜德。三九已至山橄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舍悯,已是汗流浹背航棱。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工睡雇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饮醇。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓入桂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親驳阎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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