OpenGL ES 繪制形狀(Shape)

本篇文章屬于 使用 OpenGL ES 進行圖形繪制 這個系列的第三篇文章,主要內(nèi)容是介紹在如何在 Android 應(yīng)用中利用 OpenGL 繪制圖形的形狀锥涕。文章中所有的代碼示例都已放在 Github 上剪返,可以去項目 OpenGL-ES-Learning 中查看 急凰。

在上篇文章:OpenGL ES 定義形狀 中我們定義了 OpenGL 繪制的形狀之后药蜻,下面就一起看看如何使用 OpenGL ES 2.0 接口繪制出在 OpenGL ES 定義形狀 文章中定義的形狀喳瓣。

使用 OpenGL ES 2.0 繪制圖形可能會膩比想象當中要復(fù)雜一些,因為 Android 中保留提供了大量對于圖形渲染流程控制的 API 绢片,就像我們在繪制自定義 View 時一樣嘁字,繪制控制的方法和參數(shù)都會很豐富。

其實在前面文章:配置 OpenGL ES 的環(huán)境 里面有提到 一個核心的類 GLSurfaceView.Renderer杉畜,它是控制 view 繪制過程的渲染器,之前文章中展示了如何使用 GLSurfaceView.Renderer 進行繪制黑色背景的簡單試驗衷恭,所以接下來的關(guān)于形狀的繪制必然少不了它的參與此叠。

初始化形狀

在開始繪制之前,需要對繪制的圖形進行初始化并加載。如果這些形狀結(jié)構(gòu)(原始坐標)在執(zhí)行過程不會發(fā)生變化灭袁,那么應(yīng)該在 Renderer 的 onSurfaceCreated() 方法中進行初始化和加載猬错,這樣可以更省內(nèi)存以及提升執(zhí)行效率。

public class MyGLRenderer2 implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;
    private Square mSquare;

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // initialize a triangle
        mTriangle = new Triangle();
        // initialize a square
        mSquare = new Square();
    }
    ...
}

繪制形狀

使用 OpenGL ES 2.0 繪制一個定義好的形狀需要較多代碼茸歧,因為你需要提供很多圖形渲染流程的細節(jié)倦炒,比如:

  • 頂點著色器(Vertex Shader):用來渲染形狀(shape)頂點的 OpenGL ES 代碼。

OpenGL ES 2.0 渲染管線中頂點著色器(Vertex Shader)取代了 OpenGL ES 1.x 渲染管線中的“變換和光照”

  • 片元著色器(Fragment Shader):使用顏色或紋理(texture)渲染形狀表面(face)的 OpenGL ES 代碼软瞎。

片元著色器取代了 OpenGL ES 1.x 渲染管線中的“紋理環(huán)境和顏色求和”逢唤、“霧”以及“Alpha測試”

  • 程式(Program):一個 OpenGL ES 對象,包含了你希望用來繪制一個或更多圖形(shape)所要用到的著色器(shader)涤浇。

以上三個鳖藕,你需要至少一個頂點著色器(Vertex Shader)來繪制一個形狀,以及一個片元著色器(Fragment Shader)為該形狀上色只锭。這些著色器必須被編譯然后再添加到一個OpenGL ES Program當中著恩,并利用這個 progrem 來繪制形狀。通過編寫頂點及片元著色器程序蜻展,來完成一些頂點變換和紋理顏色計算工作喉誊,實現(xiàn)更加靈活、精細化的計算與渲染纵顾。

下面的代碼在 Triangle 類中定義了基本的著色器伍茄,我們可以利用它們繪制出一個圖形:

public class Triangle {

   /**
     * 頂點著色器代碼
     * attribute變量(屬性變量)只能用于頂點著色器中,不能用于片元著色器片挂。一般用該變量來表示一些頂點數(shù)據(jù)幻林,如:頂點坐標、紋理坐標音念、顏色等
     * uniforms變量(一致變量)用來將數(shù)據(jù)值從應(yīng)用程其序傳遞到頂點著色器或者片元著色器沪饺。 該變量有點類似C語言中的常量(const),即該變量的值不能被shader程序修改闷愤。一般用該變量表示變換矩陣整葡、光照參數(shù)、紋理采樣器等讥脐。
     * varying變量(易變變量)是從頂點著色器傳遞到片元著色器的數(shù)據(jù)變量遭居。頂點著色器可以使用易變變量來傳遞需要插值的顏色、法向量旬渠、紋理坐標等任意值俱萍。 在頂點與片元shader程序間傳遞數(shù)據(jù)是很容易的,一般在頂點shader中修改varying變量值告丢,然后片元shader中使用該值枪蘑,當然,該變量在頂點及片元這兩段shader程序中聲明必須是一致的。
     * gl_Position 為內(nèi)建變量岳颇,表示變換后點的空間位置照捡。 頂點著色器從應(yīng)用程序中獲得原始的頂點位置數(shù)據(jù),這些原始的頂點數(shù)據(jù)在頂點著色器中經(jīng)過平移话侧、旋轉(zhuǎn)栗精、縮放等數(shù)學(xué)變換后,生成新的頂點位置瞻鹏。新的頂點位置通過在頂點著色器中寫入gl_Position傳遞到渲染管線的后繼階段繼續(xù)處理悲立。
     */
    private final String vertexShaderCode =
            "attribute vec4 vPosition;" +  // 應(yīng)用程序傳入頂點著色器的頂點位置
                    "void main() {" +
                    "  gl_Position = vPosition;" + // 設(shè)置此次繪制此頂點位置
                    "}";

    /**
     * 片元著色器代碼
     */
    private final String fragmentShaderCode =
            "precision mediump float;" +  // 設(shè)置工作精度
                    "uniform vec4 vColor;" +  // 應(yīng)用程序傳入著色器的顏色變量
                    "void main() {" +
                    "  gl_FragColor = vColor;" + // 顏色值傳給 gl_FragColor內(nèi)建變量,完成片元的著色
                    "}";
   ...
}

關(guān)于著色器和 GLSL 語言推薦幾篇文章
OpenGL ES 入門(一)著色器簡介
OpenGL Shading language學(xué)習(xí)總結(jié)

著色器(Shader)包含了OpenGL Shading Language(GLSL)代碼乙漓,它必須先被編譯然后才能在 OpenGL 環(huán)境中使用级历。要編譯 GLSL 代碼需要在渲染器類中創(chuàng)建一個輔助方法:

public class MyGLRenderer2 implements GLSurfaceView.Renderer 
    ...

    /**
     * 加載并編譯著色器代碼
     * @param type 渲染器類型 {GLES20.GL_VERTEX_SHADER, GLES20.GL_FRAGMENT_SHADER}
     * @param shaderCode 渲染器代碼 GLSL
     * @return
     */
    public static int loadShader(int type, String shaderCode){

        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
}

要繪制圖形前,必須先編譯著色器代碼并將它們添加至一個 OpenGL ES Program 對象中叭披,然后執(zhí)行鏈接方法寥殖。

Note:編譯 OpenGL ES 著色器及鏈接操作對于 CPU 周期和處理時間而言消耗巨大,所以應(yīng)該避免重復(fù)執(zhí)行這些事情涩蜘。這個操作建議在形狀類的構(gòu)造方法中調(diào)用嚼贡,這樣只會執(zhí)行一次。如果在執(zhí)行期間不知道著色器的內(nèi)容同诫,可以考慮使用一次后緩存以備后續(xù)使用粤策。

public class Triangle() {
    ...

    private final int mProgram;

    public Triangle() {
        ...
        
        // 加載編譯頂點渲染器
        int vertexShader = MyGLRenderer2.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);

        // 加載編譯片元渲染器
        int fragmentShader = MyGLRenderer2.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }

至此,你已經(jīng)完全準備好添加實際的調(diào)用語句來繪制你的圖形了误窖。使用 OpenGL ES 需要一些參數(shù)來告訴渲染流程(redering pipeline )你要繪制的內(nèi)容以及如何繪制叮盘,由于每個 shape 的 drawing option 都不一樣,因此將每個 shape 的繪制邏輯放到自己的類里面是一個比較好的方法霹俺。

創(chuàng)建一個 draw() 方法來繪制圖形柔吼。下面的代碼為形狀的頂點著色器和形狀著色器設(shè)置了位置和顏色值,然后執(zhí)行繪制函數(shù):

public class Triangle {

    // 繪制形狀的頂點數(shù)量
    private static final int COORDS_PER_VERTEX = 3;

    ...

    private int mPositionHandle;
    private int mColorHandle;

    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

        public void draw() {
        // Add program to OpenGL ES environment
        GLES20.glUseProgram(mProgram);

        // get handle to vertex shader's vPosition member
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // Set color for drawing the triangle
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        // Draw the triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}

一旦完成了上述所有代碼丙唧,僅需要在渲染器的 onDrawFrame() 方法中調(diào)用 draw() 方法就可以畫出我們想要畫的對象了:

public class MyGLRenderer2 implements GLSurfaceView.Renderer {

    @Override
    public void onDrawFrame(GL10 gl) {
        ...
        mTriangle.draw();
    }
}

運行這個應(yīng)用時愈魏,它看上去會像是這樣:


實際運行效果圖

實際操作過程中你發(fā)現(xiàn),這個三角形看上去有一些扁想际,另外當你改變屏幕方向時培漏,它的形狀也會隨之改變。發(fā)生形變的原因是因為對象的頂點沒有根據(jù)顯示 GLSurfaceView 的屏幕區(qū)域的長寬比進行修正胡本。你可以使用投影(Projection)或者相機視角(Camera View)來解決這個問題牌柄。

文章中所有的代碼示例都已放在 Github 上,可以去項目 OpenGL-ES-Learning 中查看 侧甫。

最后友鼻,這個三角形是靜止的傻昙,這看上去有些無聊。在后續(xù)文章會讓這個形狀發(fā)生旋轉(zhuǎn)彩扔,并使用一些 OpenGL ES 圖形處理流程中更加新奇的用法。

>>>>Next>>>> : OpenGL ES 運用投影與相機視角

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末僻爽,一起剝皮案震驚了整個濱河市虫碉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胸梆,老刑警劉巖敦捧,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碰镜,居然都是意外死亡兢卵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門绪颖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秽荤,“玉大人,你說我怎么就攤上這事柠横∏钥睿” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵牍氛,是天一觀的道長晨继。 經(jīng)常有香客問我,道長搬俊,這世上最難降的妖魔是什么紊扬? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮唉擂,結(jié)果婚禮上餐屎,老公的妹妹穿的比我還像新娘。我一直安慰自己楔敌,他們只是感情好啤挎,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著卵凑,像睡著了一般庆聘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勺卢,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天伙判,我揣著相機與錄音,去河邊找鬼黑忱。 笑死宴抚,一個胖子當著我的面吹牛勒魔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菇曲,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冠绢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了常潮?” 一聲冷哼從身側(cè)響起弟胀,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喊式,沒想到半個月后孵户,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡岔留,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年夏哭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片献联。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡竖配,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出酱固,到底是詐尸還是另有隱情械念,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布运悲,位于F島的核電站龄减,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏班眯。R本人自食惡果不足惜希停,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望署隘。 院中可真熱鬧宠能,春花似錦、人聲如沸磁餐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诊霹。三九已至羞延,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脾还,已是汗流浹背伴箩。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鄙漏,地道東北人嗤谚。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓棺蛛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巩步。 傳聞我的和親對象是個殘疾皇子旁赊,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

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