在某个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 的材质都封一个包, 也就是运行时只会根据变体数量来编译, 不会进行重复编译了吧.

 (全都在standard这个包里了)

 (打包后运行跟预想一样, 感觉没有重复编译了)

 (明显这些同一个包里的材质共用了一个编译后的Shader)

(所有材质打成的包, 只有206KB)

不信的话使用解包软件打开看

  这样看来, Built-In Shader 在打包时确实是跟未命名assetBundleName 的资源一样, 打到了每个需要的包中去了, 造成了各个资源文件的膨胀, 造成了Shader的重复编译, 以及重复编译的时间开销.

类推下来其它的比如 UI, 树 等如果用到了也会造成同样结果, 只不过UI使用的Shader比较轻量, 一般不会太过在意, 这次因为项目大量的Nature/Standard Shader被使用引起性能问题才被注意到...

  下面测试一下Unity员工说的使用下载来的Built-In Shader替换原Shader的情况, 还是分两种, 一种每个材质分包, 一种所有材质一个包

 (跟当前版本的Built-In 一样的Standard, 这个Shader先单独打包)

 (所有材质替换Shader, 材质单独打包)

 (打包后运行, 比Built-In 最小时候的6.9MB还小, 暂时不知道原因)

 (对的)

看看它打出的包文件:

 (Shader文件本身占了97KB)

 (每个材质文件占了2KB)

  结合之前的Built-In Shader每个材质98KB来看, 就是把每个材质跟Shader打成一个包了, 大量重复打包了.

再测试一下所有材质封一个包的情况

 (还是所有材质都放standard里)

 (运行时编译还是这么优秀)

 (同样的结果)

看看打包后文件

 (Shader文件本身占了97KB)

 (这里更优秀了, 所有材质只占了17KB, 总共只占114KB, 而上面的Built-In 同样情况占了208KB)

PS : 补充最后一种情况, 就是下载来的Built-In Standard Shader不设置包名, 也就是跟自带的 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%):

 (ShaderLab从5.7G减少到279MB了)

  之前只是几百个Standard的编译就能让编译时间飚到3分钟左右, 十分可怕, 现在整个场景开启也只要1分钟.

 运行内存也下来了, 虽然也是突破天际的

Unity Built-In Shader造成的运行时内存暴涨的更多相关文章

  1. vue-cli项目开发运行时内存暴涨卡死电脑

    最近开发一个vue项目时遇到电脑卡死问题,突然间系统就非常卡,然后卡着卡着就死机了,鼠标也动不了了,只能冷启动.而且因为是突然卡死,没来得及打开任务管理器. 最开始以为是硬盘的问题,但是在卡死几次后, ...

  2. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(6)-Unity 2.x依赖注入by运行时注入[附源码]

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(6)-Unity 2.x依赖注入by运行时注入[附源码] Unity 2.x依赖注入(控制反转)IOC,对 ...

  3. java运行时内存模式学习

    学习java运行时内存模式: 各区介绍: 方法区(线程共享):用于存放被虚拟机加载的类的元数据:静态变量,常量,以及编译和的代码(字节码),也称为永久代(所有该类的实例被回收,或者此类classLoa ...

  4. JVM 运行时内存结构

      1.JVM内存模型       JVM运行时内存=共享内存区+线程内存区 1).共享内存区       共享内存区=持久带+堆       持久带=方法区+其他       堆=Old Space ...

  5. JVM运行时内存结构

    原文转载自:http://my.oschina.net/sunchp/blog/369707 1.JVM内存模型 JVM运行时内存=共享内存区+线程内存区 1).共享内存区 共享内存区=持久带+堆 持 ...

  6. Java运行时内存划分与垃圾回收--以及类加载机制基础

    ----JVM运行时内存划分----不同的区域存储的内容不同,职责因为不同1.方法区:被线程共享,存储被JVM加载的类的信息,常量,静态变量等2.运行时常量池:属于方法区的一部分,存放编译时期产生的字 ...

  7. JVM运行时内存组成分为一些线程私

    JVM运行时内存组成分为一些线程私有的,其他的是线程共享的. 线程私有 程序计数器:当前线程所执行的字节码的行号指示器. Java虚拟机栈:java方法执行的内存模型,每个方法被执行时都会创建一个栈帧 ...

  8. [转]JVM运行时内存结构

    [转]http://www.cnblogs.com/dolphin0520/p/3783345.html 目录[-] 1.为什么会有年轻代 2.年轻代中的GC 3.一个对象的这一辈子 4.有关年轻代的 ...

  9. java程序运行时内存分配详解

    java程序运行时内存分配详解 这篇文章主要介绍了java程序运行时内存分配详解 ,需要的朋友可以参考下   一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个 ...

随机推荐

  1. 【cf1046】A. AI robots(动态开点线段树)

    传送门 题意: 坐标轴上有\(n\)个机器人,每个机器人带有属性\(x,r,q\),分别表示位置.可视半径以及智商. 现在定义智商相近为两个机器人的智商差的绝对值不超过$K. 现在问有多少对机器人,他 ...

  2. 剑指Offer-3.从尾到头打印链表(C++/Java)

    题目: 输入一个链表,按链表从尾到头的顺序返回一个ArrayList. 分析: 很简单的一道题,其实也就是从尾到头打印链表,题目要求返回ArrayList,其实也就是一个数组. 可以将链表中的元素全部 ...

  3. vue使用技巧

    引入外部js文件 1.在根目录创建文件夹,例如‘libs’,将js文件拷贝至libs目录下 2.修改webpack.dev.conf.js和webpack.prod.conf.js,在CopyWebp ...

  4. 【Java并发专题之二】Java线程基础

    使用线程更好的提高资源利用率,但也会带来上下文切换的消耗,频繁的内核态和用户态的切换消耗,如果代码设计不好,可能弊大于利. 一.线程 进程是分配资源的最小单位,线程是程序执行的最小单位:线程是依附于进 ...

  5. ICP 匹配定位算法学习记录

    icp 算法原理是: 选取目标点云P和源点云Q,按照一定的约束条件,找到最邻近点(pi,qi),然后计算出最优R和t(旋转和平移), 使得误差函数最小,误差函数E(R,t): 基本算法流程: 1.在目 ...

  6. VMware exsi虚拟机磁盘扩容

    创建Linux时分配磁盘空间随着使用的增加,使用率逐渐升高,需要对/root进行扩容,此时需要在添加或者扩展一下磁盘. 查看Linux版本信息 [root@localhost ~]# cat /etc ...

  7. perf性能调优

    工具准备 ubuntu:sudo apt-get install perf 嵌入式平台:下载源码编译 注意:编译过程中会有Auto-detectiing system feature的依赖库打印,注意 ...

  8. python asyncio asyncio wait

    import asyncio import time async def get_html(url): print("start get url") await asyncio.s ...

  9. jvm的组成入门

    JVM的组成分为整体组成部分和运行时数据区组成部分. JVM的整体组成 JVM的整体组成可以分为4个部分:类加载器(Classloader).运行时数据区(Runtime Data Area).执行引 ...

  10. C#中文转换为拼音NPinyin代码【转】

    项目地址:https://code.google.com/p/npinyin/ 在一个采集的程序中,为了给每个文章起一个别名,据说有很好的别名的话,对于百度.google的收录 是很有好处的.按照Se ...