Unity Built-In Shader造成的运行时内存暴涨
在某个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造成的运行时内存暴涨的更多相关文章
- vue-cli项目开发运行时内存暴涨卡死电脑
最近开发一个vue项目时遇到电脑卡死问题,突然间系统就非常卡,然后卡着卡着就死机了,鼠标也动不了了,只能冷启动.而且因为是突然卡死,没来得及打开任务管理器. 最开始以为是硬盘的问题,但是在卡死几次后, ...
- 构建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,对 ...
- java运行时内存模式学习
学习java运行时内存模式: 各区介绍: 方法区(线程共享):用于存放被虚拟机加载的类的元数据:静态变量,常量,以及编译和的代码(字节码),也称为永久代(所有该类的实例被回收,或者此类classLoa ...
- JVM 运行时内存结构
1.JVM内存模型 JVM运行时内存=共享内存区+线程内存区 1).共享内存区 共享内存区=持久带+堆 持久带=方法区+其他 堆=Old Space ...
- JVM运行时内存结构
原文转载自:http://my.oschina.net/sunchp/blog/369707 1.JVM内存模型 JVM运行时内存=共享内存区+线程内存区 1).共享内存区 共享内存区=持久带+堆 持 ...
- Java运行时内存划分与垃圾回收--以及类加载机制基础
----JVM运行时内存划分----不同的区域存储的内容不同,职责因为不同1.方法区:被线程共享,存储被JVM加载的类的信息,常量,静态变量等2.运行时常量池:属于方法区的一部分,存放编译时期产生的字 ...
- JVM运行时内存组成分为一些线程私
JVM运行时内存组成分为一些线程私有的,其他的是线程共享的. 线程私有 程序计数器:当前线程所执行的字节码的行号指示器. Java虚拟机栈:java方法执行的内存模型,每个方法被执行时都会创建一个栈帧 ...
- [转]JVM运行时内存结构
[转]http://www.cnblogs.com/dolphin0520/p/3783345.html 目录[-] 1.为什么会有年轻代 2.年轻代中的GC 3.一个对象的这一辈子 4.有关年轻代的 ...
- java程序运行时内存分配详解
java程序运行时内存分配详解 这篇文章主要介绍了java程序运行时内存分配详解 ,需要的朋友可以参考下 一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个 ...
随机推荐
- getpatch
import time import os import math import sys import os,os.path,shutil import numpy as np import cv2 ...
- oracle用户管理, 授权与回收权限
一. 用户管理参数, 0.删除用户: drop user 用户名 [cascade] 当我们删除用户时, 如改用户已创建过数据对象, 那么删除用户时必须加cascade参数, 用来同步删除 改用户的所 ...
- CF 938G Shortest Path Queries
又到了喜闻乐见的写博客清醒时间了233,今天做的依然是线段树分治 这题算是经典应用了吧,假的动态图(可离线)问题 首先不难想到对于询问的时间进行线段树分治,这样就可以把每一条边出现的时间区间扔进线段树 ...
- python-9-列表的增删改查
前言 本节是:列表(list)的增删改查.什么是列表? 列表(list)是最常用的Python数据类型,它可以作为一个方括号[]内的逗号分割值出现.如:[1,5,"b"] 一.增 ...
- Unity Shader 屏幕后效果——全局雾
Unity内置的雾效需要在每个shader中分别编写,造成了极大的不便.这里利用屏幕后处理产生可单独控制且自由度更高的雾效. 屏幕后雾效的本质在于,通过深度纹理重构出每个像素在世界空间中的位置,根据得 ...
- Vue router-link路由不同的写法,不一样的效果
我想要的路径:http://localhost:8080/#/main/hato/realtime/eventDetail/238 情况一:未进行路由配置: { path: 'eventD ...
- Python 多进程池
def get_html(n): time.sleep(n) print("sub_progress success") return n # 多进程池 pool = multip ...
- Windows 上安装 Rust 出错及其解决办法
首先去 https://www.rust-lang.org/tools/install Rust 官网上下载相应的安装包. 直接运行 rustup-init.exe,这一步 会把 rustc, car ...
- SpringBoot-@ControllerAdvice 拦截异常并统一处理
SpringBoot是为了简化Spring应用的创建.运行.调试.部署等一系列问题而诞生的产物, 自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可 ...
- 【题解】Informacije [COCI2012]
[题解]Informacije [COCI2012] 传送门:官方题面 [题目描述] 有一个长度为 \(n\) 的 序列 \(a\)(由 \([1,n]\) 中的数组成,且每个数只会出现一次),现给出 ...