[Unity] Catan Universe: Unity 的移动设备优化

英文原文:https://thegamedev.guru/unity-performance/catan-universe-mobile-optimization-on-unity/

关于本文档

  本文档的主要目的是讲一个故事。那就是关于我所遵循的步骤和方法的故事,以诊断和改善Catan Universe在移动(iOS,Android)中的性能,并对WebGL有重要的好处。因此,我可以从描述围绕游戏本身的相关事实开始,最后我将以一般的提示来结束,你可能会发现对你的项目有帮助。读者肯定会熟悉我提出的许多提示,但我敢打赌,其他一些未知的事实有时会出乎意料地方便。总而言之,我将介绍我在大约两周内从0-2FPS到50-60FPS所使用的技术。的确,这不是很多时间,所以管理好它是我的优先事项之一。

[Unity] Catan Universe: Unity 的移动设备优化_第1张图片

简介

  时间是稀缺的、昂贵的、不可恢复的,这些形容词不胜枚举。我们在生活中没有太多的时间,这就是为什么我们,开发人员,可能是游戏部门最昂贵的资源。为了明智地花费时间,对完成任务可能有的不同选择进行预先研究往往是一个绝妙的主意。然而,再多的研究和准备工作也不能使你免于做出痛苦的、但很需要的超时工作。

  这份文件是在不同的日子里写成的;凝聚力可能会显得有些偏差。另外,本条目的结构相当不存在,但如果要我勾勒一个结构,那就是以下内容。

  • 关于本文档

  • 关于 Catan Universe

  • 关于我

  • 目标

  • 起始点和参考装置

  • Catan Universe的优化

    • 优化主菜单
    • 优化游戏中的内容
  • 通用优化

    • 内存分配
    • Materials
    • Textures
    • Shaders
    • Audio
  • 额外的

    • 顶替摄像机系统
    • Unity插件
    • 工具

  不用说,我对建设性的反馈和对话持开放态度,这些反馈和对话将指导我解决问题和进行改进。负面的反馈(嘲弄)将被基本忽略。

关于 Catan Universe

  Catan Universe 是一个有3年历史的项目,从那时起就一直在积极开发。它的核心团队由大约7名开发人员和2名艺术家组成,尽管在某些时期我们有多达12名开发人员、4名艺术家、一只狗(Gaucho)和一些典型的管理费用。从它的预算来看,它可能不被认为是一个AAA级游戏,但我们确实在它身上投入了大量的资源和爱。

  Catan 最初是为PC和WebGL开发的;因此在安卓和iOS上出现问题是可以预期的。当然,独立平台的性能通常要好得多,而且不会像移动解决方案那样有那么多限制。

关于我

  我是Rubén Torres Bonet,在Exozet Games担任技术负责人。我代表我的公司从事游戏开发,负责不断改进我们的流程。我曾参与过的游戏有Catan Universe(惊喜)、Diamond Dash、Jelly Splash、Blackguards、Carcassonne以及为大众汽车、奥迪等客户做的许多3D/VR/AR项目。我对PS4/XBOX游戏机、Vive、Hololens和移动VR都有经验。就个人而言,我对建筑、图形和管理技术团队有着特殊的热情。

  对于我为什么要分享这份文件这个问题,我有非常具体的答案:(1)它迫使我在每一个主题上钻研得更深,以便保持它们应有的正确性;(2)它帮助我系统地推理我所遇到的决定;(3)它有利于通过帮助其他开发者,包括我团队中的开发者来传播知识,以减少我的总线因素;(4)它将避免我忘记所有我做过的酷事(和错误)。

目标

  我们有一个非常简单的目标:通过承诺在每个场景中持续拥有30+FPS,确保游戏的流畅性,同时避免打嗝,并将加载时间保持在一个相当大的数字。

我们的出发点

  记录我们旅程的起点是很重要的。记录这样一个过程将有助于读者–包括我在内,在某些时候–注意到投入的工作和所遇到的决定的具体原因。这样一个过程是漫长的,而且总是在一个特定的背景下开始。

  我们的出发点是持续开发一个在这些平台上运行良好的PC和WebGL版本。我们不想等待一个完美的版本发布–那对它的移植来说可能太晚了–而且我们的改进也能很好地有利于原始平台,特别是WebGL。有一个可以运行基准的基础也很重要。

  人们必须选择一些能在整个过程中指导自己的参考装置。我们总是需要它们来判断我们是否做得很好,就像学校里的分数给我们反馈了我们的进步情况。对此做出理性的选择是很重要的。

  在考虑设备时,重要的是要在目标市场规模和你想提供的视频游戏(视觉)质量之间取得平衡–你想接近的市场越大,你将不得不面对的质量越差,因为许多人使用的仍然是只能渲染一维的旧设备。你可以在Unity Hardware Stats和OpenGL ES硬件数据库等网页上做一些研究,以做出适合你的决定。在我们的案例中,我们决定选择三星S4和iPhone 5s(也可能是5)。现在,我们用这些信息做什么呢?

  我采取的下一个步骤是打印规格表,让它们一直在手边,这样当我对背后的硬件有疑问时就可以快速浏览一下。这样,我就可以把我在软件方面发现的具体问题与它所运行的硬件联系起来。为此,我用无处不在的duckduckgo搜索引擎进行搜索,并被转发到GSMArena和维基百科等来源,在那里我得到了关于CPU、GPU、RAM、屏幕等硬件规格的大部分信息。之后,我以高度熟练的DIY风格打印了这些信息,这样我就可以在工作时把它放在我的桌子周围,然后进行项目工作。对于我们的安卓参考设备,我发现以下规格最相关。

CPU 品牌、功能、核心、频率、缓存大小。所以S4有四个核心,对我们的项目来说绰绰有余。频率不是特别高,但考虑到这是一个移动平台,功耗确实是一个问题,它必须足够了。
GPU 频率、缓存级别、流处理器的数量、填充率和OpenGL ES版本。由于许多性能优化技术都依赖于OGL ES版本,因此必须掌握这些信息。
RAM S4有2GB,不错。这是可以的,但可能不足以在后台继续运行Catan,因为在最小化它时,它可能会被操作系统杀死。这不是好事,但这就是生活
支持的操作系统版本 在S4的情况下,它支持Android 5.0.1,因此,如果我们使用与操作系统互动的特定插件,这是我们必须考虑的问题。

[Unity] Catan Universe: Unity 的移动设备优化_第2张图片

  在决定了我们将支持哪些设备之后,实现我们目标的一个自然步骤是在这些设备上获得一个运行版本,以了解我们离上述目标有多远。在修复了针对新平台的编译和链接问题之后,我得到了一个在三星S4上运行的版本。在启动它的时候,我一直在祈祷–顺便说一下,这并没有帮助–。

Catan 特有的优化

你准备好了吗?让我们从 Catan 开始吧!

主菜单

  主菜单基本上是 Catan 的第一个真正的场景生成,也是其他场景转换的入口,所以开始优化这个场景是有意义的。我在S4上部署了它,并马上用我的朋友adb偷拍了这张截图。
[Unity] Catan Universe: Unity 的移动设备优化_第3张图片
  一些着色器被破坏了,但我现在可以接受。你看到屏幕左下角的那个1了吗?是的,不是毫秒,而是FPS,顺便说一下,是四舍五入的。即使没有大部分的用户界面元素,我也得到了这个数字。第一次看到这个数字时,我至少凝视了一分钟,才意识到必须完成的工作量;这的确是一个好的KPI。由于时间相当不充裕,我必须想办法在最少的时间内实现最多。

  足够幸运的是,我在大学上课时曾注意过一些问题,发现 Pareto原则 相当吸引人,而且很直接。这意味着:我必须找到导致80%的性能瓶颈的20%的问题。而且确实有很好的工具来做这件事。

  我选择RenderDoc作为起始工具,因为它在计算GPU瓶颈时提供了一个相当快速的工作流程。只需点击几下,你就可以捕捉到一帧画面,并计算出绘制事件的时间,以找出GPU可能花费最多的地方–当然,模拟是在桌面上进行的,但相对时间并没有什么不同,除了渲染透明度,因为移动端基于Tile的渲染技术,渲染透明度要贵得多。所以这就是我所做的,并发现了一些(不)令人惊讶的事实。

  Image effects是造成很大一部分问题的原因–bloom、景深和色彩校正–因此,人们必须决定如何删除它们。一种选择是依靠添加一个脚本,在 Awake 时动态地关闭它们。在我们的案例中,我最终用手机变体创建了一个新的场景,因为许多其他的手机特定的变化是可以预期的,而且编译图像效果着色器的时间太长(1-2秒)。我复制了这个场景,烘烤了灯光,对确定加载哪个场景的过程做了一些代码修改,并做了几千个修正,没有什么花哨的。在关闭它们之后,我立即得到了+5 FPS,对于一个20分钟的任务(其中10分钟是部署)来说还不错。不过,要实现一个高性能的主菜单,还有很长的路要走。

  我通过Adreno Profiler检测到的另一个问题是花在着色器上的高计算能力。着色器的ALU有95%是繁忙的,这意味着我们正在使用复杂的着色器和/或许多光源。正如你可能知道的那样,手机基本上是与前向渲染绑定在一起的,所以每个灯光都会在你的着色器中创建一个新的通道(达到你在动态灯光数量部分的质量设置的限制)。由于我们没有复杂的几何图形,所以它不得不依赖于 fragment 着色器的复杂性。实际上,我们广泛地使用了标准的着色器和自定义的着色器,而这些着色器从来没有考虑过用于移动设备。我把标准着色器换成了移动版的漫反射着色器,因此性能大大增加。我还优化了一些不使用阴影的自定义着色器,并减少了它所需的操作和内存访问。是的,我得到了一个重要的性能升级,但视觉质量真的被削弱了;我不喜欢这样。

  我决定花更多的时间来研究改善视觉质量的替代方案,并想出了一些有用的想法,所以请忍受一下。在主菜单中,摄像机是静态的,大多数背景物体也是静态的,不包括树木和羊,因为有动画。这意味着,我们可以通过不同的方式来利用这一点,以获得更好的质量-性能交易。

  • 静态和动态批处理:它有助于减少绘图调用,但这并不是这里的问题(复杂的碎片着色器,一些过度绘图)。总之,它已经在起作用了。

  • 一些元素的几何实例化:由于我们必须支持不提供这种功能的设备,所以放弃了。另外,我们没有太多的重复的几何体,大部分都是独特的。

  • 将静态元素离线渲染成屏幕截图,用于其在后台的每一帧绘制。这很有效,但有几个缺点。

    • 你是在哪种分辨率下拍摄的屏幕截图?如果它与你的设备不同,就会发生严重的采样,这将再次降低视觉质量。
    • 在编辑器中手动定位截图,使其与其他3D物体保持一致,这是一个繁琐的过程。另外,要注意深度信息(和fighting)。
    • 这的确是一个缓慢的工作流程。每次你改变场景中的东西,你都需要取一个新的,并把它设置在正确的位置。这无疑是最糟糕的缺点。

  如果我们采用混合方法呢?如果我们在实时的情况下而不是在编辑器的时间内进行截图,并且用刚才的截图代替真实的3D几何图形,我们也许能够使用正确的分辨率。这样我们就可以解决在unity编辑器中进行静态截图的所有缺点,同时保持相对较高的质量水平,包括复杂的着色器和所有类型的后期效果。当然,我们仍然要对场景进行渲染,但只有一次!之后我们可以将渲染结果存储起来。之后,我们可以将渲染结果存储在RenderTexture中。这值得一试,对吗?我还记得,在意识到某些问题会有多大问题之前,我对这个想法是多么兴奋。请在 "相机冒名顶替者 "部分查看!

  我仍然在寻找充分利用我的时间,我想如果有远程分级支持,那么我可以从移动端运行的活动应用程序中检查、关闭和打开游戏对象和组件,这将是多么有用。为此,我花了20分钟的时间,开发了一个在unity移动播放器中运行的小系统,对我电脑中的一个网页进行投票。在那里我实现了一组命令,如disable_XXXX, enable_XXXX,我可以 "发送 "给客户端让它服从。它使我能够发现哪些游戏对象和设置导致了瓶颈,只需禁用并使用英特尔系统分析器检查其性能影响。

  即使是简单的,这种自发的工具很快就被证明是一个强大的想法,值得扩展。我遇到了一个插件,正好可以做到这一点。Hdg Remote Debug。它允许我通过无线连接到一个远程设备,并远程检查其层次结构。它并不完美,因为你必须做一些变通,以使它与DontDestroyOnLoad对象一起工作,而且并非所有重要的组件属性都被实际序列化。例如,我无法改变网格渲染器的材质(这可能很难实现)。然而,这60欧元花得很值,它帮助我在不安装调试器、剖析器或帧分析器的情况下检测到了瓶颈问题。一旦我知道最大的瓶颈在哪里,我就可以投入更多的资源对它们进行单独研究。

  不要忘记,这份文件只是一个摘要,我在其中过滤了所有对读者没有用处的不相关信息。更多的工作和具体的修改被投入其中,但在这里甚至没有提到。在做完这一切之后,我对我们的主屏幕表现已经足够满意了,所以让我们跳入游戏吧。

游戏中的 Catan

  每个游戏的核心是在游戏中,所以让我们快速部署一个版本并开始比赛,看看我们的处境。在游戏开始时,我们得到了稳定的12FPS,这在桌面上已经是相当不错了。然而,当我们做一些诸如修路的动作时,这个数字就大大下降了;也就是说,我们得到的FPS峰值可能来自于CPU(脚本)。我们当然希望避免这些情况,不要让我们的玩家有足够的时间去喝咖啡休息,所以是时候做一些调查了。

  让我们进入游戏,这样我们就可以在编辑器中看一下统计数据和场景层次了。

[Unity] Catan Universe: Unity 的移动设备优化_第4张图片
[Unity] Catan Universe: Unity 的移动设备优化_第5张图片
  大约5万个三角形,170个绘图调用,70mb纹理,45mb RT,140个阴影投射器。还不错。也许我们有太多的阴影投射器和移动设备的绘制调用,但没有太疯狂。我们可能会考虑减少绘图调用量,但找到其他真正的瓶颈可能是更安全的选择。

[Unity] Catan Universe: Unity 的移动设备优化_第6张图片
  现在我们有了一些东西。400MB的纹理?这可真够多的。减少纹理大小并为Android/iOS设置压缩格式确实是必须的;这将是我放在我的todo列表中的第一个任务。更少的纹理数据意味着更少的带宽浪费,因此着色器在等待采样纹理到达时不会停滞太长时间,这意味着我们实现了更少的功耗或更多的性能。

  音频占用了61MB:加入我的待办事项清单中。

  每一个被动帧(没有采取任何行动)都会产生7KB的垃圾,分成138个垃圾分配。令人印象深刻;这比我的标准要高得多。这是一个必须减少的数字,以避免过于频繁地触发GC;这只会产生帧速率的峰值。再次欢迎你加入我的待办事项清单。很好,这个清单一直在增加 我决定做更多的研究,所以让我们使用另一个工具。

  在启动 Adreno/Snapdragon剖析器 后,我捕捉了一帧来检测进一步的问题,并做了一张截图来表达这些问题。如图所示,纹理表现得不是很好,因为每一帧都有大量相对较大的纹理被上传,占用了很大一部分内存容量以及带宽需求。这是不可以接受的,必须加以纠正。我还发现了什么?

  过度绘制的数字也引起了我的注意:分析器指出有158.26倍的过度绘制系数,这听起来很可怕。让我们在未来考虑到这一点,暂时不要太担心,因为我们在这方面有更好的工具可以使用。让我们继续。

  GPU繁忙的百分比仍然是100%,多么令人惊讶。我还检查了顶点获取滞留百分比,它仍然稳定在4%,这意味着瓶颈不太可能是在几何数据中,但它仍然是值得考虑的。然而,纹理获取停滞的百分比是12%,这是一个相当高的数字,我希望在解决纹理尺寸问题后,帧速率会有所提高。目前还没有太大变化。

  一个更有趣的图表是忙碌的着色器百分比部分,它位于75%和85%之间。这表明GPU有可能在顶点或片段着色器中进行了过多的昂贵操作。过度绘制可能是其中的一个原因;应该检查可见材料。在任何情况下,我们在其他部分看到,我们正在花费大量时间进行片段着色计算,每个片段平均有2个纹理–albedo+normal–。我敢打赌,我们在太多地方使用了标准着色器,这对移动设备来说是过度的。检查着色器ALU容量的百分比可以证实,我们做了太多的操作,这些操作并不真正依赖于访问内存,而是依赖于纯计算能力。

[Unity] Catan Universe: Unity 的移动设备优化_第7张图片
[Unity] Catan Universe: Unity 的移动设备优化_第8张图片

优化

  我们做了一个预先研究,以便获得关于问题可能来自何处的暗示;现在我们要去了解现场的具体情况。

  我的第一个实验是移除所有的光源,除了一个,因为每一个光源都会在着色器中引起一个额外的通道(前向渲染)。我留下了一个,并在其余的物体上用通过材质属性块动画化的色调颜色来伪造照明。不过最大的优化还在后面。

  正如我之前提到的,标准着色器在移动端是相当昂贵的,所以我们肯定应该去选择更简单的照明模型。我把它们全部换成了漫反射凹凸不平甚至是无光照的着色器。仅仅通过改变棋盘游戏发生的桌子上的着色器,我就得到了+3 FPS。所以我对其他的物体也做了同样的处理,这对帧率有了巨大的提升。由于我们使用动态实例化的预制体,并且我们希望保持桌面的原始质量,我不得不考虑一个系统,使我们能够在编译时根据平台在材料类型之间切换。那是怎样的呢?

  在每个平台的基础上替换materials 的一个选择是重复预制板和materials 。如果我们有Horse.prefab和Horse.mat,把它们分成Horse_desktop.prefab、Horse_mobile.prefab,分别链接到Horse_desktop.mat和Horse_mobile.mat,并修改生成它们的代码,用宏(例如UNITY_ANDROID)或运行时检查(Application.isMobile)来区分平台。但要注意:对两者的设置引用将包括所有构建中的预制构件及其链接的资源(材质、纹理、着色器)。你可以看到每个预制件会有多少乐趣:两个文件中你可以得到四个。维护它可能会变得非常昂贵,但人们可以获得很大程度的灵活性。我不喜欢支付这么高的价格,所以我研究了改变 materials 的其他可能性。

  让我们先考虑一下通过camera.RenderWithShader进行手动渲染的选项。在那里,你可以在运行时根据你的设备能力指定要运行的着色器。虽然这听起来不错,因为人们可以使用更简单的着色器来处理廉价的设备,但它会增加一些额外的问题:首先,这是一个手动的方法,往往会随着版本的升级和你在场景中的各种修改而损坏。它的维护成本很高,而且由于你是手动渲染,它往往会掩盖Unity的简单本质,在那里你通常会看到你在每个场景中得到什么。不仅如此,用不同的着色器渲染只是整个故事的一部分;我们还需要不同的材质、纹理和参数,这些都很难通过这种方式来实现。维护材质信息甚至比这一切都更难。由于所有这些原因,我不愿意为获得性能而付出这种代价;一定有更好的方法。

  而更好的方法就在我脑海中出现了。我去添加了一个在编译时运行的脚本,它的任务是搜索项目中的所有prefabs(在Assets目录中),以检查它们的所有渲染器中是否有名称以_desktop(.mat)结尾的链接材料。如果找到了,它就用艺术家应该添加的_mobile.matcounterpart来替换这个参考链接。该脚本在每次构建前都会运行,而且它的变化每当完成后都会被还原,以避免混淆git。考虑到这一点,我们实现了所有prefabs只包含对移动材质的引用(如果存在的话),扔掉了桌面材质,因此节省了宝贵的内存和着色器编译时间。工作流程仍然很简单:如果你想让一个昂贵的材料在移动端更简单,只需将原始材料重命名为_desktop,将其克隆到* _mobile*,并做相应的修改;自动构建步骤将完成其余工作。由于这个方法导致了一些好奇的问题,我决定再详细说明一下。

  对我来说,重要的是 Material 脚本能够自动工作,而不需要用户明确运行脚本。换句话说。我希望它既能在使用ctrl+B构建时执行,也能在我们基于UBS的构建管道中调用。我检查了文档,正好有两个有趣的注解。[PostProcessScene]和[PostProcessBuild]。两者都可以让你在最终构建之前(尽管它的名字是)修改场景和整个构建,所以它确实是一个有趣的地方,我们可以把我们的流程挂到这里。只要把它们添加到你选择的MonoBehaviour类的一些方法中就足够了;它只需要驻留在一个编辑器目录中。这些方法以后会被Unity以一种非常混乱的方式调用,这最初让我很吃惊。无论如何,总结一下我的脚本的行为。

步骤 描述
1 Select build & run (ctrl/cmd + b)
2 OnPostProcessScene在每个场景中被调用。在这里,如果满足两个条件,我就运行我的材质切换脚本。第一个条件是unity编辑器没有在播放,因为这个方法也会受到播放模式变化的影响。第二个条件是它不能已经被运行了,因为它在构建过程中只能被运行一次。
3 我的Matreril切换脚本通过*AssetDatabase.FindAssets(“t:prefab”)*搜索项目中的所有prefab。
4 对于每个预制件,我们用GetComponentsInChildren找到所有的渲染器(也包括皮肤网格渲染器),并将它们的材质填入一个列表中。
5 对于每个 Matreril ,我们检查在同一目录下是否有*_mobile*版本。如果有的话,就把原来的引用替换成移动版本
6 我设置了一个标志,这样脚本就不会再为其他场景运行了
7 构建刚刚完成,OnPostProcessBuild被调用。在该脚本中,如果满足两个条件,我将恢复对预制板所做的修改。像以前一样,我检查编辑器是否处于游戏模式,以及是否有东西需要恢复。恢复是过程中的一个重要步骤,以避免git显示数以千计的修改文件,包括prefabs和场景。我选择的恢复方式是对我之前填写的prefabs列表调用checkout
8 作为这个脚本的用户,一切都恢复正常,但我们最终的APK/IPA将包括移动Material,不包括桌面材料,对其他开发者来说是一个完全透明的过程。

通用优化

  现在我们谈了一些关于Catan的问题,让我们跳到更一般的性能提示。其中一些是我通过经验学到的,另一些是通过研究和实验,还访问了博客和技术帖子,并且像往常一样,超时空的帮助。我们将讨论内存、材质、纹理、着色器和音频。带上一些爆米花吧!

内存分配

  我的哲学:不必要的内存分配是电子游戏中所有邪恶的根源,应该仅限于加载屏幕。当然,这就像一个新的一年里每天都要去做的承诺,结果第二天就被打破了,但我们不要蒙蔽自己:我们需要真正关注它,并意识到其后果。基本上,当你分配内存时,你正在增加触发垃圾收集过程的机会,这个过程将阻塞所有线程–包括渲染–直到完成。我所说的增加机会是指加快GC进程的速度,这也意味着那些帧速率的打嗝会更频繁地发生。渲染线程也包括在这个列表中,导致你的玩家会在一个冻结的屏幕前等待半秒钟。你在玩FPS游戏的时候,难道没有发生过这样的情况吗?我见过很多因为这个原因而坏掉的无愧于心的键盘。当这种情况经常发生时,你会把人们的挫折感增加到一个无法忍受的程度,导致他们把手机扔到墙上,这样他们就有了购买一个新的、更有能力的手机的合理理由。他们可能会认为他们的手机是垃圾;尽管我们都知道我们的小秘密–我们要对此负责–。回到我们的主题,我们如何可能改善并因此减少内存分配?

在我看来,需要考虑的最明显的几点是。

  • 不要使用LINQ,特别是在每一帧的基础上。它们在内部产生大量的分配。
  • 不要在每一帧的操作中使用foreach,因为它们在内部产生的枚举器会产生垃圾。
  • 避免在游戏中使用GameObject.Instantiate。相反,使用一个对象池系统,如EZ对象池,并在加载屏幕中预先实例化所有你能做到的对象。
  • 对coroutine要小心。StartCoroutine是很昂贵的,特别是当你使用字符串重载变体的时候。与频繁地实例化和停止它相比,让它一直运行并有一个bool检查往往更好。
  • 另外,缓存所有你能做的 yield 操作,这样你就不会产生很多垃圾,因为每一帧的WaitForSeconds()都会产生大量的垃圾。创建一次收益率对象,并按你想要的频率返回它。如果可能的话,将new WaitUntilEndOfFrame改为返回null(而不是返回0,这也会通过装箱产生垃圾)。
  • 如果你使用列表,试着用静态变量来缓存它们,而不是在每次使用时重新创建它们(每个对象,在函数中,等等)。另外,如果你事先知道初始容量,就设置它。
  • 尝试使用堆栈,因此避免了堆(即结构而不是类)。
  • 一般来说,避免使用new的关键词,除非是在加载屏幕中。
  • 对装箱/拆箱要小心。如果你把值类型当作对象来对待,它们就会自动地、隐含地转化为对象,从而产生垃圾。
  • 如果你真的想在堆栈中分配内存以减少GC的压力(而且足够绝望),请看一下C#的stackalloc和unsafe关键字。
  • 像往常一样,检查Unity Profiler的内存分配情况。在更新时,它们尤其有害。我建议使用Profiler.BeginSample和Profiler.EndSample来划定你想分析的代码区域,同时启用深度剖析。如果深度剖析在你的大型项目中反应太慢,可以看看这个工具,将特定的框架导出到html。

关于垃圾收集,互联网上肯定有很好的参考资料,其中之一来自Unity本身。

Unity Materials

  我们仍然需要Unity提供的每个平台的 Material 管理系统。目前还没有很好的解决方案来处理这个问题,尽管这绝对是一个非常重要的话题。在Unity解决这个问题之前,找到一个变通的办法是明智的。正如在其他章节中提到的,我创建了一个预构建步骤,将项目中发现的每个预制件中的_desktop材料引用改为其* _mobile*对应的材料;这对那些严重基于预制体动态实例化的游戏是有效的。但无论如何,有一些要点你应该始终考虑。

  在构建之后,你应该总是,我是说总是,检查其中到底包含了什么。原因是:你可能会在移动构建中包含巨大的纹理或非常复杂的着色器,这将减慢加载时间,增加功耗和构建大小,等等。我建议用几个插件来解决这个问题。BuildReport, A+ Asset Explorer, FindReferences2。前两个插件将帮助你检测你的构建所吐出的内容:纹理信息(尺寸、压缩格式、尺寸)、音频信息(尺寸、压缩)、着色器、材质等等。前面提到的最后一个插件将帮助你摆脱你不想要的材质/纹理/着色器。由于你并不总是知道它们在哪里被引用过,该插件可以帮助你找到它的来源,这样你就可以解除它的链接。通常情况下,材质没有它们的链接资源(如纹理)那么重要。

Unity Textures

  纹理与网格和它们各自的Material一样,是实时渲染的基本支柱,因此必须要特别考虑。有一些重要的因素需要考虑,比如压缩、纹理尺寸、屏幕尺寸、过滤和UV映射。让我们从压缩问题开始。

  纹理压缩在手机上是最重要的。它不仅可以减少你的APK/IPA的大小–这对发布很重要–而且还可以通过减少带宽需求来提高性能。加载时间将大大降低,因为纹理将被压缩存储在持久性内存和RAM中–GPU将在飞行中解压缩,相关成本可以忽略不计–。因此,必须研究你的目标设备–更具体地说,你的参考设备–以找出支持哪些纹理压缩格式。在我们的案例中,我选择了Android的ETC2(支持OpenGL ES 3.0以上)和iPhone的PVRTC。幸运的是,与ETC相比,ETC2支持alpha通道,因此不再需要分割alpha。值得注意的是,如果你选择了一个不支持的压缩格式,Unity会在运行时将其CPU转换为支持的格式,导致加载时间的增加和可能出现的障碍,但至少受影响的纹理是可用的;不要忘记在开发构建中定期检查日志(例如logcat)以发现这类问题。顺便提一下,有些人为了获得更小的APK大小而在DXT5中压缩纹理,我不同意这种想法。总之,自己尝试一下,不要害怕破坏(一些)(开发)版本。纹理压缩只是蛋糕的一部分,我们还有更多的因素会影响到性能。

  纹理大小是你必须试验的东西。比如说很多。它在很大程度上取决于你将要运行的游戏的屏幕尺寸和分辨率。从一个合理的小尺寸开始,一步一步地增加,直到它看起来足够好,要注意一个事实,即纹理在物体中的外观很大程度上取决于屏幕投影和最坏情况下屏幕上可见的纹理数量–也就是说,当物体的表面占据了整个屏幕时–。一般来说,它能占据的屏幕空间越多,纹理就应该越大,以避免出现明显的低分辨率迹象;然而,权衡之下,更大的纹理会降低性能并增加分布大小,这是出版商真正关心的一个因素,因为它可能阻止你的游戏通过3G/4G下载。纹理过滤在性能方面也起到了一定作用。

  我遇到过许多游戏开发者,他们没有对纹理过滤给予必要的关注程度。我就是其中之一。一般来说,有 bilinear 滤波就足够了。如果我们使用的是不与屏幕平行显示的平铺纹理,你应该考虑三线过滤,以便在mipmap层之间进行过滤。Mipmapping对于显示尺寸(在屏幕上)随时间变化的动态对象来说特别有意思,这样可以动态地选择合适的纹理尺寸。这种方式可以节省内存带宽和处理能力。不过,mipmapping会多占用33%的内存。mipmapping的典型用例:地板、天花板、带有瓷砖纹理的墙壁。UI通常不应该启用mipmapping,因为一个像素通常等于一个texel(除非它是世界空间或者它被重新缩放,改变了一个像素等于一个texel的规则)。

Unity Shaders

  着色器是实时图形历史上与可编程管线一起引入的一个大步骤。每个人都在谈论它们有多酷,使用它们可以获得多大的灵活性,但却没有相当多的开发者关心它们的影响,尤其是在移动开发中。人们将不得不在所有的平台上维护它们,所以不要太慷慨地编写它们,否则你会发现自己偷偷地做超时的修复工作。

也就是说,让我们检查一下一些提示,不要在移动端失去很多性能。

  • 避免使用标准着色器;使用Lambert或Phong(移动漫反射/镜面反射)照明模型比使用PBR模型更好。当该物体占总屏幕尺寸的很大一部分时,这一点尤其重要,因为在PBR模型中,每个像素的照明要复杂得多。

  • 由于移动平台在大多数时候只支持前向渲染,所以要避免在场景中出现一个以上的灯光,因为每个光源都会产生一个额外的通行证。

  • 不要使用GrabPass,因为它破坏了并行性,并极大地增加了带宽需求(移动平台缺乏)。正是因为这个原因,在移动平台上要避免使用 post - effects。

  • 在可能的情况下,通过色调颜色使用无光和假的照明效果。摆脱照明计算将有巨大的帮助。

  • 如果不需要的话,可以禁用雾化。

  • 如果不需要的话,禁用阴影支持。考虑将阴影烘烤到纹理中。避免使用Megashaders和大的着色器,因为编译时间会增加你的应用程序的启动时间(我看到一些着色器需要5秒钟来编译)。

Audio

  检查剖析器以弄清它们占用多少内存。通常情况下,它们不应该是一个问题,但可能有一些问题。

  • 如果它们占用了太多的内存,试着将它们设置为流媒体。
  • 如果它们正在增加加载时间,你有几个选择。
    • 将它们设置为在后台加载;尽管你在第一次播放时有可能会出现不同步的情况。不过,这个设置在所有平台上都是共享的,太糟糕了。
    • 禁用预加载;但是,用户在第一次被加载时将会遇到滞后。
    • 把它们设置为流媒体。不过这将增加CPU/IO的使用率。

附加的

冒牌相机系统

  一个很大的挑战是将高端图形带到移动领域。移动平台根本不具备实时渲染这种质量的能力。一个解决方案在于我刚才写的那句话:让我们只渲染一次高质量的图片,并在我们运行时应用程序的每一帧中显示该静态图片。

  基本的想法是在Awake函数中把一个特定的相机内容渲染到RenderTexture中。该相机有一个精心挑选的剔除掩码,设置在Priorender层,所以只有这些对象会被渲染;之后这些对象会被禁用。相机组件要被禁用,所以自动渲染不会发生,而只有在用相机手动触发时才会发生。Render()。下面是一个粗略的过程描述。

  1. 我们创建一个设置与主相机相同的相机,把它的 culling mask 设置为Prayerender层,深度低两级。我们从主相机上移除该遮蔽层。我们可以把我们令人窒息的后期效果添加到冒牌相机中。
  2. 我们将我们想要预渲染的高质量几何体移到预渲染层(如果需要的话,调整灯光)。
  3. 我们为预渲染摄影机创建一个脚本,该脚本执行以下内容:
  4. 在Awake时,禁用相机组件并创建屏幕大小、ARGB32和24位深度格式的RenderTexture。
  5. 在下一帧中,将目标纹理设置为前面提到的RT,并仅调用一次camera.Render()。
  6. disable属于该层的3D静态几何体。
  7. 我们使用相同的设置在场景中创建第二个摄影机,深度比主摄影机低一个深度,并使用执行以下操作的脚本将 culling mask 设置为无:
  8. 获取对在Prerender摄影机中创建和渲染的Render纹理的引用。
  9. 使用Graphics.Blit在OnRenderImage中呈现该RT
  10. 然后,将主摄影机设置为不清除任何标志,以便在我们刚刚使用的RT之上渲染所有3D实时几何体。

  这种技术有多个优点:它不仅提高了帧速率,节省了能源,而且还允许您运行昂贵的图像效果。
这是非常有限的,因为它是高度静态的:您的相机和目标对象都不应该移动或更改。

  我很开心。但也太天真了。在大肆宣传之后,我注意到屏幕上出现了一些奇怪的东西:一些本应被遮挡的物体却没有被遮挡。我没有想到这一点,所以在拿了一杯浓咖啡后,我加载了RenderDoc,发现我的Blit操作只是写入颜色缓冲区,而不是深度信息。但是为什么呢?深度信息位于渲染纹理中。当然,为了节省资源,它可能会被跳过,因为用户不会经常处理这样的场景,但必须有一种方法将RT中的深度信息写入帧缓冲区,对吗?不,没有这个功能。仅限变通方法。

  我花了一整天的时间试图访问RenderTexture的深度缓冲区,但没有成功。我自己指定的时间框过期了,我决定尝试一个不那么优雅的解决方案:使用不同的着色器第二次渲染场景,该着色器获取相机深度,将其转换为浮点颜色,并保存为第二个RT。包含颜色编码的深度信息的第二个RT将在具有将颜色转换为深深值的自定义材质的彩色RT之后被阻挡,并写入片段着色器中的深度缓冲区。因此,在此之后,我可以在任何地方使用该系统的深度缓冲信息,这提高了它的灵活性。

  废话太多了,让我们开门见山吧。通过预先渲染静态几何体,我能够打开所有图像效果,使用复杂的着色器和几乎免费的最大质量阴影,我仍然可以获得+8FPS。我通过使用随时间变化的自定义颜色的未照明着色器来伪造一些对象的照明,我已经准备好了。

插件

  • Build Reporter 必须具有列出已部署应用程序(APK、IPA)中包含的资产。 它使资产剥离的任务变得更加容易。 我建议您在构建管道中自动化此过程,以检测错误资产或在开发过程中引入的可能问题。

  • FindReferences2当您想找到对特定实体(纹理、声音、材料)的引用以确定某些更改是否可行时,您将很难获得它们,特别是如果您使用动态实例化的预制件。这个插件使任务更容易。这在跟踪不应该在构建中着陆的着色器时特别有用,因为它们的编译成本可能非常高(以秒为单位)。假设您有一个纹理并且您想知道它在哪里按顺序使用例如,判断其决议的改变是否可行。天真的方法是在项目窗口中右键单击纹理并选择“在场景中查找引用”。这以非常有限的方式起作用:它仅在当前场景中检查,并且如果您使用将在运行时实例化的预制件,则只有在它们是平面的情况下才会起作用。让我详细说明最后一点。假设我们在运行时实例化了一个 Player 预制件。为此,我们从场景中的某处保存对它的引用。如果 prefab 的 GameObject 直接包含对纹理的引用,那么 Unity 会间接找到它。但是,如果该纹理在子组件中使用,则不会。

  此外,许多项目使用大量场景,因此搜索参考资料可能非常繁琐。 我在 Catan Universe 中遇到的一个问题是寻找一个非常复杂的着色器(为高端 PC 考虑)被包含在移动构建中的位置。 有了这样一个插件,我只需要选择着色器,我就直接找到了真正的引用。

  • A+ Asset Manager 它可以让你通过许多不同的过滤器在你的项目中找到项目。 例如,您可以按纹理大小、压缩、大小等进行过滤和排序。在针对特定平台优化资产时,它有很大帮助。

  • Hdg Remote Debug 这个插件可以让你远程分析一个正在运行的项目的层次结构。 发现瓶颈很方便,因为您可以通过单独禁用对象来进行 AB 测试,以查看帧速率(或内存)显着增加的位置。

  • EZ Object Pools 小型免费工具,用于汇集您想要预实例化的对象。

工具

按重要性排序:

  • Unity Profiler:对 CPU 分析(脚本)特别有用。
  • RenderDoc:对于检查绘制事件(包括绘制调用)的大致时间非常有用。
  • Snapdragon profiler:它有助于调试帧和系统范围的性能指标(特定于进程的指标非常有限)。 您还可以检查纹理和着色器。
  • Adreno 分析器:它提供不同的性能指标。 然而,它已经过时并且已被 snapdragon profiler 取代。 许多功能严重依赖于平台。

总结

  从一开始就考虑性能; 只是不要沉迷于此。 我强烈建议您在构建管道上努力工作,该管道允许您在目标设备上运行实时自动化性能测试。 这样的系统应该将其结果记录在像 ELK 这样的远程后端,以便您可以计算一段时间内的统计数据并检测异常情况。 我目前正在为我的下一个项目进行设置:Diamond Dash Unity。 我可能会写一篇博文,介绍我为实现它所采用的技术。 也就是说,我希望这篇文章能为您提供一些价值。 敬请关注。

[Unity] Catan Universe: Unity 的移动设备优化_第9张图片

你可能感兴趣的:(Unity,性能优化,性能优化,unity)