Cocos2d-x C++ Node GlobalZOrder、LocalZOrder、OrderOfArrival源码详解

本文深入剖析了Cocos2d-x游戏引擎中GlobalZOrder、LocalZOrder和OrderOfArrival对渲染顺序的影响。通过分析`Director::mainLoop()`、`sortAllChildren()`和渲染队列的过程,揭示了在全局z order相同的情况下,局部z order和到达顺序如何决定节点的绘制顺序。

文章目录

分析

z order主要作用于渲染阶段,先从Director::mainLoop()开始看代码(游戏运行过程中每帧都会调用mainLoop函数,用于每帧处理事件、渲染图像等):

void Director::mainLoop()
{
    //...
    {
        //处理事件并绘制图像
        drawScene();
        //...
    }
}
void Director::drawScene()
{
    //...
    if (! _paused)
    {
        _eventDispatcher->dispatchEvent(_eventBeforeUpdate);
        //处理事件
        _scheduler->update(_deltaTime);
        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }
    //清空上一帧渲染的内容
    _renderer->clear();
    experimental::FrameBuffer::clearAllFBOs();
    //...
    if (_runningScene)
    {
        //...
        if(_openGLView)
            //渲染当前运行的场景
            _openGLView->renderScene(_runningScene, _renderer);
        //...
    }
    //...
}
void GLView::renderScene(Scene* scene, Renderer* renderer)
{
    //...
    {
        //渲染场景
        scene->render(renderer, Mat4::IDENTITY, nullptr);
    }
}
void Scene::render(Renderer* renderer, const Mat4& eyeTransform, const Mat4* eyeProjection)
{
    render(renderer, &eyeTransform, eyeProjection, 1);
}
void Scene::render(Renderer* renderer, const Mat4* eyeTransforms, const Mat4* eyeProjections, unsigned int multiViewCount)
{
    {
        //...
        //遍历Scene中的节点
        visit(renderer, transform, 0);
        //...
        //渲染
        renderer->render();
        //...
    }
    //...
}
//中序遍历访问节点
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    int i = 0;
    //如果这个节点有子节点
    if(!_children.empty())
    {
        //排序所有子节点
        sortAllChildren();
        //先访问local z order < 0的节点
        for(auto size = _children.size(); i < size; ++i)
        {
            auto node = _children.at(i);
            //如果local z order >= 0就会走else跳出循环
            if (node && node->_localZOrder < 0)
                node->visit(renderer, _modelViewTransform, flags);
            else
                break;
        }
        //draw自身
        if (visibleByCamera)
            this->draw(renderer, _modelViewTransform, flags);
        //访问local z order >= 0的节点
        for(auto it=_children.cbegin()+i, itCend = _children.cend(); it != itCend; ++it)
            (*it)->visit(renderer, _modelViewTransform, flags);
    }
    //没有子节点的情况
    else if (visibleByCamera)
    {
        this->draw(renderer, _modelViewTransform, flags);
    }
    //...
}

首先看sortAllChildren函数是怎么给_children排序的:

void Node::sortAllChildren()
{
    //如果修改过z order
    if (_reorderChildDirty)
    {
        //排序
        sortNodes(_children);
        _reorderChildDirty = false;
        //...
    }
}
template<typename _T> inline
static void sortNodes(cocos2d::Vector<_T*>& nodes)
{
    //...
#if CC_64BITS
    std::sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {
        return (n1->_localZOrder$Arrival < n2->_localZOrder$Arrival);
    });
#else
    std::sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {
        //首先根据local z order从小到大排序,如果相同根据order of arrival从小到大排序
        return (n1->_localZOrder == n2->_localZOrder && n1->_orderOfArrival < n2->_orderOfArrival) || n1->_localZOrder < n2->_localZOrder;
    });
#endif
}

为什么这里可以只使用LocalZOrder$Arrival排序?

在中序遍历到自身时,调用了draw函数,但是注意调用draw函数时并没有真正渲染,而只是把渲染当前节点的Command(指令)插入到了RenderQueue(渲染队列)中(Node不进行渲染,这里使用Sprite的draw函数):

void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    //...
    {
        //初始化一个渲染指令
        _trianglesCommand.init(_globalZOrder,
                               _texture,
                               getGLProgramState(),
                               _blendFunc,
                               _polyInfo.triangles,
                               transform,
                               flags);
        //添加指令到渲染器中
        renderer->addCommand(&_trianglesCommand);
        //...
    }
}
void Renderer::addCommand(RenderCommand* command)
{
    //获取到最后一个渲染队列id(实际上默认情况只有一个渲染队列)
    int renderQueueID =_commandGroupStack.top();
    addCommand(command, renderQueueID);
}
void Renderer::addCommand(RenderCommand* command, int renderQueueID)
{
    //...
    _renderGroups[renderQueueID].push_back(command);
}
void RenderQueue::push_back(RenderCommand* command)
{
    float z = command->getGlobalOrder();
    //根据节点的global z order插入到不同的指令数组中
    if(z < 0)
    {
        _commands[QUEUE_GROUP::GLOBALZ_NEG].push_back(command);
    }
    else if(z > 0)
    {
        _commands[QUEUE_GROUP::GLOBALZ_POS].push_back(command);
    }
    else
    {
        //...
        {
            _commands[QUEUE_GROUP::GLOBALZ_ZERO].push_back(command);
        }
    }
}

到此为止,可见draw函数只是插入了一条渲染指令到渲染队列,而真正的渲染过程在之前 void Scene::render(Renderer* renderer, const Mat4* eyeTransforms, const Mat4* eyeProjections, unsigned int multiViewCount) 的renderer->render();中:

void Renderer::render()
{
    //...
    {
        for (auto &renderqueue : _renderGroups)
        {
            //对每个渲染队列排序
            renderqueue.sort();
        }
        //访问渲染队列(开始渲染)
        visitRenderQueue(_renderGroups[0]);
    }
    //...
}

先看renderqueue.sort():

void RenderQueue::sort()
{
    //...
    //global z order为负和为正的都根据compareRenderCommand函数排序
    std::stable_sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_NEG]), std::end(_commands[QUEUE_GROUP::GLOBALZ_NEG]), compareRenderCommand);
    std::stable_sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_POS]), std::end(_commands[QUEUE_GROUP::GLOBALZ_POS]), compareRenderCommand);
}
static bool compareRenderCommand(RenderCommand* a, RenderCommand* b)
{
    //按照global z order从小到大排序
    return a->getGlobalOrder() < b->getGlobalOrder();
}

由此可知最终每个渲染队列中的指令又根据global z order重新排序了一遍,而当global z order相同时,stable_sort不会改变元素的顺序,所以global z order相同的节点,顺序依然按照插入时的顺序(local z order的顺序)排序。最后是渲染:

void Renderer::visitRenderQueue(RenderQueue& queue)
{
    //处理global z order < 0的指令
    const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG);
    if (zNegQueue.size() > 0)
    {
        //...渲染
    }
    //...
    //处理global z order == 0的指令
    const auto& zZeroQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_ZERO);
    if (zZeroQueue.size() > 0)
    {
        //...渲染
    }
    
    //处理global z order > 0的指令
    const auto& zPosQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_POS);
    if (zPosQueue.size() > 0)
    {
        //...渲染
    }
}

结论

local z order和order of arrival影响的只是渲染指令的添加顺序,渲染的顺序始终首先由global z order决定,global z order相同时再根据指令添加的顺序决定。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值