cocos2d-x 绘制命令RenderComand、RenderQueue和Renderer的说明

版本: cocos3.10


简介


cocos2d-x 渲染机制简介简要说明了下UI元素的渲染流程:

  • 通过Node::visit对节点遍历,然后调用draw生成绘制命令

  • draw中,通过Renderer::addCommand将命令添加到RenderQueue绘制命令栈中

  • 通过Renderer::render对调用绘制队列进行排序

  • 排序完成后,通过Renderer::processRenderCommand执行不同的绘制命令进行渲染

本篇文章主要讲述:

  • RenderCommand 不同元素绘制命令的生成

  • RenderQueue 绘制队列,用于管理不同的绘制命令并进行排序

  • Render 负责绘制队列的管理以及执行排序,渲染等

下面开始!


RenderCommand


它是针对于不同节点而进行特定绘制方式的封装。

class CC_DLL RenderCommand {
public:
    // 绘制命令类型
    enum class Type {
        UNKNOWN_COMMAND,
        QUAD_COMMAND,				// 用于绘制多边形
        CUSTOM_COMMAND,			// 用于自定义绘制,通过回调进行渲染
        BATCH_COMMAND,			// 用于在纹理图集中批量绘制
        GROUP_COMMAND,			// 用于组相关,并不执行绘制,只是包含多个命令相关
        MESH_COMMAND,				// 用于绘制3D网格
        PRIMITIVE_COMMAND,  // 用于绘制线、点、三角形等
        TRIANGLES_COMMAND   // 用于绘制精灵等
    };
    void init(float globalZOrder, const Mat4& modelViewTransform, uint32_t flags);
    // 获取globalZOrder
    float getGlobalOrder() const { return _globalOrder; }
    // 获取命令类型
    Type getType() const { return _type; }
protected:
    RenderCommand();
    virtual ~RenderCommand();

每个RenderCommand的实例中都会包含的两个属性:

  • globalZOrder: 用于对绘制命令的排序
  • type: 不同的绘制命令类型,会用于做不同的绘制处理。

cocos2d-x提供的继承于RenderCommand的主要接口有:

CustomCommand
RenderCommand
GroupCommand
TrianglesCommand
BatchCommand
MeshCommand
PrimitiveCommand
QuadCommand

这些接口对应着RenderCommand中的枚举类型,需要注意:每个UI元素对应着至少一个RenderCommand

不同的RenderCommand进行组合完成一个UI元素的绘制。

注意绘制命令GroupCommand, 它并不会执行OpenGL的绘制命令, 它主要用于包装多个RenderCommand

其他的绘制命令大概的作用如下:

  • BatchCommand: 对于SpriteBatchNodeParticleBatchNode的绘制
  • TrianglesCommand: 对于Sprite的绘制
  • CustomCommand: 主要针对于自定义绘制
  • QuadCommand: 对于Skin的绘制
  • GroupCommand: 用来包装多个RenderCommand的集合

这些绘制命令,都会被添加到RenderQueue的绘制命令栈中。

CustomCommand

用于自定义绘制,比如ClippingNodeDrawNodeLayerColor

class CC_DLL CustomCommand : public RenderCommand {
public:
    CustomCommand();
    ~CustomCommand();
  
    void init(float globalZOrder, const Mat4& modelViewTransform, uint32_t flags);
    void init(float globalZOrder);
    void execute();
    std::function<void()> func;
};

通过 func的变量,当处理CustomCommand时,只调用func的回调方法相关

void Renderer::processRenderCommand(RenderCommand* command) {
    if(RenderCommand::Type::CUSTOM_COMMAND == commandType) {
        flush();
        auto cmd = static_cast<CustomCommand*>(command);
        cmd->execute();
    }
}
void CustomCommand::execute() {
    if(func) {
        func();
    }
}

GroupCommand

就如前文所说,它并不会执行具体的绘制命令,而是对多个的RenderCommand进行了包装。

在cocos2d-x中,它可以被用来实现子元素裁剪的ClippingNode ,实现元素绘制到纹理的RenderTexture等。

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

    void init(float globalOrder);
    int getRenderQueueID() const { return _renderQueueID; }
    
protected:
    int _renderQueueID;
};

该命令并不是添加到主绘制栈中,而是会添加到新的RenderQueue中。

/*
在初始化的时候,会调用一个GroupCommandManager的管理类
该类主要用于缓存已有的队列,减少重复创建
*/
void GroupCommand::init(float globalOrder) {
    _globalOrder = globalOrder;
    auto manager = Director::getInstance()->getRenderer()->getGroupCommandManager();
    manager->releaseGroupID(_renderQueueID);
    _renderQueueID = manager->getGroupID();
}

int GroupCommandManager::getGroupID() {
    // 重用原有的ID
    if (!_unusedIDs.empty()) {
        int groupID = *_unusedIDs.rbegin();
        _unusedIDs.pop_back();
        _groupMapping[groupID] = true;
        return groupID;
    }
    // 创建新的绘制栈
    int newID = Director::getInstance()->getRenderer()->createRenderQueue();
    _groupMapping[newID] = true;

    return newID;
}

int Renderer::createRenderQueue() {
    RenderQueue newRenderQueue;
    _renderGroups.push_back(newRenderQueue);
    return (int)_renderGroups.size() - 1;
}

添加新的绘制栈,这样有利于:

  • GroupCommand内的RenderCommand不受主绘制栈的影响
  • 可以针对于某些特殊的绘制单独的设置绘制状态
  • 在绘制命令遍历的时候,主绘制栈也不会再遍历GroupCommand内的其他绘制命令

在被addCommand后,程序会调用pushGroup将该命令的绘制栈ID添加的Renderer的绘制栈ID数组中

Renderer *renderer =  Director::getInstance()->getRenderer();
renderer->addCommand(&_groupCommand);
// 将queueId添加到绘制栈ID集合中
renderer->pushGroup(_groupCommand.getRenderQueueID());

void Renderer::pushGroup(int renderQueueID) {
    _commandGroupStack.push(renderQueueID);
}

这样做的目的主要在于执行GroupCommand的时候,可以通过对应的queueId获取到RenderQueue的绘制栈信息。

void Renderer::processRenderCommand(RenderCommand* command) {
    if(RenderCommand::Type::GROUP_COMMAND == commandType) {
        flush();
        // 获取queueId
        int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
        // 根据queueId获取指定的绘制栈信息,然后访问绘制
        visitRenderQueue(_renderGroups[renderQueueID]);
    }
}

而GroupCommand在不被使用后,需要调用popGroup进行释放

Renderer *renderer = director->getRenderer();
renderer->popGroup();

void Renderer::popGroup()
{
    _commandGroupStack.pop();
}

TrianglesCommand

它主要用于绘制Sprite,简要的看下代码:

class CC_DLL TrianglesCommand : public RenderCommand {
public:
    /**The structure of Triangles. */
    struct Triangles
    {
        /**Vertex data pointer.*/
        V3F_C4B_T2F* verts;
        /**Index data pointer.*/
        unsigned short* indices;
        /**The number of vertices.*/
        int vertCount;
        /**The number of indices.*/
        int indexCount;
    };
    // ...
}

Sprite的绘制本质是使用的三角形,原因在于: cocos2d-x 使用的是OpenGL ES版本,它是OpenGL的一个分集,它不支持四边形的绘制,所以需要两个三角形组合绘制一个四边形,知道下这个就行了。


RenderQueue

它主要用于管理绘制命令RenderCommand, 并对这些绘制命令按照globalZOrder进行排序。

class RenderQueue {
public:
    // 类型,共有5种
    enum QUEUE_GROUP
    {
        GLOBALZ_NEG = 0, 	    // globalZOrder小于0的对象
        OPAQUE_3D = 1,			// globalZOrder为0的不透明3D对象
        TRANSPARENT_3D = 2,		// globalZOrder为0的3D对象
        GLOBALZ_ZERO = 3,	    // globalZOrder为0的2D对象
        GLOBALZ_POS = 4,		// globalZOrder大于0的对象
        QUEUE_COUNT = 5,
    };

public:
    RenderQueue();
    void push_back(RenderCommand* command);
    void sort();
    void clear();
    
protected:
    std::vector<RenderCommand*> _commands[QUEUE_COUNT];
};

在UI节点遍历时,不同的元素会通过draw生成绘制命令RenderCommand, 然后通过addCommand添加到这里。

在绘制队列中,会对绘制命令RenderCommand进行再一次的排序。

  • 排序globalZOrder小于0的
  • 排序globalZOrder大于0的
void RenderQueue::sort()
{
	// globalZOrder小于0的对象
    std::stable_sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_NEG]), std::end(_commands[QUEUE_GROUP::GLOBALZ_NEG]), compareRenderCommand);
    // globalZOrder大于0的对象
    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) {
    return a->getGlobalOrder() < b->getGlobalOrder();
}

没有对globalZOrder等于0的排序的原因是: 程序默认globalZOrder为0,而节点visit遍历排序localZOrder的时候,本质上就是对globalZOrder == 0进行的排序。


Renderer


它主要负责绘制命令相关的遍历和渲染。大概的作用有:

  • draw中调用addCommand 将生成的绘制命令放到RenderQueue绘制队列中
  • 通过Renderer::render对会展队列中的命令排序且执行渲染流程
class CC_DLL Renderer {
public:
    // 添加命令
    void addCommand(RenderCommand* command);
    void addCommand(RenderCommand* command, int renderQueueID);
  
    // 用于组命令相关
    void pushGroup(int renderQueueID);
    void popGroup();
    int createRenderQueue();
    // 渲染
    void render();
protected:
    void visitRenderQueue(RenderQueue& queue);
  	void processRenderCommand(RenderCommand* command);
  
    std::vector<RenderQueue> _renderGroups;
    std::stack<int> _commandGroupStack;
}

绘制队列可有多个,但索引为0的被称为主绘制栈,也就是_renderGroups[0]

void Renderer::render() {
    _isRendering = true;   
    if (_glViewAssigned) {
        for (auto &renderqueue : _renderGroups) {
            // 遍历
            renderqueue.sort();
        }
      	// 调用主绘制栈开始渲染
        visitRenderQueue(_renderGroups[0]);			
    }
    clean();
    _isRendering = false;
}

Renderer会有多个RenderQueue, 通过_renderGroups来保存绘制命令栈的信息。而_commandGroupStack来保存对应的绘制栈的ID信息。

void Renderer::addCommand(RenderCommand* command) {
    // 获取绘制栈ID
    int renderQueueID =_commandGroupStack.top();
    addCommand(command, renderQueueID);
}

// 将绘制命令添加到指定ID的绘制栈中
void Renderer::addCommand(RenderCommand* command, int renderQueueID) {
    _renderGroups[renderQueueID].push_back(command);
}

void RenderQueue::push_back(RenderCommand* command) {
  	// 获取绘制命令的globalZOrder
    float z = command->getGlobalOrder();
    // 根据globalZOrder的不同添加到不同的绘制队列中
    if(z < 0) {
        _commands[QUEUE_GROUP::GLOBALZ_NEG].push_back(command);
    } else if(z > 0) {
        _commands[QUEUE_GROUP::GLOBALZ_POS].push_back(command);
    }
}

理解可能有误,欢迎大佬指出,祝大家学习生活愉快!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹤九日

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值