最近在项目开发过程中,无意发现游戏场景的绘制占用了大量的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. Linux 使用 arp-scan 检查是否存在IP地址冲突

    如果前期没有做好IP地址规划,即使有IP地址统一不小心也会犯错!推荐服务器IP地址使用要登记明细,上次机房批量部署服务器,就将已再用的IP又分配给另一台服务器,还好对业务没有造成大的影响. 那么在给服 ...

  2. CentOS配代理服务器

    背景: 某云上有台Windows主机,为了省钱(...),购买的1M带宽... 然后日常只有我用,特别卡,嫌弃得不行. 最近接触到代理,琢磨代理连接到局域网内带宽大的主机,是否上网速度会蹭蹭得涨?实践 ...

  3. Java编程思想(后)

    Java编程思想(后) 持有对象 如果一个程序只包含固定数量的且其生命期都是已知的对象,那么这是一个非常简单的程序. Java中的库基本类型: List, Set, Queue和Map --- 称为集 ...

  4. LeetCode.数字转罗马数字

    罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即为两个并 ...

  5. Rest API

    一.前言 在软件行业快速发展的今天,传统的软件授权已经不能足以满足一个IT类的公司的发展.虽然在大部分公司里,它还是现金池的直接源头.但是在可遇见的未来,受摩尔根理论的失效.物联网的发展等影响,应用的 ...

  6. python正则表达式--flag修饰符、match对象属性

    正则表达式—修饰符 正则表达式可以包含一些标志修饰符来控制匹配模式,用在正则表达式处理函数中的flag参数中,为可选参数. (1) re.I 全写(re.IGNORECASE) 表示使匹配时,忽略大小 ...

  7. C - Heavy Transportation && B - Frogger(迪杰斯变形)

    Background Hugo Heavy is happy. After the breakdown of the Cargolifter project he can now expand bus ...

  8. 初学python之路-day12

    本篇补上字符串的比较:按照从左往右比较每一个字符,通过字符对应的ascii进行比较 一.函数默认值的细节 # 如果函数的默认参数的默认值为变量,在所属函数定义阶段一执行就被确定为当时变量存放的值 a ...

  9. centos下设置开机启动程序

    首先,设置权限, 由于/etc/rc.local是/etc/rc.d/rc.local的软连接,所以必须确保/etc/rc.local和/etc/rc.d/rc.local都有x权限(可执行) 执行命 ...

  10. 完美解决cannot resolve symbol servlet 的报错

    1.右键点击项目,打开open module settings 2.选择Libraries 3.选择中间+号,点击java,然后选择tomcat/lib/servlet-api.jar 4.点击app ...