cocoscreator 2.4.x版本 drawcall优化 第一期(掌握控制drawcall数量的必要知识)

cocoscreator 2.4.x版本 drawcall优化 第一期(掌握控制drawcall数量的必要知识),第1张

1,测试环境

2,为何drawcall多会影响性能

3, 哪些组件支持渲染

4,影响drawcall的因素

5,一句话介绍如何减少drawcall

6,哪些渲染组件不会被渲染

7,减少drawcall的理论(放在第二期)

8,理论指导实践,实践印证理论,demo实 *** (放在第三期)

9,总结(放在第三期)

「测试环境」 :

1Mac 系统

2cocoscreator 24x版本

「为何drawcall多会影响性能?」

Drawcall: 绘制调用,指cpu调用图形绘制接口命令gpu进行图形绘制

「每一次绘制前,CPU要准备绘制参数(状态)比如色彩通道(color filter),绘图方式(shader)等复杂的数据处理,然后Drawcall,如果有大量drawcall,cpu会很“忙”,而gpu的处理能力很强,这时他可能闲置,不能充分发挥应有的能力,导致性能下降。」

「哪些组件支持渲染:」 因为一个drawcall是一次cpu调用图形绘制接口命令 gpu进行图形绘制渲染的过程,所以需要了解cocoscreator中哪些组件支持渲染,才能更好的控制drawcall

「影响drawcall的因素:」

1,层级(zindex)

2,材质(Material)(shander,贴图(纹理),混合模式(blend))。只有拥有相同材质的渲染节点 才可能进行批处理,贴图,shader 决定了材质,而层级则决定了相同的材质 是否能 进行合并处理 即合并网格(mesh) 合并drawcall,

「一句话介绍如何减少drawcall:」 绘制状态的变化 是导致drawcall增多的 主要原因。cocoscreator认为要以深度(zindex)优先的方式对渲染组件进行渲染,并且cocoscreator认为相同的材质可以被批量渲染。所以具有相同材质的并且连续的渲染节点 可以合并渲染 减少drawcall

「连续:」

1,层级相同添加顺序相邻,

2,层级不同 中间层级没有其他材质的渲染组件。比如 a的层级是1 b的层级是3 在 1-3层级之间没有其他材质的 渲染组件

「影响drawcall的因素:」

「1,渲染节点(zindex)层级」

zIndex是节点的层级是用来对节点进行排序的关键属性,它决定一个节点 在兄弟节点之间的层级,和谁被优先渲染。

1) zIndex 的取值介于 ccmacroM IN_ZINDEX 和 ccmacroMAX_ZINDEX 之间

即 - mathpow(2,15) 和 mathpow(2,15)-1之间。

实际 *** 作中一般是 -1 到 n n一般不会超过1000

2)父节点主要根据节点的 zIndex 和添加次序来排序,拥有更高 zIndex 的节点将被排在后面(后被渲染先被渲染的图在后被渲染的图下面),如果两个节点的 zIndex 一致,先添加的节点会稳定排在另一个节点之前。排在前面的节点先被渲染,也就是说两张图层级相同 先添加的会先被渲染 显示出来的结果是 在后被渲染的图的下面。

3)节点在 children 中的顺序决定了其渲染顺序。父节点永远在所有子节点之前被渲染

4)node节点放在Canvas或者父节点的zindex默认值是0

5)决定节点层级的另一个因素是siblingIndex 他的权重低于 zIndex 当我们在编辑器上编辑借点的时候 兄弟节点之间的zIndex相同,为什么会出现一个先被渲染一个后被渲染呢 ,就是因为 siblingIndex 不同,排在前面的siblingIndex要小一些后面的要大一些 最终后面的后选择然 层级就在 前面的上边。 也就是说 zindex 其决定性作用,zIndex相同 就比较siblingIndex来判定最终层级。

「2,材质」

1)纹理(贴图)

2)shander:渲染器,能够读懂的点和颜色的对应关系的程序,简单来说就是绘图的方式)

只有拥有相同材质的物体才可以进行批处理。因此,如果你想要得到良好的批处理效果,你需要在程序中尽可能地复用材质和物体。

如果你的两个材质仅仅是纹理不同,那么你可以通过 纹理拼合 *** 作来将这两张纹理拼合成一张大的纹理。一旦纹理拼合在一起,你就可以使用这个单一材质来替代之前的两个材质了。

「哪些渲染组件不会被渲染」

cocoscreator 认为 透明度 === 0 或者 active = false 的渲染组件 不会被渲染。

启动 Cocos Creator 并使用 Cocos 开发者帐号登录以后,就会打开 Dashboard 界面,在这里你可以新建项目、打开已有项目或者获得帮助信息。

界面总览

上图所示的就是 Cocos Creator 的 Dashboard 界面,包括以下几种选项卡:

最近打开项目: 列出最近打开项目,第一次运行 Cocos Creator 时,这个列表是空的,会提示 新建项目 的按钮。

新建项目: 选择这个选项卡,会进入到 Cocos Creator 新项目创建的指引界面。

打开其他项目: 如果你的项目没有在最近打开的列表里,你也可以点击这个按钮来浏览和选择你要打开的项目。

帮助: 帮助信息,一个包括各种新手指引信息和文档的静态页面。

下面我们来依次介绍这些分页面。

最近打开项目

你可以通过 最近打开项目 选项卡快速访问近期打开过的项目。第一次运行 Cocos Creator 时,这个列表是空的,在界面上会显示 新建项目 的按钮。你可以在创建了一些项目后回来,并看到你新建的项目出现在列表里。

当你的鼠标悬停在一个最近打开项目的条目上时,会显示出可以对该项目进行 *** 作的行为:

点击 打开 在 Cocos Creator 编辑器中打开该项目

点击 关闭 将该项目从最近打开项目列表中移除,这个 *** 作不会删除实际的项目文件夹。

此外,当鼠标点击选中或悬停在项目上时,你能够在 Dashboard 下方的状态栏看到该项目所在路径。

新建项目

你可以在 新建项目 选项卡里创建新的 Cocos Creator 项目。

在 新建项目 页面,我们首先需要选择一个项目模板,项目模板会包括各种不同类型的游戏基本架构,以及学习用的范例资源和脚本,来帮助你更快进入到创造性的工作当中。

注意:早期的 Cocos Creator 版本中还没有很多可选择的项目模板,我们会随着 Cocos Creator 功能逐渐完整持续添加更多模板为用户提供方便。

点击选择一个模板,你可以在页面下方看到该模板的描述。

在页面下方你可以看到项目名称和项目存放地址。可以在项目路径输入框手动输入项目存放路径和项目名称,路径的最后一节就是项目名称。

你也可以点击 浏览 按钮,打开浏览路径对话框,在你的本地文件系统中选择一个位置来存放新建项目。

一切都设置好后,点击 新建项目 按钮来完成项目的创建。Dashboard 界面会被关闭,然后新创建的项目会在 Cocos Creator 编辑器主窗口中打开。

打开其他项目

如果你在 最近打开项目 页面找不到你的项目,或者刚刚从网上下载了一个从未打开过的项目时,你可以通过 打开其他项目 选项卡按钮在本地文件系统浏览并打开项目。

点击 打开其他项目 后,会d出本地文件系统的选择对话框,在这个对话框中选中你的项目文件夹,并选择打开就可以打开项目。

注意:Cocos Creator 使用特定结构的文件夹来作为合法项目标识,而不是使用工程文件。选择项目时只要选中项目文件夹即可。

帮助

你可以通过 帮助 页面访问 Cocos Creator 用户手册和其他帮助文档。

CocosCreate可以使用引擎提供的系统事件脚本,来实现鼠标点哪去哪的功能。当使用鼠标点击对象时,游戏会检测并把点击点信息发送至相应的勾子函数,如onClick;onMouseDown、onMouseMemory等;在这些函数里可以读取到点击点的位

全文共5000+字,分为8个章节,由本人历时一周整理而来。由于篇幅问题,将本文分为8个章节分开发布。全文 ( 不 ) 详细描述了cocoscreator 引擎的240版本中,web平台的js部分引擎的渲染流程。请将文章配合源码一起食用! ​由于我尚在学习引擎源码中,文章可能有不正确的部分,所以我会不断更新内容。如有错误或补充,请留言交流! 全部章节链接: 一: 渲染流程中用到的核心类 二 : 渲染流程详解 三: RenderFlow 的运行逻辑 四: Assembler 的作用 五: ModelBatcher 数据合批 六: 材质系统 全文

在 A 场景下的 game-manager 节点上的任意脚本里,写入:

这样,你的 game-manager 节点,就不会在切换场景的时候销毁了

是在新场景的最底部(也就是所有的最顶层,除了一个系统节点外)

也就是说,我们不需要考虑 toast-manager 的图层会被新场景的元素覆盖的问题

简单来说,如果你想不断去执行某个函数,或者每隔一段时间去执行一次某函数,甚至只执行一次,这时,我们就会用到定时器。比如在游戏开发中每隔一段时间要刷新一些数据,或者要随时间的变化而做一些逻辑判断时,就常常用到定时器。

定时器就是指定时间间隔调用指定的函数,去完成特定的功能。

Cocos2d-x定时器为游戏提供定时事件和定时调用服务。所有的Node对象都知道如何调度和取消调度对象,所以也有人管定时器叫调度器。

二使用定时器有以下几个好处:

1每当Node不再可见或已从场景中移除时,定时器会停止。Cocos2d-x暂停时,定时器也会停止,比如Node被删除或setVisible(false)时与其绑定的定时器会被停止,使用Director的Pause()方法暂停游戏时定时器也会被停止掉;

2当Cocos2d-x重新开始时,定时器也会自动继续启动,如执行Director的Resume()方法时;

3Cocos2d-x封装了一个供各种不同平台使用的定时器,就是说cocos的定时器是跨平台的。

三Cocos2d-x调度器分3种:

默认调度器:scheduleUpdate()

自定义调度器:schedule()

单次调度器:scheduleOnce()

四下面我们就来依次看看这3种调度器是怎么使用的:

一).默认调度器:schedulerUpdate()

默认调度器使用Node的刷新事件update方法,该方法在每帧绘制之前都会被调用一次。

我们在使用默认调度器(schedulerUpdate)时候,需要重载Node的update方法来执行自己的逻辑代码。如果需要停止这个调度器,可以使用unschedulerUpdate()方法。

接下来举个例子来看看默认调度器的具体实现,新建一个cocos工程,我们只在HelloWorldSceneh和HelloWorldScenecpp这两个文件中做修改:

1)首先在头文件HelloWorldSceneh里声明重写的update方法,这个方法就是默认调度器的回调函数:

//声明系统自带的schedule回调函数

virtual void update(float t);

2)在HelloWorldScenecpp文件中实现update方法,可以在该方法中执行我们自己的逻辑代码,这里我只做下打印:

//实现schedule的回调函数

void HelloWorld::update(float t)

{

log("update!");

}

3)通过scheduleUpdate()方法执行调度器,该方法是与其回调方法update()绑定到一起的,也就是说该方法一旦被某个Node执行,该Node的update()方法就会被执行,我把它写在cpp文件的 HelloWorld::init()方法中:

//执行系统调度器

this->scheduleUpdate();

运行结果:

可以看到,执行scheduleUpdate()方法后,update()回调方法会不断执行打印,那么这个update()回调方法多久执行一次呢?前面说了,该方法在每帧绘制之前都会被调用一次,所以理论上在无卡顿情况下每秒执行60次update()方法。

二).自定义调度器:schedule()

顾名思义,该调度器就是我们可以对调度进行相关属性的自定义,比如调度事件触发的时间间隔、触发次数等等。

举个例子:

1)在h文件中声明自定义调度器的回调函数,注意该方法不是系统的而是我们自己写的,所以方法名也是我们自己起的:

//声明自定义调度器回调函数

virtual void customScheduleCallback(float t);

2)在cpp文件中实现刚刚声明的回调函数,这里也只做打印:

//实现回调函数

void HelloWorld::customScheduleCallback(float t)

{

log("customScheduleCallback!");

}

3)通过schedule()方法执行自定义调度器,该方法有4个参数:

schedule(SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay)

参数1:selector为要绑定的回调函数;

参数2:interval为事件触发时间间隔;

参数3:repeat为触发一次事件后还会触发的次数,默认值为kRepeatForever,表示无限触发次数;

参数4:delay表示第一次触发之前的延时。

我还把它写在cpp文件的 HelloWorld::init()方法中:

//执行自定义调度器

this->schedule(schedule_selector(HelloWorld::customScheduleCallback),20,5,3);

运行结果:

注意,第三个参数repeat值为5,表示触发一次事件后还会触发5次,所以一共有6次打印,而且每一次打印输出的时间间隔为2秒。

三).单次调度器:scheduleOnce()

单次调度器只进行一次调度,即只执行一次回调函数。该调度器的使用方法和之前2种调度器基本一致,都是声明回调方法、实现回调方法、使用调度器。

举个例子:

1)在h文件中声明单次调度器的回调函数:

//声明单次调度器回调函数

virtual void onceScheduleCallback(float t);

2)在cpp文件中实现回调函数:

//实现回调函数

void HelloWorld::onceScheduleCallback(float t)

{

log("onceScheduleCallback!");

}

3)通过scheduleOnce()方法执行单次调度器,该方法有2个参数:

参数1:要绑定的回调函数

参数2:第一次触发之前的延时

//执行单次调度器

this->scheduleOnce(schedule_selector(HelloWorld::onceScheduleCallback), 3);

运行结果:

可以看到,程序运行3秒后看到了一次打印输出,之后便不再打印了。

最后,一句话总结,使用调度器无非就是使用3个方法:scheduleUpdate()、schedule()、scheduleOnce(),另外写好需要绑定的回调函数,完事!

以上。

点击阅读全文

打开CSDN,阅读体验更佳

Cocos2d-x中关于schedule函数的一点理解_tsbyj的博客

cocos 2dx---schedule(五) zion--6135的博客 238 bool HelloWorld::init() { // 1 super init first if ( !Scene::init() ) { return false; } printf("1\n"); //this->schedule(CC_SCHEDULE_SELECTOR(HelloWorld::

cocos 2dx-Lua使用schedule定时器_平淡风云的博客_lua sched

使用方法很简单 local scheduler = require("frameworkscheduler") local handler = schedulerscheduleGlobal(function() -- 想做的事情 end, 02) -- 调用间隔 schedulerunscheduleGlobal(handler) -- 取消定时器

cocos2d-x学习笔记(13)--schedule rar

cocos2d-x学习笔记(13)--schedule rar

cocos Creator计时器schedule的使用和坑

在游戏开发中,经常会用到计时器,在cocos引擎中,为我们默认提供了多种计时器的使用。在最新的cocos开发工具Cocos Creator中,我们有4种计时器可以使用,分别是js自带的setTimeOut、interval以及cocos的schedule和scheduleOnce,setTimeOut和scheduleOnce都是执行几次 *** 作,指定一定时间后执行,interval和schedule

继续访问

cocos2dx三种定时器的使用以及停止scheduleUpdate,scheduleOnce,schedule(改)

(Sam: 原文作者有笔误,影响理解,这里做了正确的修改) 首先,什么是定时器呢?或许你有时候会想让某个函数不断的去执行,或许只是执行一次,获取你想让他每隔几秒执行一次,ok,这些都可以统统交给定时器来解决。 cocos2dx中有三种定时器:schedule,scheduleUpdate,scheduleOnce。了解其功能便会发现定时器真是太方便了,废话不多说,我们逐一学习一下。

继续访问

CocosCreator-Schedule计时器-设定及触发原理

计时器 JavaScript自带的定时任务 setTimeout 作用:设置一个定时器,在指定时间(毫秒)后触发,调用传入的回调函数。 参数类型:(function/code, delayTime, args…) function: 回调函数 code: 字符串形式的代码,会使用eval()执行,因此不推荐使

继续访问

cocoscreate使用定时器schedule

这是标题普通对象使用定时器注册定时器注销定时器特殊对象使用定时器注册定时器注销定时器 普通对象使用定时器 注册定时器 // 一般会在节点生命周期里的start函数里注册定时器 start () { // schedule函数注册定时器,第一个参数是回调函数,第二个参数是间隔时间,第三个参数是执行多少次,也可以不写,不写就是不停的执行 thisschedule(thisonTimeFuntion, 1, 6); // 这行代码表示每秒执行一次onTimeFunti

继续访问

最新发布 Cocos Creator 源码解读之Schedule

creator 里面的计时器相信大家不陌生,但是了解它的原理是必要的,它的运行机制和setInterval有什么不同呢, 先简单说说setInterval的原理:setInterval是每隔一段时间向事件队列里面添加回调函数,如果主线程忙的话调用时间不确定特别容易出问题,由于setInterval只负责定时向队列中添加函数,而不考虑函数的执行,setInterval有一个原则:在向队列中添加回调函数时,如果队列中存在之前由其添加的回调函数,就放弃本次添加(不会影响之后的计时),如果主线程忙的话,之前添加的

继续访问

cocos 2dx--------schedule(五)

bool HelloWorld::init() { // 1 super init first if ( !Scene::init() ) { return false; } printf("1\n"); //this->schedule(CC_SCHEDULE_SELECTOR(HelloWorld::fun_Callback), 10f); //每一秒运行依次fun_Callback()函数 //this->sc

继续访问

Cocos2d-x lua 使用定时器

打开一个定时器:schedulerID = ccDirector:getInstance():getScheduler():scheduleScriptFunc(调用的function, 定时时间(秒), 是否暂停(true, false)) 关闭定时器:ccDirector:getInstance():getScheduler():unscheduleScriptEntry(schedul

继续访问

Cocos-2dx lua 定时器

Cocos2dx 3x Lua 中定时器的两种使用方式:(1)self:scheduleUpdateWithPriorityLua(update, priority) > 参数一:刷新函数 > 参数二:刷新优先级 其中 self 为 Node类 的子类。 该方法默认为每帧都刷新一次,无法自定义刷新时间间隔。(2)scheduler:scheduleScriptFunc(update, inteval, false)

继续访问

Cocos2d-x 3x基础学习: 定时器更新schedule/update

大部分游戏中定时器是不可或缺的,因为即每隔一段时间,就要执行相应的刷新体函数,以更新游戏的画面、时间、进度、敌人的指令等等。 Cocos2d-x为我们提供了定时器schedule相关的 *** 作。其 *** 作函数的定义在CCNode中,所以基本上大多数的引擎类都可以设置定时器,如CCLayer、CCSprite、CCMenu等。 定时器更新的方式分为三类: (1)默认定时器 :scheduleUpdate(); (2)自定义定时器:schedule(); (3)一次性定时器:schedu

继续访问

cocos2dx三种定时器的使用以及停止schedule,scheduleUpdate,scheduleOnce

今天白白跟大家分享一下cocos2dx中定时器的使用方法。

继续访问

Cocos Creator 分帧加载(js - schedule)

效果 loadingFramejs ccClass({ extends: ccComponent, properties: { hello: ccPrefab, content: ccNode, mask: ccNode, // animation: ccAnimation, }, onLoad() { thismaskactive = false; // t

继续访问

cocos2dx调度器schedule

cocos2dx调度器schedule Cocos2d-Lua 引擎中的调度器是用来周期执行某个函数或延时执行某个函数的。功能类似于定时触发器,但它又与游戏紧密结合。 schedule调度器利用了timer类来保存了调度任务的 时间线,重复次数,事件间隔,延迟等数据。timer把相关信息以结构体的形式存进了双向链表_hashForTimers中 每帧更新后Timer做事件累加,根据时间线去触发或者取消更新回调。 每帧更新每帧更新

继续访问

Cocos2d-JS下用schedule实现倒计时

Cocos2d-JS下用schedul实现倒计时 前言 本文所记录的内容是在Cocos Code IDE平台完成的,主要是记录一下2048游戏下用schedule实现倒计时。第一次写博客,如有错误望指教。 提示:以下是本篇文章正文内容,下面案例仅供参考 一、schedule是什么? schedule可以每隔几秒执行某个自定义的函数; 而scheduleOnce是在几秒之后才执行,并且只会执行一次。 二、使用步骤 1定义 代码如下(示例): 先在对应的游戏层定义倒计时时间 var GameLayer =

继续访问

cocos定时器分析

注:COCOS分析的版本为34 COCOS的定时器是通过一个哈希表进行保存的,每一帧循环的时候都会调用定时器的update方法,并传入两帧之间的时间间隔 /void Scheduler::update(float dt)/,在update方法中对哈希表进行轮询调用回调函数。 在轮询调用中将定时器分为四种,其中三种是每一帧必定循环调用的方法,struct _listEntry _upda

继续访问

cocos 定时器schedule

1schedule如其名,以指定的时间间隔调用一个指定的函数。系统最小间隔是frame time(即帧间隔),可以由自己设定帧间隔 selector调用分为:自定义函数和update 另外动画(action)类型也会和定时器有关 如下使用方式: [self schedule:@selector(gameLogic:) interval:10] //表示以1秒时间间隔调用函数game

继续访问

Cocos Creator之schedule第一个时间段不执行的解决方法

1、 schedule的官方文档地址。 2、 介绍如下: 指定回调函数,调用对象等信息来添加一个新的定时器。 如果 paused 值为 true,那么直到 resume 被调用才开始计时。 当时间间隔达到指定值时,设置的回调函数将会被调用。 如果 interval 值为 0,那么回调函数每一帧都会被调用,但如果是这样, 建议使用 scheduleUpdateForTarget 代替。 如果回调函

继续访问

爱上cocos2d-x之十六取消定时器unSchedule

懂得如何创建定时器,当然,我们也要懂得如何取消定时器啦。此文章,建立在前两篇的基础上。 (1)取消默认的定时器,只要一句代码 this->unscheduleUpdate(); 备注:不管我们注册update函数时,用的是一个还是两个参数,在取消时都不需要传递参数,只需传递函数就可以了。 (2)取消自定义的定时器 this->unschedule(schedule_sel

参考

由浅到浅入门批量渲染(一)

由浅到浅入门批量渲染(二)

由浅到浅入门批量渲染(三)

由浅到浅入门批量渲染(四)

由浅到浅入门批量渲染(完)

Unity游戏开发静态、动态合批与GPU Instancing

本文只摘抄部分原文,达到概念性了解。

批量渲染其实是个老生常谈的话题,它的另一个名字叫做“合批”。在日常开发中,通常说到优化、提高帧率时,总是会提到它。

可以简单的理解为: 批量渲染是通过减少CPU向GPU发送渲染命令(DrawCall)的次数,以及减少GPU切换渲染状态的次数,尽量让GPU一次多做一些事情,来提升逻辑线和渲染线的整体效率。 但这是建立在GPU相对空闲,而CPU把更多的时间都耗费在渲染命令的提交上时,才有意义。

如果瓶颈在GPU,比如GPU性能偏差,或片段着色器过于复杂等,那么没准适当减少批处理,反而能达到优化的效果。所以要做性能优化,还是应该先定位瓶颈到底在哪儿,然后再考虑优化方案,而不是一股脑的就啪啪啪合批。当然,通常情况下,确实是以CPU出现瓶颈更为常见,所以适当的了解些批量渲染的技法,是有那么一丢丢必要的。

静态合批是一种听起来很常用,但在大多数手游项目里又没那么常用的合批技术。这里,我简单的将静态合批分为预处理阶段的合并,和运行阶段的批处理。

合并时,引擎将符合合批条件的渲染器身上的网格取出,对网格上的顶点进行空间变换,变换到合并根节点的坐标系下后,再合并成一个新的网格;这里需要注意的是,新网格是以若干个子网格的形式组合而成的,因为需要记录每一个合并前网格的索引数量和起始索引(相对于合并后的新网格)。

空间变换的目的,是为了“固化”顶点缓冲区和索引缓冲区内的数据,使其顶点位置等信息都在相同的坐标系下。这样运行时如果需要对合并后的对象进行空间变换(手动静态合批对象的根节点可被空间变换),则无需修改缓冲区内的顶点属性,只提供根节点的变换矩阵即可。

在Unity中,可以通过勾选静态批处理标记,让引擎在打包时自动合并;当然,也可以在运行时调用合并函数,手动合并。打包时的自动合并会膨胀场景文件,会在一定程度上影响场景的加载时间。此外,不同平台对于合并是有顶点和索引数量限制的,超过此限制则会合并成多个新网格。

运行时是否可以合批(Batch)成功,还取决于渲染器材质的设置。

当然,如果手动替换过场景中所有Material,也会打断批次。

静态合批与直接使用大网格(是指直接制作而成,非静态合并产生的网格)的不同,主要体现在两方面。

其一,静态合批可以主动隐藏部分对象。静态合批在运行时,由于每个参与合并的对象可以通过起始索引等彼此区分,因此可以通过上述多次DrawCall的策略,实现隐藏指定的对象;而直接使用大网格,则无法做到这一点。

其二,静态合批可以有效参与CPU的视锥剔除。当有剔除发生时,被送进渲染管线的顶点数量就会减少(通过参数控制),也就意味着被顶点着色器处理的顶点会减少,提升了GPU的效率;而使用大网格渲染时,由于整个网格都会被送进渲染管线,因此每一个顶点都需要被顶点着色器处理,如果摄像机只能照到一点点,那么绝大多数参与计算的顶点最后都会被裁减掉,有一些浪费。

当然,这并不意味着静态合批一定就比使用大网格要更好。如果子网格数量非常多,视锥剔除时CPU的压力也会增加,所以具体情况具体分析吧~

静态合批采用了以空间换时间的策略来提升渲染效率。

其优势在于:网格通常在预处理阶段(打包)时合并,运行时顶点、索引信息也不会发生变化,所以无需CPU消耗算力维护;若采用相同的材质,则以一次渲染命令,便可以同时渲染出多个本来相对独立的物体,减少了DrawCall的次数。

在渲染前,可以先进行视锥体剔除,减少了顶点着色器对不可见顶点的处理次数,提高了GPU的效率。

其弊端在于:合批后的网格会常驻内存,在有些场景下可能并不适用。比如森林中的每一棵树的网格都相同,如果对它采用静态合批策略,合批后的网格基本等同于:单颗树网格 x 树的数量,这对内存的消耗可能就十分巨大了。

总而言之,静态合批在解决场景中材质基本相同、网格不同、且自始至终都保持静止的物体上时,很适用。

试想一个场景:一场激烈的战斗中,双方射出的箭矢飞行在空中,数量很多,材质也相同;但因为都在运动状态,所以无法进行静态合批;倘若一个一个的绘制这些箭矢,则会产生非常多次绘制命令的调用。

对于这些模型简单、材质相同、但处在运动状态下的物体,有没有适合的批处理策略呢?有吧,动态合批就是为了解决这样的问题。

动态合批没有像静态合批打包时的预处理阶段,它只会在程序运行时发生。 动态合批会在每次绘制前,先将可以合批的对象整理在一起(Unity中由引擎自动完成),然后将这些单位的网格信息进行“合并”,接着仅向GPU发送一次绘制命令,就可以完成它们整体的绘制。

动态合批比较简单,但有两点仍然需要注意:

动态合批不会在绘制前创建新的网格,它只是将可以参与合批单位的顶点属性,连续填充到一块顶点和索引缓冲区中,让GPU认为它们是一个整体。

在Unity中,引擎已自动为每种可以动态合批的渲染器分配了其类型公用的顶点和索引缓冲区,所以动态合批不会频繁的创建顶点和索引缓冲区。

在向顶点和索引缓冲区内填充数据前,引擎会处理被合批网格的每个顶点信息,将其空间变换到世界坐标系下。

这是因为这些对象可能都不属于相同的父节点,因此无法对其进行统一的空间转换(本地到世界),需要在送进渲染管线前将每个顶点的坐标转换为世界坐标系下的坐标(所以Unity中,合并后对象的顶点着色器内被传入的M矩阵,都是单位矩阵)。

相对于上述看起来有点厉害但是本质上无用的知识而言,了解动态合批规则其实更为重要。比如:

当我们想要呈现这样的场景:一片茂密的森林、广阔的草原或崎岖的山路时,会发现在这些场景中存在大量重复性元素:树木、草和岩石。它们都使用了相同的模型,或者模型的种类很少,比如:树可能只有几种;但为了做出差异化,它们的颜色略有不同,高低参差不齐,当然位置也各不相同。

使用静态合批来处理它们(假设它们都没有动画),是不合适的。因为数量太多(林子大了,多少树都有), 所以合并后的网格体积可能非常大,这会引起内存的增加 ;而且,这个合并后的网格还是由大量重复网格组成的,不划算。

使用动态合批来处理他们,虽然不会“合并”网格,但是仍然需要在渲染前遍历所有顶点,进行空间变换的 *** 作;虽然单颗树、石头的顶点数量可能不多,但由于 数量很多,所以也会在一定程度上增加CPU性能的开销,没必要

那么,对于场景中这些模型重复、数量多的渲染需求,有没有适合的批处理策略呢?有吧,实例化渲染就是为了解决这样的问题。

实例化渲染,是通过调用“特殊”的渲染接口,由GPU完成的“批处理”。

它与传统的渲染方式相比,最大的差别在于:调用渲染命令时需要告知GPU这次渲染的次数(绘制N个)。当GPU接到这个命令时,就会连续绘制N个物体到我们的屏幕上,其效率远高于连续调用N次传统渲染命令的和(一次绘制一个)。

举个例子,假设希望在屏幕上绘制出两个颜色、位置均不同的箱子。如果使用传统的渲染,则需要调用两次渲染命令(DrawCall = 2),分别为:画一个红箱子 和 画一个绿箱子。

如果使用实例化渲染,则只需要调用一次渲染命令(DrawCall = 1),并且附带一个参数2(表示绘制两个)即可。

当然,如果只是这样,那GPU就会把两个箱子画在相同的位置上。所以我们还需要告诉GPU两个箱子各自的位置(其实是转换矩阵)以及颜色。

这个位置和颜色我们会按照数组的方式传递给GPU,大概这个样子吧:

那接下来GPU在进行渲染时,就会在渲染每一个箱子的时候,根据当前箱子的索引(第几个),拿到正确的属性(位置、颜色)来进行绘制了。

静、动态合批实质上是将可以合批的对象真正的合并成一个大物体后,再通知GPU进行渲染,也就是其顶点索引缓冲区中必须包含全部参与合批对象的顶点信息; 因此,可以认为是CPU完成的批处理。

实例化渲染是对网格信息的重复利用,无论最终要渲染出几个单位,其顶点和索引缓冲区内都只有一份数据, 可以认为是GPU完成的批处理

其实这么总结也有点问题,本质上讲: 动、静态合批解决的是合批问题,也就是先有大量存在的单位,再通过一些手段合并成为批次;而实例化渲染其实是个复制的事儿,是从少量复制为大量,只是利用了它“可以通过传入属性实现差异化”的特点,在某些条件下达到了与合批相同的效果。

无论是静态合批、动态合批或实例化渲染,本质上并无孰优孰劣,它们都只是提高渲染效率的解决方案,也都有自己适合的场景或擅长解决的问题。个人以为:

Unity下可以通过以下两种方式快速优化骨骼蒙皮动画:

在相同的测试环境下,再次进行测试后可以发现,这种方法确实可以产生一定效果。其原因我认为主要有以下两点:

开启GPU蒙皮,Unity会通过ComputeShader的方式,使用GPU进行蒙皮。GPU上有大量的ALU(算数逻辑单元),可以并行大量的数值计算,效率较高,应该很适合这种针对顶点属性的数值计算。

但是实际情况是:在移动设备上使用GPU蒙皮反而会使主线程的耗时增加。通过Profiler可以发现,CPU把更多的时间放在了执行ComputeShader上,由于骨骼动画的实例很多(500个),所以这个调用时间本身成为了性能热点。所以,以目前的情况来看,这种在移动设备上使用GPU蒙皮的方式,似乎不适合处理大量骨骼蒙皮动画实例(也许是我使用的方式存在问题)。

简单来说,它们基本的思路,都是将骨骼蒙皮动画的“结果”预先保存在一张纹理中;然后在运行时通过GPU从这张纹理中采样,并使用采样结果来更新顶点属性; 再结合实例化技术(GPU instancing) ,达到高效、大批量渲染的目的。

可以简单的将它的工作流程分为两个阶段:

这个阶段其实是为后面的播放阶段准备动画资源,你也可以把这个资源简单的理解为一种特殊类型的动画文件。

首先,在编辑状态下,让携带骨骼蒙皮动画的角色,按照一定帧率播放动画。我们知道:动画播放时,角色网格会被蒙皮网格渲染器(SkinnedMeshRenderer)更新而产生变形(顶点变化);如果在动画播放的同时,记录下每一个顶点在这一时刻相对于角色坐标系(通常是角色脚下)的位置。那么,当动画播放完毕时,我们会得到一张“每一个顶点在每一个关键帧时的位置表”,我们就叫它“帧顶点位置表”吧。可是“帧顶点位置表”名字太长,你可能不太好记,我打字也比较麻烦,所以我们后面就叫它“表表”吧。

除了表表,我们还能得到与它对应的动画信息,比如这个动画的名称、时长、帧率、总帧数、是否需要循环播放等信息。

到这里,第一个阶段我们需要的内容就准备就绪了,可以进入下一个阶段:播放动画阶段。

在动画播放时,通过一个变量(播放时长)来更新播放进度;结合上一阶段我们记录下来的动画总长度、动画总帧数信息,就可以计算出当前动画播放到了第几帧(当前帧数 = 已播放时长 / 动画总时长 x 动画总帧数)。一旦得到当前动画帧,就表示可以通过表表锁定一行顶点数据。

接下来,只要遍历这行顶点数据;然后根据索引找到网格中对应的顶点并更新它的位置,就等于完成了蒙皮工作。

既然通过一个播放进度、表表和一些简单的动画信息,就能直接完成蒙皮(对顶点属性的更新),那么我们就不再需要以下内容了:

使用CPU来读取表表,并遍历更新所有顶点,显然不如GPU来的高效;所以接下来,我们将这一步放到渲染管线中的顶点变换阶段,借着GPU处理顶点的契机,完成使用表表中的数据对顶点属性进行更新。

实例化渲染的特点是使用相同网格,相同材质,通过不同的实例属性完成大批量的带有一定差异性的渲染;而烘焙顶点恰好符合了实例化渲染的使用需求。

所以,我们只需将控制动画播放的关键属性:比如过渡动画播放的V坐标、当前和下一个动画的插值比例等,放入实例化数据数组中进行传递;再在顶点着色器中,对关键属性获取并使用即可。

与蒙皮网格渲染器的比较

烘焙顶点的主要问题

除了烘焙顶点,另一种常用的优化方案是烘焙骨骼矩阵动画。

听名字就知道,烘焙骨骼矩阵与烘焙顶点位置,原理十分相似;最大的差异在于它们在烘焙时所记录的内容不一样:烘焙顶点记录下来的是每个顶点的位置,而烘焙骨骼矩阵记录下来的是每一根骨骼的矩阵,仅此而已。

烘焙骨骼矩阵最大的意义在于它补上了烘焙顶点的短板:受顶点数量限制、烘焙的动画纹理过大 及 纹理数量较多。

烘焙顶点对于纹理(面积)的使用是受顶点数量决定的,可以简单理解为:

所以,当模型的顶点数量过多时(数以千计),这种烘焙方式或者无法烘焙下整个模型(顶点数量>2048),或者需要一张或多张(法线、切线)大尺寸纹理(<2048 && > 1024)。

但是,烘焙骨骼矩阵主要取决于骨骼的数量,可以简单理解为:

在移动平台上,通常20根左右的骨骼就可以取得不错的表现效果,所以相对于烘焙顶点,烘焙骨骼可以记录下更长的动画,同时它也不再受顶点数量的限制,也无需对法线或切线进行特殊处理(因为可以在采样后通过矩阵计算得出)。

针对这两种方案,我个人认为并没有绝对的孰优孰劣;正如你所看到的,烘焙骨骼在处理复杂模型或多动作时更具优势;但如果渲染数量较多、模型顶点数较少、表现需求较弱(无需法向量参与计算)时,烘焙顶点也是值得尝试的,因为它在性能上会更好一些。

目前静态合批方案为运行时静态合批,通过调用 BatchingUtilitybatchStaticModel 可进行静态合批。

该函数接收一个节点,然后将该节点下的所有 MeshRenderer 里的 Mesh 合并成一个,并将其挂到另一个节点下。

在合批后,将无法改变原有的 MeshRenderer 的 transform,但可以改变合批后的根节点的 transform。只有满足以下条件的节点才能进行静态合批:

引擎目前提供两套动态合批系统,instancing 合批和合并 VB 方式的合批,两种方式不能共存,instancing 优先级大于合并 VB。

要开启合批,只需在模型所使用的材质中对应勾选 USE_INSTANCING 或 USE_BATCHING 开关即可。

通过 Instancing 的合批适用于绘制大量顶点数据完全相同的动态模型,启用后绘制时会根据材质和顶点数据分组,每组内组织 instanced attributes 信息,然后一次性完成绘制。

合并 VB 合批适用于绘制大量低面数且顶点数据各不相同的非蒙皮动态模型,启用后绘制时会根据材质分组,然后每组内每帧合并顶点和世界变换信息,然后分批完成绘制

通常来说合批系统的使用优先级为:静态合批 > instancing 合批 > 合并 VB 合批。

骨骼动画是一种常见但类型特殊的动画,我们提供了 预烘焙骨骼动画 和 实时计算骨骼动画 两套系统,针对不同方向的需求,分别优化。

这两套系统的唯一开关就是 SkeletalAnimation 上的 useBakedAnimation 开关,启用时会使用预烘焙骨骼动画系统,禁用后会使用实时计算骨骼动画系统,运行时也可以无缝切换。

目前所有模型资源在导入后,prefab 中全部默认使用预烘焙系统,以达到最佳性能。我们建议只在明显感到预烘焙系统的表现力无法达标的情况下,再使用实时计算系统。虽然两套系统可以在运行时无缝切换,但尽量不要高频执行,因为每次切换都涉及底层渲染数据的重建。

基于预烘焙系统的框架设计,蒙皮模型的 instancing 也成为了触手可及的功能,但要保证正确性还需要收集一些比较底层的信息。

这里的根本问题是,同一个 drawcall 内的各个模型使用的骨骼贴图必须是同一张,如果不是同一张,显示效果会完全错乱。所以如何将动画数据分配到每张骨骼贴图上,就成为了一个需要用户自定义的信息,对应在编辑器项目设置的 骨骼贴图布局面板 进行配置。

注意

目前底层上传 GPU 的骨骼纹理已做到全局自动合批复用,上层数据目前可以通过使用 批量蒙皮模型组件(BatchedSkinnedMeshRenderer)将同一个骨骼动画组件控制的所有子蒙皮模型合并:

在Android环境中,可以使用 androidcontentClipboardManager 来复制和粘贴数据

在iOS环境中,可以使用 UIPasteboard 来复制和粘贴数据

在网上中复制粘贴是最麻烦的,要考虑到不同系统不同浏览器的情况,复制(设置剪切版)稍微还好一点,但是要获取剪切版的内容就太麻烦了,暂时还未找到兼容性很好的方法。

以上就是关于cocoscreator 2.4.x版本 drawcall优化 第一期(掌握控制drawcall数量的必要知识)全部的内容,包括:cocoscreator 2.4.x版本 drawcall优化 第一期(掌握控制drawcall数量的必要知识)、dashboard直接放文件夹合并、cocoscreate实现鼠标点哪去哪等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9717162.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-01
下一篇 2023-05-01

发表评论

登录后才能评论

评论列表(0条)

保存