关于Unity中的资源管理,你可能遇到这些问题
8 个月前
原文链接:关于Unity中的资源管理,你可能遇到这些问题 - Blog
在优化Unity项目时,对资源的管理可谓是个系统纷繁的大工程。鉴于Unity独特又绝(cao)妙(dan)资源打包的AssetBundle管理机制,不同资源的属性适合于不同的存储和加载方式。此外,要处理好成百上千个资源之间的相互依赖关系也非易事。谁说良好的资源管理不是个艺术呢?:)
——————————————————
关键字
AssetBundle
资源制作 纹理\网格\材质\Shader\音频\动画
Lightmap
一、AssetBundle 相关
Q1:Unity中的SerializedFile是怎么产生的?请问用Unload(false)可以清除吗?因为读取了Bundle里面的内容后已经赋值给其他物体了。而且我把图片都打成了Bundle,然后读取出来,图片的大小应该是超过了这个SerializedFile的大小的?
SerializedFile是AssetBundle加载时产生的序列化信息,一般为LoadFromCacheOrDownload、LoadFromFile和New WWW加载本地AssetBundle文件所致。如果AssetBundle中的资源已经加载,且后续没有依赖该AssetBundle的资源进行加载,那么可以通过Unload(false)将其删除。SerializedFile中记录的是AssetBundle的序列化信息,而不是其包含资源的内容,因此,其大小要小于或远小于资源的实际内存。
Q2:我们现在采取了2种资源管理方式, 我想了解下内存占用问题。 方式A:AssetBundle加载好以后在内存中保留, 如果需要创建对象就通过Instantiate来创建。方式B:AssetBundle加载好以后立刻通过Instantiate实例化一个对象, 然后Unload(false)这个AssetBundle,如果需要,创建对象通过clone来完成。对于方式A,在Profiler中发现 WebStream中有相关AssetBundle文件存在, 我们认为这份内存是多余的, 所以就采取了方式B, 方式B又面临资源卸载不干净的情况。想确认一个问题, 如果采取方式A: 一个 xxx.assetBundle原始文件大小是 1MB, 解压到Webstream内存中是2MB, 最终实际内存占用是2MB 还是3MB 呢?
如果采取方式A:最终实际内存占用是2MB,而不是3MB,WebStream中已经包含原始AssetBundle的数据。对于没有依赖关系的AssetBundle文件,我们推荐方式B的形式对AssetBundle进行卸载,这样可以免除不必要的内存占用,对于这种方式加载出来的资源,可以通过Resources.UnloadAsset和Resources.UnloadUnusedAssets来进行卸载,如果无法卸载,则该资源一定被缓存了,研发团队可通过检测自己的缓存池/Constainer来进行检测。同时,如果是Unity 5.3以后版本,可尝试通过Memory Profiler来进一步查看。
Q3:打包时候AssetBundle的md5总变化(被打包的东西没变),请问能怎么解决?有说法是加上DeterministicAssetBundle就可以,但是我尝试后发现md5依然变化。
该方法确实并不受用。对于Unity 4.x版本的AssetBundle文件,其md5值在某些情况下确实会前后不一致(哪怕是完全一样的内容进行打包)。对于该系列版本,仅能建议开发团队建立配置文件来对AssetBundle进行管理。
而对于Unity 5.x版本,则可以在打包时开启 AppendHashToAssetBundleName 选项,这样Unity引擎会在每个AssetBundle文件后生成一个唯一的HashID(显示地放在文件名后),开发团队可以通过该ID来判断对应的AssetBundle文件是否发生改变。
Q4:如果采用依赖打包的话,比如NGUI,图集A作为被依赖包,界面1、2、3作为独立包,分别依赖打包。当图集A重新打包的时候,是不是界面1、2、3也都要重新打包?目前我们是界面打包时清空全部的图集信息再打包,在客户端加载后再动态赋值回来。这样可以实现单独更新图集,但是代价就是加载的性能。请问有什么更好的解决方案呢?
“当图集A重新打包的时候,是不是界面1、2、3也都要重新打包”,这是不需要的,Unity 4.x的依赖打包的限制在于,在重新打一个包时需要将它依赖的包都重新打一次,但不需要重打依赖它的包。
Q5:请问内置的shader怎么打包?我用到了内置材质球,不只是Shader,这时候在Profiler中看到加载的结果中会出现多份,如下图所示
通常有两种方式对内置的Shader进行打包:
- 将其添加到Graphics Settings中的Always Included Shaders 中,此时添加后的内置Shader就不会被打入AssetBundle包中;
- 在Unity - Get Unity - Download Archive下载内置的 Shader,将其导入项目,并替换成非内置的材质球,从而可以直接通过脚本来控制其打包的方式。
Q6:请问粒子特效的Shader是否不能使用依赖打包? 我们对Shader的模型和特效使用了依赖打包,运行的时候发现模型显示是正常的,但是粒子特效使用的Shader就不能正常运行,特效显示不正常。而在编辑器中,我们看到Material中的Shader是存在的。这时候如果重新手动给这个Material指定同样的Shader,这个粒子特效就能正常显示,请问这是什么原因引起的?
部分 Shader 在打包到 Android 版本的 Assetbundle 之后,会因为平台不兼容而无法正确显示,这是因为打包后的 Shader 代码只保留了目标平台的预编译代码,不一定能够在 Editor 下运行,所以这是正常现象。 但这并不会影响依赖打包,因为在真机上并不会出现类似的问题。
Q7:Resource的场景下有两个场景Scene1.unity和Scene2.unity。我要对这些文件进行打包,生成了
Scene1.assetbundle
Scene1.assetbundle.meta
Scene2.assetbundle
Scene2.assetbundle.meta
如果我有相同的资源,理论上它会在这两个包里各存一份,这样就造成了包体过大。所以有没有办法把共享资源做成依赖项单独打包,这样的话每个场景就不会过大了。Unity 5.x的BuildAssetBundles打包机制是否和Unity 4.x不一样?原来的打包机制已经被剔除了吗?
用Unity 4.x 的 Push/Pop 是可以抽出相同的资源,并且据我们所知该方法在Unity 5.x 中也受用。根据Unity 5.x新的打包机制,只要把相同资源的 AssetBundle Name 设置好,打包时就会自动抽出来。
Q8:现在生成AssetBundle的时候每个文件会多生成一个Manifest文件,这个文件也需要一起随着AssetBundle上传吗,在资源加载的时候具体怎么用呢?
每个文件多生成的Manifest 文件是不需要上传的,其作用就是供开发人员查看AssetBundle 中的依赖关系等信息。
但除了每个文件多生成的 Manifest 以外,根目录下还会有一个与根目录同名的AssetBundle 以及 Manifest 文件,通过运行时加载这个AssetBundle,可以得到一个 AssetBundleManifest 对象,然后就可以通过这个对象得到AssetBundle直接的依赖关系。
更多信息可以参考AssetBundleManifest
Q9:如果我有一个Prefab,它的Dependencies都在Resources文件夹中,那么,当我在AssetBundle打包时,只打包这个Prefab(不指定BuildAssetBundleOptions.CompleteAssets和BuildAssetBundleOptionsCollectDependencies)的话,这个Prefab能够正确实例化吗?
这是不能正确实例化的,因为AssetBundle中的资源和Resource文件夹下资源是不会建立依赖关系的(脚本除外,因为开启BuildAssetBundleOptionsCollectDependencies 时,脚本依然不会打包到AssetBundle中)。所以会出现Mesh、Material等的丢失。
Q10:我们通过AssetBundle预加载Shader后,并没有卸载AssetBundle,但是发现后面加载的Object并没有引用到正确的Shader,这可能是由于什么原因呢?
很可能是项目中AssetBundle的依赖关系打包不正确。后续加载的AssetBundle都需要与Shader的AssetBundle文件进行依赖,这样Unity引擎才会在加载后续AssetBundle时,将Shader进行关联。
建议开发团队通过UWA资源检测来检测下AssetBundle文件的依赖关系。主要查看两处,一个是Shader是否被冗余打包;一个是其> 他的AssetBundle是否与Shader的AB进行正确的依赖。具体检测效果如下:
如下图红框所示,开发团队可以直接查看Shader以及其他资源在AssetBundle包中的冗余情况。
Q11:我游戏里重复的特效较多,有些只是图案相同但改变了颜色参数,如果都打成独立AssetBundle,则内存里面会有多份Texture。关于这样的打包一般有什么推荐的方法呢?
如果是相同内容且仅是整体颜色不同的话,那么建议项目中只保留一份初始纹理资源,并通过Shader在运行时改变其整体配色,从而达到不同的效果。但如果是局部配色不同,那么可以在原始纹理的基础上加一种或几种Mask纹理,用来负责颜色的自适应调配,然后再通过Shader来达到不同的展示效果。
Q12:请问一下,我两个预设都引用了第三个AssetBundle的贴图,如果不希望这张贴图存在两份,一定要等这两个预设都加载好了,才能卸载贴图的AssetBundle吗 ?
是的,但并不是因为这样做会使“这张贴图存在两份”,而是因为如果先卸载贴图的AssetBundle会导致后续加载两个预设时会丢失依赖,即找不到贴图。如果脚本中会对这个情况进行检查并重新加载贴图的AssetBundle,那么此时才会造成“这张贴图存在两份”的问题。
Q13:UGUI的图集操作中我们有这么一个问题,加载完一张图集后,使用这个方式获取其中一张图的信息:assetBundle.Load (subFile, typeof (Sprite)) as Sprite; 这样会复制出一个新贴图(图集中的子图),不知道有什么办法可以不用复制新的子图,而是直接使用图集资源 。
经过测试,这确实是 Unity 在 4.x 版本中的一个缺陷,理论上这张“新贴图(图集中的子图)”是不需要的,并不应该加载。 因此,我们建议通过以下方法来绕过该问题:
在 assetBundle.Load (subFile, typeof (Sprite)) as Sprite; 之后,调用
Texture2D t = assetBundle.Load (subFile, typeof (Texture2D)) as Texture2D;
Resources.UnloadAsset(t);
从而卸载这部分多余的内存。
Q14:下图一是刚进游戏时获取的信息,第二张是开关几次同一个UI界面后获取,对比两图我们发现有多份重复的Texture。请问这是为什么?我们的加载方式是UI通过AssetBundle加载,加载后会释放AssetBundle,然后再次加载 UI 就会造成纹理资源的冗余。
刚进游戏时获取的图中出现的“重复”资源可能并不是冗余,因为 Atlas的一个 Group 中可能包含多张一样大小的Page(即纹理),而这几个Page在内存中的名字是一样的。
但是,如果同一UI界面多次开启后,内存中出现了更多同样的资源,则说明UI的管理方式存在一定问题。对于频繁使用的UI,我们建议在加载之后通过缓冲池对其进行缓存,后续使用时,直接通过缓冲池获取即可。而不要每次均通过AssetBundle进行加载,这种做法既会造成更大的CPU占用,同样会很大几率造成资源的冗余。
同时,如果多次开启的是不同UI界面,并且造成内存中同种资源的增加,则很有可能是UI在AssetBundle打包时形成了冗余(这种情况在目前的UGUI系统中较为常见)。对此,如果开发团队使用的是UGUI,那么我们的建议如下:
- 对于使用Unity 5.x的新AssetBundle打包系统,则打包时尽可能将同种Atlas的UI界面打成一个AssetBundle文件,否则将很有可能出现资源冗余的情况;
- 对于使用Unity 4.x的老AssetBundle打包系统,则可以将一个含有Atlas的Prefab(或其他Object)先打包,其他UI元素对其进行依赖即可。
此外,开发团队可以考虑用 http://WWW.LoadFromCacheOrDownload 来加载共享包,因为 new WWW 的方式会在内存中形成 WebStream 造成较多的内存开销。关于该函数的具体优劣,开发者可以参考你应该知道的AssetBundle管理机制。
Q15:项目在发布时,Player Setting中勾选的这个选项(Optimize Mesh Data),对于已经打包并且放到了Streaming Assets中的AssetBundle文件有效果吗?模型资源里有个Optimize Mesh,是否能达到同样的效果?
理论上 Optimize Mesh Data 是 Build Player 或者 Bundle 时才生效的,所以之前打好的 Bundle 就没效果了。另外,两个选项的效果是不同的,后者是调整面片排序的。
二、资源使用
纹理相关
Q1:关于贴图类型设置请问有什么好的建议呢?是否所有的贴图都设置为Advanced比较好?这种情况下的贴图内存是不是比较小?
Unity默认情况下会将绝大多数纹理设置为Texture模式。一般来说,Texture模式对于绝大多数纹理资源也都是合适的。上面的例子中,Advanced模式下之所以内存占用较小,是因为关闭了Mipmap选项,所以其内存下降了。其本质跟Advanced模式无关。Advanced模式较之Texture模式和其他模式,可以更大自由地对纹理资源进行控制。因此,如果你想对纹理资源进行更加自定义地设置,可以选择Advanced模式进行编辑。
Q2:同样的包同一个图集,ETC2格式,在红米Note1上会比酷派的内存会大四倍,请问这是什么原因造成的?如果不支持OpenGL 3.0,会造成这么大的影响吗?
ETC2 的格式理论上只在OpenGL ES 3.0 的设备上被支持,而在不被支持的设备上则会内部自动转成 RGBA32/ARGB32的格式,这对于 RGBA Compressed ETC2 8bits 的纹理就是放大了 4 倍。因此,如果希望在 OpenGL ES 2.0 的设备上对透明材质进行压缩,那么可以尝试使用分离 Alpha 通道的方式,用两个 ETC1 来进行压缩。
Q3:我现在动态加载StreamingAssets下的贴图,代码如下
我发现这种方式内存消耗很大,一张512 x 512的贴图占用了2MB,看官方的解析是内存和显存各需一份。想了解下动态加载贴图有什么推荐的方式吗?
一般来说,我们比较建议通过AssetBundle来动态加载资源,而非通过bytes流来进行加载。如果你的项目正在使用这种方式来加载纹理,我们建议从策略上考虑将其更改。在我们目前来看,通过bytes流来生成资源,绝大部分原因是想对其进行加密,从而让资源难于破解。但其实这种加密方式用处不大,因为据我们所知,现在有很多工具可以直接通过底层显卡层来直接查看各种纹理、Mesh资源,比如Mali Graphics Debugger、Qualcomn Profiler等。因此,如果是从加密的角度来通过bytes流生成资源,那么我们建议通过AssetBundle这种直接的方式进行加载。
Q4:iOS平台需要对图集做RGB和Alpha通道的分离吗?我发现在同样大小的图片(正方形),RGB Compressed PVRTC 4bits和RGBA Compressed PVRTC 4bits两种格式,占用内存是一样的,如果把一张图片分成两张,那么在iOS平台是不是占用内存多一倍?有透明通道的,对于它的图集怎么处理会更好一点?
通常iOS下是不需要做通道分离的,因为 iOS 通用的 PVRTC 格式支持 Alpha 通道。但目前也有团队反馈,在 iOS 上进行通道分离有助于减少失真,可以在一定程度上提高视觉效果,因此也可以尝试做一个对比。
如果发现占用内存是一样的,那么原始图片是RGB的。如果iOS上做通道分离,内存确实会增加一倍。UI的纹理在iOS下可以直接选择默认的 Compress,在打Atlas时会自动处理成 PVRTC,开发团队可以从Sprite packer窗口来看Atlas的压缩格式做个确认。
Q5:在Unity 4.x的版本中,所有UI贴图使用ETC2格式,即使目标设备不支持该格式,也会解压成RGBA32使用。 而在目前使用的Unity 5.3.4版本中,iOS平台无法设置ETC2格式。如果压缩只能使用PVRTC格式,那么PVRTC存在显示效果比较差,并且图片必须为正方形,但如果用RGBA32格式,贴图占用的内存和存储又都过大,请问目前版本的Unity在iOS平台上应该如何设置UI贴图的压缩格式?
我们建议可以先尝试其他的压缩格式看是否可以达到类似的效果,如ASTC格式。ASTC 在 iOS 的高端机上是被支持的,因此理论上在 Editor 下不会强制把 ASTC 转为 RGBA32,建议尝试设置为 ASTC 后打包,从 Editor.log 中或者直接从包体大小上可以看出是否确实使用了 ASTC。
一般来说,如果 RGBA16 的效果可以接受的话,建议使用 RGBA16,虽然打包时相对大一些,但是内存中相比 RGBA32 能够减半,但使用 ASTC 的话,虽然打包时比较小,但是在普通机型上会被处理成 RGBA32,导致过大的内存开销。
Q6:请问Unity引擎中使用什么贴图压缩格式,可以保证在占用内存相对较小的情况下True Color效果和原图相当?同时在iOS和Android平台上图片的压缩格式分别用什么比较合适?有什么需要注意的地方吗?
目前来讲,并不存在一个所有GPU平台都支持硬件解压的压缩格式。 ETC1 和 PVRTC 分别是Android和iOS上我们最推荐的格式。 但对于透明纹理,ETC1不支持,而 PVRTC 则可能有较大失真,因此更推荐使用 RGBA 16。
一般来说建议直接使用 Unity 默认的压缩格式(即选择 Compressed 即可,不需要做特殊设置),Unity 会做如下处理:
- Android 上不带Alpha通道的图片采用 ETC1,带Alpha通道的图片采用True Color中的RGB16,TrueColor中的 RGBA16 会>比 RGBA32 更节省空间,但图像的显示质量会差一些;
- iOS 上使用 PVRTC,但PVRTC格式要求纹理的长宽相等,且都是2的幂次(即POT,在ImportSettings中可以将NPOT的纹理自动转换成POT)。
另外,针对Android 上的带Alpha通道的图片,还有一种常见的做法,即把Alpha通道独立出来作为另一张纹理,从而将 RGB 部分和 Alpha 部分分别采用 ETC1来压缩,但渲染时就需要自定义的 Shader来处理。
同时,我们不建议直接使用 RGBA32 格式的纹理,因为它占用了很大的内存。一般建议使用 RGBA16 和 ETC 格式的纹理来进行加载。 如果转换到 RGBA16 格式时出现了类似“色阶”的颜色问题,则建议尽可能避免大量的过渡色使用。
Q7:请问游戏中特效使用的很多贴图, 一般有什么好的方式去管理吗 ? 不合并图集的话会有上千张小的透明贴图, 合并图集又会有占用内存过多的问题。
可以合并成Atlas,一般将尽可能同时出现频率较高的Texture合成Atlas,这样并不会造成内存过大。在这方面也可以参考我们前不久推荐的插件Mesh Baker。
Q8:iOS上方形POT图片有时候会失真,请问这种情况如何避免?一张NPOT的图变换成POT,是否有推荐的方法? 采用 ToLarger 的模式拉成POT是否会有损失呢?
在其他设置一致的情况下,这两种方式无论在加载还是渲染方面其实并没有实质上的差别。在我们接触到的大多数案例中,纹理资源方面的问题除了尺寸外,纹理格式、Mipmap设置和Read&Write功能同样是需要研发团队时刻关注的。
Q9:纹理Atlas是建议合成一张2048(尺寸)的纹理还是四张1024的纹理?
在其他设置一致的情况下,这两种方式无论在加载还是渲染方面其实并没有实质上的差别。在我们接触到的大多数案例中,纹理资源方面的问题除了尺寸外,纹理格式、Mipmap设置和Read&Write功能同样是需要研发团队时刻关注的。
Q10:NGUI的图集在内存里存了多份,求问怎么清理?
游戏运行中,UI Mesh出现多份不同内存的情况,是正常的,因为随着UI widget使用的增加或减少,创建的UI Mesh是会随着变化的。同时,如果不同UIPanel中存在相同Atlas的Widgets,则也会出现上图中的情况。因此,建议大家遇到这种情况时,查看单帧中NGUI UI Mesh重名的是否有多份重名资源。如果存在,则说明相同Atlas中的资源被多个不同的UIPanel所使用,这种情况是需要尽可能避免的。
Q11:我在UWA报告中看到大量的n/a资源,其格式的高度和宽度等信息都无法获取,并且存在大量重复,请问我该如何定位这些文件?能否优化解决呢?
"图中n/a资源是我们在项目运行时检测到的无名称资源,一般来说,该类型资源并非Asset文件夹中的美术资源,而是项目通过脚本动态生成的资源,比如new Mesh、new Texture等操作,即会生成这种类型的无名称资源。
对此,我们建议研发团队详细检测逻辑代码/第三方插件中诸如New Mesh/Textue等此类操作,同时,在脚本动态生成该类资源后,为其赋上一个名字,这样即可在后续测试中看到对应名称的资源使用情况。"
网格相关
Q1:如果一个模型对应Skinned Mesh Renderer实例,那其所占的内存会随着角色增加而增长么 ?
简单地从一个角色Prefab实例化(Instantiate)出多个实例时,Mesh并不会出现多份(这个行为与其他资源是一致的,包括Texture,AnimationClip,Material等等)。如果在内存中发现多份,可以考虑从项目中AssetBundle的加载方式入手,因为即使是同一个AssetBundle中的同一个角色Prefab,如果被反复进行“加载-实例化-卸载”操作,依然是会导致Mesh出现多份的(当然其他的资源也是一样)。
Q2:我有一个特效依赖了两个FBX。我把这两个FBX的这个勾选去掉,Editor运行正常。否则就会宕机,请问这是什么原因?
将FBX上的Read/Write Enabled关闭后,内存中便不再保存其Mesh的副本(只存在显存中),因此其属性就不可再被访问和修改。而粒子系统通常需要动态地修改其粒子的顶点属性。因此,理论上来说,供粒子系统使用的Mesh是需要开启Read/Write Enabled的,而在Editor下Mesh和Texture都是强制开启的,所以在真机上就会出现问题。
Q3:MeshBaker 烘焙的Mesh可以保存到Prefab中,但是不能像FBX一样,设置Model导入设置中的Generate Lightmap UVs 等信息,请问有没有大招可以处理此情况?
首先从理论上说,一个 Mesh 只能有一个 LightmapIndex 和 LightmapOffset 属性,这就决定了 Mesh 的合并必须在 Lightmap 的烘焙之前。
而在做Mesh合并时,需要注意到UV2对于一个Mesh而言,其所有三角面的UV区域都必须是互不重叠的,所以不能简单直接地合并。
考虑到合并前每一个Mesh的UV2区域都应该是在[0,1]x[0,1]的区间中,在合并时可以给每一个UV2区域做一个缩小和平移,从而可以把每个区间在互不重叠的情况下,放到同一个[0,1]x[0,1]的区间中。
在UV2也正确拼合后,重新进行Lightmap的烘焙即可得到正确的效果。
动画片段相关
Q1:我想要在Editor下批量地对Animator Controller文件中每一个的State里的Animation Clip进行替换,但好像没有看到 Unity引擎有提供类似的API,只提供了能在Runtime时进行替换的方法,类似下图:
我现在想要达到的目地是在Editor下写一个批量替换Animation Clip的插件,请问下Unity引擎是否有提供这样的接口呢?
在Unity 5.x 中已有一套完整且稳定的 API 可以访问、修改和创建 Animator 中的任何元素。其命名空间是在 UnityEditor.Animations 下,以下Blog 中有一个简单的例子是通过脚本创建一个 AnimatorController 以及其中的各种属性和参数。Shiny new animation features in Unity 5.0 – Unity Blog
另外,如果需要修改Animation Clip,可以直接修改一个 AnimationState 的 Motion 属性。 Animation Clip可以继承自 Motion。
Q2:如果我的Animator是直接引用了FBX里的动画文件,而不是复制了FBX的动画文件出来再引用,那么打包的时候会把FBX都打包吗?
这种情况下并不会把 FBX 打入 AssetBundle 中。将动画文件Copy出来通常是为了直接对动画文件打包,作为依赖包。
Q3:我们的动画是放在FBX文件里的
这样做导致打包的时候把一些无用的文件也打进AssetBundle包里了,实际上我们只想使用最后这个动画文件。但在编辑器里选中FBX文件里的动画文件时却没有AssetLabels这个窗口,设置不了AssetBundle Name。
是不是只能用代码设置?还是意味着不能设置AssetBundle Name,只能把动画文件提取出来,类似下图这样单独的一个文件?
在Unity 5.x 的打包机制下确实无法手动为 FBX 下的 Mesh 或 AnimationClip 单独资源设置 AssetBundle Name。因此,如果需要将这部分资源抽出来作为依赖包,目前确实就需要先通过脚本将这部分资源提取出来了。
音频相关
Q1:请问音频中的 Quality 什么意思?一般设置为多少合适?我拖进去一首歌曲,试了一下 在0 和 100 的情况下区别不大,但是生成的音频文件大小差别很大。
Quality 表示在压缩音频时的失真程度(实际上可以认为是压缩算法的一个参数),该值越大,压缩后的文件越大,但音质保留的越好。而对于其失真的程度是视音频数据以及内部的压缩算法而定的,确实会有区别不大的情况。该值的设置原则就是,在音质失真可接受的情况下,越小越好。
材质相关
Q1:我们发现材质实例数量特别多,想问下这个对性能的影响如何,有没有什么建议?
Material的内存占用一般很小,所以大量的Material资源对于内存的压力其实很小的。但是,它本身对于场景的切换时间是有影响的,即资源冗余得越多,切换场景时,UnloadUnusedAssets的开销越大,进而增加了场景的切换时间,同时也会影响DrawCall的拼合。所以,建议研发团队尽可能定位资源冗余的原因,并对其进行修复和完善。这一点,我们在UWA性能测评报告中的“分析和建议”中都有详细说明。
Q2:我在UWA上进行了性能检测,在资源内存这里看到详情如下。请问这个数量峰值大于1是不是就是有问题的?但一个Material是很可能被实例化多份的。实例化后对象释放掉,然后清理掉原始的Material,按道理就不算冗余吧?毕竟大部分都是材质在操作,对贴图资源应该没有影响。而且,如果确实有那么多角色同屏怎么办呢?
是的,如果数量峰值>1,则说明存在资源冗余的风险。如果是Material实例化,那么后者是有个(instance)后缀的,比较好识别。如果是new Material(Shader)出来的,那么是没有instance后缀的。但无论是哪种方式,如果数量峰值较大,都应引起大家注意。一般都是可以通过其他方法来尽可能避免冗余的。
如果变化不是随机的,且Material参数变化情况比较少,那么可以根据参数的变化情况只做几种Material,然后通过脚本动态赋值。如果是随机或者动画,那就没什么办法了,要么改需求,要么就接受这么多。
Shader相关
Q1:我用内建的Shader渲染场景,深度图里有内容。而用自己的Shader,取到的深度图什么都没有,都是1,什么原因导致的呢?我已经打开ZWrite了。
Unity的_CameraDepthTexture 的生成,会根据Rendering Path的选择和设备的不同使用不同的方法实现,一种是直接从depth buffer获取,另一种是添加额外的Pass来渲染到纹理。目前在移动设备上主要是通过后者实现,而后者的实现借助了Shader Replacement的机制(批量替换为简单的Shader,并将深度渲染到纹理中)。
因此,在使用自定义的Shader时,就需要正确地设置RenderType的Tag(所有内置Shader都是设置好的),从而使得Shader Replacement正确地执行。具体可见文档:Rendering with Replaced Shaders
Q2:我们的游戏使用 Spine 插件,因为要用到裁切动画,所以修改了Shader,但在使用的时候出现异常: Shader wants normals, but the mesh Skeleton Mesh doesn‘t have them,可能是什么原因?
开发团队需要注意:Surface Shader在生成代码中默认会处理normal(即 Spine/Skeleton 实际上是需要 normal 的),而对应的Mesh并没有包含normal,所以在预览窗口里渲染的时候会检查 Mesh 是否包含 normal 信息,没有的话会报这个错误。
开发团队可以尝试编写一个Vertex & Fragment Shader 从而避免处理normal,也可以尝试创建带normal的Mesh,来避免该问题。
Q3:以前端游时代,材质根据Pass不同、光照环境不同可以离线预编译成ShaderCache,运行时并不需要拼材质再实时编译,只要加载二进制代码就好了。那Unity有没有做这件事呢?我们是根据平台和环境预编译的Shader。
对于支持 Binary Shader 加载的设备,在首次编译某个 Shader 的时候是会生成其对应的 Binary Shader Cache ,生成的 Binary 文件位于和 Application.persistantPath 并列的Cache 目录下。
Q4:相同效果前提下,就性能而言,Shader 是用 V&F 还是Surface好?
Surface生成的V&F比较庞杂,分支较多,如果不注意 #pragma surface 参数的选择,容易出现不必要的开销。举例来说,如果直接用 Unity 5.x 中默认创建的 Surface Shader (默认参数为 #pragma surface surf Standard fullforwardshadows),那么 Shader 是会做 Physically based Standard Lighting 的,而这在移动端开销非常大,且并非必要。
Q5:某个Shader里设置了Culling Off,会影响到后面所有Shader的渲染状态么(假设后面不再设置Culling)?
一次 Draw Call 提交所相关的 Render State 是不会影响到下一次的渲染状态的。如果不在 Shader 中显示指定 Cull 模式,则会使用默认的 Cull Back。
Q6:我们将Shader放到了Resource的目录下,也已加到Editor的GraphicSetting里,也试过在加载一个空的Prefab时绑定对应的Shader。 但该Shader在Editor里无法正常显示,看运行时指向是有的,重新指一下就能显示了, 然而打包以后在手机上显示正常。
这确实是Unity已知的一个问题,Android 和 iOS 的部分Shader在打包后,在Editor 下无法正常显示。 主要原因是在打包时,只会把对应平台的Shader预编译代码(如 gles )打入包中,因此在 Editor 下会执行失败(通常 Editor 是 d3d 驱动)。 因此,目前只能尝试在Editor下重新指定Shader来绕过这个问题。
Q7:我在shader里这么写的代码
o.texcoord1 = vec2(mod(v.texcoord.x,1.0),mod(v.texcoord.y,1.0));
但是报错 undeclared identifier ‘mod‘ at line 106 (on d3d11),请问是什么原因导致的呢?
报错表明mod在d3d11的Shader中是未定义的,如果开发团队无需在d3d11的平台上使用该 Shader,则可以添加以下预编译指令:
#pragma exclude_renderers d3d11
如果Shader依旧无法正常显示,那可能是因为在Editor中使用的是 DX11(可以从标题栏中看出)。 可以尝试修改DX9的参数 :Build Settings -> 点击 PC, Mac ... -> Player Settings(不需要点击 Switch Platform) -> 去掉 Auto Graphics API for Windows 的勾选,只保留 Direct3D9。同时,开发团队也可以直接使用 fmod 替换 mod,理论上 fmod 在各个平台都是支持的。
字体相关
Q1:我们现在为了美观,需要同时使用2套字体。但是每增加一套字体,就会内存增加50MB左右。请问你们在优化其他Unity游戏时,怎么处理类似情况的?
如果美术字不多的话,建议使用单独的字体贴图来进行实现,从而来达到特定美术字显示的效果;如果美术字较多的话,那么仅能建议使用另外一个套字体来进行实现。一般来说,字体的内存量应该也是完全可以控制在10MB以下的。
开发者可以直接在Unity Profiler中查看字体的内存占用,也可以通过UWA网站上测评报告中的具体资源信息页面查看对应的字体资源使用情况。
Q2:我做的预设用到了这个字体
我将这个预设打成AssetBundle包,通过Profiler分析时发现占了两份内存。
下面这个SIMHEI应该是那个TTF的,为什么会占两份内存呢?
通常TTF文件会包含一个字体的多个字型,如可能包含正常字型、加粗字型、斜体字型等。而在Unity中会将其分为不同的Font资源,且他们之间会相互依赖。所以,如果项目中确实需要加粗字型的话,内存里出现两个Font是正常的,但如果实际上不需要加粗,那么可以尝试寻找一个不包含加粗字型的字体文件来替换该TTF文件。
粒子系统
Q1:我在UWA上提交了资源检测,资源打的是依赖包,报告显示Default—Particle这个资源存在大量冗余,这个是正常的吗?
Default—Particle 这个是粒子系统的默认资源。如果使用的是默认的粒子系统,没有对Material进行修改,且每个粒子系统都是打一个AssetBundle文件的话,那么该冗余问题是正常的。对此,我们建议开发团队把下图中默认的Material换成自己的资源,不使用Built-in资源,这样通过依赖关系打包,就不会出现该资源冗余的问题了。
Lightmap
Q1:Unity 5.x 能把指定集合的Mesh烘焙到一张Lightmap里吗?
Unity 5.x 是没有这样的功能项的。据我们的判断,这样的需求通常是为了动态加载,因此可以尝试将Mesh分组到不同的场景分别烘焙。而在加载时可通过脚本动态地合并Lightmap,同时将物件的Lightmap Index进行正确的偏移即可。
Q2:Lightmap在Baked GI的等待时间比较长(Realtime GI已关闭),想请教有没有什么建议的参数或是方式,可以缩短等待的时间?
目前就我们的了解,在Unity 5.x比较影响烘焙时间的主要是大面积的面片导致Light Transport 过程过久(Enlighten 的机制所限)。可以尝试拆分面积较大的面片,来提高烘焙的速度(通常,拆分大面积的面片对渲染性能也会有所提升)。
主要原因可参考如下的帖子: Light transport problem with large objects
Q3:如下图所示,第一张显示的是没有烘培Lightmap的场景效果,里面有一盏点光源;第二张显示的是烘培Lightmap后的场景效果。请问为什么同一盏点光源照亮的效果差别那么大?
简单来说,这就是实时的直接光照和全局光照的差别。
在渲染时前者只能处理光源和单个物件之间的直接光照,而后者在烘焙时是通过光线跟踪或者辐射度等复杂算法,计算出所有物体各个表面之间相互反射的光照信息,这也是烘焙Lightmap需要较久的时间的原因 。可以发现在全局光照下,即使是物体的背面也会因为其它表面的反射而被照亮,这在直接光线下就无法实现这样的效果。
Q4:当关闭预渲染GI时会出现IndirectResolution,这个参数有什么用,为什么调大了以后会大大增加渲染时间,但是烘培出来没有啥效果。
简单来说,该值主要控制的是GI的烘焙密度,数值越大,表示每个单位距离内的texel越多,即烘焙得越精致,自然烘焙的时间也越长。该值并不需要越大越好,场景越小,建议该值越低。该值为1时,对于多数场景,其烘焙效果已经足够了,升高该值,其效果也不会有明显提升。
开发者也可以参考Unity官方的说明文档:
Unity - Manual: Lighting Window
Q5:Lightmap丢失。用Unity5.1.2的AssetBundle做热更新,资源导出的时候分析了所有的依赖项单文件导出。比如在导出场景的时候场景的烘焙出来的LightmapSnapshot.asset文件导出不了,导致运行的时候场景的Lightmap丢失了。
LightmapSnapshot.asset 本身是Editor下使用的,并不能单独被打包进AssetBundle。 运行时加载Lightmap,一种方法是把场景(.unity 文件)打成AssetBundle加载,Lightmap 信息会打入场景AssetBundle(因为Lightmap信息和场景绑定)。另一种是通过Lightmapsettings.Lightmaps方法来运行时设置。需要注意的是,同时还需要重设Prefab的Lightmap信息(Lightmapindex和Lightmapscaleoffset),因为Lightmap信息在Unity 5.x下不会保存在Prefab 上。
另外存在一种可能,Unity 5.x中加入了Shader Stripping功能,在打包时,默认情况下会根据当前场景的Lightmap及Fog设置对资源中的Shader进行代码剥离。这意味着,如果在一个空场景下进行打包,则Bundle中的Shader会失去对Lightmap和Fog的支持,从而出现运行时Lightmap和Fog丢失的情况。 而通过将Edit->Project Settings->Gaphics下Shader Stripping中的modes改为Manual,并勾选相应的Mode即可避免这一问题。
Q6:Lightmap在PC上显示正常,但是转到Android平台上存在色差,颜色普遍偏暗。
一般来讲,有两种情况可能会导致色偏和亮度差异。
- Unity烘焙的Lightmap是32bit的HDR图,而移动设备通常不支持HDR图(32bit per channel),会按照LDR图(8bit per channel)的形式进行处理,因此会出现色偏问题。因此我们建议:
1)在移动平台下使用Mobile/Diffuse材质,可载入Standard Assets(Mobile) package获得。如果要获得更合适的效果,需要自行修改Lightmap的DecodeLightmap函数,该函数可在Unity\Editor\Data\CGIncludes\UnityCG.cginc文件中找到。需要说明的是,这种方法也不能达到与PC端完全一致的效果。
2)如果需要PC和移动平台的显示效果一致,可以用图像编辑软体修改Lightmap為LDR格式,例如PNG(8bit per channel)。
3)为了避免类似问题,请不要使用过于强烈的Light进行烘焙,因為Light的强度(Intensity)越高,色偏问题会越严重。若有阴影丢失时,可以尝试检查一下模型的Lightmapindex、Lightmapscaleoffset、UV2等影响Lightmap采样的一些参数。- 另一种可能是存在过曝现象,可以尝试将playersettings -> use direct3d 11关闭,看问题是否解决。
Q7:在同一场景里烘培的Lightmap,我用了2张10241024的光照图,大小是5.3MB;别人用了3张10241024的图,大小是4.3MB。请问是什么影响这个光照图的大小,在哪里调?
首先,请确认下Lightmap的类型,Single类型只生成一张,而Dual和Directional会生成两张。 其次,请确认下当前的发布平台,Android下的Lightmap会比Standalone更小。因为不同平台采用的压缩格式不同。此外,Lightmapping中的Lock Atlas,Resolution,Padding等选项也会影响最后烘焙光照图的大小。
Q8:在游戏中,有些Mesh在编辑时候是接收Lightmap的,出于某些原因我们合并了相同的Mesh(材质也相同)。但是发现原先的Lightmap不再影响合并后的Mesh,请问怎么才能实现让合并后的Mesh也接收原先的Lightmap?
如果Lightmap不止一个的话,手动合并Mesh是会出现问题的,因为合并的Mesh烘焙信息很可能出现在不同的Lightmap中,但合并之后的Mesh在渲染时只能使用一个Lightmap,这样uv2读取到Lightmap信息就会出现问题,进而出现这种现象。其实,对于材质相同的Static物体并不需要手动对其Mesh进行combine,因为Unity的Static Batching会自行完成。而如果由于某种特定需求一定要将Mesh进行合并的话,那么也要将其所需要的Lightmap也一并合并,同时改变相应的uv2。不仅如此,Shader中Lightmap也需要进行相应修改,这是比较复杂的,所以我们并不建议这样的做法,因为可能会花掉开发团队大量的开发时间。
Q9:当关闭预渲染GI时会出现IndirectResolution,这个参数有什么用,为什么调大了以后会大大增加渲染时间,但是烘培出来却没有什么效果。
该值主要控制的是GI的烘焙密度,数值越大,表示每个单位距离内的Texel越多,即烘焙得越精致,自然烘焙的时间也越长。
该值并非越大越好,场景越小建议该值越低。该值为1时,对于多数场景,其烘焙效果已经足够了,升高该值,其效果也不会有明显提升。