简介


Cocos2d 3.x之后,渲染从UI树进行了分离,想比较2.x而言,新的设计更优雅,灵活,易于拓展。

特点:

  • 将绘制逻辑分离,也就是从draw中分离出去,添加了绘制命令Command相关
  • 采用应用程序级别的视口裁剪,如果UI元素的坐标在视窗区域外,则不会添加到绘制命令栈中。这样的好处:
    • 减少绘制命令的数量
    • 减少绘制命令的排序时间
    • 减少对GPU的浪费,在OpenGL ES图元组装阶段会将视口之外的图元丢弃或裁剪。
  • 采用了自动批绘制BatchCommand, 主要针对的是使用相同纹理的UI元素
#mermaid-svg-baJyxy9XYw5ALyx6 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-baJyxy9XYw5ALyx6 .error-icon{fill:#552222;}#mermaid-svg-baJyxy9XYw5ALyx6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-baJyxy9XYw5ALyx6 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-baJyxy9XYw5ALyx6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-baJyxy9XYw5ALyx6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-baJyxy9XYw5ALyx6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-baJyxy9XYw5ALyx6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-baJyxy9XYw5ALyx6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-baJyxy9XYw5ALyx6 .marker.cross{stroke:#333333;}#mermaid-svg-baJyxy9XYw5ALyx6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-baJyxy9XYw5ALyx6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-baJyxy9XYw5ALyx6 .cluster-label text{fill:#333;}#mermaid-svg-baJyxy9XYw5ALyx6 .cluster-label span{color:#333;}#mermaid-svg-baJyxy9XYw5ALyx6 .label text,#mermaid-svg-baJyxy9XYw5ALyx6 span{fill:#333;color:#333;}#mermaid-svg-baJyxy9XYw5ALyx6 .node rect,#mermaid-svg-baJyxy9XYw5ALyx6 .node circle,#mermaid-svg-baJyxy9XYw5ALyx6 .node ellipse,#mermaid-svg-baJyxy9XYw5ALyx6 .node polygon,#mermaid-svg-baJyxy9XYw5ALyx6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-baJyxy9XYw5ALyx6 .node .label{text-align:center;}#mermaid-svg-baJyxy9XYw5ALyx6 .node.clickable{cursor:pointer;}#mermaid-svg-baJyxy9XYw5ALyx6 .arrowheadPath{fill:#333333;}#mermaid-svg-baJyxy9XYw5ALyx6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-baJyxy9XYw5ALyx6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-baJyxy9XYw5ALyx6 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-baJyxy9XYw5ALyx6 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-baJyxy9XYw5ALyx6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-baJyxy9XYw5ALyx6 .cluster text{fill:#333;}#mermaid-svg-baJyxy9XYw5ALyx6 .cluster span{color:#333;}#mermaid-svg-baJyxy9XYw5ALyx6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-baJyxy9XYw5ALyx6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

1.UI遍历
2.渲染
Director::mainLoop
Scene::render
Node::visit
Renderer::render
Application::run
Director::drawScene
Label::draw
Sprite::draw
...
Renderer::addCommand
RenderQueue
Renderer::visitRenderQueue
Renderer::processRenderCommand
CustomCommand
BatchCommand
TrianglesCommand
drawBatchedTriangles
GroupCommand
cmd::execute
cmd::execute

简单的理解其流程就是:

  • 通过mainLoop每秒60帧刷新
  • 通过drawScene绘制屏幕
  • 通过visit对UI节点树遍历
  • 每个UI元算通过draw生成绘制命令
  • RenderQueue队列中,绘制命令排序
  • 执行绘制命令,调用OpenGL绘制屏幕

mainLoop


Cocos2d-x 默认的是每秒60帧(fps)对屏幕进行刷新。

Application::run的while循环中, 按照FPS的设定,每帧调用的方法就是mainLoop

void Director::mainLoop() {if (! _invalid) {// 绘制场景drawScene();// 内存管理相关PoolManager::getInstance()->getCurrentPool()->clear();}
}

它主要做的两件事就是:

  • 渲染
  • 内存释放

关于内存管理相关,可参考:cocos2d-x 内存管理机制

绘制(渲染)


通过drawScene最后会进入到Scene::render中,绘制的流程简要分为两步:

  • UI遍历,主要通过Node::visit执行
  • 添加到绘制命令栈中, 主要通过不同UI元素的draw执行
void Scene::render(Renderer* renderer, const Mat4* eyeTransforms, const Mat4* eyeProjections, unsigned int multiViewCount) {auto director = Director::getInstance();Camera* defaultCamera = nullptr;// 转换坐标系,将ui元素相对坐标转换为世界坐标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;
}

从渲染的角度来说,如上代码做的两件主要事情:

  1. 调用visit 执行节点的UI树遍历
  2. 调用render 调用绘制命令相关开始渲染流程

UI树遍历


UI树节点的遍历,其规则采用的是中序(in-order)深度优先算法,也就是:

  • 遍历左边的子节点(localZOrder < 0)
  • 遍历根节点(localZOrder = 0)
  • 遍历右边的字节点(localZOrder > 0)
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags) {// 如果不可见,则不再绘制if (!_visible)return;uint32_t flags = processParentFlags(parentTransform, parentFlags);_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()) {// 该接口会根据节点的localZOrder进行排序sortAllChildren();// 遍历localZOrder < 0for(auto size = _children.size(); i < size; ++i) {auto node = _children.at(i);if (node && node->_localZOrder < 0)node->visit(renderer, _modelViewTransform, flags);elsebreak;}// 绘制自身if (visibleByCamera)this->draw(renderer, _modelViewTransform, flags);// 遍历localZOrder > 0for(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);
}

而针对于于UI树的遍历,主要接口为:

void Node::sortAllChildren() {if (_reorderChildDirty) {sortNodes(_children);_reorderChildDirty = false;_eventDispatcher->setDirtyForNode(this);}
}// 以localZOrder进行遍历
static void sortNodes(cocos2d::Vector<_T*>& nodes) {std::sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {return (n1->_localZOrder < n2->_localZOrder);});
}

UI树的遍历,有助于对项目功能的性能优化,比如:

  • 如果某些UI不需要显示,可以设置为隐藏
  • 减少下节点的嵌套复杂度,有助于提升节点的遍历速度

生成绘制命令


draw在3.x之后不再执行绘制,而是生成绘制命令,以Sprite::draw为例:

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 updatedauto visitingCamera = Camera::getVisitingCamera();auto defaultCamera = Camera::getDefaultCamera();if (visitingCamera == nullptr) {_insideBounds = true;}else 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->addCommand(&_trianglesCommand);}
}

通过addCommand添加的绘制命令队列RenderQueue中。

void Renderer::addCommand(RenderCommand* command) {int renderQueueID =_commandGroupStack.top();addCommand(command, renderQueueID);
}void Renderer::addCommand(RenderCommand* command, int renderQueueID) {// std::vector<RenderQueue> _renderGroups;    绘制命令栈_renderGroups[renderQueueID].push_back(command);
}

绘制命令排序


等UI元素遍历结束后,会开始执行绘制命令相关

void Renderer::render() {_isRendering = true;if (_glViewAssigned) {for (auto &renderqueue : _renderGroups) {// 绘制命令遍历,主要针对的是globalZOrderrenderqueue.sort();}visitRenderQueue(_renderGroups[0]);}clean();_isRendering = false;
}

绘制前,会对绘制栈中的命令进行遍历

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的原因在于:

前面的visit遍历中,已经对localZOrder执行了排序, 而在没有设定globalZorder的情况下,它默认就是0.

因此RenderQueue仅需要关注globalZOrder不为0的排序即可。

另外,关于RenderQueue的排序,让我们可以认识到globalZOrder的优劣:

  • 可以更灵活的改变层级的显示
  • 会影响绘制命令的排序,简单的说,UI元素的绘制不是根据localZOrder进行排序的

执行绘制命令


在遍历完成后,会通过visitRenderQueue进入到processRenderCommand

void Renderer::processRenderCommand(RenderCommand* command) {auto commandType = command->getType();if( RenderCommand::Type::TRIANGLES_COMMAND == commandType) {flush3D();auto cmd = static_cast<TrianglesCommand*>(command);if(_filledVertex + cmd->getVertexCount() > VBO_SIZE || _filledIndex + cmd->getIndexCount() > INDEX_VBO_SIZE) {drawBatchedTriangles();}}else if(RenderCommand::Type::GROUP_COMMAND == commandType) {flush();int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();CCGL_DEBUG_PUSH_GROUP_MARKER("RENDERER_GROUP_COMMAND");visitRenderQueue(_renderGroups[renderQueueID]);CCGL_DEBUG_POP_GROUP_MARKER();}else if(RenderCommand::Type::CUSTOM_COMMAND == commandType) {flush();auto cmd = static_cast<CustomCommand*>(command);CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_CUSTOM_COMMAND");cmd->execute();}else if(RenderCommand::Type::BATCH_COMMAND == commandType) {flush();auto cmd = static_cast<BatchCommand*>(command);CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_BATCH_COMMAND");cmd->execute();}
}

不同的UI元素会选择对应的命令执行执行OpenGL ES的渲染,最后将效果绘制到屏幕中。

结语

cocos2d-x 的渲染流程步骤说明下:

  1. 在游戏每帧刷新的情况下调用Director::mainLoop
  2. 通过Director::drawScene绘制当前场景
  3. 对场景下的所有节点采用的方式是: 先遍历后渲染
  4. 遍历通过Node::visit 按照LocalZOrder进行排序遍历,并生成不同类型的绘制命令
  5. 在遍历结束后,对绘制命令按照GlobalZorder进行排序遍历
  6. 然后对绘制命令会进行合批处理等,然后调用OpenGL开始渲染。

这个就是它大概的流程。从项目优化的角度来说,需要我们以后可以明白:

  • 如果UI不需要显示,可以直接进行隐藏
  • 减少节点的嵌套复杂度,有助于提升节点的遍历速度
  • 减少全局层级的设置,有助于提升绘制命令的遍历
  • 尤其针对于ClipingNode的使用,它使用的是自定义绘制命令,它会影响渲染合批

关于绘制命令相关,可以参考博客: cocos2d-x 绘制命令

最后,祝大家学习生活愉快!

cocos2d-x 渲染流程简要说明相关推荐

  1. cocos2d-x游戏引擎核心(3.x)----启动渲染流程

    (1) 首先,这里以win32平台下为例子.win32下游戏的启动都是从win32目录下main文件开始的,即是游戏的入口函数,如下: #include "main.h" #inc ...

  2. [转贴]Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程

    看了opengles有一段时间了,算是了解了一下下.然后,就在基本要决定还是回归cocos2dx 3.2的,看了这篇好文章,欣喜转之~ 推荐看原帖: Cocos2d-x3.2与OpenGL渲染总结(一 ...

  3. Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程

    转载自:https://www.2cto.com/kf/201409/336234.html 最近几天,我都在学习如何在Cocos2d-x3.2中使用OpenGL来实现对图形的渲染.在网上也看到了很多 ...

  4. 前端百题斩【031】——从渲染流程认识重绘和回流

    写该系列文章的初衷是"让每位前端工程师掌握高频知识点,为工作助力".这是前端百题斩的第31斩,希望朋友们关注公众号"执鸢者",用知识武装自己的头脑. 在&quo ...

  5. react16 渲染流程

    前言 react升级到16之后,架构发生了比较大的变化,现在不看,以后怕是看不懂了,react源码看起来也很麻烦,也有很多不理解的地方. 大体看了一下渲染过程. react16架构的变化 react ...

  6. 【逆向】UE4 渲染流程分析

    UE4作为当今商业引擎界的大佬,渲染和图形质量一直是首屈一指的水准,但是相对于unity来说UE4基本上是一套完整方案提供,不通过源码修改对渲染进行定制的可能性比较小,而且同时UE4这方面的文档很少, ...

  7. COCOS学习笔记--Cocos引擎渲染流程

    近期在研究Cocos引擎的渲染流程.在这里将其整个渲染流程进行一下梳理: 梳理之前我们要知道一些东西,就是我们的Cocos引擎是通过使用OpenGL的一些API来进行渲染绘制的,所以假设我们要彻底理解 ...

  8. 【Android 应用开发】Paint 渲染 之 BitmapShader 位图渲染 ( 渲染流程 | CLAMP 拉伸最后像素 | REPEAT 重复绘制图片 | MIRROR 绘制反向图片 )

    文章目录 1. 位图渲染 BitmapShader 简介 ( 1 ) 位图渲染综述 ( ① 三种方式 : Shader.TileMode.CLAMP | Shader.TileMode.REPEAT ...

  9. html将页面分成三块_导航渲染流程你真的知道从输入URL到页面展示发生了什么吗?(内附思维导图)...

    导航渲染流程 通过这篇文章当你被问到从URL输入到页面展示都发生了什么的时候,基本都能对答如流,甚至可以一直深入的说,说到面试官闭麦哈哈哈~ 以下是本文的思维导图,直接拿图「点个赞」再走吧 ~ 求求了 ...

  10. 5渲染判断if_React 16 渲染流程

    学过微机的同学都应该很熟悉「中断」这个概念: CPU 正常运行程序时,内部事件或外设提出中断请求: CPU 予以响应,同时保护好 CPU 执行主程序的现场,转入调用中断服务程序: 调用完毕后恢复现场. ...

最新文章

  1. 8、JavaScript深入浅出——数据类型
  2. 平衡树 - FHQ 学习笔记
  3. 洛谷 P2463 [SDOI2008]Sandy的卡片 解题报告
  4. (精)【ACM刷题之路】POJ题目详细多角度分类及推荐题目
  5. leetcode 有效的字母异位词
  6. UIWebView清空本地缓存
  7. ArcGIS10.5资源分享
  8. Java菜鸟教程 if语句和switch语句
  9. opencv实时录像+视频打码
  10. 浅谈IPv4/IPv6转换技术
  11. 论文查重检测系统的原理是什么?
  12. 求Kinetics400,AVA,prcv2018,Moments in time challenge2018,youtube8M,ActivityNet数据集,原始视频
  13. 17个支持图片外链的免费相册
  14. 【mongoDB】一一一一安装报错1058解决方案
  15. 测地距geodesic
  16. 信号处理琐碎知识点 — OFDM 正交频分复用
  17. java开发面试评语
  18. 国产软件曝光美国51区真相 UFO?天坑?荒漠神秘圆圈?
  19. 京东数科,走不一样的路,看不一样的风景
  20. Suricata+ELK集群监控办公网流量

热门文章

  1. poj a b java_POJ2752-Java(小猫非常著名,很多夫妇Byteland翻山越
  2. Spring Data JPA 一对一 聚合查询
  3. 运动检测-基于帧间比较减少灯光影响-opencv-python
  4. 租赁市场的上海方(浦东/张江)
  5. 2006百度之星程序设计大赛试题-变态比赛规则(解答)
  6. week9|查阅文章 Mac os M2芯片 arm64架构|安装paddlepaddle问题
  7. MATLAB | 如何绘制这种带竖线散点的核密度图
  8. 干货|开卷有益,大神级程序猿都在看的17本书
  9. 邮件伪造之SPF绕过的5种思路
  10. 【尚硅谷】电商数仓V4.0丨大数据数据仓库项目实战【学习记录】第一节