浅谈cocos2dx渲染方式

news2025/1/15 20:01:35

场景的渲染

在这里插入图片描述

Node:visit

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

部分代码如下

if(!_children.empty())
{
    sortAllChildren();
    // draw children zOrder < 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;
    }
    // self draw
    if (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_64BITS
    std::sort(std::begin(nodes), std::end(nodes), [](_T* n1, _T* n2) {
        return (n1->_localZOrderAndArrival < n2->_localZOrderAndArrival);
    });
#else
    std::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 ID
    for (auto &renderqueue : _renderGroups)
    {
        renderqueue.sort();
    }
    visitRenderQueue(_renderGroups[0]);
}
clean();
_isRendering = false;

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

void RenderQueue::sort()
{
    // Don't sort _queue0, it already comes sorted
    std::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 queues
    flush3D();

    auto cmd = static_cast<TrianglesCommand*>(command);
    
    // flush own queue when buffer is full
    if(_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 then
    if (!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图元。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/379197.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HU4056H耐压高达28V,具有电源OVP功能的1A单节锂离子电池线性充电IC

产品概述 HU4056H是一款完整的采用恒定电流/恒定电压的高压、大电流、单节锂离子电池线性充电 IC。最高耐压可达 28V&#xff0c; 6.5V 自动过压保护&#xff0c;充电电流可达 1A。 由于采用了内部 PMOSFET 架构&#xff0c;加上防倒充电路&#xff0c;所以不需要外部隔离二…

【博学谷学习记录】超强总结,用心分享丨人工智能 机器学习 集成学习错题总结

目录题目1&#xff1a;下面关于提升树的说法哪个是正确的&#xff1f;题目2&#xff1a;下面关于随机森林和梯度提升集成方法的说法哪个是正确的&#xff1f;集成学习主要有哪几种框架&#xff1f;工作过程是&#xff1f;题目1&#xff1a;下面关于提升树的说法哪个是正确的&am…

亿发软件:钉钉移动ERP业务在线,审批、管理更方便!

钉钉系统是企业级智能移动办公平台&#xff0c;平台覆盖大中小微各量级企业&#xff0c;帮助中国企业移动办公管理。企业无需复杂的部署操作&#xff0c;在对应的功能制定流程和相关负责人即可。 亿发企业ERP管理系统于2022年与钉钉系统做了对接&#xff0c;提供一站式的企业管…

VIF-Benchmark: All infrare and visible image fusion method in one framework

VIF_Benchmark Github 地址: https://github.com/Linfeng-Tang/VIF-Benchmark 完整Project下载地址&#xff1a;https://download.csdn.net/download/fovever_/87514164 我们把所有主流的基于深度学习的红外和可见光图像融合方法都集成在了这个框架中。 这些方法包括&#xff1…

MM32开发教程(LED灯)

文章目录前言一、MM32介绍和STM32的区别二、板载LED灯原理图三、代码编写总结前言 今天将为大家介绍一款性能高体积小的MM32&#xff0c;这款开发板出自百问网团队。他就是灵动的MM32F3273&#xff0c;他体积非常小便于携带。 有128KB的SRAM、512KB的Flash、而且还支持双TypeC…

Mutual-Structure for Joint Filtering

以前的联合/引导滤波器将参考图像中的结构信息直接传输到目标图像&#xff0c;它的主要缺点&#xff1a;两个图像中可能存在完全不同的边缘。简单地将所有图像传递给目标可能会出错。 作者对结构不一致性问题&#xff0c;提出了相互结构的概念&#xff0c;以增强基于目标图像和…

【项目管理】始于需求,而终于需求的最终落地

每个产品都是需要一系列需求的慢慢搭建&#xff0c;并且需求对于一个产品来说是非常重要的&#xff1b;我们对需求进行分配以及执行&#xff0c;需要一整个团队的配合以及执行&#xff0c;才可以最终达到一个好的效果&#xff1b; 项目一般是由一系列的需求组成的&#xff0c;需…

548、RocketMQ详细入门教程系列 -【消息队列之 RocketMQ (二)】 2023.02.28

目录一、Java 访问 RocketMQ 实例1.1 引入依赖1.2 消息生产者1.3 消息消费者1.4 启动 Name Server1.5 启动 Broker1.6 运行 Consumer1.7 运行 Producer二、参考链接一、Java 访问 RocketMQ 实例 RocketMQ 目前支持 Java、C、Go 三种语言访问&#xff0c;按惯例以 Java 语言为例…

SQL数据库权限管理-10个数据库角色

为便于管理数据库中的权限&#xff0c;SQL 数据库提供了服务器角色、数据库角色、用户等来划分不同用户拥有的权限差异。今天给大家介绍数据库角色对应的权限。 数据库级角色 存在两种类型的数据库级角色&#xff1a; 数据库中预定义的“固定数据库角色”可以创建的“用户定…

CSO面对面丨中核华辉刘博:应对大型央国企数字化转型道路上必须攻克的安全难题

“极致”&#xff0c;一直是大型央国企网络安全工作建设追求的目标。随着我国数字化转型全面走深向实&#xff0c;网络安全风险、数据管理、层出不穷的网络攻击&#xff0c;为各领域大型央国企数字化转型带来了更多的挑战。如何充分发挥优势、携手各方构筑网络安全屏障、提升安…

Codeforces Round #854 by cybercats (Div. 1 + Div. 2)

A. Recent Actions给出n个格子&#xff0c;从上到下是1~n&#xff0c;其他的n1~。。。不在格子内。给出m个操作&#xff0c;若该操作的数字不在格子内&#xff0c;那就将它拿到格子的第一个位置&#xff0c;同时格子第n个位置的数被挤下去&#xff1b;若操作的数字在格子内&…

Java简单的生成/解析二维码(zxing qrcode)

Hi I’m Shendi Java简单的生成/解析二维码&#xff08;zxing qrcode&#xff09; 在之前使用 qrcode.js 方式生成二维码&#xff0c;但在不同设备上难免会有一些兼容问题&#xff0c;于是改为后端&#xff08;Java&#xff09;生成二维码图片 这里使用 Google 的 zxing包 Jar…

基于STM32的DHT11温湿度控制系统仿真设计

基于STM32的DHT11温湿度控制系统仿真设计(仿真程序报告讲解&#xff09;演示视频1.主要功能2.仿真3. 程序4. 设计报告1主控制器选择5.设计内容 百度云网盘下载链接仿真图proteus 8.9程序编译器&#xff1a;keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;C0076 演示…

Android Qcom Display学习(十三)

该系列文章总目录链接与各部分简介&#xff1a; Android Qcom Display学习(零) 在上一篇中dump GraphicBuffer中&#xff0c;知道了护眼模式中调用setColorTransform应用于每一层Layer&#xff0c;于是想往上了解一些&#xff0c;color是针对屏幕的&#xff0c;不是对单个Layer…

[YOLO] yolo博客笔记汇总(自用

pip下载速度太慢&#xff0c;国内镜像&#xff1a; 国内镜像解决pip下载太慢https://blog.csdn.net/weixin_51995286/article/details/113972534​​​​​​​ YOLO v2和V3 关于设置生成anchorbox&#xff0c;Boundingbox边框回归的过程详细解读 YOLO v2和V3 关于设置生成an…

Airbnb系列三《Managing Diversity in Airbnb Search》 搜索多样性

abstract 搜索系统中一个长期的问题是结果多样性。从产品角度讲&#xff0c;给用户多种多样的选择&#xff0c;有助于提升用户体验及业务指标。 多样性需求和模型的目标是相矛盾的&#xff0c;因为传统ctr模型是 point wise&#xff0c;只看单个相关性不管相邻之间item差异。 …

Jvisualvm监控Tomcat以及相关参数优化

Tomcat阻塞模式 阻塞模式&#xff08;BIO&#xff09; 客户端和服务器创建一个连接&#xff0c;它就会创建一个线程来处理这个连接&#xff0c;以为这客户端创建了几个连接&#xff0c;服务端就需要创建几个线程来处理你&#xff0c;导致线程会产生很多&#xff0c;有很多线程…

数学小课堂:无穷小(平均速度和瞬间速度的关系)

文章目录 引言I 速度1.1 平均速度1.2 瞬间速度(某一时刻特定的速度)1.3 解释飞箭是静止的悖论II 导数2.1 概念2.2 导数的现实影响2.3 微积分的意义III 无穷小3.1 贝克莱挑战牛顿(无穷小悖论)3.2 无穷小的定义引言 柯西和魏尔斯特拉斯给出的无穷小的定义: 它不是零;它的绝对…

vue2+element封装rules, 支持json多层级

一、封装介绍 封装前景&#xff1a;表单内容多、表单类型重复且校验项较多 下面就参考element的例子写个实例 element地址&#xff1a;https://element.eleme.cn/2.15/#/zh-CN/component/form 实现效果如下: 今天给大家写三种表单校验实现方式 普通表单实现、正常定义rules…

【svg】引入svg(非图标)

这里写目录标题直接插入页面—— 有各层svg内容并可赋值属性css 背景图 ——不可更改各层svg属性创建标签&#xff08;动态添加&#xff09;——可改属性但是还不如直接插入不常用&#xff08;没弄明白&#xff09;目的&#xff1a;如果直接以图片的方式引用svg 不能改变内层sv…