Cocos2dx3.0的自动批次渲染原理

         网上流传着一份《Cocos2d (v.3.0) rendering pipeline roadmap 》,因为需要,我已经放到CSDN上了。看了这篇文档之后去看Cocos2dx的源码发现文档有些是错的,如果大家不信也可以去看,先了解个大概再自己跟着源码去看个究竟。

        Cocos2dx3.0引入了globalZOrder的概念,与 v3.0 之前的 localZOrder相比就是世界坐标系跟本地坐标系的关系,localZOrder就是节点相对与其直接父节点的逻辑深度,globalZOrder就是所有节点相对于根节点的逻辑深度。如果大家还不清楚可以参考这篇文章。

        Cocos2dx3.0的自动批次渲染说白了就是对所有节点的 globalZOrder 从小到大进行排序(为什么不是从大到小,因为Cocos用的是笛卡尔坐标系!!),然后按照 globalZOrder 从小到大的顺序,如果连续的节点使用相同的纹理,相同的混合函数,相同的shader,就可以合并为一个drawcall(为什么要这三个相同?这属于图形渲染管线的知识,大家可以去参考《real time rendering》,或者《GPU编程与Cg语言之阳春白雪下里巴人》)。见下图:

Cocos2dx3.0的自动批次渲染原理_第1张图片

VBO 是Vertex Buffer Object的缩写。

        换言之,如果我们改变节点的 globalZOrder ,游戏的 draw call 可能因此改变。实验如下:

    auto sprite = Sprite::create("HelloWorld.png");
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    this->addChild(sprite,1);

	auto sprite1 = Sprite::create("Clear.png");
	sprite1->setPosition(Vec2(origin.x, origin.y));
	this->addChild(sprite1,2);

	auto sprite2 = Sprite::create("HelloWorld.png");
	sprite2->setPosition(Vec2(origin.x + 100, origin.y + 100));
	this->addChild(sprite2,1);

	auto sprite3 = Sprite::create("Clear.png");
	sprite3->setPosition(Vec2(origin.x + 500, origin.y + 500));
	this->addChild(sprite3,2);

	auto sprite4 = Sprite::create("HelloWorld.png");
	sprite4->setPosition(Vec2(origin.x, origin.y + 500));
	this->addChild(sprite4,1);

draw call 次数如下:

Cocos2dx3.0的自动批次渲染原理_第2张图片


改变下 localZOrder 的数值(同时改变了节点的 globalZOrder数值),draw call 次数就改变了:

    auto sprite = Sprite::create("HelloWorld.png");
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    this->addChild(sprite,1);

	auto sprite1 = Sprite::create("Clear.png");
	sprite1->setPosition(Vec2(origin.x + 500, origin.y));
	this->addChild(sprite1,2);

	auto sprite2 = Sprite::create("HelloWorld.png");
	sprite2->setPosition(Vec2(origin.x + 300, origin.y + 300));
	this->addChild(sprite2,3);
	 
	auto sprite3 = Sprite::create("Clear.png");
	sprite3->setPosition(Vec2(origin.x + 500, origin.y + 500));
	this->addChild(sprite3,4);

	auto sprite4 = Sprite::create("HelloWorld.png");
	sprite4->setPosition(Vec2(origin.x, origin.y + 500));
	this->addChild(sprite4,5);
Cocos2dx3.0的自动批次渲染原理_第3张图片

下面是代码分析过程,注释标有“跟踪进去”的代码就是我们要跟踪进去的,代码有省略,只给出关键代码。

int Application::run()
{
    
    //此处省略代码
    while(!glview->windowShouldClose())
    {
        QueryPerformanceCounter(&nNow);
        if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
        {
            nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);
            
            director->mainLoop();  跟踪进去
            glview->pollEvents();
        }
        else
        {
            Sleep(1);
        }
    }
    //此处省略代码
    return 0;
}
void DisplayLinkDirector::mainLoop()
{
     // 省略代码
     drawScene(); //跟踪进去
     // 省略代码
 }

void Director::drawScene()
{
    // 省略代码
    // draw the notifications node
    if (_notificationNode)
    {
        _notificationNode->visit(_renderer, Mat4::IDENTITY, 0);  //遍历节点,跟踪进去
    }

    if (_displayStats)
    {
        showStats();
    }
    _renderer->render(); // 跟踪进去

    _eventDispatcher->dispatchEvent(_eventAfterDraw);

    // 省略代码
    
}

void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    // 省略代码
    if(!_children.empty())
    {
        sortAllChildren();  // 根据节点的localZOrder进行排序,个人觉得没什么用,因为后面渲染的时候又对节点的globalZOrder进行排序
        // draw children zOrder < 0
        for( ; i < _children.size(); i++ )
        {
            auto node = _children.at(i);

            if (node && node->_localZOrder < 0)
                node->visit(renderer, _modelViewTransform, flags);
            else
                break;
        }
        // self draw
        if (visibleByCamera)
            this->draw(renderer, _modelViewTransform, flags);   // 跟踪进去

        for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
            (*it)->visit(renderer, _modelViewTransform, flags);
    }
    else if (visibleByCamera)
    {
        this->draw(renderer, _modelViewTransform, flags);
    }

    
}

void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) //以Sprite的渲染为例
{
       // 省略代码
        _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform, flags);
        renderer->addCommand(&_quadCommand);   // 把要渲染的节点压入渲染队列
       // 省略代码
}
然后我们可以去看render 函数的代码了:

void Renderer::render()
{
    //省略代码
    if (_glViewAssigned)
    {
        //Process render commands
        //1. Sort render commands based on ID
        for (auto &renderqueue : _renderGroups)
        {
            renderqueue.sort();  //这里就是按照节点的globalZOrder进行排序
        }
        visitRenderQueue(_renderGroups[0]);  // 跟踪进去
    }
    //省略代码
}

void Renderer::visitRenderQueue(RenderQueue& queue)
{
     // 省略代码
     processRenderCommand(*it); //跟踪进去
     // 省略代码
}

void Renderer::processRenderCommand(RenderCommand* command)
{
    auto commandType = command->getType();
    if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
    {
        
        
    }
    else if ( RenderCommand::Type::QUAD_COMMAND == commandType )
    {
        //Draw if we have batched other commands which are not quad command
        flush3D();
        flushTriangles();
        
        //Process quad command
        auto cmd = static_cast(command);
        
        //Draw batched quads if necessary
        if(cmd->isSkipBatching()|| (_numberQuads + cmd->getQuadCount()) * 4 > VBO_SIZE )
        {
            
            //Draw batched quads if VBO is full
            drawBatchedQuads();      // 跟踪进去
        }
        
        //Batch Quads
        _batchQuadCommands.push_back(cmd);
        
        fillQuads(cmd);
        
        if(cmd->isSkipBatching())
        {
            drawBatchedQuads();
        }
    }
    else if (RenderCommand::Type::MESH_COMMAND == commandType)
    {
       
    }
    else if(RenderCommand::Type::GROUP_COMMAND == commandType)
    {
     
    }
    else if(RenderCommand::Type::CUSTOM_COMMAND == commandType)
    {
      
    }
    else if(RenderCommand::Type::BATCH_COMMAND == commandType)
    {
       
    }
    else if(RenderCommand::Type::PRIMITIVE_COMMAND == commandType)
    {
       
    }
    else
    {
      
    }
}

我们渲染 sprite 是用的是Quad_Command,所以这里我们只跟踪Quad_Command相关的代码:

void Renderer::drawBatchedQuads()
{
            
    //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;
        }
        
    }
   
}

在这里我们可以看到,如果连续的节点(节点之前按照globalZOrder从小到大的顺序排好了)使用的不是相同的MaterialIDea就进行一次渲染。(我看到源码中有TODO注释)。

你可能感兴趣的:(Cocos2dx3.0的自动批次渲染原理)