在某个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. RAID几种方式

    RAID(Redundant Array of Independent Disk 独立冗余磁盘阵列)技术是加州大学伯克利分校1987年提出,最初是为了组合小的廉价磁盘来代替大的昂贵磁盘,同时希望磁盘失 ...

  2. pyquery解析库

    这一篇整理一下pyquery这个解析库.还是菜,若有错误的地方,欢迎大家随时指正.......(come on.......) pyquery:是一个css选择器,再使用时,也需要传入HTML文本来初 ...

  3. CentOS7 通过 devstack 安装 OpenStack

    安装前的准备 修改源 (可跳过) 将下载源变更到国内可以时下载速度大大提升 打开下面的文件 vim /etc/yum.repos.d/CentOS-Base.repo 将原来的注释掉改成: [base ...

  4. CF-1238 C.Standard Free2play

    题目大意: 有一个墙,高度为h,在每一个高度处都有一个踏板,有的踏板是隐藏着的,有的是伸出来的,小人站在h高度处(题目保证h高度处的踏板一定是伸出来的),这个小人每站到一个踏板上,就可以点一个开关,将 ...

  5. 图像检索——VLAD

    今天主要回顾一下关于图像检索中VLAD(Vector of Aggragate Locally Descriptor)算法,免得时间一长都忘记了.关于源码有时间就整理整理. 一.简介 虽然现在深度学习 ...

  6. stacky footer

    div{border:1px solid #CCC;} .wrapper{ width:100%; height:100%; display:flex; flex-flow: column; } .c ...

  7. 树莓派4B 更新wiringPi库到2.52的方法的wiringPi库2.5.2版本wiringpi-latest.deb下载

    树莓派4B 更新wiringPi库到2.52的方法 – 树莓派中文站 http://www.52pi.net/archives/1918 通过如上链接可知,需要通过如下命令下载wiringpi-lat ...

  8. Spring源码系列 — Envoriment组件

    何为Envoriment Envoriment是集成在Spring上下文容器中的核心组件,在Spring源码中由Envoriment接口抽象. 在Environment中,有两大主要概念: Profi ...

  9. LeetCode 1290. Convert Binary Number in a Linked List to Integer

    题目 /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListN ...

  10. GAN简介

    GAN Generative Adversarial Networks 生成对抗网络.学习真实世界的真实数据的分布,用于创造以假乱真的数据.比如前段时间很火的应用deep fake.deep nude ...