UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - 纹理
在上一期《动画优化：关于AnimationClip的三两事》中，我们针对动画优化中的“AnimationClip的压缩方式”以及“AnimatorController挂载的AnimationState”做了解释。这些看似细小的知识点，很容易在大家的开发和学习过程中被疏忽，而长期的问题积累最终都会反映到项目的性能表现上。为此，我们将这些规则列出，并且以一个个知识点的形式向大家逐一解读。 本期，我们将面向粒子系统，讲解UWA本地资源检测中的规则：“网格发射数超过5的粒子系统”、“检测粒子系统使用的网格Read/Write选项”、“引用网格面片数超过500的粒子系统”和“引用纹理数超过5的粒子系统”。我们将力图以浅显易懂的表达，让职场萌新或优化萌新深入理解。 1、网格发射数超过5的粒子系统 关于粒子系统的定义，我们在此不再赘述。我们重点来看一下粒子系统的渲染模式。 粒子的渲染模式可分为两大类：2D的Billboard（公告牌）图形模式和Mesh模式。Billboard是3D游戏中用的非常多的一种技术，使用该模式，粒子会以一个平面的形式存在，并始终以一定的角度对着我们的镜头。举个例子来讲，场景中有一棵树，是实体存在的；但在各种Billboard模式下，你只需要看到这棵树的一个“面”。在展示效果上表现其实一样，但渲染一个“面”和渲染一整个实体，前者在消耗的内存和运算量上会更有优势。 Billboard模式，粒子会始终面向摄像机； Stretched Billboard模式下，粒子在面向摄像机的同时，会在速度的方向上进行拉伸，可以设置粒子的各种缩放和拉伸效果； Horizontal Billboard模式下，粒子平面会和XZ面平行； Vertical Billboard模式下，粒子平面在面向摄像机的同时，会在Y轴方向上直立（垂直于XZ平面）； 而“Mesh”模式就是我们今天的主题。这个模式允许粒子系统发射3D网格而非2D的Billboard，以此来实现更为复杂和贴合需求的粒子效果。 3D网格性能开销显然是高于2D的Billboard的。如果粒子系统的发射的网格数量过大，会带来较高的CPU计算开销、GPU渲染压力甚至较高的堆内存分配。所以我们对使用Mesh进行渲染的粒子系统的“最大粒子数”要求更为严苛。一般的粒子系统中，粒子发射数上限建议不超过30（我们在之前的文章中有进行讨论，详见《【性能黑榜】掌握了这些规则，你已经战胜了80%的对手！》）。经由UWA的测试结果表明：渲染模式为Mesh、网格发射数在不超过5的情况下，能够维持粒子效果和性能消耗上的一个相对平衡。 所以开发团队在本条规则执行后，需要对筛选出来的粒子系统进行显示效果和性能消耗上的平衡考量，并对相应的粒子系统进行修改。 2、所使用的网格未开启Read/Write选项的粒子系统 由于粒子系统的渲染对象从传统的粒子变成了3D网格，所以引用的网格对象本身的属性也会对整个粒子系统产生影响。在这种情况下，很多针对网格的优化建议也同样适用于Mesh模式下的粒子系统，只不过针对特定应用环境，部分优化建议需要“反着看”。 在之前的文章《网格优化：溃堤之穴，一个也不能放过》中，我们对于网格的Read/Write选项进行过一定的说明。仅仅针对网格而言，我们是不建议开启相关的读写权限。 但是在粒子系统应用中，往往需要去实现各种各样的动态效果。所以当渲染模式为Mesh时，网格就需要开启Read/Write选项以实现对3D网格的动态修改，来更好地实现相应的渲染展示效果。 Unity官方文档告诉我们，网格必须设置为Read/Write Enabled才能在粒子系统上工作。若不开启，在某些设备上运行时，可能会出现模型丢失的情况。可见问答：https://answer.uwa4d.com/question/5cf732fed27511377098284e。因此我们将该类异常的资源检测出来，希望研发团队进行规范，防止出现意外的显示结果。 3、引用网格面片数超过500的粒子系统 和上一条规则不同，这条便是网格优化在粒子系统中的正向应用。在以前文章《那些年给性能埋过的坑，你跳了吗？》中，我们对于网格的面片数有过相关的简单讲解。对3D网格而言，过多的面片数会带来内存，加载和渲染上的额外消耗。在粒子系统中也是同样如此。 在Mesh模式下，发射的3D网格对面片数的要求也是同样严苛的。所以开发团队需要在本条规则下，去检查相关粒子系统引用的网格是否存在面片数过多的情况，并视实际的表现效果和性能消耗去决定是否需要做出相关的修改。 4、引用纹理数超过5的粒子系统 在之前的文章《材质优化：如何正确处理纹理和材质的关系》中，我们简单讲了下材质的Shader中引用了过多纹理会在内存和计算上带来的影响。 同样的，在粒子系统中，如果在材质内包含了过多的引用纹理，也同样会导致存储和运算上的额外消耗，从而对整个的粒子系统带来不必要的负担。 所以开发团队在为纹理系统引入材质时，需要关注相关材质内引用的纹理数量。在满足使用的前提下，从材质做瘦身，从而为整个粒子系统调优。 以上，大家其实可以发现，看似各个独立的部分：网格、粒子系统、材质和纹理等，其实在优化上都有着互相的关联。所以面对项目优化，开发团队要以一个整体的高度去看待项目的各个部分。 希望以上这些知识点能在实际的开发过程中为大家带来帮助。需要说明的是，每一项检测规则的阈值都可以由开发团队依据自身项目的实际需求去设置合适的阈值范围，这也是本地资源检测的一大特点。同时，也欢迎大家来使用UWA推出的本地资源检测服务，可帮助大家尽早对项目建立科学的美术规范。 我们已支持Luacheck功能啦~ 我们已支持粒子特效检测~ 下周会有重磅新功能推出喔 这么多实用贴心的功能 还不走你~ 万行代码屹立不倒，全靠基础掌握得好！ 性能黑榜相关阅读 《那些年给性能埋过的坑，你跳了吗？》 《那些年给性能埋过的坑，你跳了吗？（第二弹）》 《掌握了这些规则，你已经战胜了80%的对手！》
本期聚集话题：材质（贴图）之间的性能影响、粒子系统不断修改发射点的性能最优解、如何渲染一个低分辨率的DepthTexture、Unity中物体实时光照的性能开销... 这是第105篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间15分钟，认真读完必有收获。文末，我们的互动话题是：.net 4.6是否值得尝鲜？ UWA 问答社区：answer.uwa4d.com UWA QQ群：793972859（仅限技术交流） 制作 Q1：请教一个问题，我们项目中有一种粒子系统需要不断地修改发射点，用worldspace模式确实可以达到一定的效果，但是目前需要在一帧里从若干个位置同时发射粒子，用多个相同的Particle System确实可以达到目的，但总觉得有些浪费，容易造成频繁的创建和销毁，如果都缓存的话总数可能就会很大了，不清楚有没有可能用一个Particle System来实现这个效果？ 可以参考这个文档: https://docs.unity3d.com/ScriptReference/ParticleSystem.Emit.html 通过调用ParticleSystem的Emit方法来发射粒子，可以在一帧里用不同的EmitParams进行多次调用Emit。这样的话，粒子系统本身就不用移动了，只是每次Emit的位置参数不同。 感谢zZ@UWA社区提供了回答，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5abde6cd425802635474fbb1 资源管理 Q2：情景1：一张512x256的纹理。情景2：两张256x256的纹理。请问以上哪个情景消耗会更大呢？ 进一步再说，一个同样的角色，情景1：一个材质球，一张512x256的纹理，情景2：两个材质球，各256x256的纹理，这种情况下又是哪个情景消耗更大呢？判断依据和原理是什么呢？ UWA：就内存方面，设置相同的情况下，二者应该是一致的（某些需要长宽相等的压缩格式除外）。 在CPU开销方面，那么二者基本上也可能是差异很小的。如果是一个材质，一张和两张的区别在于采样次数。一张纹理会减少一次纹理的绑定开销，理论上减小的耗时开销是在ns级别的，所以在绝大多数实际情况下CPU端的差异都可以忽略不计；如果是两个材质，影响的因素就会多了，可能会造成更多的Draw Call、更多的RenderState切换等等，这就是为什么要采用Atlas Packing的优化原因。 至于GPU上的开销，多一张纹理的Shader采样肯定要多一些计算，所以Shader Instruction和Cycles肯定也会更多一些。但是否真的是会带来更大的实际开销呢？不一定，因为GPU压力不光是要看一个Shader的Instruction，同时还要看Runtime到底有多少像素是通过这个Shader来渲染的，如果本身占比很小，那么多一次采样和少一次采样，在实际情况下没啥区别。 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5abdca59910222635366dc77 渲染 Q3：我想渲染一个低分辨率的DepthTexture，有什么比较方便的做法吗？ 一种方式是自己创建一个低分辨率的RenderTexture，然后通过Shader Replacement来渲染一个DepthTexture（Shader可以参考Unity的ShadowCaster的实现）。 还有一种是通过_LastCameraDepthTexture来访问其他的低分辨率相机（如果有的话）渲染出来的DepthTexture。 感谢zZ@UWA社区提供了回答，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5abde736910222635366dc78 渲染 Q4：我想了解下Unity中物体接受灯光、产生阴影、接受阴影的性能开销情况。之所以这么问，是想看看Unity中的以上三种的性能开销。另外，在移动端是否有开启以上三种，或者部分的可能性，谢谢。 UWA：题主的“接受灯光”是指接受Real time光照吗？如果是的话，那么这种性能开销是没法预估的，因为它跟你的渲染场景复杂程度、接受光照物体的多少等都有很大关系，并不能简单理解成“开启就耗时、绝对不可以开启”。建议看看这篇文章：【求知探新】独立游戏《Abi》场景耗时分析 “产生阴影”和“接受阴影”是实时阴影功能的两个选项而已，建议在UWA性能报告中关注Shadows.RenderShadowMap和Shadows.PrepareShadowMap两项，从而来看到你项目的具体性能耗时情况。 目前，实时光照和实时阴影在很多移动项目中都已经开始大量使用。 最后，UWA Blog中有很多精彩的实时阴影文章，建议查看，希望对题主有所帮助。 https://blog.uwa4d.com/search/实时阴影 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ab8b537d35eb22c10a0a426 管理 Q5：我们项目正在立项初期，在Mono和IL2CPP之间犹豫徘徊。到底是Unity ILCPP编译好还是Mono的编译更好？IL2CPP的性能是否真有显著的变化？麻烦用过的朋友来分享下，谢谢！ A1：IL2CPP还是Mono，现阶段主要是安卓上的选择。 首先，这个问题在立项的时候无须纠结，就现在IL2CPP的成熟程度，随时打开就能用了，无须多虑。前期为了出包快，用Mono出。正式版本或者做性能和兼容性测试的版本，上IL2CPP，妥妥的。（当然指的是没有用.net做dll代码热更新的情况） 从个人在项目中尝试和得知的一些信息，稍微总结一下： Mono的好处： 1）久经考验； 2）出包速度快； 3）包体略小； 4）支持安卓上DLL动态载入程序集热更新（本人没有用过，不肯定IL2CPP完全不支持）。 IL2CPP的好处： 1）没有堆内存只涨不降的问题； 2）C#侧的计算密集型算法性能暴增N倍（我们项目的同地图寻路、自动建筑位置规划等算法的速度都有3倍以上提升）； 3）Lua热更无任何问题，反射调用还是导出式调用都很完美支持（应该不能算优点，但确实在我的预料之外）。 再说一下IL2CPP的问题： 构建时间长好多，安卓NDK的windows版工具链怎么就能性能这么低呢！AMD 16核32线程撕裂者走起，NDK编译时完美支持并行化的，核多了编译慢也不是事儿了； IL2CPP后，如果仅编译ARMv7的版本，部分模拟器无法启动游戏。用FAT（ARMv7 + x86）就可以了，而且模拟器上运行也非常流畅了，当然包又大了20多MB（我们的c#代码就是这么多…）。 Mono模式已经不可能支持64位了，面向未来，IL2CPP是唯一的选项，Unity现在已经做得很好了。 感谢郑昊提供了以上回答 A2：补充一点，安卓2019年想上架Google Play必须要提供64位的程序库，只能走IL2CPP这一条路了。 另外IL2CPP在安卓上还个坑 “Failed to extract resources needed by Il2CPP”，应该是目前是唯一阻碍我们使用的问题了（亲测在2017.3也会重现） https://forum.unity.com/threads/failed-to-extract-resources-needed-by-il2cpp-unity-5-4-0f3.419593/ 简单描述一下这个问题，在安卓上Unity要把IL2CPP需要用到的资源解压出来（assets/bin/Data/Managed目录，里面有Mono的config，及各种meta数据），每次启动都会这么干，关键问题是这个操作还会失败。其实Unity直接从apk里读就好了，但愿官方能早日完善这个问题。 该回答由littlesome@UWA问答提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5abdea21425802635474fbb4 互动话题 Unity 2017中可以选择.net 4.6了，那要不要使用4.6的特性呢？你有什么高见吗？ 欢迎来拍砖！
如何更深入地掌握研发项目的物理性能？
本期我们聚集了这几个话题：如何提高Unity物理引擎Physx的性能、如何禁止Unity自动编译脚本、使用Texture2DArray时开启Mipmap效果... 这是第104篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间15分钟，认真读完必有收获。文末，我们的互动话题是，Mono和IL2CPP，哪个更受你的青睐，我们期待你的灼见！ UWA 问答社区：answer.uwa4d.com UWA QQ群：793972859（仅限技术交流） 封面图片来自Google 物理 Q1：如何提高Unity物理引擎Physx的性能？我在场景中加入100个具有物理刚体的小人，并充分碰撞，发现低端机的帧率只能跑到10帧以下。 UWA：如果需要充分进行物理计算，这些拥挤在一起的小人很可能会造成大量的物理碰撞，进而造成很高的物理耗时。 在优化之前，题主需要先了解一些基础物理数值推荐，并不是所有设备都能流畅跑起100个自由制作的蒙皮模型的。所以，建议先看这篇文章：https://blog.uwa4d.com/archives/Simple_PA_Physics.html 另外，题主还需要看一下真机上的具体耗时瓶颈是否真正耗时在物理上，100个角色，如果Mesh量很大，渲染耗时也是不可忽视的，所以建议通过Unity Profiler来先确定一下真机上性能瓶颈。 最后，是否每个小人都需要精确的物理计算，是否可以通过一些射线求交、范围判断等性价比高的判断方法来代替纯物理碰撞？这需要题主从设计的角度上来进行权衡。 你并没有什么办法直接调用PhysX的API，所以所谓优化物理引擎的性能，倒不如说优化相关组件的性能。首先优先使用CharacterController而非RigidBody。然后尽量避免使用MeshCollider，用BoxCollider、SphereCollider和CapsuleCollider代替，如果不可避免，那么钩上Convex。当然，最好的优化就是不要用物理碰撞。射线能解决很多问题，但是射线也不是免费的，它的消耗跟场景里的面数正相关，当然设置射线的maxDistance和LayerMask能节省掉不必要的消耗。最好就是用简单的物理模型来描述问题，自己写代码实现。 感谢凯奥斯提供了回答 欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ab47292d35eb22c10a0a403 编辑器 Q2：有什么方法可以禁止Unity自动编译脚本呢？ UWA：可以把Preferences > General > Auto Refresh的勾去掉，不过如果这样，如果资源在外部被修改，也不会自动重新导入了，会有些影响。 另外，也可以试试这两个函数EditorApplication.LockReloadAssemblies/ UnlockReloadAssemblies，参考文档：https://docs.unity3d.com/ScriptReference/EditorApplication.LockReloadAssemblies.html 该问题由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ab8a76dd35eb22c10a0a41f 渲染 Q3：我使用Texture2DArray的时候发现没有Mipmap效果，想问下怎么让Mipmap生效？原始的纹理都是开启Mipmap的。 UWA：在创建Texture2DArray时需要注意倒数第二个参数，设为true就表示开启Mipmap，参考文档： https://docs.unity3d.com/ScriptReference/Texture2DArray-ctor.html 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ab7afd60b827e2c0bfdd18f 制作 Q4：现在有一张外部生成的Texture可以作为mainTex渲染，但希望获取它的内容保存下来，但是GetPixels是Texture2D的函数，所以想问问这样的Texture的内容该如何获取？ UWA：可以先通过Graphics.Blit把内容画到Render Texture里面，然后通过ReadPixels从RenderTexture生成一张Texture2D，假设sourceTex是原始的Texture，tex2D是最终生成的Texture2D，则代码如下： RenderTexture renderTexture = new RenderTexture(sourceTex.width, sourceTex.height, 32); Graphics.Blit(sourceTex, renderTexture); Texture2D tex2D = new Texture2D(sourceTex.width, sourceTex.height, TextureFormat.RGBA32, false); RenderTexture oldRT = RenderTexture.active; RenderTexture.active = renderTexture; tex2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0); tex2D.Apply(); RenderTexture.active = oldRT; 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ab8a73c0b827e2c0bfdd196 Shader Q5：我正在尝试自定义Terrain的Shader，发现内置Shader的末尾有这样两行，想问问其中的“Dependency”是做什么用的，后面指定的Shader起什么作用？ Dependency “AddPassShader” = “Hidden/TerrainEngine/Splatmap/Diffuse-AddPass” Dependency “BaseMapShader” = “Diffuse” UWA：Unity的文档中应该是没有描述过这个的作用， 不过在这篇博客里分析的比较清楚，https://alastaira.wordpress.com/2013/12/07/custom-unity-terrain-material-shaders 这是当Terrain使用的纹理超过4张时，Unity会用其中的AddPassShader来进行第二次渲染，并且blend上去。而BaseMapShader是用在低端显卡上的，只用一张纹理来显示的Shader。 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5ab8b537d35eb22c10a0a426 互动话题 近年来，随着Unity官方对IL2CPP的大力支持和改进，越来越多的开发者在立项初期在Mono和IL2CPP之间犹豫徘徊。到底是Unity ILCPP编译好还是Mono的编译更好？IL2CPP的性能是否真有显著的变化？用过的朋友不妨来交流下你的心得！
在Unity移动平台的游戏开发过程中，贴图资源是往往是占资源量最大的资源。如何在保证视觉效果的同时，尽可能地减少贴图资源，是开发团队会经常遇到的问题。通常来说，对于3D物体的纹理，是可以采用ETC/PVRTC等压缩比很大的算法处理，但是对于细节要求很高的UI纹理，这样处理造成的失真往往达不到质量要求。对于这类的贴图，我们可以考虑使用失真较小的16位贴图格式存储。 但是对于颜色数较高的纹理，Unity提供的默认转换方法会呈现明显的色阶。针对该问题，keijiro实现了一种Dither4444的改进算法。从下图1上可以看到，对于画面细节比较平滑的图片，该算法虽然消除了色阶现象，同时带来了肉眼可见的噪点。 <图1> 左：原始图 右：keijiro的 Dither4444示意图 笔者在keijiro的算法基础上进行了改进，提供了一个将RGB24 Bit图Dither之后转RGB565的方法，基本消除了肉眼可见的失真，实际效果见下图2。 <图2> 左：原始图 右：笔者的 Dither565示意图 实际在我的项目的应用中，对于不适合ETC/PVRTC压缩的图片，都采用了该文章中的RGB565或者RGB565+A8的方式。在肉眼基本无失真的基础上，节省了部分资源。 最后附上OnPostprocessTexture代码： void OnPostprocessTexture (Texture2D texture) { if(assetPath.Contains ("_dither565")) { var texw = texture.width; var texh = texture.height; var pixels = texture.GetPixels (); var offs = 0; var k1Per31 = 1.0f / 31.0f; var k1Per32 = 1.0f / 32.0f; var k5Per32 = 5.0f / 32.0f; var k11Per32 = 11.0f / 32.0f; var k15Per32 = 15.0f / 32.0f; var k1Per63 = 1.0f / 63.0f; var k3Per64 = 3.0f / 64.0f; var k11Per64 = 11.0f / 64.0f; var k21Per64 = 21.0f / 64.0f; var k29Per64 = 29.0f / 64.0f; var k_r = 32; //R&B压缩到5位，所以取2的5次方 var k_g = 64; //G压缩到6位，所以取2的6次方 for(var y = 0; y < texh; y++){ for(var x = 0; x < texw; x++){ float r = pixels [offs].r; float g = pixels [offs].g; float b = pixels [offs].b; var r2 = Mathf.Clamp01 (Mathf.Floor (r * k_r) * k1Per31); var g2 = Mathf.Clamp01 (Mathf.Floor (g * k_g) * k1Per63); var b2 = Mathf.Clamp01 (Mathf.Floor (b * k_r) * k1Per31); var re = r - r2; var ge = g - g2; var be = b - b2; var n1 = offs + 1; var n2 = offs + texw - 1; var n3 = offs + texw; var n4 = offs + texw + 1; if(x < texw - 1){ pixels [n1].r += re * k15Per32; pixels [n1].g += ge * k29Per64; pixels [n1].b += be * k15Per32; } if(y < texh - 1){ pixels [n3].r += re * k11Per32; pixels [n3].g += ge * k21Per64; pixels [n3].b += be * k11Per32; if(x > 0){ pixels [n2].r += re * k5Per32; pixels [n2].g += ge * k11Per64; pixels [n2].b += be * k5Per32; } if(x < texw - 1){ pixels [n4].r += re * k1Per32; pixels [n4].g += ge * k3Per64; pixels [n4].b += be * k1Per32; } } pixels [offs].r = r2; pixels [offs].g = g2; pixels [offs].b = b2; offs++; } } texture.SetPixels (pixels); EditorUtility.CompressTexture (texture, TextureFormat.RGB565, TextureCompressionQuality.Best); } } 这是侑虎科技第288篇原创文章，感谢作者喵小逗供稿，欢迎转发分享，未经作者授权请勿转载。当然，如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859） 作者知乎专栏：https://zhuanlan.zhihu.com/p/28624490，作者也是U Sparkle活动参与者，UWA欢迎更多开发朋友加入U Sparkle开发者计划，这个舞台有你更精彩！
关于Unity中的资源管理，你可能遇到这些问题
关键字 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包中； 在http://unity3d.com/cn/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直接的依赖关系。 更多信息可以参考http://docs.unity3d.com/ScriptReference/AssetBundleManifest.html 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元素对其进行依赖即可。 此外，开发团队可以考虑用 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是否会有损失呢？ 如果可以的话，建议直接将其制作成POT图片，而非进行二次转换。ToLarger确实可以将纹理拉伸成POT纹理，但如果是UI界面（开启Pixel Perfect）的话，可能显示时会有较大视觉损失。 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 以及其中的各种属性和参数。http://blogs.unity3d.com/2014/06/26/shiny-new-animation-features-in-unity-5-0/ 另外，如果需要修改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 4.x中，Unity的_CameraDepthTexture 的生成，会根据Rendering Path的选择和设备的不同使用不同的方法实现，一种是直接从depth buffer获取，另一种是添加额外的Pass来渲染到纹理。目前在移动设备上主要是通过后者实现，而后者的实现借助了Shader Replacement的机制（批量替换为简单的Shader，并将深度渲染到纹理中）。 因此，在使用自定义的Shader时，就需要正确地设置RenderType的Tag（所有内置Shader都是设置好的），从而使得Shader Replacement正确地执行。具体可见文档：http://docs.unity3d.com/Manual/SL-ShaderReplacement.html 而在Unity5.x中，则不再通过Shader Replacement来实现了，因此需要添加一个包含了"LightMode"="ShadowCaster"的Tags的额外Pass，用来写入深度值。 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 的机制所限）。可以尝试拆分面积较大的面片，来提高烘焙的速度（通常，拆分大面积的面片对渲染性能也会有所提升）。 主要原因可参考如下的帖子： http://forum.unity3d.com/threads/light-transport-problem-with-large-objects.310405 Q3：如下图所示，第一张显示的是没有烘培Lightmap的场景效果，里面有一盏点光源；第二张显示的是烘培Lightmap后的场景效果。请问为什么同一盏点光源照亮的效果差别那么大？ 简单来说，这就是实时的直接光照和全局光照的差别。 在渲染时前者只能处理光源和单个物件之间的直接光照，而后者在烘焙时是通过光线跟踪或者辐射度等复杂算法，计算出所有物体各个表面之间相互反射的光照信息，这也是烘焙Lightmap需要较久的时间的原因 。可以发现在全局光照下，即使是物体的背面也会因为其它表面的反射而被照亮，这在直接光线下就无法实现这样的效果。 Q4：当关闭预渲染GI时会出现IndirectResolution，这个参数有什么用，为什么调大了以后会大大增加渲染时间，但是烘培出来没有啥效果。 简单来说，该值主要控制的是GI的烘焙密度，数值越大，表示每个单位距离内的texel越多，即烘焙得越精致，自然烘焙的时间也越长。该值并不需要越大越好，场景越小，建议该值越低。该值为1时，对于多数场景，其烘焙效果已经足够了，升高该值，其效果也不会有明显提升。 开发者也可以参考Unity官方的说明文档： http://docs.unity3d.com/Manual/GlobalIllumination.html 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也需要进行相应修改，这是比较复杂的，所以我们并不建议这样的做法，因为可能会花掉开发团队大量的开发时间。