Android OpenGL ES 2.基本框架-Hello World

一.視圖組件 GLSurfaceView

Android上用于顯示OpenGL視圖,一般是使用GLSurfaceView嚎幸,一個繼承自SurfaceView的組件。
它的渲染繪制在一個單獨的線程中罗捎,而非主線程钥平。
GLSurfaceView一般是結(jié)合一個GLSurfaceView的內(nèi)部接口類Renderer來使用。Renderer類負(fù)責(zé)渲染圖形圖像疫剃,而GLSurfaceView負(fù)責(zé)觸摸事件等邏輯的處理钉疫。

Renderer接口

  • onSurfaceCreated(GL10 gl, EGLConfig config):GLSurfaceView內(nèi)的Surface被創(chuàng)建時會被調(diào)用到
  • onSurfaceChanged(GL10 gl, int width, int height):Surface尺寸改變時調(diào)用到
  • onDrawFrame(GL10 gl):渲染繪制每一幀時調(diào)用到

所以一般情況下,首次創(chuàng)建GLSurfaceView時巢价,會按順序調(diào)用onSurfaceCreated牲阁、onSurfaceChanged、onDrawFrame這3個方法壤躲,然后每繪制一幀城菊,都會不停地回調(diào)onDrawFrame方法。

GLSurfaceView常用方法

  • setEGLContextClientVersion:設(shè)置OpenGL ES版本柒爵,2.0則設(shè)置2
  • onPause:暫停渲染役电,最好是在Activity、Fragment的onPause方法內(nèi)調(diào)用棉胀,減少不必要的性能開銷法瑟,避免不必要的崩潰
  • onResume:恢復(fù)渲染,用法類比onPause
  • setRenderer:設(shè)置渲染器
  • setRenderMode:設(shè)置渲染模式
  • requestRender: 請求渲染唁奢,由于是請求異步線程進行渲染霎挟,所以不是同步方法,調(diào)用后不會立刻就進行渲染麻掸。渲染會回調(diào)到Renderer接口的onDrawFrame方法酥夭。
  • queueEvent:插入一個Runnable任務(wù)到后臺渲染線程上執(zhí)行。相應(yīng)的,渲染線程中可以通過Activity的runOnUIThread的方法來傳遞事件給主線程去執(zhí)行

GLSurfaceView渲染模式

  • RENDERMODE_CONTINUOUSLY:不停地渲染
  • RENDERMODE_WHEN_DIRTY:只有調(diào)用了requestRender之后才會觸發(fā)渲染回調(diào)onDrawFrame方法

二.編程流程

  • 編寫GLSL:重點學(xué)習(xí)
  • 編譯GLSL熬北,獲取OpenGL程序?qū)ο螅夯竟潭ǜ砻瑁恍枰烙洠斫饧纯裳纫:笃跁M行封裝起胰,便于使用。
  • 獲取GLSL中變量的引用:理解調(diào)用方式
  • 通過內(nèi)存Buffer,將數(shù)據(jù)傳遞給變量引用巫延,從而控制繪制圖形效五、顏色:重點學(xué)習(xí)

1. 簡單的GLSL

/**
 * 頂點著色器
 */
private static final String VERTEX_SHADER = "" +
        // vec4:4個分量的向量:x、y炉峰、z畏妖、w
        "attribute vec4 a_Position;\n" +
        "void main()\n" +
        "{\n" +
        // gl_Position:GL中默認(rèn)定義的輸出變量,決定了當(dāng)前頂點的最終位置
        "    gl_Position = a_Position;\n" +
        // gl_PointSize:GL中默認(rèn)定義的輸出變量疼阔,決定了當(dāng)前頂點的大小
        "    gl_PointSize = 40.0;\n" +
        "}";

/**
 * 片段著色器
 */
private static final String FRAGMENT_SHADER = "" +
        // 定義所有浮點數(shù)據(jù)類型的默認(rèn)精度戒劫;有l(wèi)owp、mediump竿开、highp 三種谱仪,但只有部分硬件支持片段著色器使用highp。(頂點著色器默認(rèn)highp)
        "precision mediump float;\n" +
        "uniform mediump vec4 u_Color;\n" +
        "void main()\n" +
        "{\n" +
        // gl_FragColor:GL中默認(rèn)定義的輸出變量否彩,決定了當(dāng)前片段的最終顏色
        "    gl_FragColor = u_Color;\n" +
        "}";

注意

在聲明vec向量的時候,一定要標(biāo)識其精度類型嗦随,否則會導(dǎo)致部分機型花屏列荔,如紅米note2

2.1 編譯著色器

使用compileVertexShader、compileFragmentShader兩個方法分別調(diào)用上面定義的頂點著色器枚尼、片段著色器贴浙。

/**
 * 編譯頂點著色器
 *
 * @param shaderCode 編譯代碼
 * @return 著色器對象ID
 */
public static int compileVertexShader(String shaderCode) {
    return compileShader(GLES20.GL_VERTEX_SHADER, shaderCode);
}

/**
 * 編譯片段著色器
 *
 * @param shaderCode 編譯代碼
 * @return 著色器對象ID
 */
public static int compileFragmentShader(String shaderCode) {
    return compileShader(GLES20.GL_FRAGMENT_SHADER, shaderCode);
}

/**
 * 編譯片段著色器
 *
 * @param type       著色器類型
 * @param shaderCode 編譯代碼
 * @return 著色器對象ID
 */
private static int compileShader(int type, String shaderCode) {
    // 1.創(chuàng)建一個新的著色器對象
    final int shaderObjectId = GLES20.glCreateShader(type);

    // 2.獲取創(chuàng)建狀態(tài)
    if (shaderObjectId == 0) {
        // 在OpenGL中,都是通過整型值去作為OpenGL對象的引用署恍。之后進行操作的時候都是將這個整型值傳回給OpenGL進行操作崎溃。
        // 返回值0代表著創(chuàng)建對象失敗。
        if (LoggerConfig.ON) {
            Log.w(TAG, "Could not create new shader.");
        }
        return 0;
    }

    // 3.將著色器代碼上傳到著色器對象中
    GLES20.glShaderSource(shaderObjectId, shaderCode);

    // 4.編譯著色器對象
    GLES20.glCompileShader(shaderObjectId);

    // 5.獲取編譯狀態(tài):OpenGL將想要獲取的值放入長度為1的數(shù)組的首位
    final int[] compileStatus = new int[1];
    GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

    if (LoggerConfig.ON) {
        // 打印編譯的著色器信息
        Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode + "\n:"
                + GLES20.glGetShaderInfoLog(shaderObjectId));
    }

    // 6.驗證編譯狀態(tài)
    if (compileStatus[0] == 0) {
        // 如果編譯失敗盯质,則刪除創(chuàng)建的著色器對象
        GLES20.glDeleteShader(shaderObjectId);

        if (LoggerConfig.ON) {
            Log.w(TAG, "Compilation of shader failed.");
        }

        // 7.返回著色器對象:失敗袁串,為0
        return 0;
    }

    // 7.返回著色器對象:成功,非0
    return shaderObjectId;
}

2.2 創(chuàng)建OpenGL程序?qū)ο蠛粝铮溄禹旤c著色器囱修、片段著色器

/**
 * 創(chuàng)建OpenGL程序?qū)ο? *
 * @param vertexShader   頂點著色器代碼
 * @param fragmentShader 片段著色器代碼
 */
protected void makeProgram(String vertexShader, String fragmentShader) {
    // 步驟1:編譯頂點著色器
    int vertexShaderId = ShaderHelper.compileVertexShader(vertexShader);
    // 步驟2:編譯片段著色器
    int fragmentShaderId = ShaderHelper.compileFragmentShader(fragmentShader);
    // 步驟3:將頂點著色器、片段著色器進行鏈接王悍,組裝成一個OpenGL程序
    mProgram = ShaderHelper.linkProgram(vertexShaderId, fragmentShaderId);

    if (LoggerConfig.ON) {
        ShaderHelper.validateProgram(mProgram);
    }

    // 步驟4:通知OpenGL開始使用該程序
    GLES20.glUseProgram(mProgram);
}

/**
 * 創(chuàng)建OpenGL程序:通過鏈接頂點著色器破镰、片段著色器
 *
 * @param vertexShaderId   頂點著色器ID
 * @param fragmentShaderId 片段著色器ID
 * @return OpenGL程序ID
 */
public static int linkProgram(int vertexShaderId, int fragmentShaderId) {

    // 1.創(chuàng)建一個OpenGL程序?qū)ο?    final int programObjectId = GLES20.glCreateProgram();

    // 2.獲取創(chuàng)建狀態(tài)
    if (programObjectId == 0) {
        if (LoggerConfig.ON) {
            Log.w(TAG, "Could not create new program");
        }
        return 0;
    }

    // 3.將頂點著色器依附到OpenGL程序?qū)ο?    GLES20.glAttachShader(programObjectId, vertexShaderId);
    // 3.將片段著色器依附到OpenGL程序?qū)ο?    GLES20.glAttachShader(programObjectId, fragmentShaderId);

    // 4.將兩個著色器鏈接到OpenGL程序?qū)ο?    GLES20.glLinkProgram(programObjectId);

    // 5.獲取鏈接狀態(tài):OpenGL將想要獲取的值放入長度為1的數(shù)組的首位
    final int[] linkStatus = new int[1];
    GLES20.glGetProgramiv(programObjectId, GLES20.GL_LINK_STATUS, linkStatus, 0);

    if (LoggerConfig.ON) {
        // 打印鏈接信息
        Log.v(TAG, "Results of linking program:\n"
                + GLES20.glGetProgramInfoLog(programObjectId));
    }

    // 6.驗證鏈接狀態(tài)
    if (linkStatus[0] == 0) {
        // 鏈接失敗則刪除程序?qū)ο?        GLES20.glDeleteProgram(programObjectId);
        if (LoggerConfig.ON) {
            Log.w(TAG, "Linking of program failed.");
        }
        // 7.返回程序?qū)ο螅菏。瑸?
        return 0;
    }

    // 7.返回程序?qū)ο螅撼晒Γ?
    return programObjectId;
}

/**
 * 驗證OpenGL程序?qū)ο鬆顟B(tài)
 *
 * @param programObjectId OpenGL程序ID
 * @return 是否可用
 */
public static boolean validateProgram(int programObjectId) {
    GLES20.glValidateProgram(programObjectId);

    final int[] validateStatus = new int[1];
    GLES20.glGetProgramiv(programObjectId, GLES20.GL_VALIDATE_STATUS, validateStatus, 0);
    Log.v(TAG, "Results of validating program: " + validateStatus[0]
            + "\nLog:" + GLES20.glGetProgramInfoLog(programObjectId));

    return validateStatus[0] != 0;
}

3. 獲取GLSL中的索引

根據(jù)索引的類型鲜漩,調(diào)用不同的方法去獲取索引源譬,索引的值類型都是int

// 獲取頂點坐標(biāo)屬性在OpenGL程序中的索引
aPositionLocation = GLES20.glGetAttribLocation(mProgram, A_POSITION);

// 獲取顏色Uniform在OpenGL程序中的索引
uColorLocation = GLES20.glGetUniformLocation(mProgram, U_COLOR);

4.1 將數(shù)據(jù)傳遞到Native層內(nèi)存緩沖中

/**
 * Float類型占4Byte
 */
private static final int BYTES_PER_FLOAT = 4;

/**
 * 創(chuàng)建一個FloatBuffer
 */
public static FloatBuffer createFloatBuffer(float[] array) {
    FloatBuffer buffer = ByteBuffer
            // 分配頂點坐標(biāo)分量個數(shù) * Float占的Byte位數(shù)
            .allocateDirect(array.length * BYTES_PER_FLOAT)
            // 按照本地字節(jié)序排序
            .order(ByteOrder.nativeOrder())
            // Byte類型轉(zhuǎn)Float類型
            .asFloatBuffer();

    // 將Java Dalvik的內(nèi)存數(shù)據(jù)復(fù)制到Native內(nèi)存中
    buffer.put(array);
    return buffer;
}

4.2 將內(nèi)存堆中的值傳遞給GLSL引用

接下來,我們把頂點信息傳遞給GLSL中的頂點位置引用

// 將緩沖區(qū)的指針移動到頭部孕似,保證數(shù)據(jù)是從最開始處讀取
mVertexData.position(0);
// 關(guān)聯(lián)頂點坐標(biāo)屬性和緩存數(shù)據(jù)
// 1. 位置索引踩娘;
// 2. 每個頂點屬性需要關(guān)聯(lián)的分量個數(shù)(必須為1、2鳞青、3或者4霸饲。初始值為4。)臂拓;
// 3. 數(shù)據(jù)類型厚脉;
// 4. 指定當(dāng)被訪問時,固定點數(shù)據(jù)值是否應(yīng)該被歸一化(GL_TRUE)或者直接轉(zhuǎn)換為固定點值(GL_FALSE)(只有使用整數(shù)數(shù)據(jù)時)
// 5. 指定連續(xù)頂點屬性之間的偏移量胶惰。如果為0傻工,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0孵滞。
// 6. 數(shù)據(jù)緩沖區(qū)
GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT,
        false, 0, mVertexData);

// 通知GL程序使用指定的頂點屬性索引
GLES20.glEnableVertexAttribArray(aPositionLocation);

然后中捆,我們給圖形上色

// 更新u_Color的值,即更新畫筆顏色
GLES20.glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);

最后坊饶,再根據(jù)需求繪制不同的圖形泄伪。當(dāng)前案例中,我就只繪制一個點匿级。

// 使用數(shù)組繪制圖形:1.繪制的圖形類型蟋滴;2.從頂點數(shù)組讀取的起點;3.從頂點數(shù)組讀取的數(shù)據(jù)長度
GLES20.glDrawArrays(GLES20.GL_POINTS, 0, 1);

注意:這里一定要先上色痘绎,再繪制圖形津函,否則會導(dǎo)致顏色在當(dāng)前這一幀使用失敗,要下一幀才能生效孤页。

刷屏顏色

// 設(shè)置刷新屏幕時候使用的顏色值,順序是RGBA尔苦,值的范圍從0~1。這里不會立刻刷新行施,只有在GLES20.glClear調(diào)用時使用該顏色值才刷新允坚。
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
// 使用glClearColor設(shè)置的顏色,刷新Surface
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

注意

Buffer數(shù)據(jù)在傳遞給GLSL之前悲龟,一定要調(diào)用position方法將指針移到正確的位置屋讶,當(dāng)前是0,之后會有課程講解到非0的情況须教。

// 將緩沖區(qū)的指針移動到頭部皿渗,保證數(shù)據(jù)是從最開始處讀取
mVertexData.position(0);

將數(shù)組數(shù)據(jù)put進buffer之后斩芭,指針并不是在首位,所以一定要position到0乐疆,至關(guān)重要划乖!否則會有很多奇妙的錯誤!如:

java.lang.ArrayIndexOutOfBoundsException: remaining() < count < needed

效果

基礎(chǔ)框架效果圖

參考

Android OpenGL ES學(xué)習(xí)資料所列舉的博客挤土、資料琴庵。

GitHub代碼工程

本系列課程所有相關(guān)代碼請參考我的GitHub項目GLStudio

課程目錄

本系列課程目錄詳見 簡書 - Android OpenGL ES教程規(guī)劃

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仰美,一起剝皮案震驚了整個濱河市迷殿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌咖杂,老刑警劉巖庆寺,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诉字,居然都是意外死亡,警方通過查閱死者的電腦和手機壤圃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門陵霉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伍绳,你說我怎么就攤上這事踊挠。” “怎么了冲杀?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵止毕,是天一觀的道長。 經(jīng)常有香客問我漠趁,道長,這世上最難降的妖魔是什么忍疾? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任闯传,我火速辦了婚禮,結(jié)果婚禮上卤妒,老公的妹妹穿的比我還像新娘甥绿。我一直安慰自己,他們只是感情好则披,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布共缕。 她就那樣靜靜地躺著,像睡著了一般士复。 火紅的嫁衣襯著肌膚如雪图谷。 梳的紋絲不亂的頭發(fā)上翩活,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音便贵,去河邊找鬼菠镇。 笑死,一個胖子當(dāng)著我的面吹牛承璃,可吹牛的內(nèi)容都是我干的利耍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼盔粹,長吁一口氣:“原來是場噩夢啊……” “哼隘梨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起舷嗡,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤轴猎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咬崔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體税稼,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年垮斯,在試婚紗的時候發(fā)現(xiàn)自己被綠了郎仆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡兜蠕,死狀恐怖扰肌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情熊杨,我是刑警寧澤曙旭,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站晶府,受9級特大地震影響桂躏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜川陆,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一剂习、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧较沪,春花似錦鳞绕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至控轿,卻和暖如春冤竹,著一層夾襖步出監(jiān)牢的瞬間拂封,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工贴见, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烘苹,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓片部,卻偏偏與公主長得像镣衡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子档悠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348