OpenGL ES 定義形狀

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

如同學(xué)習(xí)繪制自定義 View 一樣,定義圖形的形狀是實(shí)現(xiàn)各種復(fù)雜的圖形的基礎(chǔ),下面將介紹 OpenGL ES 相對于 Android 設(shè)備屏幕的坐標(biāo)系、定義形狀和形狀繪制等基礎(chǔ)知識。

定義一個(gè)三角形

OpenGL ES 允許我們使用三維空間的坐標(biāo)來定義繪畫對象。所以在我們能畫三角形之前,必須先定義它的坐標(biāo)。在 OpenGL 中,典型的辦法是為坐標(biāo)定義一個(gè) Float 類型的頂點(diǎn)數(shù)組。為了效率最大化,我們可以將坐標(biāo)寫入一個(gè) ByteBuffer,它將會傳入 OpenGl ES 的 pipeline 來處理。

ByteBuffer 俗稱緩沖器,在 NIO 中,數(shù)據(jù)的讀寫操作始終是與緩沖區(qū)相關(guān)聯(lián)的。讀取時(shí)信道 (SocketChannel) 將數(shù)據(jù)讀入緩沖區(qū),寫入時(shí)首先要將發(fā)送的數(shù)據(jù)按順序填入緩沖區(qū)。緩沖區(qū)是定長的,基本上它只是一個(gè)列表,它的所有元素都是基本數(shù)據(jù)類型。ByteBuffer 是最常用的緩沖區(qū),它提供了讀寫其他數(shù)據(jù)類型的方法,且信道的讀寫方法只接收 ByteBuffer。關(guān)于 ByteBuffer 的更多內(nèi)容,推薦一篇文章 Android中直播視頻技術(shù)探究之—基礎(chǔ)核心類ByteBuffer解析

public class Triangle {

    /**
     * 定義三角形頂點(diǎn)的坐標(biāo)數(shù)據(jù)的浮點(diǎn)型緩沖區(qū)
     */
    private FloatBuffer vertexBuffer;

    // 坐標(biāo)數(shù)組中的頂點(diǎn)坐標(biāo)個(gè)數(shù)
    static final int COORDINATES_PRE_VERTEX = 3;
    static float triangleCoords[] = {   // 以逆時(shí)針順序;
            0.0f,  0.622008459f, 0.0f,  // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
            0.5f, -0.311004243f, 0.0f   // bottom right
    };

    // Set color with red, green, blue and alpha (opacity) values
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle(){
        // 初始化形狀中頂點(diǎn)坐標(biāo)數(shù)據(jù)的字節(jié)緩沖區(qū)
        // 通過 allocateDirect 方法獲取到 DirectByteBuffer 實(shí)例
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(
                // 頂點(diǎn)坐標(biāo)個(gè)數(shù) * 坐標(biāo)數(shù)據(jù)類型 float 一個(gè)是 4 bytes
                triangleCoords.length * 4
        );

        // 設(shè)置緩沖區(qū)使用設(shè)備硬件的原本字節(jié)順序進(jìn)行讀取;
        byteBuffer.order(ByteOrder.nativeOrder());

        // 因?yàn)?ByteBuffer 是將數(shù)據(jù)移進(jìn)移出通道的唯一方式使用,這里使用 “as” 方法從 ByteBuffer 中獲得一個(gè)基本類型緩沖區(qū)(浮點(diǎn)緩沖區(qū))
        vertexBuffer = byteBuffer.asFloatBuffer();
        // 把頂點(diǎn)坐標(biāo)信息數(shù)組存儲到 FloatBuffer
        vertexBuffer.put(triangleCoords);
        // 設(shè)置從緩沖區(qū)的第一個(gè)位置開始讀取頂點(diǎn)坐標(biāo)信息
        vertexBuffer.position(0);
    }
}

跟 View 的坐標(biāo)系原點(diǎn)位于屏幕左上角不同,默認(rèn)情況下 OpenGL ES 會假定一個(gè)坐標(biāo)系,在這個(gè)坐標(biāo)系中,[0, 0, 0](分別對應(yīng)X軸坐標(biāo), Y軸坐標(biāo), Z軸坐標(biāo))對應(yīng)的是GLSurfaceView 的中心。如 [1, 1, 0] 對應(yīng)的是右上角,[-1, -1, 0] 對應(yīng)的則是左下角。

在 OpenGL 里,我們要渲染的一切物體都要映射到 X 軸和 Y 軸上 [-1,1] 的范圍內(nèi),對于Z軸也一樣。這個(gè)范圍內(nèi)的坐標(biāo)被稱為歸一化設(shè)備坐標(biāo),其獨(dú)立于屏幕實(shí)際尺寸或形狀。也就是說在 Android 設(shè)備上顯示圖形時(shí)屏幕的尺寸和形狀雖然會有所不同,但是 OpenGL 假設(shè)了一個(gè)平方均勻的坐標(biāo)系,默認(rèn)情況下將這些坐標(biāo)按比例繪制非正方形屏幕上,就好像它是完全正方形一樣。

OpenGL 坐標(biāo)系

如上圖所示的坐標(biāo)系,左圖是默認(rèn)的 OpenGL 坐標(biāo)系,右圖是實(shí)際展示 Android 設(shè)備屏幕時(shí)的坐標(biāo)系,會看到三角形會有一個(gè)明顯的拉伸。默認(rèn)情況下,對于 OpenGL 而言不管硬件設(shè)備屏幕是不是正方形,都把它當(dāng)作一個(gè)正方形來處理,三維坐標(biāo)都限定在 [-1, 1]內(nèi)。 所以 Open GL 的坐標(biāo)體系獨(dú)立于實(shí)際的屏幕尺寸。

要處理畫面被拉伸的問題,可以考慮調(diào)整坐標(biāo)空間,把屏幕的形狀考慮在內(nèi),可行的一個(gè)方法是把較小的范圍固定在 [-1,1] 內(nèi),而按屏幕尺寸的比例調(diào)整較大的范圍。這里推薦一篇文章:Android OpenGL ES 調(diào)整屏幕的寬高比,文章中提到了如何處理 Open GL 在實(shí)際展示時(shí)寬高比控制。而這篇文章:OpenGL ES 透視投影則是對歸一化設(shè)備坐標(biāo)到視口(視口:OpenGL 渲染操作最終顯示窗口)的窗口坐標(biāo)轉(zhuǎn)化說明。這些內(nèi)容建議暫時(shí)放一下,遇到相關(guān)問題時(shí)在深入了解。

三角形頂點(diǎn)坐標(biāo)

注意到上面這個(gè)形狀的坐標(biāo)是以逆時(shí)針順序定義的。繪制的順序非常關(guān)鍵,因?yàn)樗x了哪一面是形狀的正面(希望繪制的一面),以及背面(使用 OpenGL ES 的 Cull Face 功能可以讓背面不要繪制)。更多關(guān)于該方面的信息,可以閱讀 OpenGL ES 開發(fā)手冊。

定義一個(gè)矩形

在 OpenGL 中定義三角形非常簡單,那定義一個(gè)矩形呢?有很多方法可以用來定義矩形,不過在 OpenGL ES 中最典型的辦法是使用兩個(gè)三角形拼接在一起:


繪制矩形

同樣的我們通過按逆時(shí)針順序?yàn)槿切雾旤c(diǎn)定義坐標(biāo)來表示這個(gè)圖形,并將值放入一個(gè) ByteBuffer 中。為了避免由兩個(gè)三角形重合的那條邊的頂點(diǎn)被重復(fù)定義,可以使用一個(gè)繪制列表(drawing list)來告訴 OpenGL ES 繪制順序。下面是代碼樣例:

public class Square {

    /**
     * 頂點(diǎn)坐標(biāo)數(shù)據(jù)緩沖區(qū)(float 類型)
     */
    private FloatBuffer vertexBuffer;

    /**
     * 繪制順序數(shù)據(jù)緩沖區(qū)(short類型)
     */
    private ShortBuffer drawListBuffer;


    /**
     * 頂點(diǎn)坐標(biāo)數(shù)據(jù)的數(shù)組
     */
    static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
            0.5f, -0.5f, 0.0f,   // bottom right
            0.5f,  0.5f, 0.0f }; // top right

    /**
     * 繪制頂點(diǎn)順序
     */
    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices


    public Square() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
}

該樣例可以看作是一個(gè)如何使用 OpenGL 創(chuàng)建復(fù)雜圖形的啟發(fā),通常來說我們需要使用三角形的集合來繪制對象。

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

上述內(nèi)容主要是對如何定義簡單形狀進(jìn)行一個(gè)說明,了解 OpenGL 坐標(biāo)體系規(guī)則,下面將了解如何在屏幕上畫這些形狀。

>>>>Next>>>> : 繪制形狀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容