最近在项目开发过程中,无意发现游戏场景的绘制占用了大量的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. Mysql 层级、执行顺序、执行计划分析

    逻辑分层 下面是MySQL的逻辑分层图: 连接层:连接与线程处理,这一层并不是MySQL独有,一般的基于C/S架构的都有类似组件,比如连接处理.授权认证.安全等. 服务层:包括缓存查询.解析器.优化器 ...

  2. 找到多个与名为“Home”的控制器匹配的类型。解决方法

    “/”应用程序中的服务器错误. 找到多个与名为“Home”的控制器匹配的类型.如果为此请求(“{controller}/{action}/{id}”)提供服务的路由没有指定命名空间以搜索与此请求相匹配 ...

  3. 【linux终端操作】

    1. ctr + alt + t 打开新的终端窗口2. ctr + shift + + 终端窗口字体放大3. ctr + - 终端窗口字体缩小4. ls : 查看目录下的文件信息5. pwd: 查看目 ...

  4. 关于/tmp/ 目录自动清理文件

    问题:今天开发人员给我说了一个错误:The temporary upload location [/tmp/tomcat.1337767218595042057.80/work/Tomcat/loca ...

  5. flask蓝图的简单使用

    1.flask的简单使用 from flask import Flask # from flask import make_response app = Flask(__name__) app.con ...

  6. 3D模型网站分享

    http://www.cgjoy.com/work.php  CG作品网 http://www.cgmxw.com/ CG模型王 http://www.cgmodel.cn/ CG模型网

  7. margin合并和浮动

    1.父子元素margin塌陷问题子元素设置margin-top作用于父元素时, 会产生margin合并问题. 解决方法是: 给父元素的::before伪元素设置为display:table属性, 其中 ...

  8. 三, 练习 python索引 (list和tuple)

    (1) 练习 请用索引取出下面list的指定元素: 1, # -*- coding: utf-8 -*- L = [ ['Apple', 'Google', 'Microsoft'], ['Java' ...

  9. shell 运算符章节笔记

    // 运算符 算数运算符 关系运算符 布尔运算符 字符串运算符 文件运算符 1.算数运算符 + - * / % = == != echo `expr 1 + 1`; echo `expr 1 - 2` ...

  10. JMeter 测试中,使用 JDBC 查询条件为中文时解决办法

    在 JMeter 的 jdbc 链接配置插件上,连接 mysql 的 url上加useUnicode=true&characterEncoding=UTF-8即可解决问题.