在某个PC项目中使用了大量的材质球, 并且都使用了自带的Standard Shader, 在编辑器运行的时候, 一切良好, 运行内存只在1G左右, 然而在进行AssetBundle打包之后, EXE运行内存暴涨至20G都还没进入场景, 简直不可思议.
PS: 所有测试都在 Unity5.6 / Unity2017.3 / Unity2018.3 / Unity2019.2 中测试过
看SVN美术人员添加了SpeedTree, 各种花草树木, 考虑到是不是shader的变体过多导致的shader编译问题, 就先把所有Nature开头的built-in shader加入到GraphicSettings的AlwaysIncludedShaders里面去
这样加了之后可以打开场景了, 运行内存仍然飚到12G......developerment build 连接到Profiler查看, 光是ShaderLab就占了5.7G... 这样看来果然是shader造成的了, 然后检查一下Asset中各个Shader的资源情况发现
相同的Shader没有重用, 在每个对象上都进行了重复编译!!!
(注意这里显示的shader大小只是引用大小, 实际的编译大小都算到ShaderLab里面去了)
猜测该不会是因为运行时的变体编译混乱导致的每个对象都被认为是唯一的变体, 导致每个物体都进行了编译呢? 于是在编辑器下运行场景, 然后把所有变体都自动记录下来, 运行时自动加载试试.
(运行场景之后在运行状态下创建Preload Shader Variants Collection)
然而并没有什么用, 内存还是那样.
尝试把Standard Shader加入到AlwaysIncludedShaders里面去, 结果跟官方说的一样, 在Build Exe时就卡死了, 几十分钟没反应那种, 这个不能加进去.
去找Unity官方论坛吧, 也没人回复, 找到一个老帖子, 里面有说道:
https://forum.unity.com/threads/standard-shader-duplicated-in-asset-bundle-build.593248/
Unity团队的这个人叫我们自己把Built-In Shader下载了放到工程里面去, 替换掉原来使用的Standard Shader, 可是为什么? 也不说明原因, 然后后面也没有什么有用的回复了,
这不是多此一举吗? 所以根据他的说法, 我猜测在使用AssetBundle时, Built-In Shader的封包方式应该跟未命名assetBundleName 的资源一样, 哪个包需要它, 它就被封到哪个包里去,
然后在实例化的时候, 直接从那个包里对shader读取然后编译, 因为很多材质是被交叉使用的, 很多材质都单独成包, 所以会造成明明是相同的shader并且变体都一样仍然被多次编译的BUG.
既然这样猜测了, 那就实测一下吧, 新建一个工程, 拖进去一些建筑之类的, 首先测试Built-In 的Standard Shader.
因为材质都单独成包了, 所以运行时应该Standard Shader应该会每个材质对应一次编译
这里看到了ShaderLab相当大, 并且还真是每个编译的Standard Shader 对应一个材质... 查看文件的话, 每个材质把Shader封进去了之后, 达到98KB大小, 非常大.
总之就是最坑人的情况 : 文件又大, 运行又占内存, 运行时编译又花时间!!!
接下来我把所有材质封到一个包里, 这样理论上来说所有用到Standard Shader 的材质都封一个包, 也就是运行时只会根据变体数量来编译, 不会进行重复编译了吧.
不信的话使用解包软件打开看
这样看来, Built-In Shader 在打包时确实是跟未命名assetBundleName 的资源一样, 打到了每个需要的包中去了, 造成了各个资源文件的膨胀, 造成了Shader的重复编译, 以及重复编译的时间开销.
类推下来其它的比如 UI, 树 等如果用到了也会造成同样结果, 只不过UI使用的Shader比较轻量, 一般不会太过在意, 这次因为项目大量的Nature/Standard Shader被使用引起性能问题才被注意到...
下面测试一下Unity员工说的使用下载来的Built-In Shader替换原Shader的情况, 还是分两种, 一种每个材质分包, 一种所有材质一个包
(跟当前版本的Built-In 一样的Standard, 这个Shader先单独打包)
(打包后运行, 比Built-In 最小时候的6.9MB还小, 暂时不知道原因)
看看它打出的包文件:
结合之前的Built-In Shader每个材质98KB来看, 就是把每个材质跟Shader打成一个包了, 大量重复打包了.
再测试一下所有材质封一个包的情况
看看打包后文件
(这里更优秀了, 所有材质只占了17KB, 总共只占114KB, 而上面的Built-In 同样情况占了208KB)
PS : 补充最后一种情况, 就是下载来的Built-In Standard Shader不设置包名, 也就是跟自带的 Standard Shader 一样的情况, 会怎么样呢?
所以可以总结 : 使用系统自带Shader打包的时候, 因为无法设置Built-In Shader的包名, 所以根据依赖打包会把所有依赖Shader的包都打进相应Shader,
造成资源包变大, 重复编译, 运行时内存暴涨等问题. 解决方法:
1. 所有使用到相同Built-In Shader的材质都打到同一个包里......
2. 下载一份Built-In Shader, 所有资源使用下载来的Shader, 每个Shader如果有多次被引用, 一定要设置包名.
3. 貌似Unity2019的BuildPipeline 的task还是啥的, 有设置Shader的相关参数, 没试过...
归根结底还是打包依赖的问题, 跟Shader的变体编译那些都没有关系.
补充一下经过优化后的运行时内存 (启动时间已经缩短了几乎70%):
之前只是几百个Standard的编译就能让编译时间飚到3分钟左右, 十分可怕, 现在整个场景开启也只要1分钟.