场景的渲染

Node:visit

其作用是遍历整个场景渲染树。

部分代码如下

if(!_children.empty())
{sortAllChildren();// draw children zOrder < 0for(auto size = _children.size(); i < size; ++i){auto node = _children.at(i);if (node && node->_localZOrder < 0)node->visit(renderer, _modelViewTransform, flags);elsebreak;}// self drawif (visibleByCamera)this->draw(renderer, _modelViewTransform, flags);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);
}

如果子节点不为空,那么就对子节点进行排序。排序算法如下:

static void sortNodes(cocos2d::Vector<_T*>& nodes)
{static_assert(std::is_base_of<Node, _T>::value, "Node::sortNodes: Only accept derived of Node!");
#if CC_64BITSstd::sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {return (n1->_localZOrderAndArrival < n2->_localZOrderAndArrival);});
#elsestd::stable_sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {return n1->_localZOrder < n2->_localZOrder;});
#endif
}

我们对localZOrder 很熟悉,但是对localZOrderAndArrival就可能就会懵。实际上,localZOrderAndArrival在addChild的时候就会生成一个,在前面addChild的时候,生成的会小于后面addChild的。

排完序之后,对Node继续进行遍历,这里会优先遍历localZOrder小于0的子节点,然后调用visit函数递归遍历。

所以这里的遍历顺序就是,小于0的子节点 > 父节点本身 > 大于0的子节点

遍历完之后,调用draw函数。

Node的draw函数是空的。一般都是子类进行实现自己的draw函数。

Sprite::draw

举个简单的例子,Spirte的draw函数。

只看最重要的

_trianglesCommand.init(_globalZOrder,_texture,getGLProgramState(),_blendFunc,_polyInfo.triangles,transform,flags);renderer->addCommand(&_trianglesCommand);

这是cocos2dx 3.x改变最大的地方,draw函数只进行RenderCommon的生成。将生成好的命令存储到render中。

Render:render()

还记得前面调用场景的visit函数之后,调用了render函数么?

它在 CCRender这个类中。

_isRendering = true;if (_glViewAssigned)
{//Process render commands//1. Sort render commands based on IDfor (auto &renderqueue : _renderGroups){renderqueue.sort();}visitRenderQueue(_renderGroups[0]);
}
clean();
_isRendering = false;

_renderGroups中存储的就是需要发送给OpenGL进行渲染的指令集合。这里会先进行一次排序。(这个排序很关键,这也是决定了为啥有些不能合批的原因。)

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

排序的方法很简单。

TRANSPARENT_3D是3D的,根据景深来,这里不谈。

主要是下面

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

这里可以看得出,是根据globalZOrder来的。

globalZOrder是个好东西,也是个坏东西。他可以决定场景中的渲染先后顺序,也就决定了谁在前,谁在后。

可能有人会说,localZOrder不也是这样么?

localZOrder只是在同一个父节点上决定渲染先后顺序,它会受父节点的影响。

globalZOrder则是决定OpenGL的渲染先后顺序,也就是不管父节点是谁,它会凌驾于其他比他低的上面。

我们一般globalZOrder都是设置为0,那么设置为0,这里就不会进行排序,那么节点渲染的顺序就是之前加入RenderCommon的顺序,也就是之前对子节点排序的顺序。

Render:visitRenderQueue

遍历渲染队列。

怎么遍历的呢?

  1. RenderQueue::QUEUE_GROUP::GLOBALZ_NEG globalZOrder < 0
  2. RenderQueue::QUEUE_GROUP::OPAQUE_3D 3D不透明的对象
  3. RenderQueue::QUEUE_GROUP::TRANSPARENT_3D 3D对象透明的对象
  4. RenderQueue::QUEUE_GROUP::GLOBALZ_ZERO globalZOrder == 0
  5. RenderQueue::QUEUE_GROUP::GLOBALZ_POS globalZOrder > 0

Render:visitRenderQueue

根据上面的顺序,会依次进入processRenderCommand函数

在说这个函数之前,我们要了解cocos2dx的几种渲染命令。

  1. TRIANGLES_COMMAND:TrianglesCommand,渲染三角形,可以合并命令减少OpenGL的调用提高渲染效率
  2. MESH_COMMAND:MeshCommand,渲染3D
  3. GROUP_COMMAND:GroupCommand,创建渲染分支,使用_renderGroups[0]之外的RenderQueue。也是多个renderCOmmand的集合,里面的命令不参与全局排序。可用于子元素裁剪,绘制元素到纹理等。
  4. CUSTOM_COMMAND:CustomCommand,自定义渲染命令
  5. BATCH_COMMAND:BatchCommand,同时渲染多个使用同一纹理的图形,提高渲染效率
  6. PRIMITIVE_COMMAND:PrimitiveCommand,渲染自定义图元

TRIANGLES_COMMAND

其功能不是绘制三角形,比如我们的2D图片、Sprite类等都是用这个绘制。

这个命令有一个非常大的特点,也就是合批渲染。

合批渲染,本质上来说就是将符合要求的渲染命令合并成一个OpenGL Draw Call的调用。

每次渲染,都会将渲染命令发送给OpenGL进行渲染,每一次调用就会使得Draw Call +1。而Draw Call越高,画面掉帧越厉害。

if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
{// flush other queuesflush3D();auto cmd = static_cast<TrianglesCommand*>(command);// flush own queue when buffer is fullif(_filledVertex + cmd->getVertexCount() > VBO_SIZE || _filledIndex + cmd->getIndexCount() > INDEX_VBO_SIZE){CCASSERT(cmd->getVertexCount()>= 0 && cmd->getVertexCount() < VBO_SIZE, "VBO for vertex is not big enough, please break the data down or use customized render command");CCASSERT(cmd->getIndexCount()>= 0 && cmd->getIndexCount() < INDEX_VBO_SIZE, "VBO for index is not big enough, please break the data down or use customized render command");drawBatchedTriangles();}// queue it_queuedTriangleCommands.push_back(cmd);_filledIndex += cmd->getIndexCount();_filledVertex += cmd->getVertexCount();
}

drawBatchedTriangles 函数有点多,先看主要的部分

 // in the same batch ?
if (batchable && (prevMaterialID == currentMaterialID || firstCommand))
{CC_ASSERT(firstCommand || _triBatchesToDraw[batchesTotal].cmd->getMaterialID() == cmd->getMaterialID() && "argh... error in logic");_triBatchesToDraw[batchesTotal].indicesToDraw += cmd->getIndexCount();_triBatchesToDraw[batchesTotal].cmd = cmd;
}
else
{// is this the first one?if (!firstCommand) {batchesTotal++;_triBatchesToDraw[batchesTotal].offset = _triBatchesToDraw[batchesTotal-1].offset + _triBatchesToDraw[batchesTotal-1].indicesToDraw;}_triBatchesToDraw[batchesTotal].cmd = cmd;_triBatchesToDraw[batchesTotal].indicesToDraw = (int) cmd->getIndexCount();// is this a single batch ? Prevent creating a batch group thenif (!batchable)currentMaterialID = -1;
}

这里,就可以看的出,从队列中取出第一条指令,放入 _triBatchesToDraw 中。后续,会判断下一条的指令的ID是否和上一个相同,如果相同,那么就继续放入 _triBatchesToDraw中一个序列中。

/************** 3: Draw *************/
for (int i=0; i<batchesTotal; ++i)
{CC_ASSERT(_triBatchesToDraw[i].cmd && "Invalid batch");_triBatchesToDraw[i].cmd->useMaterial();glDrawElements(GL_TRIANGLES, (GLsizei) _triBatchesToDraw[i].indicesToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (_triBatchesToDraw[i].offset*sizeof(_indices[0])) );_drawnBatches++;_drawnVertices += _triBatchesToDraw[i].indicesToDraw;
}

这就是合批渲染的原理。

那么可以看得出,要合批渲染降低drawcall的条件是。

  1. 相邻的命令
  2. 有相同的ID

相邻的命令好理解,也就是localZOrder的顺序或者globalZOrder的顺序。

相同的ID呢?

void TrianglesCommand::generateMaterialID()
{// glProgramState is hashed because it contains://  *  uniforms/values//  *  glProgram//// we safely can when the same glProgramState is being used then they share those states// if they don't have the same glProgramState, they might still have the same// uniforms/values and glProgram, but it would be too expensive to check the uniforms.struct {GLuint textureId;GLenum blendSrc;GLenum blendDst;void* glProgramState;} hashMe;hashMe.textureId = _textureID;hashMe.blendSrc = _blendType.src;hashMe.blendDst = _blendType.dst;hashMe.glProgramState = _glProgramState;_materialID = XXH32((const void*)&hashMe, sizeof(hashMe), 0);
}

该函数就是ID的生成函数,或者说获取函数。

从该函数可以得知

  1. 相同的纹理
  2. 相同的混合模式(blend)
  3. 相同的shader

所以,要想能合批渲染,那么就必须满足上面的条件。

MESH_COMMAND

MeshCommand用于3D网格绘制,类CCSprite3D、Particle3DquadRender等等使用了MeshCommand绘制。

MeshCommand绘制可以分为两种,一种是绘制建模生成的3D模型,另一种是直接使用glDrawElements直接绘制。

不作详细说明。有兴趣可以深入研究下。

GROUP_COMMAND

GroupCommand本身并不绘制任何东西,GroupCommand是用于创建渲染分支,使得某些特殊的绘制可以单独设置绘制状态,不影响主渲染分支。类ClippingNode、RenderTexture、NodeGrid等待使用了GroupCommand。

Renderer类中的 _renderGroups 数组支持多个渲染队列,_renderGroups[0]是主渲染队列,其他为渲染分支。

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

可以看得出,其实就是递归调用visitRenderQueue函数进行渲染。

CUSTOM_COMMAND

CustomCommand是所有命令中最简单的一个,也是最灵活的一个,绘制的内容和方式完全交由我们自己决定。LayerColor、DrawNode、Skybox等待都是使用CustomCommand命令进行绘制的。

else if(RenderCommand::Type::CUSTOM_COMMAND == commandType)
{flush();auto cmd = static_cast<CustomCommand*>(command);CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_CUSTOM_COMMAND");cmd->execute();
}//std::function<void()> func;  execute

调用命令自己的execute函数进行渲染。

BATCH_COMMAND

BatchCommand可以同时绘制同一纹理的多个小图,用于2D绘制,类SpriteBatchNode和类ParticleBatchNode使用了BatchCommand减少OpenGL的调用

BatchCommand绘制主要由类TextureAtlas实现,TextureAtlas::drawQuads可以一次绘制多个使用同一纹理的矩形,Renderer处理BatchCommand也只是执行TextureAtlas::drawQuads函数

else if(RenderCommand::Type::BATCH_COMMAND == commandType)
{flush();auto cmd = static_cast<BatchCommand*>(command);CCGL_DEBUG_INSERT_EVENT_MARKER("RENDERER_BATCH_COMMAND");cmd->execute();
}void BatchCommand::execute()
{// Set material_shader->use();_shader->setUniformsForBuiltins(_mv);GL::bindTexture2D(_textureID);GL::blendFunc(_blendType.src, _blendType.dst);// Draw_textureAtlas->drawQuads();
}

PRIMITIVE_COMMAND

TMXLayer瓦片地图类使用了PrimitiveCommand进行绘制

Primitive图元,指的是OpenGL图元。

浅谈cocos2dx渲染方式相关推荐

  1. 浅谈CSRF攻击方式

    一.CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSR ...

  2. 浅谈 CSRF 攻击方式

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 一.CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨 ...

  3. 《转》浅谈CSRF攻击方式

    原文 一.CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为: ...

  4. 计算机技术如何影响现代教育,浅谈信息技术时代对现代教育的影响

    摘要:随着二十一世纪的到来,知识经济.信息时代也相继以清晰的步伐迈入,在这个日新月异的时代,传统的计算机技术不断发展,而新兴的信息科学技术也推动计算机技术的大步前进,其发展更是一日千里.而现在已经普遍 ...

  5. 计算机思维在数学中的应用,浅谈数学思维方式在计算机教学中的应用

    浅谈数学思维方式在计算机教学中的应用 (5页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.90 积分 浅谈数学思维方式在计算机教学中的应用高一微机组 ...

  6. 浅谈ssr服务器渲染、客户端渲染和预渲染以及前端打包部署

    浅谈ssr服务器渲染.客户端渲染和预渲染以及前端打包部署 1.客户端渲染: 2.服务器渲染(SSR) 3.预渲染 前端打包文件dist结合nginx和node原理图(个人见解) 今天下班在地铁上直到现 ...

  7. COCOS2DX 与 UNITY3D 的发展浅谈

    浅谈: 目前游戏界的发展趋势逐渐把游戏引擎当做一个比较成熟的中间件了,许多toolset和基础构建在不同游戏中是相通的,曾经有人比喻游戏引擎在不久的将来会像现在的操作系统一样,呈现出一种比较稳定的状态 ...

  8. aes key长度_原创 | 浅谈Shiro反序列化获取Key的几种方式

    点击"关注"了解更多信息 关于Apache Shiro反序列化 在shiro≤1.2.4版本,默认使⽤了CookieRememberMeManager,由于AES使用的key泄露, ...

  9. 浅谈pytorch 模型 .pt, .pth, .pkl的区别及模型保存方式 pth中的路径加载使用

    首先xxx.pth文件里面会书写一些路径,一行一个. 将xxx.pth文件放在特定位置,则可以让python在加载模块时,读取xxx.pth中指定的路径. Python客栈送红包.纸质书 有时,在用i ...

最新文章

  1. 极客新闻——06、刘润:给年轻人的10条工作建议
  2. 战略设计,必须首先把握产业的脉搏
  3. Linux中文件系统简介
  4. c语言程序大型案例分析,C语言程序设计习题解析与应用案例分析(第2版)
  5. LD(Levenshtein distance)莱文斯坦距离----编辑距离
  6. 其它综合-VMware虚拟机安装Ubuntu 19.04 版本
  7. ROS2——Win10上的rqt_graph无法正常运行
  8. opencv提供的带参数例程
  9. 央视推荐的护眼台灯是什么牌子?教育照明灯具品牌
  10. exe打包工具,封装exe安装程序--Inno Setup
  11. MySQL的锁到底有多少内容?和腾讯大佬的技术面谈,我真菜
  12. ubuntu14上nvidia 1080和 titan xp 驱动安装踩的坑
  13. 使用PowerDesigner反向生成数据模型
  14. Spark SQL读取Oracle的number类型的数据时精度丢失问题
  15. 写好作文的6大步骤,把写作文变成一件“轻松事儿”!
  16. 233. 数字1的个数
  17. 取消Irp引起蓝屏(BugCheck:0x18)
  18. 智慧环保在GIS系统中的应用体现
  19. 一款比较好用的审计系统软件
  20. 如何基于 Apache Doris 与 Apache Flink 快速构建极速易用的实时数仓

热门文章

  1. 唉,又一款老牌的代码编辑器,停止维护了...
  2. Python接口自动化测试框架(工具篇)-- 接口测试神器postman
  3. 【linux 检测端口的命令】curl、netstat和nc命令
  4. 前端路由hash和history
  5. 坦克大战--chapter01
  6. mock.js如何使用?简单易懂,一学就会,一篇文章即可出师
  7. 【解决error】sqlcmd不是内部或外部命令,也不是可运行的程序解决方法
  8. 平行泊车系统路径规划(2)
  9. html5屏蔽技术,关于EMC你不知道的那些屏蔽技术!
  10. linux lvcreate,LFCS:如何使用vgcreate,lvcreate和lvextend命令管理和创建LVM - 第11部分...