cocos2D-X源碼分析之從cocos2D-X學習OpenGL(1)----cocos2D-X渲染結構

        個人原創,歡迎轉載,轉載請註明原文地址http://blog.csdn.net/bill_man

        從本篇文章開始,將分析cocos2D-X 3.0源代碼,第一部分是從cocos2D-X學習OpenGL,也就是分析cocos2D-X 3.0的渲染代碼,本篇首先介紹cocos2D-X 3.0的渲染結構,使用的是3.0正式版。

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)
{
    //只有一種情況會調用到這裏來,就是導演類調用end函數
        _purgeDirectorInNextLoop = false;
        //清除導演類
        purgeDirector();
    }
    else if (! _invalid)
    {
        //繪製
        drawScene();
        //清除內存
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

分析的起點是mainLoop函數,這是在主線程裏面會調用的循環,其中drawScene函數進行繪製。那麼就進一步來看drawScene函數。

void Director::drawScene()
{
    //計算間隔時間
    calculateDeltaTime();
    
    //如果間隔時間過小會被忽略
    if(_deltaTime < FLT_EPSILON)
    {
        return;
    }
    //空函數,也許之後會有作用
    if (_openGLView)
    {
        _openGLView->pollInputEvents();
    }

    //非暫停狀態
    if (! _paused)
    {
        _scheduler->update(_deltaTime);
        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //切換下一場景,必須放在邏輯後繪製前,否則會出bug
    if (_nextScene)
    {
        setNextScene();
    }

    kmGLPushMatrix();
    //創建單位矩陣
    kmMat4 identity;
    kmMat4Identity(&identity);

    //繪製場景
    if (_runningScene)
    {
        _runningScene->visit(_renderer, identity, false);
        _eventDispatcher->dispatchEvent(_eventAfterVisit);
    }

    //繪製觀察節點,如果你需要在場景中設立觀察節點,請調用攝像機的setNotificationNode函數
    if (_notificationNode)
    {
        _notificationNode->visit(_renderer, identity, false);
    }
    //繪製屏幕左下角的狀態
    if (_displayStats)
    {
        showStats();
    }
    //渲染
    _renderer->render();
    //渲染後
    _eventDispatcher->dispatchEvent(_eventAfterDraw);

    kmGLPopMatrix();

    _totalFrames++;

    if (_openGLView)
    {
        _openGLView->swapBuffers();
    }
    //計算繪製時間
    if (_displayStats)
    {
        calculateMPF();
    }
}

其中和繪製相關的是visit的調用和render的調用,其中visit函數會調用節點的draw函數,在3.0之前的版本中draw函數就會直接調用繪製代碼,3.0版本是在draw函數中將繪製命令存入到renderer中,然後renderer函數去進行真正的繪製,首先來看sprite的draw函數。

void Sprite::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated)
{
    //檢查是否超出邊界,自動裁剪
    _insideBounds = transformUpdated ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;

    if(_insideBounds)
    {
        //初始化
        _quadCommand.init(_globalZOrder, _texture->getName(), _shaderProgram, _blendFunc, &_quad, 1, transform);
        renderer->addCommand(&_quadCommand);
//物理引擎相關繪製邊界
#if CC_SPRITE_DEBUG_DRAW
        _customDebugDrawCommand.init(_globalZOrder);
        //自定義函數
        _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this);
        renderer->addCommand(&_customDebugDrawCommand);
#endif
    }
}

這裏面用了兩種不同的繪製命令quadCommand初始化後就可以加入到繪製命令中,customDebugDrawCommand傳入了一個回調函數,具體的命令種類會在後面介紹。其中自定義的customDebugDrawCommand命令在初始化的時候只傳入了全局z軸座標,因爲它的繪製函數全部都在傳入的回調函數裏面,_quadCommand則需要傳入全局z軸座標,貼圖名稱,shader,混合,座標點集合,座標點集個數,變換。

void Renderer::render()
{
    _isRendering = true;
    
    if (_glViewAssigned)
    {
        //清除
        _drawnBatches = _drawnVertices = 0;

        //排序
        for (auto &renderqueue : _renderGroups)
        {
            renderqueue.sort();
        }
        //繪製
        visitRenderQueue(_renderGroups[0]);
        flush();
    }
    clean();
    _isRendering = false;
}

Render類中的render函數進行真正的繪製,首先排序,再進行繪製,從列表中的第一個組開始繪製。在visitRenderQueue函數中可以看到五種不同類型的繪製命令類型,分別對應五個類,這五個類都繼承自RenderCommand。

QUAD_COMMAND:QuadCommand類繪製精靈等。

所有繪製圖片的命令都會調用到這裏,處理這個類型命令的代碼就是繪製貼圖的openGL代碼,下一篇文章會詳細介紹這部分代碼。

CUSTOM_COMMAND:CustomCommand類自定義繪製,自己定義繪製函數,在調用繪製時只需調用已經傳進來的回調函數就可以,裁剪節點,繪製圖形節點都採用這個繪製,把繪製函數定義在自己的類裏。

這種類型的繪製命令不會在處理命令的時候調用任何一句openGL代碼,而是調用你寫好並設置給func的繪製函數,後續文章會介紹引擎中的所有自定義繪製,並自己實現一個自定義的繪製。

BATCH_COMMAND:BatchCommand類批處理繪製,批處理精靈和粒子

其實它類似於自定義繪製,也不會再render函數中出現任何一句openGL函數,它調用一個固定的函數,這個函數會在下一篇文章中介紹。

GROUP_COMMAND:GroupCommand類繪製組,一個節點包括兩個以上繪製命令的時候,把這個繪製命令存儲到另外一個_renderGroups中的元素中,並把這個元素的指針作爲一個節點存儲到_renderGroups[0]中。

整個GROUP_COMMAND的原理需要從addCommand講起。

void Renderer::addCommand(RenderCommand* command)
{
    //獲得棧頂的索引
    int renderQueue =_commandGroupStack.top();
    //調用真正的addCommand
    addCommand(command, renderQueue);
}

void Renderer::addCommand(RenderCommand* command, int renderQueue)
{
    CCASSERT(!_isRendering, "Cannot add command while rendering");
    CCASSERT(renderQueue >=0, "Invalid render queue");
    CCASSERT(command->getType() != RenderCommand::Type::UNKNOWN_COMMAND, "Invalid Command Type");
    //將命令加入到數組中
    _renderGroups[renderQueue].push_back(command);
}

addCommand有“真假”兩個,幾乎所有添加渲染命令的地方,調用的都是第一個“假” addCommand,它實際上不是真正的把命令添加到_renderGroups中,它是獲得需要把命令加入到_renderGroups位置中的索引,這個索引是從_commandGroupStack獲得的,_commandGroupStack是個棧,當我們創建一個GROUP_COMMAND時,需要調用pushGroup函數,它是把當前這個命令在_renderGroups的索引位置壓到棧頂,當addCommand時,調用top,獲得這個位置

_groupCommand.init(_globalZOrder);

renderer->addCommand(&_groupCommand);

renderer->pushGroup(_groupCommand.getRenderQueueID());

GROUP_COMMAND一般用於繪製的節點有一個以上的繪製命令,把這些命令組織在一起,無需排定它們之間的順序,他們作爲一個整體被調用,所以一定要記住,棧是push,pop對應的,關於這個節點的所有的繪製命令被添加完成後,請調用pop,將這個值從棧頂彈出,否則後面的命令也會被添加到這裏。

接下來就可以解釋爲什麼調用的起始只需調用

visitRenderQueue(_renderGroups[0]);,爲什麼只是0,其他的呢?

它們會在處理GROUP_COMMAND被調用

else if(RenderCommand::Type::GROUP_COMMAND == commandType) {
            flush();
            int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
            visitRenderQueue(_renderGroups[renderQueueID]);
}

如有錯誤,歡迎指出

下一篇介紹貼圖和批處理的openGL代碼部分

同時推薦子龍山人的openGL相關博客:http://4gamers.cn/archives/category/opengl-es-2-0

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