最近在项目开发过程中,无意发现游戏场景的绘制占用了大量的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. P2921 [USACO08DEC]在农场万圣节Trick or Treat on the Farm

    对于一个牛,它存在两种状态:1.处于联通分量 2.不处于联通分量.对于处于联通分量的牛,求出联通分量的大小:对于不处于联通分量的牛,求出其距离联通分量的路程+联通分量大小. 不同的联通分量,染上不同的 ...

  2. 很好用的电脑桌面远程控制软件 支持多平台 Win,Mac,Debian… 等操作系统 Anydesk

    很好用的电脑桌面远程控制软件 支持多平台 Win,Mac,Debian, Ubuntu, FreeBSD… 等操作系统 Anydesk 官网下载地址:https://anydesk.com/remot ...

  3. Q - N! HDU - 1042

    使用java还不熟练,错误在于读入.应用in.hasNext() 代码 import java.text.ParseException; import java.text.SimpleDateForm ...

  4. [JavaScript]ECMA-6 箭头函数

    概述 箭头函数的作用是为Js提供一种函数的简写方法,箭头函数作用域内不包含this, arguments, super, or new.target,并且不能用于对象的构造函数: 基本语法 [(][p ...

  5. SQL语句:子查询

    一,子查询定义: 子查询就是嵌套在主查询中的查询. 子查询可以嵌套在主查询中所有位置,包括SELECT.FROM.WHERE.GROUP BY.HAVING.ORDER BY. 但并不是每个位置嵌套子 ...

  6. 面向对象(OOP)基本概念

    面向对象(OOP)基本概念 面向对象编程 —— Object Oriented Programming 简写 OOP 目标 了解 面向对象 基本概念 01. 面向对象基本概念 我们之前学习的编程方式就 ...

  7. Clipboard---将文本复制到剪切板上

    第一步:链接 Clipboard 的js文件 < script src = “ https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.m ...

  8. CSS 屏幕大小自适应

    要想实现css屏幕大小自适应,首先得引入 CSS3 @media 媒体查询器: media的使用和规则: ①被链接文档将显示在什么设备上. ②用于为不同的媒介类型规定不同的样式. 语法:  @medi ...

  9. 洛谷 P1414 又是毕业季II

    题目链接 https://www.luogu.org/problemnew/show/P1414 题目背景 “叮铃铃铃”,随着高考最后一科结考铃声的敲响,三年青春时光顿时凝固于此刻.毕业的欣喜怎敌那离 ...

  10. json格式的中文输出显示

    print json.dumps(json.loads(result),ensure_ascii=False)