更新

  • 2019/04/21 增加源码链接
  • 2018/12/16 初次发布

问题

优化 Shader 加载时间官方文档有详细的介绍:Unity - Manual: Optimizing Shader Load Time

但是平时对 Shader 加载时间感受最深的应该是首次显示 Shader 对应的材质时,游戏会卡一下,通过 Profiler 分析可以看到编译时间占了大头。

One downside of this default behavior, however, is a possible hiccup for when some shader variant is needed for the first time - since a new GPU program code has to be loaded into the graphics driver. This is often undesirable during gameplay, so Unity has ShaderVariantCollection assets to help solve that.

官方在上述方档中也提到了解决方案,就是使用新增加的 ShaderVariantCollection 来收集并编译。

解决方案

如何生成 ShaderVariantCollection?Unity 并没有提供公开 API 收集 Shader 的变体,经过上网仔细搜索查找后,发现并没有合适的方案。

文本分析

尝试分析 Unity 保存的 ShaderVariantCollection 文件,发现里面存有两类属性:Keyword 与 PassType。

Keyword

通过在所有 *.mat 文件中全局搜索文本 m_Keywords:,可以找到材质中的所有 Keyword。

检查其中保存的 Keyword,发现并不能与 ShaderVariantCollection 保存的属性完全匹配:有一部分 Keyword 是缺失的(光照与阴影),另一部分的组合并不存在。

PassType

并没有找到一个合适的方式获得 PassType。

Unity 导出

Unity 在 GraphicsSettings 可以将在编辑器中出现过的 Shader 相关信息导出为 ShaderVariantCollection。可以通过反射调用 Unity 内置 API。

1
2
var methodInfo = type.GetMethod(method, BindingFlags.NonPublic | BindingFlags.Static);
methodInfo.Invoke(null, parameters);

其次可以分别将每一个材质都放到一个 3D 模型上,然后使用相机观察,最后将结果保存为文件。

如何自动化导出呢?可以尝试使用 update 钩子,然后执行定时任务。

查找范围

材质有很多种来源:

  1. Resources 目录中的材质
  2. 要打包场景中依赖的材质

收集完成后保存到字典中供后续步骤使用

准备相机

根据材质的数量与 3D 模型的大小,确定相机的显示范围,设置好相机的参数。

其他变量

由于 Unity 中存在全局变量,如 ShaderGlobalLOD 值。需要定时将这些全局变量设为不同的值,以便 Unity 能完整地收集到所有的 Keywords 组合。

保存

最终在设置完相关的信息,等待一小段时间后可以直接保存结果。

结果

最终使用较为山寨的方法获得 ShaderVariantCollection,此方法虽然简单便利,但是获得的组合并不完整,很且每次执行结果会有不同,不过好在只需要收集到大部分组合就可以了,剩下的显示时遇到再去编译影响也不大。

源码:Unity 导出 ShaderVariantCollection - 狂飙