5 cocos2dx 3.0源碼分析 渲染 render


渲染,感覺這個挺重要了,這里代入一個簡單的例子 Sprite 建立及到最后的畫在屏幕上, 我們描述一下這個渲染的流程:
 
1 sprite 初始化(紋理, 坐標,及當前元素的坐標大小信息)
2 主循環調用sprite的draw(), 把繪制命令發送到系統的render的渲染隊列中。 
3 Render拿到渲染隊列中的渲染命令, 分別對每個命令進行處理, 我們這里的QUAD_COMMAND, 把這個命令中的坐標信息復制到自己的渲染緩沖中, 之后通過調用OpenGL命令對當前的矩形進行繪制。 
 

 
1 Sprite初始化:
 
如:
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Sprite *sp = Sprite::create("HelloWorld.png");
    sp->setPosition(Point(visibleSize.width * .5, visibleSize.height * .5));
    this->addChild(sp, 1);
 
1.1 簡述:
 
1> 初始化Sprite, 從紋理緩存中讀取紋理的大小及相關信息。 
2> 初始化當前對象中的_quad屬性,Sprite對OpenGL來說就是一個矩形的繪制,要繪制它, 就需要它的頂點信息,紋理信息, 這些信息都在Sprite初始化時被添加到了_quad屬性中。 具體看一下下面的這個屬性的數據結構。 
 
1.2 詳細:
 
1> 初始化Sprite 
 
做了一堆的初始化工作這里我們注意一下_quad. 這個對象記錄了當前的Sprite, 矩形的坐標,及紋理相關的信息。我們之后的繪制就需要這個對象。 
void Sprite::setTextureRect(const Rect& rect, bool rotated, const Size& untrimmedSize)

1. _quad

V3F_C4B_T2F_Quad _quad;
V3F_C4B_T2F_Quad, 這個數據結構記錄了我們的左上,左下,右上, 右下,這個矩形的4個頂點信息, 每個頂點是由一個V3F_C4B_T2F結構組成的, 可以看到,其中有坐標, 顏色, 紋理的數據存儲。
 
//! 4 Vertex3FTex2FColor4B
struct CC_DLL V3F_C4B_T2F_Quad
{
    //! top left
    V3F_C4B_T2F    tl;
    //! bottom left
    V3F_C4B_T2F    bl;
    //! top right
    V3F_C4B_T2F    tr;
    //! bottom right
    V3F_C4B_T2F    br;
};
 
//! a Vec2 with a vertex point, a tex coord point and a color 4B
struct CC_DLL V3F_C4B_T2F
{
    //! vertices (3F)
    Vec3    vertices;            // 12 bytes

    //! colors (4B)
    Color4B      colors;              // 4 bytes

    // tex coords (2F)
    Tex2F        texCoords;           // 8 bytes
};

 


 
2. 紋理相關的初始化 setTextureCoords()
 
     _quad.bl.texCoords.u = left;
        _quad.bl.texCoords.v = bottom;
        _quad.br.texCoords.u = right;
        _quad.br.texCoords.v = bottom;
        _quad.tl.texCoords.u = left;
        _quad.tl.texCoords.v = top;
        _quad.tr.texCoords.u = right;
        _quad.tr.texCoords.v = top;

 

3.  坐標相關初始化
        
  // Atlas: Vertex
        float x1 = 0 + _offsetPosition.x;
        float y1 = 0 + _offsetPosition.y;
        float x2 = x1 + _rect.size.width;
        float y2 = y1 + _rect.size.height;

        // Don't update Z.
        _quad.bl.vertices = Vec3(x1, y1, 0);
        _quad.br.vertices = Vec3(x2, y1, 0);
        _quad.tl.vertices = Vec3(x1, y2, 0);
        _quad.tr.vertices = Vec3(x2, y2, 0);

  

2 調用Sprite draw() 
 
還記得之前一篇的主循環嗎?
 
Director 每幀都調用drawScene(), drawScene()中會調用當前Scene->render() , Scene->render() 又會遞歸遍歷其子元素,分別調用子元素的visit() 或 draw() , 當有子元素時, 用visit() , 沒有子元素時直接調用其draw. 我們這里的Sprite被調用了, 這里調用的是draw(). 
 
這個函數, 主要做了一件事,初始化一個矩形繪制命令, 把當前繪制命令發送到當前的系統 Render 的繪制隊列中。
 
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    // Don't do calculate the culling if the transform was not updated
    _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;

    if(_insideBounds)
    {
        _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
        renderer->addCommand(&_quadCommand);
}
 

 

3 Render:render() 
 
Render內部保存了一個命令的繪制隊列,cocos2dx的所有繪制都會被封裝成為命令發送到當前的繪制隊列中。
 
當整個的子節點遍歷后, 即所有的繪制命令加入到繪制隊列上時, 這里調用 render->render()
這個函數的作用是繪制當前繪制隊列中的繪制信息, 最終是調用OpenGL的命令完成了,繪制。 
下面看看它是怎么做的。 
 
 
3.1 排序, 對繪制命令進行排序
 
這里默認使用的遍歷順序排序. 當然也提供了可以改變這個順序的方法,  這里不展開。 
 
3.2 遍歷當前的繪制隊列
 
命令的類型:
TRIANGLES_COMMAND
QUAD_COMMAND
GROUP_COMMAND
CUSTOM_COMMAND
BATCH_COMMAND
PRIMITIVE_COMMAND
MESH_COMMAND
 
1》根據不同的命令類型來對命令進行處理, 這里我們只關注,QUAD_COMMAND, 之前我們的Sprite就發送的這種類型的繪制命令。
 
  
else if ( RenderCommand::Type::QUAD_COMMAND == commandType )
        {
            flush3D();
            if(_filledIndex > 0)
            {
                drawBatchedTriangles();
                _lastMaterialID = 0;
            }
            auto cmd = static_cast<QuadCommand*>(command);
            //Batch quads
            if( (_numberQuads + cmd->getQuadCount()) * 4 > VBO_SIZE )
            {
                CCASSERT(cmd->getQuadCount()>= 0 && cmd->getQuadCount() * 4 < VBO_SIZE, "VBO for vertex is not big enough, please break the data down or use customized render command");
                //Draw batched quads if VBO is full
                drawBatchedQuads();
            }
 
            _batchQuadCommands.push_back(cmd);
           
            fillQuads(cmd);
        }
 
有2個關鍵點,
 
1 把繪制命令加到到批繪制列表中。_batchQuadCommands.push_back(cmd)
 
2 處理當前的命令中的頂點數據, fillQuads(cmd) 

void Renderer::fillQuads(const QuadCommand *cmd)
{
    memcpy(_quadVerts + _numberQuads * 4, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());
    
    const Mat4& modelView = cmd->getModelView();
    
    for(ssize_t i=0; i< cmd->getQuadCount() * 4; ++i)
    {
        V3F_C4B_T2F *q = &_quadVerts[i + _numberQuads * 4];
        Vec3 *vec1 = (Vec3*)&q->vertices;
        modelView.transformPoint(vec1);
    }
    
    _numberQuads += cmd->getQuadCount();
}
 
1》首先, Render在初始化時, 初始化了一個的很大的頂點信息的緩存區, 我們的QuadCommand中保存的節點信息, 會先被復制到這個緩存區中。 
    
memcpy(_quadVerts + _numberQuads * 4, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());

 

2》對當前的頂點坐標信息, 做頂點坐標變化, 也就是模型坐標到世界坐標的變換
for(ssize_t i=0; i< cmd->getQuadCount() * 4; ++i)
    {
        V3F_C4B_T2F *q = &_quadVerts[i + _numberQuads * 4];
        Vec3 *vec1 = (Vec3*)&q->vertices;
        modelView.transformPoint(vec1);
    }

 

3 記錄當前Render中有多少個這種矩形信息。 
    
_numberQuads += cmd->getQuadCount();
 
 
3.3 繪制
 
flush() 關於這塊的OpenGL相關的函數,(VAO, VBO,glDrawBuffer) 參考下面的附錄:
 
1 綁定VAO, 這里的VAO在 setupVBOAndVAO里已經初始化過了。初始化時,對頂點的索引值進行了初始化。 
      
  GL::bindVAO(_quadVAO);

 

2 設置VBO數據 
        
//Set VBO data
        glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);
        // option 3: orphaning + glMapBuffer
        glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * _numberQuads * 4, nullptr, GL_DYNAMIC_DRAW);
        void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
        memcpy(buf, _quadVerts, sizeof(_quadVerts[0])* _numberQuads * 4);
        glUnmapBuffer(GL_ARRAY_BUFFER);
       
3 綁定頂點索引數據 , 這里的索引數據, 詳細看 setupVBOAndVAO()
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
 
4 遍歷之前的批繪制列表,(這個有一個優化,就是當相鄰的矩形的材質相同時,統一在一個繪制命令中繪制)
   
 for(const auto& cmd : _batchQuadCommands)

 

5 繪制
 
 glDrawElements(GL_TRIANGLES, (GLsizei) indexToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (startIndex*sizeof(_indices[0])) );
 
 
源碼:
 
void Renderer::drawBatchedQuads()
{
    //TODO: we can improve the draw performance by insert material switching command before hand.
    
    int indexToDraw = 0;
    int startIndex = 0;
    
    //Upload buffer to VBO
    if(_numberQuads <= 0 || _batchQuadCommands.empty())
    {
        return;
    }
    
    if (Configuration::getInstance()->supportsShareableVAO())
    {
        //Bind VAO
        GL::bindVAO(_quadVAO);
        //Set VBO data
        glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);
        // option 3: orphaning + glMapBuffer
        glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * _numberQuads * 4, nullptr, GL_DYNAMIC_DRAW);
        void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
        memcpy(buf, _quadVerts, sizeof(_quadVerts[0])* _numberQuads * 4);
        glUnmapBuffer(GL_ARRAY_BUFFER);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
    }
    else
    {
    }
    //Start drawing verties in batch
    for(const auto& cmd : _batchQuadCommands)
    {
        auto newMaterialID = cmd->getMaterialID();
        if(_lastMaterialID != newMaterialID || newMaterialID == MATERIAL_ID_DO_NOT_BATCH)
        {
            //Draw quads
            if(indexToDraw > 0)
            {
                glDrawElements(GL_TRIANGLES, (GLsizei) indexToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (startIndex*sizeof(_indices[0])) );
                _drawnBatches++;
                _drawnVertices += indexToDraw;
                startIndex += indexToDraw;
                indexToDraw = 0;
            }
            //Use new material
            cmd->useMaterial();
            _lastMaterialID = newMaterialID;
        }
        
        indexToDraw += cmd->getQuadCount() * 6;
    }
    
    //Draw any remaining quad
    if(indexToDraw > 0)
    {
        glDrawElements(GL_TRIANGLES, (GLsizei) indexToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (startIndex*sizeof(_indices[0])) );
        _drawnBatches++;
        _drawnVertices += indexToDraw;
    }
    
    if (Configuration::getInstance()->supportsShareableVAO())
    {
        //Unbind VAO
        GL::bindVAO(0);
    }
    else
    {
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }
    
    _batchQuadCommands.clear();
    _numberQuads = 0;
}

 

 
參考:
VAO&VBO 
OpenGL ES 2.0 
cocos2D-X源碼分析之從cocos2D-X學習OpenGL(2)----QUAD_COMMAND
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM