zoukankan      html  css  js  c++  java
  • cocos2d-x的渲染流程及原理

    环境: cocos3.10 Xcode

    UI元素的渲染流程图示:

     1. 从main进入到Application:run中,该方法下有个while循环,用于处理设定的每帧(FPS)刷新相关

    {// ...
        glview->retain();
        
        while (!glview->windowShouldClose()) {       
            director->mainLoop(); 
        }  
        return 0;
    }

    2. mainLoop做的事情如下:

    void Director::mainLoop()
    {
        if (! _invalid)
        {
            // 绘制场景
            drawScene(); 
            // 自动内存释放相关,每帧结束后,检测释放掉内存池中引用计数为1的节点
            PoolManager::getInstance()->getCurrentPool()->clear();
        }
    }

    3.绘制场景drawScene的主要代码:

    void Director::drawScene()
    {
        // 渲染当前运行场景
        if (_runningScene)
        {
            //clear draw stats
            _renderer->clearDrawStats();
            
            //render the scene
            _openGLView->renderScene(_runningScene, _renderer);
            
            _eventDispatcher->dispatchEvent(_eventAfterVisit);
        }
    }

    4. 通过renderScene会进入到Scene::render()中,它是处理渲染相关的主要接口:

    void Scene::render(Renderer* renderer, const Mat4* eyeTransforms, const Mat4* eyeProjections, unsigned int multiViewCount)
    {
        auto director = Director::getInstance();
        Camera* defaultCamera = nullptr;
        // 转换坐标系,将ui元素相对坐标转换为世界坐标,原因在于OpenGL ES坐标系与cocos世界坐标系一致
        const auto& transform = getNodeToParentTransform();
    
        for (const auto& camera : getCameras())
        {
            if (!camera->isVisible())
                continue;
    
            Camera::_visitingCamera = camera;
            if (Camera::_visitingCamera->getCameraFlag() == CameraFlag::DEFAULT)
            {
                defaultCamera = Camera::_visitingCamera;
            }
            // 遍历场景中UI树,会进入到Node::visit中,进行UI树遍历
            visit(renderer, transform, 0);
            // 绘制,会进入到Renderer::render中,执行绘制
            renderer->render();
            // 
            camera->restore();
            //
            for (unsigned int i = 0; i < multiViewCount; ++i)
                director->popProjectionMatrix(i);
        }
        Camera::_visitingCamera = nullptr;
    }

    5. visit主要用于UI树的遍历,遍历的规则使用的是中序(in-order)深度优先算法。即:

    1. 遍历左边的子节点(小于0) 

    2. 遍历根节点(等于0)

    3. 遍历右边的子节点(大于0)

    void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
    {
        // 如果不可见,则不再绘制
        if (!_visible)
        {
            return;
        }
      
    bool visibleByCamera = isVisitableByVisitingCamera(); int i = 0; if(!_children.empty()) {
         // 按照localZOrder排序,若相同则按照UI树顺序排序 sortAllChildren();
    // 遍历 localZOrder < 0 for(auto size = _children.size(); i < size; ++i) { auto node = _children.at(i); if (node && node->_localZOrder < 0) node->visit(renderer, _modelViewTransform, flags); else break; } // 遍历自身 if (visibleByCamera) this->draw(renderer, _modelViewTransform, flags); // 遍历localZOrder > 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); } _director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); }

    而遍历的方法主要通过sortAllChildren方法实现,比较的是localZOrder。

    // sortAllChildren
    void Node::sortAllChildren()
    {
        if (_reorderChildDirty)
        {
            sortNodes(_children);
            _reorderChildDirty = false;
        }
    }
    
    // sortNodes
    static void sortNodes(cocos2d::Vector<_T*>& nodes)
        {
            static_assert(std::is_base_of<Node, _T>::value, "Node::sortNodes: Only accept derived of Node!");
           // 按照localZOrder排序
            std::stable_sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {
                return n1->_localZOrder < n2->_localZOrder;
            });
        }

    6. 在visit排序遍历中,会依次根据localZOrder依次执行draw, 我们以Sprite为例:

    void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
    {
        // 判定纹理是否有效
        if (_texture == nullptr)
        {
            return;
        }
    
    #if CC_USE_CULLING
        // Don't calculate the culling if the transform was not updated
        auto visitingCamera = Camera::getVisitingCamera();
        auto defaultCamera = Camera::getDefaultCamera();
        if (visitingCamera == defaultCamera) {
            _insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY) || visitingCamera->isViewProjectionUpdated()) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
        }
        else
        {
            // XXX: this always return true since
            _insideBounds = renderer->checkVisibility(transform, _contentSize);
        }
    
        // 判定渲染的纹理是否在可见区域内
        if(_insideBounds)
    #endif
        {    
            _trianglesCommand.init(_globalZOrder,
                                   _texture,
                                   getGLProgramState(),
                                   _blendFunc,
                                   _polyInfo.triangles,
                                   transform,
                                   flags);
            // 将绘制命令添加到renderer绘制栈RenderQueue中
            renderer->addCommand(&_trianglesCommand);
        }
    }

    7. draw并未执行绘制,而是生成了RenderCommand绘制命令,放置到了RenderQueue中。

    void Renderer::addCommand(RenderCommand* command)
    {
        int renderQueue =_commandGroupStack.top();
        addCommand(command, renderQueue);
    }

    8. UI树遍历结束后,会生成一系列的绘制命令,此时Renderer::render()开始对绘制命令进行排序,绘制。

    这样做的目地主要使得渲染系统可以对绘制做一些优化,比如:使用相同纹理的Command可执行自动批绘制。

    void Renderer::render()
    {
        _isRendering = true;
        
        if (_glViewAssigned)
        {
            for (auto &renderqueue : _renderGroups)
            {
                // 执行遍历,遍历的是globalZOrder相关
                renderqueue.sort();
            }
            visitRenderQueue(_renderGroups[0]);
        }
        clean();
        _isRendering = false;
    }

    关于其遍历sort的实现看下:

    void RenderQueue::sort()
    {
        // 3D相关
        std::sort(std::begin(_commands[QUEUE_GROUP::TRANSPARENT_3D]), std::end(_commands[QUEUE_GROUP::TRANSPARENT_3D]), compare3DCommand);
        // GLOBALZ_NEG 表示globalZOrder < 0
        std::sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_NEG]), std::end(_commands[QUEUE_GROUP::GLOBALZ_NEG]), compareRenderCommand);
        // GLOBALZ_POS 表示globalZOrder > 0
        std::sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_POS]), std::end(_commands[QUEUE_GROUP::GLOBALZ_POS]), compareRenderCommand);
    }
    
    static bool compareRenderCommand(RenderCommand* a, RenderCommand* b)
    {
        return a->getGlobalOrder() < b->getGlobalOrder();
    }

    在这里,我们可以明白,RenderQueue对RenderCommand下globalZOrder不为0的,又执行了排序。我们可以总结到:

    1. 过多的使用globalZOrder会影响UI元素的绘制性能

    2. UI元素的绘制顺序并非仅受localZOrder的影响,也会受到globalZOrder的影响

    9. 通过 visitRenderQueue 这一步会进入到渲染流程,而最终执行到的代码为:processRenderCommand

    void Renderer::processRenderCommand(RenderCommand* command)
    {
        auto commandType = command->getType();
        if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
        {
            // ...
        }
        else if (RenderCommand::Type::MESH_COMMAND == commandType)
        {
            flush2D();
            // ...
        }
        else if(RenderCommand::Type::GROUP_COMMAND == commandType)
        {
            flush();
            int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
            CCGL_DEBUG_POP_GROUP_MARKER();
        }
        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::PRIMITIVE_COMMAND == commandType)
        {
            flush();
            auto cmd = static_cast<PrimitiveCommand*>(command);
            cmd->execute();
        }
    }

    可以看到,更具渲染类型不同,会调用不同的渲染方法等。其相关命令主要有:

    enum class Type
    {
        /** Reserved type.*/
        UNKNOWN_COMMAND,
        // 用于绘制1个或多个矩形区域,比如Sprite, ParticleSystem
        QUAD_COMMAND,
        /**Custom command, used for calling callback for rendering.*/
        CUSTOM_COMMAND,
        /*
        用来绘制一个TextureAtlas, 比如Label, TileMap等
        而对于TextureAtlas来说,它主要用于对同一纹理下多个精灵的封装
        */
        BATCH_COMMAND,
        // 用来包装多个RenderCommand,而其中的RenderCommand不会参与全局排序,多用于ClippingNode,RenderTexture等
        GROUP_COMMAND,
        /**Mesh command, used to draw 3D meshes.*/
        MESH_COMMAND,
        /**Primitive command, used to draw primitives such as lines, points and triangles.*/
        PRIMITIVE_COMMAND,
        /**Triangles command, used to draw triangles.*/
        TRIANGLES_COMMAND
    };

    ...

  • 相关阅读:
    C#实现程序的版本升级更新
    c#获取系统内存等信息
    C# 时间格式设置
    SMS(System Management Server)学习笔记。
    C#调用WMI获取本机MAC地址列表。
    sharepoint portal service 一处隐蔽应用。
    ubuntu安装显卡驱动后亮度不能调节问题
    linux下boost编译及链接到系统目录
    在windows下修改右键菜单以实现使用vs2010快速编译代码
    vs2010使用boost::interpocess编译出错
  • 原文地址:https://www.cnblogs.com/SkyflyBird/p/12577202.html
Copyright © 2011-2022 走看看