cocos2D-X源碼分析之從cocos2D-X學習OpenGL(5)---繪製基本圖形

最近完整的學習了learnopenglhttp://www.learnopengl.com/),覺得非常有啓發,從而又想起了這個長草許久的專題,正好趁這段時間,從本篇起完成這個專題,需要說明的是,從本系列的第五篇起將使用cocos2d-x3.10版本對應的代碼,最早的三篇採用的是相對比較老的代碼,但是我重新閱讀後發現對理解有沒有什麼影響,所以暫時我不會升級之前的代碼,但是爲了保持和最新代碼的一致性,從本篇起我們會採用最新的代碼。

       cocos2d-x的底層調用的是openGL ESapi,因此所有的繪製命令追根到底都是調用openGLES,本系列的教程會“刨根問底”的從cocos2d-x的功能講解到底層代碼的調用,從而達到“從cocos2d-x學習openGL”的目的。

幾乎所有openGL教程都會從渲染過程調用着色器的渲染順序講起,由於我們這個系列是從cocos2d-x學習openGL,所以,我們的順序略有不同,我們會從cocos2d-x的應用層講起,然後講到調用openGL的代碼,將這個過程講清楚後,我們會在下一節從openGL的層面介紹渲染順序。

下面的代碼會被添加到我們的場景的上,從而我們的屏幕會顯示一條直線:

auto drawNode = DrawNode::create();
    drawNode->drawLine(Vec2(100, 100), Vec2(200 ,200), Color4F::BLUE);
    this->addChild(drawNode);
        細節我想應該不用詳細解釋了,我們創建了一個DrawNode節點,然後讓他繪製了一個從(100,100)到(200,200)的藍色直線,顯示的結果如圖所示:

                

那麼這個直線到底是如何繪製出來的呢?首先,我們先複習一下VAO和VBO的概念,VBO即頂點緩衝對象,他可以發送頂點數據到GPU上,VAO是頂點數組對象,可以綁定VBO,這樣就不用每次都重新配置VBO了,任何對VBO數據層面的修改都可以直接映射,類似數據的指針和組織形式,但是並不是所有設備都支持VAO,所以,我們cocos2d-x通過一個函數來判斷當前設備的openGL是否支持VAO:

Configuration::getInstance()->supportsShareableVAO()

         這個函數返回布爾型的返回值,如果是true說明設備支持VAO,我們首先看DrawNode類的init函數,我們從相對簡單的VBO部分說起

//生成VBO
    glGenBuffers(1, &_vbo);
    //綁定VBO
    glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    //傳入數據
    glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)* _bufferCapacity, _buffer, GL_STREAM_DRAW);
    //直線部分
    glGenBuffers(1, &_vboGLLine);
    glBindBuffer(GL_ARRAY_BUFFER, _vboGLLine);
    glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)*_bufferCapacityGLLine, _bufferGLLine, GL_STREAM_DRAW);
    //點部分
    glGenBuffers(1, &_vboGLPoint);
    glBindBuffer(GL_ARRAY_BUFFER, _vboGLPoint);
    glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)*_bufferCapacityGLPoint, _bufferGLPoint, GL_STREAM_DRAW);
    //解綁
    glBindBuffer(GL_ARRAY_BUFFER, 0);

        glGenBuffers返回n(第一個參數)個當前未使用的緩存對象名稱,並保存到第二個參數的地址中。這裏返回的名稱只用於分配其他緩存對象,它們在綁定之後只會記錄一個可用的狀態。

       然後是調用glBindBuffer綁定緩衝,glBindBuffer完成三項工作,1)如果是第一次綁定,且它是一個非零的無符號整型,那麼將創建一個與該名稱相應得新緩存對象。2)如果綁定到一個已經創建的緩存對象,那麼它將成爲當前被激活的緩存對象。3)如果綁定的buffer值爲0,那麼openGL將不再對當前target應用任何緩存對象。要記住,openGL是個狀態機,所有的設置都是設置當前的狀態,如果進行下一次設置,那麼當前的狀態將會繼續保留,就像一個工作臺,我們把需要處理的零件放在工作臺上才能處理,爲了防止零件被做額外的處理,在處理完成後我們要把工作臺清理乾淨,這裏“glBindBuffer(GL_ARRAY_BUFFER, _vbo);”就是把零件放在加工臺上,而“glBindBuffer(GL_ARRAY_BUFFER, 0);”就是將零件臺清理乾淨。

       最後就是調用glBufferData裝入數據,它主要有兩個任務:分配頂點數據所需的存儲空間,然後將數據從應用程序的數組中拷貝到openGL服務端的內存中。這個函數有五個參數,它們的意義如下:

        第一個參數:頂點屬性數據類型,頂點數據是GL_ARRAY_BUFFER,索引數據是GL_ELEMENT_ARRAY_BUFFER,紋理緩衝數據GL_TEXTURE
        第二個參數:數據大小
        第三個參數:要麼是個客戶端內存指針,以便初始化緩存對象,要麼是NULL。如果傳入的指針非空,那麼數據將從客戶端拷貝到服務器端,如果傳入的指針爲空,那麼將保留未初始化數據。
        第四個參數:分配數據的讀取和寫入方式,包括GL_STATIC_DRAW(幾乎不會改變),GL_DYNAMIC_DRAW(數據易變),GL_STEAM_DRAW(每次都會被改變)等
        需要說明的是,這裏把數據分爲vbo,線vbo和點vbo,因爲這裏繪製的基本圖形不是由線組成的,就是用點組成的(是的,畫圓的時候要設置segments,其實就是設置直線的片段數)。
        下面是VAO,VAO也分爲vao,線vao和點vao,這裏節約空間,只介紹vao一個:

//VAO的處理
    glGenVertexArrays(1, &_vao);
    GL::bindVAO(_vao);
    //設置VBO數據
    glGenBuffers(1, &_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)* _bufferCapacity, _buffer, GL_STREAM_DRAW);
    //綁定VAO
    //頂點
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, vertices));
    //顏色
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_COLOR);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, colors));
    //貼圖
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_TEX_COORD);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, texCoords));
    //解綁
    GL::bindVAO(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
         整個過程和VBO類似,首先調用glGenVertexArrays生成一個VAO,然後調用GL::bindVAO(_vao);綁定VAO,最後調用GL::bindVAO(0);解綁

         中間是VAO和VBO的數據連接的過程

         




        如圖就是VBO和VAO的關係,VAO索引到VBO數據上,像個指針,索引着VBO上的數據。

        接下來調用glEnableVertexAttribArray和glVertexAttribPointer連接頂點屬性,glEnableVertexAttribArray設置是否啓用與index(參數)相關聯的頂點數據,默認是關閉的。glVertexAttribPointer是一個非常靈活的命令,我們手動指定我們的數據中的那部分對應相應的着色器上的那部分,參數的意義如下
        第一個參數:着色器位置索引,在GLProgram中我們定義了一個枚舉,索引了所有的屬性,這個參數和glEnableVertexAttribArray一致
        第二個參數:每個頂點的元素數目,第三個參數:數據類型
        第四個參數:是否對參數進行標準化,標準化即把參數轉換成(-1,1)的範圍,注意這項如果設爲TRUE會進行額外的轉化計算,會造成額外的開銷,這時需要注意的

        第五個參數:步長,兩段相同數據之間的距離,第六個參數:偏移量,爲什麼要設計偏移量呢,因爲數據未必是連續的,有可能是位置和顏色混在一起,就如同cocos2d-x中的一樣,我們來看V2F_C4B_T2F結構體的定義:

struct V2F_C4B_T2F
{
    /// vertices (2F)
    Vec2       vertices;
    /// colors (4B)
    Color4B        colors;
    /// tex coords (2F)
    Tex2F          texCoords;
};
        也就是首先是二維位置,然後是顏色和貼圖,所以,偏移量就是我們需要的數據的起始位置,注意,我們2d圖形只有x,y座標,所以就是Vec2,DrawNode3D就是三維的。

       爲什麼在init中採用VAO的代碼量反倒多了呢,因爲VAO一勞永逸的把數據的組織方式都設置好了,後面不再修改,VBO則要在繪製的每一幀都設置一遍這些內容,代碼如下:

if (_dirty)
    {
        glBindBuffer(GL_ARRAY_BUFFER, _vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)*_bufferCapacity, _buffer, GL_STREAM_DRAW);
        
        _dirty = false;
    }
    if (Configuration::getInstance()->supportsShareableVAO())
    {
        GL::bindVAO(_vao);
    }
    else
    {
        GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);

        glBindBuffer(GL_ARRAY_BUFFER, _vbo);
        // vertex
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, vertices));
        // color
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, colors));
        // texcood
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, texCoords));
    }

    glDrawArrays(GL_LINES, 0, _bufferCount);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    if (Configuration::getInstance()->supportsShareableVAO())
    {
        GL::bindVAO(0);
    }

       這裏只有一個新函數,就是glDrawArrays,它使用當前綁定的頂點數組元素(VAO)來建立一系列幾何圖元,第一個參數是繪製圖形,第二個參數是起始位置,第三個參數是元素個數
       以上就是OpenGL的VAO和VBO的使用,以及繪製流程,下一篇將介紹着色器

       

       能力不足,水平有限,如有錯誤,多謝指出。


       





         

   








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章