最近在项目开发过程中,无意发现游戏场景的绘制占用了大量的Batches,几乎一个模型显示就占用了一个Batch,而Saved by batching数量几乎为0,即没有任何合批渲染优化。这显然跟预期相去甚远,因为虽然场景里有多达上百个模型需要绘制,但大部分都是一模一样的卡牌模型,引用相同的材质球,按理绝大部分都是可以被Unity自动dynamic batching,进行合并批处理的。哪到底是哪里出了问题?

于是翻看Unity Manual,检查Dynamic Batching的规则,可以简单概括为以下几条:

  1. 一般情况下,Unity仅支持对Meshes小于900顶点的物体进行Dynamic Batching,如果Shader里使用了顶点位置,法线,UV值,则仅支持300顶点以下的物体;如果使用顶点位置,法线,UV0,UV1和Tangent向量,则仅支持180顶点以下的物体。

  2. 如果两个物体的scale刚好是呈镜像的,如scale分别为(-1,-1,-1)或(1,1,1),他们不会被Dynamic Batching。

  3. 引用材质实例不同的物体不会被Dynamic Batching,即使两个物体的材质本质上没有任何不同。这句话的理解有点绕,简单举例就是说,相同的材质实例化了两份,分别被A和B引用了,那么A和B是不会被Dynamic Batching的,因为他们引用的是两个不同的实例。

  4. 拥用lightmaps的物体将不会被Dynamic Batching,除非他们指向了lightmap的同一部分。

  5. 拥有多Pass通道的Shader的物体不会被Dynamic batching。

简单排查下,模型是达到Unity的1,2,4,5点要求的。于是开始怀疑材质实例引用不同导致了问题。

查看Inspector,可以看到Mesh Renderer引用的Material确实就是所需要的材质球,但后面却加了Instance字眼,单击图中红框处,Project窗口也并没有跳转到材质球文件所有位置,说明这份material是在运行时被实例化的,不是Prafab预设时指向的材质球引用。由此我们可以推测,每份模型都各自实例化了一份自己的material实例,导致没有被Dynamic Batching。知道了问题原因,还要知道问题出在哪里。检查物体上绑定的脚本,查看与material相关的代码,可以看到有这么两处:

    private Texture _mainPlaneTex;
private Texture _flashPlaneTex;
public MeshRenderer m_rendererPlane;
private Material _cachedPlaneMaterial = null;
private Material cachedPlaneMat
{
get
{
if (_cachedPlaneMaterial == null && m_rendererPlane != null)
{
_cachedPlaneMaterial = m_rendererPlane.material;
}
return _cachedPlaneMaterial;
}
}
    private void SetGray(bool bGray)
{
if (bGray)
{
cachedPlaneMat.SetTexture("_MainTex", _flashPlaneTex);
}
else
{
cachedPlaneMat.SetTexture("_MainTex", _mainPlaneTex);
}
}

这个模型有模型变灰色的需求,实现方式是通过获取该物体的material,再视情况将MainTexture设置为正常/变灰贴图。出问题的地方十有八九就在这块代码。查看MeshRenderer的接口,原来Unity提供了两个获取Material的方法接口,分别是material及sharedMaterial。

搞懂这两个接口的区别,弄清楚Unity在底层到底搞了什么鬼,估计离真相就不远了。

Renderer.material

Returns the first instantiated Material assigned to the renderer.

Modifying material will change the material for this object only.

If the material is used by any other renderers, this will clone the shared material and start using it from now on.

Unity官方说当使用Renderer.material获取Material引用时,会把Render里Materials列表第一个预设的Material进行实例化,并将返回实例。这样,当我们对这个物体的Material进行修改时,只会修改实例,而不会修改到最原本的材质球,比如我们将同个模型Prafab拖放N多个到场景里,当我们对其中一个模型A的材质球进行修改,比如替换贴图,其他模型是不会受到影响的,因为A修改的仅仅只是自己实例化出来的材质球。

Renderer.sharedMaterial

The shared material of this object.

Modifying sharedMaterial will change the appearance of all objects using this material, and change material settings that are stored in the project too.

It is not recommended to modify materials returned by sharedMaterial. If you want to modify the material of a renderer use material instead.

而如果调用Renderer.sharedMaterial接口,Unity则不会多此一举地帮我们做实例化,再返回实例了,直接就是返回最原本的材质球。还是上面那个例子,当我们给A模型的材质球替换贴图,会发现场景的所有模型都被替换了贴图。

看完了两个接口的差异,也终于明白了问题的根本原因。为了实现模型变灰的需求,代码里使用Renderer.material来获取material实例,殊不知每个物体都各自持有了一份不同的material实例,使得Unity没办法将这些一模一样的物体进行合批渲染,当同屏显示的物体数目多达上百,就意味着多出上百个不必要的Drawcall,多了不必要的损耗。

而这个问题也不能简单地将Renderer.material改为调用Renderer.sharedMaterial就解决了,总不能把一个模型变灰,所有模型都要跟着变灰吧?正确的解决方法就是预设正常/灰色两个材质球,根据不同的情况替换材质。

最后贴下优化前后,场景Drawcall的对比:



优化前



优化后

可以看到,在同屏模型较多的情况下,Batch数由近100骤降到2,基本都被Unity动态合批处理了。

记一次Dynamic Batching不生效的爬坑实例分析[Unity]的更多相关文章

  1. Unity Dynamic Batching

    public class test1 : MonoBehaviour { public GameObject prefab; void Start() { ; i < ; i++) { Game ...

  2. 记一次SpringBoot 开发中所遇到的坑和解决方法

    记一次SpringBoot 开发中所遇到的坑和解决方法 mybatis返回Integer为0,自动转型出现空指针异常 当我们使用Integer去接受数据库中表的数据,如果返回的数据中为0,那么Inte ...

  3. DHCP (Dynamic Host Configuration Protocol )协议的探讨与分析

    DHCP (Dynamic Host Configuration Protocol )协议的探讨与分析 问题背景 最近在工作中遇到了连接外网的交换机在IPv6地址条件下从运营商自动获取的DNS地址与本 ...

  4. .NET Core爬坑记 1.0 项目文件

    前言: 之所以要写这个系列是因为在移植项目到ASP.NET Core平台的过程中,遇到了一些“新变化”,这些变化有编译方面的.有API方面的,今天要讲的是编译方面的一些问题.我把它们整理后分享出来,以 ...

  5. 记node前后端代码共用的一次坑

    项目背景 nodejs项目,webpack打包,用axios请求,Promise封装,nunjucks模板引擎: 之前已将nunjucks模板通过webpack打包策略,做成前后端共用: 目前需要将网 ...

  6. 【MyBatis】MyBatis自动生成代码查询之爬坑记

    前言 项目使用SSM框架搭建Web后台服务,前台后使用restful api,后台使用MyBatisGenerator自动生成代码,在前台使用关键字进行查询时,遇到了一些很宝贵的坑,现记录如下.为展示 ...

  7. 记一次docker问题定位(perf,iostat等性能分析)

    背景 最近参与的项目是基于 OpenStack 提供容器管理能力,丰富公司 IaaS 平台的能力.日常主要工作就是在开源的 novadocker 项目(开源社区已停止开发)基础上进行增强,与公司的其他 ...

  8. 记一次有关spark动态资源分配和消息总线的爬坑经历

    问题: 线上的spark thriftserver运行一段时间以后,ui的executor页面上显示大量的active task,但是从job页面看,并没有任务在跑.此外,由于在yarn mode下, ...

  9. uni-app 入门之 nvue (weex) 爬坑记

    前言 uni-app 是 DCloud 出品的新一代跨端框架,可以说是目前跨端数最多的框架之一了,目前支持发布到:App(Android/iOS).H5.小程序(微信小程序/支付宝小程序/百度小程序/ ...

随机推荐

  1. JVM虚拟机和垃圾回收算法

    类加载机制 双亲委派模型 垃圾回收算法 CMS G1 类加载机制 双亲委派模型 双亲委派模型: 需要加载一个类,先委托父类加载,父类找父类,依次递归加载;加载不到再由自己加载 垃圾回收算法 JVM的内 ...

  2. Django_路由详

    动态路由和动态参数捕获 动态路由:url支持正则表达式, 访问的url能够匹配成功就去执行对应的视图函数 捕获参数: # 捕获参数,位置传参 url(r'^data/([0-9]{4})/([0-2] ...

  3. 爬虫 requests模块的其他用法 抽屉网线程池回调爬取+保存实例,gihub登陆实例

    requests模块的其他用法 #通常我们在发送请求时都需要带上请求头,请求头是将自身伪装成浏览器的关键,常见的有用的请求头如下 Host Referer #大型网站通常都会根据该参数判断请求的来源 ...

  4. 20175226 2018-2019-2 《Java程序设计》第六周学习总结

    20175226 2018-2019-2 <Java程序设计>第六周学习总结 教材学习内容总结 内部类与异常类 内部类:Java支持在一个类中定义另一个类(可以是static类) 外嵌类的 ...

  5. Windows安装redis并将redis设置成服务

    Redis 作为一种缓存工具,主要用于解决高并发的问题,在分布式系统中有着极其广泛的应用,Redis 本身是应用于 Linux/Unix 平台的(部署在服务器上边),官方并没有提供 Windows 平 ...

  6. php 数组写入文件

    $fiele = '../data/conf/watermark.php';//指定要写入的文件 $param = $this->request->param(); $watermark ...

  7. elasticsearch 学习

    docker run -p : -d elasticsearch #直接拉取运行 #指定条件搜索curl --request GET \ --url 'http://localhost:9200/im ...

  8. [转] iphoneX、iphoneXS、iphoneXSMax、iphoneXR适配

    基础知识 1. 关于iphoneX .iphoneXS.iphoneXSMax.iphoneXR机型的大小和像素   机型尺寸 注意:开发人员只需要记住开发尺寸 2. 屏幕组成 齐刘海(44px) + ...

  9. 为什么要使用getters和setters/访问器?

    Why use getters and setters/accessors? 实际上会有很多人问这个问题....尤其是它成为Coding Style中一部分的时候. 文章出自LBushkin的回答 T ...

  10. SourceInSight自定义命令说明与应用

    1.自定义命名设置界面(Tool -> Custom Commands...) Command 显示当前选中命令的名字. 下拉列表中包含了所有的自定义命令. Run 命令行,当调用自定义命令时, ...