UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - lightmap
本期我们聚集了这些话题：项目骨骼规范已经确定的情况下如何增加骨骼捏脸功能、网格重建定位、ScriptableObject加载速度分析、光照贴图和材质组合导致额外DrawCall... 这是第132篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群：793972859（原群已满员） 骨骼动画 Q1：我所在的项目的骨骼规范已经确定，cs骨骼和bone骨骼加起来有60根。现正在调研捏脸的方案，基本确定通过增加面部骨骼来进行捏脸。先描述下我对面部骨骼的理解，如有错误请大家指正。 增加的骨骼与原有的骨骼同时导出制作Avatar供骨骼动画使用，但是骨骼动画并不控制这些骨骼。玩家捏脸后的数据，也就是骨骼的缩放和旋转，直接应用到这些面部骨骼上，完成脸部的定制。实际应用到项目里，有如下几个问题： 1、现有项目中，导入时Optimize Game Object选项是勾选的，这样就无法在C#层拿到骨骼信息，Expose出来的骨骼是“只读”的，没办法把捏脸数据直接应用到运行时的骨骼上。 暂时只能通过“使用额外的未开优化的Avatar动态生成目标Mesh”的方法来绕过，不知道还有没有其他更直接的方法？ 2、已有的骨骼规范没有脸部的骨骼，如果要加上，就需要重新导出之前所有的动画。 有没有不修改现有骨骼的方案呢？ 头部和身体分离，不过接缝处可能会穿帮。 另外还脑洞了一个方案：专门为头部做GPU Skin。导入模型时做预处理，将头部的骨骼和权重添加Mesh的uv2和uv3上（代码里区分一下是否是头部骨骼，身体部分的全是零就好了），另存为asset。然后每帧传骨骼矩阵到Shader里，在vs里面修改顶点的坐标。不过这样会占用额外的带宽。 感谢凯奥斯@UWA问答社区提供了回答 接楼上，头和身体分离，QualitySettings->Blend Weights改成4，可以解决大部分接缝穿帮问题。其实现在游戏中，大部分头发也会有动画，所以头发也要分离。捏脸后的Mesh保存下来，在游戏中和身体拼接的时候，用保存的Mesh（大部分游戏脸部没有动作）。实际游戏中，并不需要我们捏脸时的骨骼。 感谢郑骁@UWA问答社区提供了回答 离线骨骼采样的时候不需要勾Optimize Game Object。等整合出脸了以后再，把脸型贴回去。或者只限定脸部的骨头可读写。 感谢马古斯@UWA问答社区提供了回答 该问答来自UWA问答社区，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5bbc2dba17780b3478091926 UI Q2：在Profiler中，我们能否看到重建的具体是哪些网格呢？ 目前我只能通过VB Uploads的大小来判断是否引起了网格重建，但是并不知道一个界面中哪些元素会引起网格重建。 这里我有个测试案例，image放进动态canvas和不放进canvas里，这个上传的绘制内容会有很大的变化，但是具体是哪些东西如果每个东西都去测试一遍，感觉很耗时。 用VB Uploads来判断重建可能不太直观，因为SkinnedMesh和粒子系统应该也会引起这一项的变化。 在UGUI里，网格重建分为两个部分：Rebuild和Rebatch。其中Rebuild是以UI元素为单位的，比如一个Image的Color改变了，那么这个Image就会发生Rebuild操作，开销是包含在Canvas.SendWillRenderCanvases中的，只要是改变了UI元素的顶点属性的操作都会引起SendWill的开销。至于是哪一个UI元素发生了Rebuild，从Profiler里确实是看不出来的。 而Rebatch是以Canvas为单位的，只要Canvas里有任何一个UI元素发生了变化（也包含位置变化），那么这个Canvas就要进行Rebatch，开销是包含在Canvas.BuildBatch和子线程中的Canvas.SortJob、Canvas.GeometryJob等函数中的。至于是哪一个Canvas发生了Rebatch，可以通过Canvas.BuildBatch函数来确定，在Profiler里选中Canvas.BuildBatch，然后在右侧显示相关的对象，里面列出的就是当前帧发生Rebatch的Canvas的名字，但这个Rebatch是由哪些UI元素的变化引起的，在Profiler里也是看不出来的。 该回答由UWA提供，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5bbb2aa217780b34780918ed 渲染 Q3：发现Unity内部的光照贴图分配方式有一些问题。很多同材质的物体，因为光照贴图被分配到不同的Atlas上，导致无法合并批次。 比如下面三个连续的DrawCall，实际MeshRenderer的材质完全一样，偏偏由于光照贴图在几个不同的Atlas上，而无法实现合并。 经观察，如果能够将出现这种情况的物体实现合并，场景的Draw Call消耗可能降低超过1/3。 所以，现在希望有一个办法，能够优化光照贴图的分配流程，实现同材质物体尽量被分配到同一张Atlas上。 两种做法： 1）自定义LightmapParameters，通过设置Bake tag，相同tag的物件，会烘焙到同一张Lightmap中。Terrain_1_1是新创建的LightmapParameters，替换掉默认的Pamameters。 2）通过Renderer的uv，从完整的Lightmap贴图中获取对应的贴图，然后把这些贴图合并生成一个新的Lightmap贴图(可以用tinyexr结合stb的库来读写exr)。 感谢江南@UWA问答社区提供回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5bbc460690b1a8347db6dd46 内存管理 Q4：我在优化项目的时候发现当删除人物模型，其材质和网格与Texture2D依然存在内存中。使用的删除方法是（DestroyImmediate）。查看Texture2D的引用如图： 发现其被一个实例化的Material引用，进一步查找之后，如图： 为什么会有这么多被引用项呢？还是我查找的方式不对？请问如何释放这部分内存？（人物模型确认被删除了） 材质和贴图如果要立即释放，也要手动Destroy才行，否则就是当没有其他引用的时候，调用Resources.UnloadUnusedAssets才行，不过Resources.UnloadUnusedAssets会造成卡顿，建议还是手动管理材质和贴图。 感谢旭军@UWA问答社区提供回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5bbc6bb290b1a8347db6dd4a 序列化 Q5：ScriptableObject的加载速度，同插件MessagePack，或者一般Json插件反序列化的速度相比，大概在什么水平？现在自己测试的结果，Unity直接用C#序列化 / 文本读取再Parse / MessagePack插件，三者的加载同样的一堆散碎配置文件的时间比大概是 10:4:3。 但是即使是最快的MessagePack插件的读取时间也不令人满意。所以，希望能知道 ScriptableObject 或者 Sqlite是否可以比MessagePack更快地加载配置文件。 Sqlite其实读取数据是最快的，但是重点是数据结构，可以使用反射进行反序列化，但是效率上没有MessagePack高。个人认为还是从设计上解决问题，用空间换时间，提前解析数据，占用内存，使用时直接读取内存数据比较合理。 感谢郑晓@UWA问答社区提供回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5bb8a578afd2174f6fe946b1 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（原群已满员）
本期聚集话题：Unity中光照图错乱问题研究；UGUI静态、动态画布； AssetBundle打包零冗余方法；AssetBundle依赖打包报错... 这是第121篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Lightmap Q：我来分享下Unity项目中可能会引起光照图错乱的问题及其处理方式。 近期项目使用Unity 2017 版本升级后，团队中某些人的机器光照图总是不正确，而有的人是正确的。我们一开始怀疑模型资源的问题，检查了下资源和导出设置，是有些问题但不是引起错乱的原因，后来看到光照图错乱和正确的机器上，同一个模型的UV确实完全不一样。 后来终于确认原因并且可以复现：Unity导入模型会做顶点优化，顶点数并不是和Max 中一致但是面数绝对一致，重点是不同Unity 版本即使同一个模型优化过的顶点数也不一致（应该是算法不同的问题），顶点数不同自然光照图 UV 分布生成的也不同。所以比如在Unity 2017.3中导入一个模型并生成光照图，导出成Package并导入到 Unity 2017.4或者其它版本，会发现光照图 UV 错乱，因为重新导入的模型顶点和光照图 UV 都不同了。所以同一个团队中的美术同学一定要和开发人员使用相同版本。 这里有一个更隐蔽的情况：大家都使用同一个Unity版本，但是不同的机器依然会出现某些人光照图错乱的问题。表现正常的机器是由于使用低版本的Unity烘焙完光照图或者更新了别人烘焙完的光照图后，然后直接用高版本的Unity 打开这样是不存在问题的；如果同一个项目删掉Library后，再用高版本的 Unity 重新打开这时候就不对了。究其原因是因为不删除Library升级项目每个模型不会被重新导入，顶点数维持在老版本导入的结果，一旦删除Unity就会重新按照新版本来导入并生成顶点数，当然其实你在新版本中修改模型的设置，也会导致按照新版本来重新导入模型而发生光照图错乱。 以上就是所有原因，并可以轻松复现，结论就是： 1）建议团队中所有人使用同版本的Unity开发； 2）升级过程中需要删除本地的 Library 然后再用新版本Unity打开。 希望其它的朋友可以避过这个坑，在此感谢钱康来@UWA问答社区在查找过程中的帮助：） 感谢Yaukey@UWA问答社区分享了该经验，欢迎大家转至社区进一步交流： https://answer.uwa4d.com/question/5b4ed6e61e88b37d34e650a4 UGUI Q2：之前了解过UGUI的画布上的图片或者文字发生改变，画布会重新算一遍UV，重新合并图集，以达到减少DrawCall的目的，所以为了减少画布重新绘制带来的性能问题，我把经常变动的UI放到一个动态画布上，不频繁改变的放到静态画布上。这里产生了一点疑惑：如果我一个UI窗口制作成一个预设，这个UI有动有静的，那我是将这个UI放到静态画布上还是动态画布上好，这个怎么放？ 另外，不在同一个画布下面，UI之前的层级关系怎么处理？比如画布A放静态，画布B放动态，现在有一个UI叫C，放到A，一个UI叫D放到B；如果画布B是挡在画布A前面，但是我需要UI C挡在UI D前面，这种怎么处理？ 题主的提问里有些描述可能不太准确，下面先简单说一下：“重新算一遍UV”的操作是发生在Canvas.SendWillRenderCanvases这个函数中的，发生变化（改图片、改文本、改Alpha等）的UI元素越多，开销就越大，通过“动静分离”是优化不了这部分的开销的，这部分开销需要从控制变化频率来入手。 “动静分离”主要是用来优化Canvas.BuildBatch的开销，这部分开销产生的原因是：当某个Canvas里任何元素发生任何变化（图片、文字、alpha、位置、缩放等等…）的时候，该Canvas就需要进行合并Mesh的操作。如果Canvas比较复杂，耗时就会比较高。 而在进行“动静分离”时，理论上是需要根据Canvas的复杂度来规划的，个人觉得Canvas的总顶点数可以作为一个简单的指标。以下用几个例子来说，可能比较直观一些： 对于一个常见的MMO项目来说，常规做法是会把技能面板，任务面板独立划分Canvas。其中，技能面板即使是在读CD的时候，一般来说顶点数也不太会超过1000个，UI元素数量也不会太多，所以即使这个面板里有动态元素，也有静态元素，但其实不太需要“动静分离”，因为这样的面板BuildBatch的开销不太明显。 而任务面板中，由于条目比较多，产生的顶点数通常也会很高，2000以上也是很常见的，所以这个面板里如果存在动态的UI元素（比如一个高亮效果的序列帧动画），那就会引起耗时较高的BuildBatch操作，那么这个面板就需要“动静分离”了。 具体操作时，“动静分离”可能也会遇到一些纠结的地方，因为零碎的动态UI如果都拆成Canvas的话，很容易导致DrawCall的显著上升，前后遮挡也会不好控制，所以推荐@贾伟昊的这篇文章：使用Shader对UGUI进行优化。简单说就是通过Shader来做UI动画，直接避免Canvas.SendWill和Canvas.BuildBatch的开销，具体就不介绍了，文章写得很详细了 ：） 该回答由UWA提供，欢迎大家转至进一步交流： https://answer.uwa4d.com/question/5b514cf21e88b37d34e650e9 资源管理 Q3：我想在Unity 4.x版本下用AssetBundle实现零冗余，但在实现的时候遇到一个问题不知道该如何解决。例如： BuildPipeline.PushAssetDependencies() BuildPipeline.BuildAssetBundle 打包TextureA BuildPipeline.PushAssetDependencies() BuildPipeline.BuildAssetBundle打包A资源 BuildPipeline.PopAssetDependencies() 这时我需要打包一个B资源，B资源中引用了Texture A并且引用了Texture B，而且Texutre B又被C资源引用，C资源又不需要Texture A，这时逻辑需要如何调整才能实现资源 A和资源 B依赖Texture A，然后资源 B依赖Texture A和Texture B，资源 C Texture B和其他资源呢？ 恩，是的，Unity4.x的AssetBundle打包确实有这样的弊端，但还是有方法的，就是分步打包。 即先通过下面的方式让资源A和B依赖TextureA： Push Build TextureA Push Build 资源A和资源B Pop Pop 然后，通过下面方式重新打包资源B和TextureA，并第一次打包TextureB： Push Build TextureA和TextureB Push Build 资源B Pop Pop 对于后续的资源 C来说也是如此。这个时候你肯定会想这不是打出了两个Texture A？ 是的，没错，这里需要有个注意的地方，就是在Unity 4.x中一定要把Deterministic Option打开，当开启这个选项后，上述方法才能奏效。即让第二次打出来的TextureA的AssetBundle直接覆盖第一次的TextureA AssetBundle，由于是Deterministic的，所以它们的Hashid是一致的，这样既可以保证资源A的AssetBundle与新的TextureA的AssetBundle有依赖关系，同时还可以做到资源B与Texture A和B都有依赖关系。 通过以上方式，题主就可以打出零冗余的AssetBundle文件了：）加油！ 该回答由UWA提供，欢迎大家转至社区进一步交流： https://answer.uwa4d.com/question/5b45b5222a652c518d8d539e 资源管理 Q4：Unity 5.5.4p5版本采用AssetBundle加载资源（有依赖），有很大几率出现如下错误，怎么处理呢？ “The referenced script on this Behavior is missing!”, “The referenced script on this Behavior (Game Object ‘Prefab 1’) is missing!” and “GameObject (named ‘Prefab 1’) references runtime script in scene file. Fixing!” “The referenced script on this Behavior is missing!”，如果是Unity 5.0以后的打包方式，那么原因很可能是：脚本的序列化信息变化了，需要重新打包AssetBundle来修复这个问题。 而如果是Unity 4.x的老版本打包方式，那么除上述原因外，还有一种可能是脚本处于依赖的AssetBundle中，而后续AssetBundle加载时，其依赖的AssetBundle还未加载成功或者已经被卸载，那么也会出现该问题。 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b46bcb9339d267d357c6aef UGUI Q5：Unity 5.5.6版本中Texture import Settings中选Sprite类型，Sprite mode中Mesh Type，如果作为UI图集应该使用哪种模式选择？ Full Rect。MeshType是给SpriteRenderer用的，会按照图片的形状生成一个近似的Mesh。但是对于UGUI来说都是一样的，都是按照ImageType来画Mesh，但是如果使用Tight的话，就意味着（可能）需要额外的内存，所以还是建议使用Full Rect。 感谢Chaos@UWA问答社区提供了回答，欢迎大家转至社区进一步交流： https://answer.uwa4d.com/question/5b4422bbf91024518ab77aed 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（原群已满员）
本期聚集话题：LightingDataAsset Delete后Lightmap展开问题、Gamma和Linear颜色空间问题、AlphaTest和Alpha Blend的性能差异... 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com 制作 Q1：我这边做了个测试，把场景中的物件进行StaticBatch，用Lightmap烘焙，并且在烘焙完记录每个物件Renderer的lightmapindex and offset，然后在运行中，我删除了生成的LightingData.Asset，自己设置了Lightmapsetting，并且恢复了每个物体Renderer的Lightmapindex and offset, 结果我发现烘焙出来的场景Lightmap还是不对的。我怀疑是不是LightingData.Asset 还记录了别的东西，想问问看UWA有没有什么经验呢？ 我在answer.uwa4d.com上打包一个测试用例，打开Scene/LightMapTest.unity 这个场景即可。 这个问题还是出在Static Batching上。经过测试我们发现如果去掉场景中物件的“Static Batching”标记，换成手动Batching，即采用StaticBatchingUtility.Combine的方式结果就正确了。 同时，我们发现使用LightingData.Asset时，开启Static Batching与不开启都正确，因此我们认为LightingData.Asset可能记录了与CombinedMesh有关的数据，导致如果仅仅使用gameobject的LightMap参数无法正确读取CombinedMesh的LightMap UV。另外一种可能是Unity的一个Bug。 最后，我们建议采用上述手动Batching的方式，即可解决此问题。 该问题来自UWA问答社区，如您对该问题仍有疑问，可以转至社区进行进一步交流。 https://answer.uwa4d.com/question/59c0d16c5cd7219d3bbfb3b3 制作 Q2：我们场景中有两种Unlit物体，一种是在脚本中通过Mesh顶点色（Mesh.colors）赋颜色值，一种是用Shader属性传递颜色值，在Gamma颜色空间时两种物体的渲染颜色是一致的： 但是切换到Linear空间发现不一样了，如下图，左边是顶点色，右边是Shader属性颜色： 顶点颜色和属性都没改过，只是切换了颜色空间，请问是什么情况？ 我能想到的有两种可能性： 1）顶点色的颜色值在Shader中没有被正确处理； 2）右边使用贴图的导入设置中sRGB的选项有错误。 看上去左边的在线性空间下变亮了，而右侧的方块颜色在两种Space下差别不大，因此推测第一种的可能性比较大，顶点色在很多情况下并不被当作颜色处理，而作为值来存储，所以有可能这里处理有问题。至于为什么变亮了，感觉是值经过了伽马校正变大了。 Unity有接口在两种颜色空间下转换，建议题主可以查一下，在Shader中修改代码来排查下是哪里的问题。 该问题来自UWA 社区，感谢贾伟昊提供了回答，如您对该问题仍有疑问，可以转至社区进行进一步交流。 https://answer.uwa4d.com/question/59c387de79a00b1e16da7414 UI 制作 Q3：当使用Linear设置时，UI上图片透明度等也跟着变化，出来的效果与美术人员的需求不一样。请问有什么办法可以让UI不使用Linear或者让UI可以在Linear影响下也正常呢？ 推荐题主可以看下这个帖子 https://forum.unity.com/threads/bug-with-bypass-srgb-sampling.282469 “When switching to linear mode it (greatly) changes how blending works, this is because when sampling only the rgb channels are linearised as it is assumed the alpha is a linear scale representation already.” 简单说，这是正常现象，里面有三种解决方法： 1）改资源的 alpha 通道： Reauthor the content for linear blending (this is supported in photoshop). 2）用 Shader 提前处理下颜色空间的转换： Add a workaround in the shader if in linear space to ‘pretend’ the alpha from the source texture is in the correct format. 3）用屏幕后处理来绕过 UI 的 linear 处理。 可以直接搜 topsekret 的回答。 该问题来自UWA问答社区，如您对该问题仍有疑问，可以转至社区进行进一步交流。 https://answer.uwa4d.com/question/59c1e5c66c8b329c76a5f66e 制作 Q4：我们项目在实现显示部分模型像素的时候用了Builtin的Cutout Shader，还有一种方式是用Blend，也就是把Alpha设成0。我想请教下这两种方式有什么区别？性能上哪个好？在什么情况下使用比较高效？ 答案一：功能上，Alpha Test无法实现半透的效果，只能让物体的某些部分按照Alpha的值“透”或者“不透”，Alpha Blend可以做到半透，但是在多个Alpha Blend的物体重叠的情况下，由于不写Z会导致先后顺序可能存在问题。 由于Alpha Test的实现是基于discard的方式，即在PS里发现Alpha的阈值不符合要求，就将该像素丢弃掉不进行渲染。这种方式会导致使用了类似early-z技术的GPU优化失效，导致性能下降。 另外，知乎上有讨论——“对于PowerVR系列而言会影响HSR（隐藏面消除），打断ISP处理覆盖同一像素的几何体（更具体一点，ISP要得到PS执行后的结果才能正确进行HSR，在这一过程中，所有覆盖了带有discard操作像素的几何体全部都要等待）”。 参考：https://www.zhihu.com/question/27875435 所以整体来说，在移动设备上，Blend具有更高的性能，但是有些效果未必能够达到，结果可能不是美术设计想要的效果，特效中用得较多。 题主也可以参考乐乐的这篇：http://blog.csdn.net/candycat1992/article/details/41599167 感谢贾伟昊提供了以上回答 答案二：不考虑硬件去讨论这个问题有点形而上学，另外，实际使用中还要考虑Overdraw的影响，而不是单纯的一层像素，再有就是，网上很多资料其实已经落后了。所以在知乎上搜这个问题，会有大牛告诉你Blend更快，但是会马上有人把测试截图贴出来说看我们的实测数据“test明显更快”。 建议题主分别用Mali，PowerVR，Adreno的移动设备来测一下就知道差异了。 另外，PowerVR的HSR，楼主可以试试看用PowerVR Tool，看看Blend和Test，是不是像传说的那样，都不能开启HSR，可能会有意向不到的结果。目前我们测试最后的结论是，尽量还是用test。 感谢 kylda提供了以上回答 该问题来自UWA问答社区，如您对该问题仍有疑问，可以转至社区进行进一步交流。 https://answer.uwa4d.com/question/59c09367a3d42c411c37a0cd/ 制作 Q5：我们有一个需求，是在不同的动画播放的时候切换不同的NormalMap，但是动画的“Add Property”没有NormalMap等变量，请问有什么好的办法可以解决吗？ 可以自己写一个脚本，挂载需要切换NormalMap的节点上面，public NormalMap等变量，然后在脚本里面写段代码，当发现NormalMap修改了，就去改模型的NormalMap。而动画是可以修改这个脚本变量了。 该问题来自UWA问答社区，如您对该问题仍有疑问，可以转至社区进行进一步交流。 https://answer.uwa4d.com/question/59bf72d0ca564ff3748d4f27 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流）
本期我们聚集了这些话题：GameObject.Active耗时、Font.CacheFontForText开销过高、Unity 5.X下烘焙Lightmap的UV缩锁定... 我们将从日常技术交流中精选若干个开发相关的问题，建议阅读时间15分钟，认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。 UWA QQ群：793972859 UWA 问答社区：answer.uwa4d.com 性能优化 Q1：Unity对GameObject.active()这个底层具体做了什么？因为在优化项目的时候看见有些界面active()这个函数造成的开销比较高，我采取的办法是移除摄像机的范围，这是参照UWA以前文章找到的办法，但是我还是想知道Unity对Active底层具体做了哪些操作，否则会有点困惑。 有一点可以明确的是，至少SetActive后，如果GameObject被激活，那么会调用该GameObject和所有子GameObject上的所有组件的OnEnable函数。而各种不同的UI组件的OnEnable中也有各种不同的操作。 如果题主希望深入研究下，可以看一下NGUI或者UGUI的源码，OnEnable具体做了什么都可以看到。同时，针对UI界面而言，还会在同一帧里触发其他的相关函数，出现后续的开销，如UGUI里的SendWillRenderCanvases和NGUI里的UIPanel.LateUpdate。 该问题来自UWA 社区，如您对该问题仍有疑问，可以转至社区进行进一步交流。 https://answer.uwa4d.com/question/5996b23886e632f11f9c1582 资源管理 Q2：还是老生常谈的昼夜变换效果，需要两份Lightmap来lerp。开始我认为，只要模型不变，烘培参数不变，如果只改变灯光，Lightmap的分布是一样的（事实上开始测试的几个小场景也的确没问题）。但是在某个大场景烘培2份LM的时候，发现部分物体UV2分布的位置变了。请问，在不手动分uv2的情况下，是否有办法锁定UV2分布？因为资源量比较大，美术人员不希望手动分UV2。 根据我的实践结论，如果场景物体没有移动，没有改变过Lightmap Scale，光罩面板里的分辨率等参数也保持不变，只是灯光位置和参数发生了变化，那么是不会引起物体的光照Index和Scale 这两个参数的变化。但是为了避免多次渲染，最好连续为不同光线条件进行渲染，以防无意移动了某个物体，造成整份渲染无用。另外手动保存Index，Scale两个参数好像也是无效的，因为如果缺少场景同名文件夹内的Lightmapping Asset的那个光照文件，场景光照贴图就无法加载，而且通常我们场景内的物体是要批量静态的，而批量了后的物体是无法手动赋值Index和Scale这两个参数的。 实例：我的项目中 这三套光照贴图都是公用一套"UV"的，在编辑器中还可以自己写个Editor脚本来静态加载调试。 此问答来自于UWA 问答社区，感谢 ID为 Blandon 的朋友提供了相关回答，如您对该问题仍有疑问，可以转至社区进行进一步交流。 https://answer.uwa4d.com/question/5992e3aae85f22ea2dc0c603 资源管理 Q3：我们在iPhone5c上测试时发现Font.CacheFontForText会造成很严重的卡顿，看了UWA的Blog，了解到可能是因为创建FontTexture导致的开销。那是否可以认为如果我们一开始申请了足够大的Font Texture，后面即便有新的文字，开销也不会很大？ 如果可以这么做，一开始申请使用的字符串是根据自己项目生成一个字典，还是直接使用网上3、4千字的常用字库更好？ 是的，如果Font.CacheFontForText开销较大，那么在无法降低字体数量的情况下，一开始增大Font Texture是较为可行的方法。建议根据自己项目的字体来撑大Font Texture，避免不必要的浪费。 同时，也建议看一下这篇关于字体的精简优化方法。 此问答来自于UWA 问答社区，如您对该问题仍有疑问，可以转至社区进行进一步交流。 https://answer.uwa4d.com/question/59917a6e1ae4fee75dc97e3d 资源管理 Q4：有什么办法可以获取到MemoryProfiler里的Other那项里面的Objects和SerializedFile的详情信息呢？ 目前Unity引擎是没有API来获取这两处的详细信息的。 不过，对于Objects的大小，一般来说不需要去特别注意，因为在真机上是没有的。 而SerializedFile下面主要是AssetBundle的具体使用情况，可在UWA线上性能检测版本中查看详细每帧的AssetBundle的使用情况，如下图所示。 此问答来自于UWA 问答社区，如对该问题仍有疑问，可以转至社区进行进一步交流。 https://answer.uwa4d.com/question/5996a08e86e632f11f9c1580 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站（answer.uwa4d.com）上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（仅限技术交流）
关于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也需要进行相应修改，这是比较复杂的，所以我们并不建议这样的做法，因为可能会花掉开发团队大量的开发时间。