1. 程式人生 > >OpenGL.ES在Android上的簡單實踐:3-曲棍球(頂點歸一化、增加顏色)

OpenGL.ES在Android上的簡單實踐:3-曲棍球(頂點歸一化、增加顏色)

OpenGL.ES在Android上的簡單實踐:3-曲棍球(頂點歸一化 、增加顏色)

1、頂點歸一化

承接上 簡單實踐系列文章:2。   執行程式後,大家看見了什麼,是不是如下圖? what the fxxk?!

以上這個問題詳細原因很複雜,隨著文章深入,答案自然就會迎刃而解;目前,我們來認識一件事,無論是x座標還是y座標,OpenGL都會把螢幕對映到 [-1,1]的範圍內。這就意味著螢幕的左邊對應x軸的-1,而螢幕的右邊對應+1,;螢幕的底邊會對應y軸的-1,而螢幕的定邊就對應+1

不管螢幕是什麼形狀和大小,這個座標範圍都是一樣的,如果我們需要在螢幕上顯示任何東西,都需要在這個範圍內繪製它們。讓我們迴歸到tableVerticesWithTriangles,重新定義這些座標點。

float[] tableVerticesWithTriangles = {
            // 第一個三角形
            -0.5f, -0.5f,
            0.5f, 0.5f,
            -0.5f, 0.5f,
            // 第二個三角形
            -0.5f, -0.5f,
            0.5f, -0.5f,
            0.5f, 0.5f,
            // 中間的分界線
            -0.5f, 0f,
            0.5f, 0f,
            // 兩個木槌的質點位置
            0f, -0.25f,
            0f, 0.25f
    };

這次又看到什麼呢,是不是如下圖所示?

這看起來比之前的好多了,但是木槌位置的質點呢?在OpenGL看來,對於點來說,是需要指定在螢幕上所顯示的點的大小的。我們修改頂點著色器simple_vertex_shader.glsl 檔案,如下:

attribute vec4 a_Position;

void main()
{
    gl_Position = a_Position;
    gl_PointSize = 10.0; //指定點的大小為10.0
}

通過給另外一個特殊的輸出變數gl_PointSize賦值,我們告訴OpenGL這些點的大小應該是10。你可能會懊惱,怎麼又蹦出了一個特殊變數啊,究竟有多少個這些特殊變數啊。 其實我之前剛開始學的時候也一臉懵逼,隨後慢慢的就開始接受了,並整理了一堆shader的內建變數/常量/函式,詳細請參考

這裡面的 “一些基本的glsl概念”。

這次執行程式,效果應該不差了吧。看看是不是下面這個狀態:

現在看起來有模有樣了,但還是單調了點了,離我們想要的效果還差多著呢~是時候為我們的曲棍球增添色彩了。


2、頂點間完成平滑著色

之前我們瞭解到uniform裡用單一的顏色繪製頂點片段,其實OpenGL還允許我們平滑地混合一條直線或一個三角形的表面上,每個頂點的顏色值。現在計劃使用這種平滑著色,使得桌子中心表現得更加明亮,而其他邊緣顯得比較暗淡,這就好像一盞燈掛在桌子中間的上方一樣。然而,在做這些之前,我們需要更新桌子的頂點結構。

現在我們是用兩個三角形繪製桌子的,我們怎樣才能讓中間顯得更明亮呢?我們需要在中間位置加入這個點,這樣,就可以在桌子的中間和邊緣之間混合顏色。所以,我們以下圖的形式更新桌子頂點結構吧。

讓我們按照上圖更新tableVerticesWithTriangles的頂點資料,並修改onDrawFrame的程式碼

float[] tableVerticesWithTriangles = {
            //// 第一個三角形
            //-0.5f, -0.5f,
            //0.5f, 0.5f,
            //-0.5f, 0.5f,
            //// 第二個三角形
            //-0.5f, -0.5f,
            //0.5f, -0.5f,
            //0.5f, 0.5f,
            // 三角扇
             0,     0,
            -0.5f, -0.5f,
             0.5f, -0.5f,
             0.5f,  0.5f,
            -0.5f,  0.5f,
            -0.5f, -0.5f,
            // 中間的分界線
            -0.5f, 0f,
            0.5f, 0f,
            // 兩個木槌的質點位置
            0f, -0.25f,
            0f, 0.25f
    };
    @Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        //GLES20.glUniform4f(uColorLocation, 1.0f,1.0f,1.0f, 1.0f);
        //GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
        GLES20.glUniform4f(uColorLocation, 1.0f,1.0f,1.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 6);
        
        GLES20.glUniform4f(uColorLocation, 1.0f,0.0f,0.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_LINES, 6, 2);

        GLES20.glUniform4f(uColorLocation, 0.0f,0.0f,1.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_POINTS, 8, 1);
        GLES20.glUniform4f(uColorLocation, 0.0f,1.0f,0.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_POINTS, 9, 1);
    }

這裡引入三角扇GL_TRIANGLE_FAN標誌,從示意圖我想大家都能弄懂什麼是三角扇了。 一個三角扇形以一箇中心頂點作為開始,使用相鄰的兩個頂點建立第一個三角形,接下來的每個頂點都會建立一個三角形,圍繞起始的中心點按扇形展開。為了使這個扇形閉合,我們只需要在最後重複第二個點即可。
之後我們glDrawArrays選擇繪製GL_TRIANGLE_FAN,從位標0開始,共6個點。   這時候看看執行狀態吧。

通過在桌子中心增加一個額外的點,我們已經更新了桌子的資料結構,現在我們可以給每個點加入一個顏色屬性。讓我們把整個資料的陣列更新如下:

float[] tableVerticesWithTriangles = {
            //  X, Y,        R, G, B
            // 三角扇形
        0,     0,     1f,  1f,  1f,
       -0.5f,    -0.5f,  0.7f,0.7f,0.7f,
        0.5f,    -0.5f,  0.7f,0.7f,0.7f,
        0.5f,    0.5f,  0.7f,0.7f,0.7f,
       -0.5f,    0.5f,  0.7f,0.7f,0.7f,
       -0.5f,    -0.5f,  0.7f,0.7f,0.7f,
        // 中間的分界線
       -0.5f,    0f,     1f,  0f,  0f,
        0.5f,    0f,     1f,  0f,  0f,
        // 兩個木槌的質點位置
        0f,    -0.25f,    0f,  0f,  1f,
        0f,     0.25f,    1f,  0f,  0f,
    };

下一步就是從著色器中去掉uniform定義的顏色,並用一個屬性替換它。接下來會更新Java程式碼以體現這段新的著色器程式碼。

attribute vec4 a_Position;
attribute vec4 a_Color;

varying vec4 v_Color;

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

我們加入了一個新的屬性a_Color,也加入了一個叫做v_Color的新的varying。 可能會問“varying究竟是什麼呀?”還記得我們說過我們需要在一個三角形平面上讓顏色產生變化(vary)麼? 就是通過這個被稱為varying的特殊的變數型別實現的。為了更好地理解一個varying是做什麼的,讓我回顧一下圖元光柵化的過程。
當OpenGL構建一條直線的時候,它會用兩個頂點構成這條直線,併為這條直線生成片段;當OpenGL構建一個三角形的時候,它會同樣使用三個頂點構建這個三角形。然後,對於每一個被生成的片段,片段著色器都會被執行一次。

varying是一個特殊的變數型別,它把給它的那些值進行混合,並把這些混合後的值傳送給片段著色器。如果頂點0的a_Color是紅色,且頂點1的a_Color是綠色,然後,通過把a_Color賦值給v_Color,來告訴OpenGL我們需要每個片段都接收一個混合的顏色。接近頂點0的片段,混合後的顏色顯得更紅,而接近頂點1的片段,顏色就會越綠。

接下來,我們把varying也加入到片段著色器。修改simple_fragment_shader.glsl,修改如下:

precision mediump float;

//uniform vec4 u_Color;
varying vec4 v_Color;

void main()
{
    gl_FragColor = v_Color;
}

我們用varying變數v_Color替換了原來的uniform。如果那個片段屬於一個三角形,那OpenGL就會用構成那個三角形的三個頂點計算其混合的顏色。

既然已經更新了著色器,我們也需要在Renderer類中更新java程式碼,以便我們傳遞新的顏色屬性給頂點著色器的a_Color。

      private static final int BYTES_PER_FLOAT = 4;
    private static final int POSITION_COMPONENT_COUNT = 2;
    private static final int COLOR_COMPONENT_COUNT = 3;
    private static final int STRIDE = (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT;

      //private static final String U_COLOR = "u_Color";
    //private int uColorLocation;
    private static final String A_POSITION = "a_Position";
    private int aPositionLocation;
    private static final String A_COLOR = "a_Color";
    private int aColorLocation;

      @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        int programId = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);
        GLES20.glUseProgram(programId);
        // uColorLocation = GLES20.glGetUniformLocation(programId, U_COLOR);
        aPositionLocation = GLES20.glGetAttribLocation(programId, A_POSITION);
        aColorLocation = GLES20.glGetAttribLocation(programId, A_COLOR);


        vertexData.position(0);
        GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT,
                GLES20.GL_FLOAT, false, STRIDE, vertexData);
        GLES20.glEnableVertexAttribArray(aPositionLocation);


        vertexData.position(2);
        GLES20.glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT,
                GLES20.GL_FLOAT, false, STRIDE, vertexData);
        GLES20.glEnableVertexAttribArray(aColorLocation);
    }

這些程式碼比較重要,因此讓我們花點時間仔細地理解每一行程式碼:

        1、首先,我們可以去掉那些舊的常量和與u_Color相關的變數。 然後增加一個名為“STRIDE”的特殊常量,因為我們現在在同一個資料數組裡面既有位置又有顏色屬性,OpenGL不能再假定下一個位置是緊跟著前一個位置的。一旦OpenGL讀入了一個頂點的位置,如果它想讀入下一個頂點的位置,它不得不跳過當前頂點的顏色資料。我們要用那個跨距(stride)告訴OpenGL每個位置之間有多少個位元組,這樣它就知道需要跳過多少了。

        2、我們把vertexData的位置設為POSITION_COMPONENT_COUNT,這個值被設為2。為什麼執行這一步呢?這是因為,當OpenGL開始讀入顏色屬性時,我們要它從第一個顏色屬性開始,而不是第一個位置屬性。當需要跳過第一個位置時,我們就要把位置分量的大小計算在內;把那個位置設定為POSITION_COMPONENT_COUNT,緩衝區的指標就被調整到第一個顏色屬性的位置了。

        3、接下來,我們呼叫glVertexAttribPointer把顏色資料和著色器的a_Color關聯起來。那個跨距告訴OpenGL每個顏色之間有多少個位元組,這樣,當需要讀入所有頂點的顏色時,它就知道要讀取下一個頂點的顏色需要跳過多少個位元組了。跨距以位元組為單位是非常重要的。 儘管OpenGL中的一種顏色有四個分量(紅、綠、藍和透明度),我們並不必要指定所有分量,OpenGL會用預設值替換屬性中未指定值的分量。前三個分量預設為0,第四個分量被設為1。

        4、最好,就像前面講過的位置屬性一樣,我們要使能顏色屬性陣列。

我們就剩下最後一件事情了,更新onDrawFrame。既然我們已經把頂點資料和a_Color關聯起來了,只需要呼叫glDrawArrays即可,OpenGL會自動從vertexData資料裡讀入顏色屬性。

    @Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        //GLES20.glUniform4f(uColorLocation, 1.0f,1.0f,1.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 6);

        //GLES20.glUniform4f(uColorLocation, 1.0f,0.0f,0.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_LINES, 6, 2);

        //GLES20.glUniform4f(uColorLocation, 0.0f,0.0f,1.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_POINTS, 8, 1);
        //GLES20.glUniform4f(uColorLocation, 0.0f,1.0f,0.0f, 1.0f);
        GLES20.glDrawArrays(GLES20.GL_POINTS, 9, 1);
    }

看看是不是以下效果吧。

小結:

        因為我們已經有一個基本的框架,給每一個頂點增加顏色並不太困難。為此,我們給頂點資料和頂點著色器增加了一個新的屬性,並且告訴OpenGL如何使用跨距讀入資料。接著我們學習瞭如何使用一個varying在三角形平面上進行插值。

        一個需要記住的重要內容:當傳遞屬性資料時,我們要確保給分量計算和跨距(位元組位單位)傳遞正確的值。如果它們錯了,我們最終可能會看到一個混亂的螢幕。