作者:张鑫
链接:https://zhuanlan.zhihu.com/p/21949663
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

接上一篇 加载模块深度解析(二),我们重点讨论了网格资源的加载性能。今天,我们再来为你揭开Shader资源的加载效率。

这是侑虎科技第59篇原创文章,欢迎转发分享,未经作者授权请勿转载。同时如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)

资源加载性能测试代码

与上篇所提出的测试代码一样,我们对于Shader资源的加载性能分析同样使用该测试代码。同时,我们将Shader文件制作成一定大小的AssetBundle文件,并逐一通过以下代码在不同设备上进行加载,以期得到相应的资源加载性能比较。

测试环境

引擎版本:Unity 5.2版本
测试设备:三台不同档次的移动设备(Android:红米2、红米Note2和三星S6)

Shader资源

Shader资源与之前的网格资源和纹理资源不同,其本身物理Size很小。一般来说,一个Shader资源的物理Size仅几KB,在内存中也不过几十KB。所以,Shader资源的效率加载瓶颈并不在其自身大小的加载上,而是在Shader内容的解析上。因此,我们在本文中选择四种大家项目中最常使用的Shader资源,来让大家更好地了解Shader资源加载的具体开销。

测试1:不同种类的Shader资源加载效率测试
我们选取了四种当前项目中最为常用的Shader资源,它们分别是Mobile-Diffuse,Mobile-VertexLit,Mobile-Bumped Diffuse和Mobile-Particles Additive 。四组网格资源的内存占用分别为93KB、115KB、110KB和6.9KB,其对应AssetBundle大小为10KB、7KB、12KB和3KB(LZMA压缩)。

我们在三种不同档次的机型上加载这些网格资源,为降低偶然性,每台设备上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下表所示。

通过上述测试,我们可以得到以下结论:

1、Shader资源的物理体积与内存占用虽然很小,但其加载耗时开销的CPU占用很高,这主要是因为Shader的解析CPU开销很高,成为了Shader资源加载的性能瓶颈;

2、Mobile/Particles Additive在解析方面的耗时远小于Mobile/Diffuse、Mobile/Bumped Diffsue甚至Mobile/VertexLit;

3、除Mobile/Particles Additive外,其他三个主流Shader在加载时均会造成明显的降帧,甚至卡顿。因此,研发团队应尽可能避免在非切换场景时刻进行Shader的加载操作;

4、随着硬件设备性能的提升,其解析效率差异越来越不明显。

测试2:Mobile Shader vs. Normal Shader
在UWA性能测评报告中,我们发现项目中除使用Mobile Shader之外,也在大量使用Diffuse、Bumped Diffuse等Shader。二者之间在渲染方面的差别大家应该早已知道,那么在加载方面是否也存在一定的性能差距呢?

对此,我们进行了以下这组实验。我们在测试1中的Shader资源的基础上加入了与其对应的Normal Shader,并在三种测试设备上重复进行十次加载操作并将取其平均值作为最终性能开销,具体测试结果如下图所示。

通过上述测试,我们可以得到以下结论:

1、Mobile Shader较之同种Normal Shader在加载方面确实有一定的性能提升;

2、设备性能越低,性能差距越大,比如Mobile/Bumped Diffuse和Bumped Diffuse的加载性能差距在红米2低端机上达到30ms+。

看到这里,想必大多数读者都会产生不小的惊讶,那就是几个小小的Shader,其加载耗时居然要高于几张Atlas纹理或者拥有上万片面的Mesh网格?!是的,没错,Shader的加载开销经常在几百甚至上千毫秒以上。

在UWA性能测评报告中,我们发现大量项目的Shader加载都占据了很大的性能开销。下图则为一款项目在运行过程中的Shader加载耗时情况。可以看出,在切换场景处,Shader加载达到上百毫秒的CPU占用,而在一般副本进行时,也会伴有几十毫秒的CPU耗时。这些对于运行帧率和场景切换效率都会有很大的影响。

那么,问题来了,我们该如何优化它呢?

在优化之前,我们首先要做的是了解Shader解析时的真正耗时原因。一般情况下,Shader加载的CPU耗时与其Keyword数量有关,Keyword数量越多,则加载开销也越大。通过Unity 5.x的Inspector可以看到,Mobile/Bumped Diffuse的Keyword变量数量为39,Mobile/Diffuse的Keyword变量数量为27,Mobile/VertexLit的Keyword变量数量为15,Mobile/Particles Additive的Keyword变量数量为1。类似的,在Unity 4.x中,Mobile/Bumped Diffuse的Keyword变量数量为44,Mobile/Diffuse的Keyword变量数量为25,Mobile/VertexLit的Keyword变量数量为6,Mobile/Particles Additive的Keyword变量数量为0。这也是Mobile/Particles Additive解析开销如此之低的主要原因。

注意:Shader的Keyword数量是会随着场景设置的不同而变化的。在Unity 5.x中,Unity默认会根据场景设置、Shader Pass等来调整Shader的Keyword,比如如果存在Lightmap的使用,则会默认将对应的Keyword打开,而对于没有使用Fog的项目,则会直接将相关Keyword关闭。

在了解了Keyword对于Shader加载效率的重要性之后,我们需要想办法来降低Shader的Keyword数量。对此,我们建议研发团队尝试以下方法:

方法一:
对于Unity 5.x项目,可通过skip_variants操作在Shader中直接去除相关Keyword。

举个例子,在Unity5.2版本中,默认情况下,Mobile/VertexLit的Keyword数量为15,如下图所示。可以看出在Shader片段#1中存在八个Keyword,对此,我们可以在对应的Shader代码中添加skip_variants操作将其去除,则去除后,Mobile/VertexLit的Keyword数量变为8,原来的8个Keyword不再使用,仅留下一个默认的。


由上可以看出,该方法可以有效降低Keyword的数量,但该方法同样有一定的局限,一是目前skip_variants操作仅能在Unity 5.0以上版本中使用,二是该方法需要研发团队对Shader具备一定程度的了解,可根据项目实际情况有针对性对Shader进行修改。

方法二:
直接去除Shader中的Fallback选项。Fallback功能是对于无法使用当前Shader的硬件设备可以使用对硬件设备要求更低的Fallback Shader来进行渲染,以保证渲染的稳定性。但是,就目前的移动市场而言,不支持Mobile/Diffuse和Mobile/Bumped Diffuse的设备已经相当少(或者说,我们目前还没遇到不支持Mobile/Diffuse Shader的设备反馈)。因此,对于使用Mobile Shader的项目,可以尝试直接将其FallBack去掉来大幅降低Keyword的数量。在我们的测试项目中,去掉FallBack功能,Mobile/Bumped Shader的Keyword从原来的39下降到12,Mobile/Diffuse的Keyword从原来的27下降到12。

该方法不会像“方法一”那样完全去除“无用”的Keyword,但该方法简单易用,只需一步操作,因此,性价比很高。同时,该方法完全支持Unity 4.x引擎的项目。

读到这里,你肯定会有一个疑问,即就算Keyword数量可以降下来,Shader的解析效率到底会有多大的提升呢?对此,我们进行了下面的实验:

测试3:开启/关闭Fallback功能的加载效率测试
为简单起见,我们直接关闭Mobile/Bumped Diffuse和Mobile/Diffuse的Fallback功能来制作一组对比数据。关闭Fallback后,这两个Shader的Keyword数量均为12,而原始Shader的Keyword为39和27。

与测试1相同,我们在三种不同档次的Android机型上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下图所示。


通过上述测试可以看出,Keyword的降低确实可以大幅降低Shader的解析时间,进而提升加载效率。

加载方式

以上我们通过具体实验展示和分析了Shader的加载性能、耗时成因和优化方法。在真实的项目研发过程中,大家还需要特别关注一点,那就是Shader的加载方式。正如下图看到的Shader加载情况,每次切换场景时,Shader加载均占用大量的CPU耗时,而实际上,这其实是大量相同Shader重复解析造成的。究其原因,是因为Shader被打包到不同的AssetBundle文件中,每次切换场景时,AssetBundle均会被频繁地进行加载和卸载,从而造成了大量相同的Shader被重复加载和卸载。

针对以上问题,如果你正在使用AssetBundle来加载资源,那么我们推荐的加载方式如下:

1、通过依赖关系打包,将项目中的所有Shader抽离并打成一个独立的AssetBundle文件,其他AssetBundle与其建立依赖;

2、Shader的AssetBundle文件在游戏启动后即进行加载并常驻内存,因为一款项目的Shader种类数量一般在50~100不等,且每个均很小,即便全部常驻内存,其内存总占用量也不会超过2MB;

3、后续Prefab加载和实例化后,Unity引擎会通过AssetBundle之间的依赖关系直接找到对应的Shader资源进行使用,而不会再进行加载和解析操作。

注意:对于Unity4.x版本,Shader的AssetBundle加载后只需LoadAll即可完成所有Shader的加载和解析,但对于Unity5.x版本,除执行LoadAllAssets操作外,还需要进行Shader.WarmupAllShaders操作,因为在Unity5.x版本中,Shader的解析和CreateGPUProgram操作是分离的。

对于Unity 5.x版本且正在使用Resources.Load来加载资源的研发团队,可以尝试使用ShaderVariantCollection来对Shader进行Preload,同样也可以达到避免相同Shader重复加载的效果。

注意:对于Unity5.x版本,如果可以通过AssetBundle来加载和解析Shader,则不建议通过ShaderVariantCollection来处理Shader的加载。在目前最新的Unity 5.3.5中,我们经过大量测试,发现ShaderVariantCollection在Shader的加载和管理中仍然存在一定的问题,我们暂时无法确定是否为引擎的问题,这已经不属于本篇文章的讨论范畴,在此不再赘述。

通过以上测试和分析,我们对于Shader资源的管理建议如下:

1、在保证渲染效果和项目需求的情况下,尽可能降低Shader的Keyword数量,以提升Shader的加载效率;

2、对于简单Shader,可尝试去除Fallback操作,该方法非常适合于目前正在大量使用的Mobile/Diffuse、Mobile/Bumped Diffuse等Built-in Shader;

3、尽可能对Shader进行单独、依赖关系打包并对其进行预加载,以降低后续不必要的加载开销。

以上为Shader资源在加载时的性能测试。关于加载模块的性能问题,我们会不断推出音频、动画片段等其他资源的加载性能分析、资源卸载性能分析、资源实例化性能分析、不同加载方式的性能分析等一系列技术文章,并对目前UWA所检测过项目的共性问题进行总结,以期让大家对项目的加载效率有更加深入的认知,并提升对加载模块的掌控能力。

Unity加载模块深度解析(Shader)的更多相关文章

  1. Unity加载模块深度解析(网格篇)

    在上一篇 加载模块深度解析(一)中,我们重点讨论了纹理资源的加载性能.这次,我们再来为你揭开其他主流资源的加载效率. 这是侑虎科技第53篇原创文章,欢迎转发分享,未经作者授权请勿转载.同时如果您有任何 ...

  2. Unity加载模块深度解析(纹理篇)

    在游戏和VR项目的研发过程中,加载模块所带来的效率开销和内存占用(即“加载效率”.“场景切换速度”等)经常是开发团队非常头疼的问题,它不仅包括资源的加载耗时,同时也包含场景物件的实例化和资源卸载等.在 ...

  3. CesiumJS 2022^ 源码解读[7] - 3DTiles 的请求、加载处理流程解析

    目录 1. 3DTiles 数据集的类型 2. 创建瓦片树 2.1. 请求入口文件 2.2. 创建树结构 2.3. 瓦片缓存机制带来的能力 3. 瓦片树的遍历更新 3.1. 三个大步骤 3.2. 遍历 ...

  4. AngularJS中多个ng-app(手动加载模块)

    1.当有多个ng-app时:(首先是要加载angularJS) <div ng-app=""> <p>姓名:<input type="tex ...

  5. [转]全面理解Unity加载和内存管理

    [转]全面理解Unity加载和内存管理 最近一直在和这些内容纠缠,把心得和大家共享一下: Unity里有两种动态加载机制:一是Resources.Load,一是通过AssetBundle,其实两者本质 ...

  6. apache加载模块的说明

    转: apache加载模块的说明 2017年04月11日 15:23:35 刚子狂想 阅读数:1432   LoadModule auth_basic_module modules/mod_auth_ ...

  7. Linux驱动之内核加载模块过程分析

    Linux内核支持动态的加载模块运行:比如insmod first_drv.ko,这样就可以将模块加载到内核所在空间供应用程序调用.现在简单描述下insmod first_drv.ko的过程 1.in ...

  8. AngularJs 通过 ocLazyLoad 实现动态(懒)加载模块和依赖

    好了,现进入正题,在 AngularJs 实现动态(懒)加载主要是依赖于3个主JS文件和一段依赖的脚本. 实现的过程主要是引用3个主要的JS文件 <script src="angula ...

  9. AngularJs 动态加载模块和依赖

    最近项目比较忙额,白天要上班,晚上回来还需要做Angular知识点的ppt给同事,毕竟年底要辞职了,项目的后续开发还是需要有人接手的,所以就占用了晚上学习的时间.本来一直不打算写这些第三方插件的学习笔 ...

随机推荐

  1. javaScript 正则表达式匹配日期

    // yyyyMMddhhmmss var pattern = /^(?:(?!0000)[0-9]{4}(?:(?:0[1-9]|1[0-2])(?:0[1-9]|1[0-9]|2[0-8])|(? ...

  2. windbg学习---.browse打开一个新的command 窗口

    .browse r eax .browse <command>将会显示新的命令浏览窗口和运行给出的命令

  3. 循序渐进Python3(十)-- 0 -- RabbitMQ

    RabbitMQ     RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.AMQP 的出现其实也是应了广大人民群众的需求,虽然在同步消息 ...

  4. bootstrap datetimerange

    天用的了bootstrap日期插件感觉搜索的资料不是很多在此写下一些使用的心得: 插件开源地址:daterangepicker日期控件, 插件使用只要按照开源中的文档信息来就好先包括以下引用: < ...

  5. 从宏观到微观理解coding

    宏观思维就是对所做事情的整体有一个清晰认识,并知晓我们追求的目标是什么.在理解了整体之后,我们需要针对自己感兴趣部分做深入的了解,这就是所谓的微观思维.古语"不谋全局者,不足谋一域" ...

  6. python之路——基础篇(2)模块

    模块:os.sys.time.logging.json/pickle.hashlib.random.re 模块分为三种: 自定义模块 第三方模块 内置模块 自定义模块 1.定义模块 将一系列功能函数或 ...

  7. 【NoSql】Redis

    [NoSql]Redis 一. 文档 1. 官网 2. Windows 安装包 3. C# Driver a. ServiceStack.Redis 最新版本是收费的 b. StackExchange ...

  8. 利用Service bus中的queue中转消息

    有需求就有对策就有市场. 由于公司global的policy,导致对公司外发邮件的service必须要绑定到固定的ip地址,所以别的程序需要调用发邮件程序时,问题就来了,如何在azure上跨servi ...

  9. hdu 1258 Sum It Up(dfs+去重)

    题目大意: 给你一个总和(total)和一列(list)整数,共n个整数,要求用这些整数相加,使相加的结果等于total,找出所有不相同的拼凑方法. 例如,total = 4,n = 6,list = ...

  10. python之socket 网络编程

    提到网络通信不得不复习下osi七层模型: 七层模型,亦称OSI(Open System Interconnection)参考模型,是参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互 ...