【Cocos2d-x 3.x】 3.0版本的全新绘制系

在Cocos2d-x 3.0的版本之前,Cocos2d-x的每个元素的绘制逻辑均分布于每个元素内部的draw()方法里,紧密依赖UI树的遍历;3.0开始,对绘制部分进行了重构,新的代码将绘制部分从UI树的遍历中分离出来,使得绘制系统设计更优雅、更灵活和易于扩展。

UI树的遍历

这是渲染系统比较重要的一个职责,遍历UI树中每一个元素,遍历的有两个重要的目的,一是遍历的顺序基本决定了元素被绘制的顺序,二是在遍历过程中实现元素的模型视图变换矩阵的计算,计算结果供OpenGL ES渲染管线计算顶点位置。

在3D渲染系统中,元素可以用任何顺序被绘制,最终图形惯性能够根据元素的Z轴,使用深度测试进行正确的绘制;在2D图形绘制中,各个元素在渲染管线中具有相同的Z深度,这些元素之间的层级以及绘制关系必须依赖同一个逻辑的深度,Cocos2d-x使用localZOrder来表示元素的逻辑深度,UI树的遍历采用中序的深度优先算法进行遍历。

遍历顺序及特点为:

遍历左边的子节点;

遍历根节点;

遍历右边子节点。

Cocos2d-x按元素的层级关系组织了一颗“”二叉树“”,左边的子节点表示逻辑深度小于0的子元素,右边的“”子节点“”表示逻辑深度大于0的子元素,这样,就能通过逻辑深度的顺序来表示元素被绘制的顺序,参见Node::visit()方法:

void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    // quick return if not visible. children won't be drawn.
    if (!_visible)
    {
        return;
    }

    uint32_t flags = processParentFlags(parentTransform, parentFlags);

    // IMPORTANT:
    // To ease the migration to v3.0, we still support the Mat4 stack,
    // but it is deprecated and your code should not rely on it
    _director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    _director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);

    bool visibleByCamera = isVisitableByVisitingCamera();

    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
        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);
    }

    _director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    // FIX ME: Why need to set _orderOfArrival to 0??
    // Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
    // reset for next frame
    // _orderOfArrival = 0;
}

可见,Node先会将所有子节点排序,如果两个子节点的localZOrder值相同,则按照它们出现的顺序来表示绘制的顺序:

bool nodeComparisonLess(Node* n1, Node* n2)
{
    return( n1->getLocalZOrder() < n2->getLocalZOrder() ||
           ( n1->getLocalZOrder() == n2->getLocalZOrder() && n1->getOrderOfArrival() < n2->getOrderOfArrival() )
           );
}

void Node::sortAllChildren()
{
    if (_reorderChildDirty)
    {
        std::sort(std::begin(_children), std::end(_children), nodeComparisonLess);
        _reorderChildDirty = false;
    }
}

排序之后,Node就会按照小于0,根节点,然后是大于0的节点这样的顺序来一次绘制每个节点。

渲染命令和渲染队列

概述

新的绘制流程大致分为三步:生成绘制命令、对绘制命令进行排序、执行绘制命令。

生成绘制命令

在UI树的遍历的时候,对每一个元素生成一个绘制命令,RenderCommand表示一个绘制类型,它定义了如何去绘制一个元素,

class CC_DLL RenderCommand
{
public:

    enum class Type
    {
        UNKNOWN_COMMAND,
        QUAD_COMMAND,
        CUSTOM_COMMAND,
        BATCH_COMMAND,
        GROUP_COMMAND,
        MESH_COMMAND,
        PRIMITIVE_COMMAND,
        TRIANGLES_COMMAND
    };

    /**
     * init function, will be called by all the render commands
     */
    void init(float globalZOrder, const Mat4& modelViewTransform, uint32_t flags);

    /** Get Render Command Id */
    inline float getGlobalOrder() const { return _globalOrder; }
    //...其他定义省略
};

Type这个enum class里定义了几种绘制的类型,一般情况下,每个UI元素会关联0个或1个RenderCommand,并重写基类Node::draw()方法,在draw方法中将绘制命令发送给render。

void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
#if CC_USE_CULLING
    // Don't do calculate the culling if the transform was not updated
    _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;

    if(_insideBounds)
#endif
    {
        _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform, flags);
        renderer->addCommand(&_quadCommand);

#if CC_SPRITE_DEBUG_DRAW
        _debugDrawNode->clear();
        Vec2 vertices[4] = {
            Vec2( _quad.bl.vertices.x, _quad.bl.vertices.y ),
            Vec2( _quad.br.vertices.x, _quad.br.vertices.y ),
            Vec2( _quad.tr.vertices.x, _quad.tr.vertices.y ),
            Vec2( _quad.tl.vertices.x, _quad.tl.vertices.y ),
        };
        _debugDrawNode->drawPoly(vertices, 4, true, Color4F(1.0, 1.0, 1.0, 1.0));
#endif //CC_SPRITE_DEBUG_DRAW
    }
}

Sprite::draw()方法示意了这样绘制分离的方式,它只负责将绘制命令发送给render,并不会执行任何的GL命令,render会将RenderCommand放入一个栈中,等所有的UI元素遍历结束,render才开始执行所有的RenderCommand。

绘制命令的排序

绘制命令被执行的顺序不一定是UI元素被遍历的顺序,Cocos2d-x使用一个新的globalZOrder直接设置元素的绘制顺序,因此,UI元素绘制的顺序首先由globalZOrder决定,

然后再由遍历的顺序决定。

绘制命令执行

最后,render对经过排序的绘制命令执行绘制。 对于一般的RenderCommand,按顺序执行;对于Sprite使用的QuadCommand,如果两个QuadCommand相邻且使用相同的纹理、着色器等,render会将它们组合合成一个QuadCommand,这种情况称为自动批绘制。自动批绘制减少了绘制次数,提升了绘制性能。

绘制命令、绘制队列和绘制类

RenderCommand

class CC_DLL RenderCommand
{
public:

    enum class Type
    {
        UNKNOWN_COMMAND,
        QUAD_COMMAND,
        CUSTOM_COMMAND,
        BATCH_COMMAND,
        GROUP_COMMAND,
        MESH_COMMAND,
        PRIMITIVE_COMMAND,
        TRIANGLES_COMMAND
    };

    /**
     * init function, will be called by all the render commands
     */
    void init(float globalZOrder, const Mat4& modelViewTransform, uint32_t flags);

    /** Get Render Command Id */
    inline float getGlobalOrder() const { return _globalOrder; }
};

每一个R enderCommand实例中,都包含一个globalZOrder属性,它是决定绘制顺序的重要属性。还有一个属性是Type,引擎内置了多个RenderCommand类型,其中QUAD_COMMAND用来绘制1个或多个矩形区域(比如说Sprite和ParticalSystem),相邻的QuadCommand如果使用相同的纹理,则可以实现自动批绘制。

BATCH_COMMAND用来绘制一个TextAtlas,如Label、TileMap等。 GROUP_COMMAND可以包装多个RenderCommand的集合,而且GroupCommand中的每一个RenderCommand都不会参与全局的排序。

RenderQueue

class RenderQueue {
public:
    enum QUEUE_GROUP
    {
        GLOBALZ_NEG = 0,
        OPAQUE_3D = 1,
        TRANSPARENT_3D = 2,
        GLOBALZ_ZERO = 3,
        GLOBALZ_POS = 4,
        QUEUE_COUNT = 5,
    };

public:
    RenderQueue()
    {
        clear();
    }
    void push_back(RenderCommand* command);
    ssize_t size() const;
    void sort();
    RenderCommand* operator[](ssize_t index) const;
    void clear();
    inline std::vector<RenderCommand*>& getSubQueue(QUEUE_GROUP group) { return _commands[group]; }
    inline ssize_t getSubQueueSize(QUEUE_GROUP group) const { return _commands[group].size();}

    void saveRenderState();
    void restoreRenderState();

protected:

    std::vector<std::vector<RenderCommand*>> _commands;

    //Render State related
    bool _isCullEnabled;
    bool _isDepthEnabled;
    GLboolean _isDepthWrite;
};

场景中每一个UI元素的绘制命令被发送到一个RenderQueue的绘制栈上,由类的定义可知,RenderQueue中存储着一组RenderCommand。而在QUEUE_GROUP中定义了每个RenderCommand应该添加到vector的哪个对应索引下:

static bool compareRenderCommand(RenderCommand* a, RenderCommand* b)
{
    return a->getGlobalOrder() < b->getGlobalOrder();
}

static bool compare3DCommand(RenderCommand* a, RenderCommand* b)
{
    return  a->getDepth() > b->getDepth();
}

void RenderQueue::sort()
{
    // Don't sort _queue0, it already comes sorted
    std::sort(std::begin(_commands[QUEUE_GROUP::TRANSPARENT_3D]), std::end(_commands[QUEUE_GROUP::TRANSPARENT_3D]), compare3DCommand);
    std::sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_NEG]), std::end(_commands[QUEUE_GROUP::GLOBALZ_NEG]), compareRenderCommand);
    std::sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_POS]), std::end(_commands[QUEUE_GROUP::GLOBALZ_POS]), compareRenderCommand);
}

这是RenderQueue的排序方式,有sort()函数体的代码可以看到,QUEUE_GROUP::TRANSPARENT_3D表示的是3D的物体的绘制命令,将这些绘制命令排序时用到compare3DCommand,比较他们的Depth;而QUEUE_GROUP::GLOBALZ_NEG和QUEUE_GROUP::GLBALZ_POS分别表示globalZOrder小于0和大于0的绘制命令。

Render类实际上维护着一个RenderQueue的数组,每一个RenderQueue对应一组RenderCommand或者一个GroupCommand。

GroupCommand

class CC_DLL GroupCommand : public RenderCommand
{
public:
    GroupCommand();
    ~GroupCommand();

    void init(float depth);

    inline int getRenderQueueID() const {return _renderQueueID;}

protected:
    int _renderQueueID;
};

每个GroupCommand都对应着一个单独的RenderQueue,由_renderQueueID标识。

Renderer

class CC_DLL Renderer
{
public:
    static const int VBO_SIZE = 65536;
    static const int INDEX_VBO_SIZE = VBO_SIZE * 6 / 4;

    static const int BATCH_QUADCOMMAND_RESEVER_SIZE = 64;
    static const int MATERIAL_ID_DO_NOT_BATCH = 0;

    /** Adds a `RenderComamnd` into the renderer */
    void addCommand(RenderCommand* command);

    /** Adds a `RenderComamnd` into the renderer specifying a particular render queue ID */
    void addCommand(RenderCommand* command, int renderQueue);

    /** Pushes a group into the render queue */
    void pushGroup(int renderQueueID);

    /** Pops a group from the render queue */
    void popGroup();

    /** returns whether or not a rectangle is visible or not */
    bool checkVisibility(const Mat4& transform, const Size& size);
protected:

    void processRenderCommand(RenderCommand* command);
    void visitRenderQueue(RenderQueue& queue);

    std::stack<int> _commandGroupStack;

    std::vector<RenderQueue> _renderGroups;

    MeshCommand*              _lastBatchedMeshCommand;
    std::vector<TrianglesCommand*> _batchedCommands;
    std::vector<QuadCommand*> _batchQuadCommands;

    GroupCommandManager* _groupCommandManager;
};

Renderer类主要的部分如上,_commandGroupStack保留了一个RenderQueue的栈,开始一个GroupCommand时,会对应新建一个新的RenderQueue的Id入栈,默认情况下,addCommand会将RenderCommand添加到_commandGroupStack栈的最后一个元素所对应的RenderQueue中,这样就能将所有子元素的RenderCommand添加到单独一个RenderQueue中,当分组结束,GroupCommand从_commandGroupStack上移除自己,后续的RederCommand将继续加入之前的RederQueue中:

int GroupCommandManager::getGroupID()
{
    //Reuse old id
    for(auto it = _groupMapping.begin(); it != _groupMapping.end(); ++it)
    {
        if(!it->second)
        {
            _groupMapping[it->first] = true;
            return it->first;
        }
    }

    //Create new ID
//    int newID = _groupMapping.size();
    int newID = Director::getInstance()->getRenderer()->createRenderQueue();
    _groupMapping[newID] = true;

    return newID;
}

这是GroupCommand获取groupID的方法,在GroupCommandManager的init()方法中,说明了GroupCommand所在的_commandGroupStack索引不能是0 :

bool GroupCommandManager::init()
{
    //0 is the default render group
    _groupMapping[0] = true;
    return true;
}

而在Renderer::Renderer()中,说明了_commandGroupStack[0]表示默认的绘制队列:

_commandGroupStack.push(DEFAULT_RENDER_QUEUE); // DEFAULT_RENDER_QUEUE == 0

Renderer还有两个函数,当创建一个GroupCommand并将其作为一个普通的RenderCommand发送到当前的RenderQueue上,GroupCommand会在Renderer上创建新的RenderQueue,并调用pushGroup()方法将其renderQueueId添加到_commandGroupStack栈中,结束时,调用popGroup():

void Renderer::pushGroup(int renderQueueID)
{
    CCASSERT(!_isRendering, "Cannot change render queue while rendering");
    _commandGroupStack.push(renderQueueID);
}

void Renderer::popGroup()
{
    CCASSERT(!_isRendering, "Cannot change render queue while rendering");
    _commandGroupStack.pop();
}

个人的一些源码阅读理解,欢迎路过的各位大大指出错误~ 后续还会有更新~

时间: 2025-01-02 16:16:56

【Cocos2d-x 3.x】 3.0版本的全新绘制系的相关文章

7、Cocos2dx 3.0游戏开发找小三之3.0版本的代码风格

重开发者的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27691337 Cocos2d-x代码风格 前面我们已经多次提到 Cocos2d-x 源自于 Cocos2d-iPhone.Cocos2d-iPhone 是一个十分出色的游戏引擎,许多优秀的 iOS平面游戏都基于 Cocos2d-iPhone 开发,而它的实现语言是 Objective-C.因此,Cocos2d-x 也就沿袭了 Objective-C 的

8、Cocos2dx 3.0游戏开发找小三之3.0版本的内存管理

重开发者的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27693365 复杂的内存管理 移动设备上的硬件资源十分有限,内存尤为宝贵,开发者必须十分慎重地利用内存,避免不必要的消耗,更要防止内存泄漏. 基于 Cocos2d-iPhone 的 Objective-C风格的内存管理是 Cocos2d-x 的一个特色. 把 Objective-C 的内存管理方式引入 C++,使得游戏开发的内存管理难度下降了个层次.

从零开始教你制作cocos2dx-3.0 版本FlappyBird(可上架版本)(包括添加广告等)完整制作过程

本文原创:转载请注明地址:http://blog.csdn.net/zp522123428/article/details/29357851 一.coco2dx-3.0环境搭建 这个网上很多参考教程也属于基础部分我就不再敷述,这里给大家提供csdn博客上的大家可以参考一下,我大概看了一下比较全面了: 参考地址:http://blog.csdn.net/aa4790139/article/details/8086635 二.coco2dx-3.0下创建项目 1.进入coco2dx-3.0的目录下,

升级PowerShell至4.0版本

为了更好的使用Cmder v1.2,不得不升级PowerShell为4.0. 不知道Cmder的,可以点击这里:https://github.com/cmderdev/cmder 和 逆天神器 cmder. Powershell是运行在Windows机器上实现系统和应用程序管理自动化的命令行脚本环境. 需要.NET环境的支持, 同时支持.NET对象.当前PowerShell有5个版本,分别为1.0.2.0.3.0.4.0.5.0 如果系统是Windows 7或者Windows Server 20

CI框架3.0版本以后,前后台分离的方法。

笔者认为,CI框架官方其实并没有考虑这个前后台分离的问题,所以没有官方的分离方法.而且,2.0版本的分离,也被官方认为这是一个bug.所以在前后台分离这个问题上,其实并不如thinkphp框架. 在CI框架2.0版本时的,大多数人认为可以这样做,前后台分离是可以直接在controller下,分admin和home目录的. 这是2.0版本时 其实,今天我用的是3.0版本的CI框架.在前后台分离这个问题,我也纠结了比较久.但是为了项目结构目录的清晰,还是要做前后台分离的. 我大概是做了这样一个分离.

主流区块链技术特点及Fabric V0.6&V1.0版本特点

声明:文章内容来源于网络. 一.主流区块链技术特点 二.Hyperledger的fabric V0.6总体架构: 对应的0.6版本的运行时架构: 0.6版本的架构特点是: 结构简单: 应用-成员管理-Peer的三角形关系,主要业务功能全部集中于Peer节点:    架构问题:由于peer节点承担了太多的功能,所以带来扩展性.可维护性.安全性.业务隔离等方面的诸多问题,所以0.6版本在推出后,并没有大规模被行业使用,只是在一些零星的案例中进行业务验证: 三.Hyperledger的fabric V

Question2Answer 1.7.0 版本的中文语言包

http://www.androidren.com/index.php?qa=212&qa_1=question2answer-1-7-0-版本的中文语言包 最近Question2Answer发布了1.7.0 版本.我已经安装好.感兴趣的可以上http://android-studio.cn 体验.感觉变化不是很大. 所以http://androidren.com 暂时不考虑升级.不过1.7.0版本的中文语言包我做好了.有需要的朋友可以下载: Q2A 1.7.x - Chinese Simpli

重写lucene.net的分词器支持3.0.3.0版本

lucene.net中每个分词器都是一个类,同时有一个辅助类,这个辅助类完成分词的大部分逻辑.分词类以Analyzer结尾,辅助类通常以Tokenizer结尾.分类词全部继承自Analyzer类,辅助类通常也会继承某个类. 首先在Analysis文件夹下建立两个类,EasyAnalyzer和EasyTokenizer. 1 using Lucene.Net.Analysis; 2 using System.IO; 3 4 namespace LuceneNetTest 5 { 6 public

Android 6.0版本以后运行时权限提醒

Android发布6.0以后对app运行所需要的权限提示进行了友好的提示,类似于苹果系统,比如在某个页面要用到打电话的权限,会弹出一个提示框,提示你是否需要同意这个权限,如果同意则app就有了打电话的权限,既可以拨打电话了,不同意则不能拨打电话,只能去设置中勾选,在6.0以前,权限配置都是在AndroidManifest.xml文件中添加例如: <?xml version="1.0" encoding="utf-8"?> <manifest xml