cocos2dx的渲染树

来源:互联网 发布:在线讲课软件 编辑:程序博客网 时间:2024/06/12 18:10

cocos2dx的渲染顺序是由渲染树来决定的。渲染树是由各种游戏元素按照层次关系构成的树结构,表示Cocos2d-x游戏的绘制层次。
由导演类Director控制渲染树的根节点场景Scene,场景中包含层Layer,层Layer中又包含精灵等。
层次关系如图:
这里写图片描述
按照官网开发者指南中的说法,所有节点都是存储在一个场景图_scene graph_中,_scene graph_是一个用来存储场景图形的数据结构,其实是一个树形结构。
可以通过API中的addChild()方法来创建_scene graph_场景。
在对节点之间添加调用addchild(node,Zorder,tag)的时候,就把该子节点添加到该父节点的子树中,ZOrder决定了子节点是在父节点左边的子树还是右边的子树。没有设置ZOrder的会按照添加的前后顺序决定渲染次序。
对_scene graph_进行遍历的时候,cocos2dx采用了_in-order walk_算法(按顺序)。类似于二叉树的中根遍历,先遍历左子树,然后访问根结点,最后遍历右子树。所以ZOrder值小的先渲染,ZOrder值大的后渲染,最右边的子树会最后显示到场景中,在所有节点的最上面。

接着从代码中看一下cocos2dx的渲染结构。
游戏程序从win32目录下的main.cpp开始运行

int APIENTRY _tWinMain(HINSTANCE hInstance,                       HINSTANCE hPrevInstance,                       LPTSTR    lpCmdLine,                       int       nCmdShow){    UNREFERENCED_PARAMETER(hPrevInstance);    UNREFERENCED_PARAMETER(lpCmdLine);    // create the application instance    AppDelegate app;    return Application::getInstance()->run();}

运行Application单例的run方法。继续追踪run方法

int Application::run(){   ......    while(!glview->windowShouldClose())    {        QueryPerformanceCounter(&nNow);        if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)        {            nLast.QuadPart = nNow.QuadPart;            director->mainLoop();            glview->pollEvents();        }        else        {            Sleep(0);        }    }......}

通过导演类来运行mainLoop方法。

void DisplayLinkDirector::mainLoop(){    if (_purgeDirectorInNextLoop)    {        _purgeDirectorInNextLoop = false;        purgeDirector();    }    else if (! _invalid)    {        drawScene();        // release the objects        PoolManager::getInstance()->getCurrentPool()->clear();    }}

drawScene方法

void Director::drawScene(){    ......    // draw the scene    if (_runningScene)    {        _runningScene->visit(_renderer, Mat4::IDENTITY, false);        _eventDispatcher->dispatchEvent(_eventAfterVisit);    }    // draw the notifications node    if (_notificationNode)    {        _notificationNode->visit(_renderer, Mat4::IDENTITY, false);    }    if (_displayStats)    {        showStats();    }    _renderer->render();    _eventDispatcher->dispatchEvent(_eventAfterDraw);    ......}

场景的visit方法

void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags){    ......    int i = 0;    if(!_children.empty())    {        sortAllChildren();        // 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        this->draw(renderer, _modelViewTransform, flags);        for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)            (*it)->visit(renderer, _modelViewTransform, flags);    }    else    {        this->draw(renderer, _modelViewTransform, flags);    }    ......}

先获取子节点,然后递归调用子节点的visit()函数,到了没有子节点的节点,开始调用draw()函数。
在节点Node的visit方法可以看到,要完成渲染树的绘制,就要遍历根节点,然后绘制自己的左根节点,再绘制根节点,最后绘制右根节点。继续追踪节点的draw方法

void Node::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags){}

居然什么都没做?原来draw是个虚函数,上面this指针调用的是其子类的draw方法,那就看Sprite的draw方法。

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);#if CC_SPRITE_DEBUG_DRAW        _customDebugDrawCommand.init(_globalZOrder);        _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this);        renderer->addCommand(&_customDebugDrawCommand);#endif //CC_SPRITE_DEBUG_DRAW    }}

看到draw函数在向renderer添加了两个绘制命令,其中quadCommand初始化后就可以加入到绘制命令中,customDebugDrawCommand传入了一个回调函数。再来看addCommand函数。

void Renderer::addCommand(RenderCommand* command){    int renderQueue =_commandGroupStack.top();    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);}

最终把绘制命令添加到_renderGroups数组中,然后在Renderer::render()中实现。

void Renderer::render(){    //Uncomment this once everything is rendered by new renderer    //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    //TODO setup camera or MVP    _isRendering = true;    if (_glViewAssigned)    {        // cleanup        _drawnBatches = _drawnVertices = 0;        //Process render commands        //1. Sort render commands based on ID        for (auto &renderqueue : _renderGroups)        {            renderqueue.sort();        }        visitRenderQueue(_renderGroups[0]);        flush();    }    clean();    _isRendering = false;}

Renderer::render()中实现了真正的绘制,首先清除,然后排序,最后通过visitRenderQueue方法执行_renderGroups数组中的绘制命令

void Renderer::visitRenderQueue(const RenderQueue& queue){    ssize_t size = queue.size();    for (ssize_t index = 0; index < size; ++index)    {        auto command = queue[index];        auto commandType = command->getType();        if(RenderCommand::Type::QUAD_COMMAND == commandType)        {            flush3D();            auto cmd = static_cast<QuadCommand*>(command);            //Batch quads            if(_numQuads + cmd->getQuadCount() > VBO_SIZE)            {                CCASSERT(cmd->getQuadCount()>= 0 && cmd->getQuadCount() < VBO_SIZE, "VBO is not big enough for quad data, please break the quad data down or use customized render command");                //Draw batched quads if VBO is full                drawBatchedQuads();            }            _batchedQuadCommands.push_back(cmd);            memcpy(_quads + _numQuads, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());            convertToWorldCoordinates(_quads + _numQuads, cmd->getQuadCount(), cmd->getModelView());            _numQuads += cmd->getQuadCount();        }        else if(RenderCommand::Type::GROUP_COMMAND == commandType)        {            flush();            int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();            visitRenderQueue(_renderGroups[renderQueueID]);        }        else if(RenderCommand::Type::CUSTOM_COMMAND == commandType)        {            flush();            auto cmd = static_cast<CustomCommand*>(command);            cmd->execute();        }        else if(RenderCommand::Type::BATCH_COMMAND == commandType)        {            flush();            auto cmd = static_cast<BatchCommand*>(command);            cmd->execute();        }        else if (RenderCommand::Type::MESH_COMMAND == commandType)        {            flush2D();            auto cmd = static_cast<MeshCommand*>(command);            if (_lastBatchedMeshCommand == nullptr || _lastBatchedMeshCommand->getMaterialID() != cmd->getMaterialID())            {                flush3D();                cmd->preBatchDraw();                cmd->batchDraw();                _lastBatchedMeshCommand = cmd;            }            else            {                cmd->batchDraw();            }        }        else        {            CCLOGERROR("Unknown commands in renderQueue");        }    }}

drawBatchedQuads()中调用OpenGL的API来进行渲染.

流程图如下:
这里写图片描述

0 0