前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity基础系列(五)——每秒帧率(测试性能)

Unity基础系列(五)——每秒帧率(测试性能)

作者头像
放牛的星星
发布2020-07-24 11:14:13
2.6K0
发布2020-07-24 11:14:13
举报
文章被收录于专栏:壹种念头壹种念头

目录

1 构造原子核2 使用Profiler3 测量FPS4 帧平均每秒5 给文本上色

本章重点: 1、用物理学来创造一个不断成长的原子核。 2、使用分析器排查性能。 3、统计并显示帧速率。 4、阻止创建临时字符串。 5、通过平均多个帧来稳定帧速率。 6、对不同帧率进行分色显示。

本教程将创建一个简单的测试场景,然后测试其性能。先用profiler排查,然后创建我们自己的帧率计数器。

本教程要求对Unity脚本有基本的理解。兼容Unity5.0.1及以上版本。如果你还不熟悉Unity脚本操作,可以先看一看前面几个章节。

(聚集球体,知道你的帧率开始承受不住)

1 构造原子核

要测试性能,就需要搭建一个测试场景。一个理想的测试场景应该涵盖高性能和低性能的情况。所以这里通过将越来越多的核子融合在一起来创造一个原子核。随着核变的越来越大,性能逐渐恶化。

核子只是简单的球体,它们会被吸引到场景的中心,在那里它们会聚集在一起形成一个球。这当然不是原子的正确表示,但这不是重点。

我们可以用一个默认的球和一个自定义的Nucleon 组件来模拟一个核。该组件要要同时添加刚体组件,然后简单地将其拉向原点。拉力的强度由可配置的吸引力和距中心的距离决定。

缺少一些访问修饰符?

是的,字段和方法声明中可以省略private 修饰符,因为在默认情况下它们就是私有的。

用球体制造两个核子预制体,一个用于质子,另一个用于中子。分别给不同的材质球,让它们看起来不同。其实性能演示只需要一种核子类型就够了,但这样感觉就没意思了。

什么是预制体?

预制体是一个不存在于场景中且未被激活的Unity对象或对象层次结构。你可以使用它作为模板,创建它的克隆并将它们添加到场景中。要创建一个对象,可以像往常一样在场景中构造一个对象,然后将其拖到项目窗口中。场景对象将成为一个预置实例,如果你不再需要它,可以直接删除。

(核子预制体)

为了产生这些核子,还需要制造另一个成分-- NucleonSpawner 。它需要知道不同副本之间的时间间隔,离中心有多远,要产生什么。

创建一个空的游戏对象,附加一个NucleonSpawner组件,并根据你的需要配置它。

(核子生成器)

为了定期生成,就需要跟踪从上次生成的时间。可以使用一个简单的FixedUpdate方法来完成这个任务。

为什么使用 FixedUpdate 而不是 Update?

使用FixedUpdate会让生成与帧速率无关。如果在子程序之间配置的时间比帧时间短,使用Update会导致产生延迟。因为这个场景的重点是阻碍帧率的,所以这必然会发生。

可以使用一个while循环取代if检查来加速追赶漏产生的核子,但是当timeSinceLastSpawn 意外的被设置为零时,这将导致无限生产循环。将生产限制在每一个固定的时间步骤是一个理智的限制。

实际产生由三个步骤组成。选择一个随机的预制件,实例化它,并在期望的距离上给它一个随机的位置。

(通过轰炸建立一个核)

播放这个场景应该会导致球体向中间聚集。它们会挣脱一段时间,直到相互碰撞,形成一个球。这个球将继续增长,物理计算将变得更加复杂,并且在某一时刻你会注意到帧率的下降。

如果你的机器硬件好,需要很长时间才能看到性能恶化的话,则可以增加产生的速率。通过增加时间尺度的配置来加快时间是比较有效的。可以通过Edit Project Settings Time 找到。当然也可以减 Fixed Timestep ,这也会导致每秒更多的物理计算。

(Unity的时间设置)

为什么在低时间尺度下运动不流畅的?

当time scale被设置为0.1这样的低值时,时间会移动得非常慢。由于固定的时间步长是恒定的,这意味着物理系统更新的频率会降低。因此,物理物体将保持静止,直到一个固定的更新发生,也就是每隔几个帧就更新一次。

随着时间的增加,你可以通过减少固定的time scale来对抗这种现象。或者,可以更改刚体部件的插值模式,以便它们在物理步骤之间进行插值,从而隐藏较低的更新频率。

2 使用Profiler

现在已经有了一个场景,并且最终能降低任何机器的帧率,现在是衡量实际性能的时候了。你能做的最快的事情就是启用游戏视图的统计数据。

(游戏视图统计显示)

然而,光是帧率显示根本不准确,它更像是一个粗略的猜测。可以通过打开Unity的Profiler分析器来看到更直观的数据统计,通过 Window / Profiler 可以打开分析器窗口。分析器给我们提供了很多有用的信息,特别是CPU使用情况和内存数据。

(分析器 显示大量的Vsync时间)

如果启用了vsync,它很可能在一开始就主导CPU图形。为了更好地了解场景需要多少CPU资源,请关闭vsync。您可以通过Edit Project Settings Quality 来关闭。在页面靠下的部分,Other的标头下面。

(关闭垂直同步)

现在我们有超高的帧率了!

如果没有vsync,简单场景就可以获得非常高的帧率,远远超过100。这会给硬件带来不必要的压力。你可以通过设置Application.targetFrameRate属性通过代码强制执行最大帧速率来防止这种情况。请注意,即使在退出播放模式之后,此设置在编辑器中仍然存在。将其设置为?1会消除限制。

现在,你可以更好地了解CPU的使用情况。在这个例子中,物理花费看大部分时间,其次是渲染,然后是脚本。这在很长一段时间内都是正确的,尽管随着球体计数的增加,所有都会变慢。

(没有垂直同步的结果)

这里还可以观察到两个意想不到现象。首先,偶尔会出现CPU使用率的高峰。其次,内存图显示频繁的GC分配峰值,这表明内存被分配并随后就被释放。但示例只是在创建新的对象,并没有丢弃任何东西,这就很奇怪了。

这两种现象都是由Unity编辑器造成的。每当你在编辑器中选择某些内容时,CPU峰值就会发生。内存分配是由对GameView.GetMainGameViewRenderRect的调用引起的,而编辑器会调用GameView.GetMainGameViewRenderRect。除此之外,还有额外的开销,特别是当你同时看到游戏和场景的时候。简而言之,编辑器本身会干扰我们的观测。

但即便如此你仍然可以从编辑器内的概要中获得大量有用的信息,但是如果想要从度量中消除编辑器本身的影响,则必须进行独立构建。如果你进行development 构建,甚至在运行应用程序时自动连接到它,你仍然可以使用分析器。您可以通过“File / Build Settings ”来配置.

(profiler绑定在standalone的构建上 )

分析独立构建的时候,数据看起来差别很大。内存分配现在只由生成核子引发,不再发生垃圾回收。在本示例中,渲染需要更多的时间,因为我运行的应用程序是全屏的。而脚本是如此的微不足道,以至于它们在图形中都是不可见的。

3 测量FPS

profiles 给我们提供了很多有用的信息,但它仍然不能很好地测量帧率。FPS数字显示的应该是1除以CPU时间,我们需要自己来实现。

要要一个简单的组件,告诉我们当前应用程序每秒运行的帧数即可。它只需要一个公共变量就足够了用整数来表示,一般帧率都比较大,所以并不在乎末尾的小数。

这个属性代表什么意思?

属性其是假装为字段的方法。我们将FPS作为公共信息提供,但只有组件本身需要更新该值。所使用的语法是自动生成属性的简写符号,类似于这样。

int fps;

public int fps{get{back fps;}

private {fps=value;}

此简写不能用于Unity的序列化,但在这里没问题,因为并不需要持久化保存FPS值。

我们通过将1除以当前帧的时间增量来测量每秒的帧数,然后将结果转换为整数,进行适当的舍入。

然而,这种方法存在一个问题。时间增量不是处理最后一个帧所需的实际时间,它会受当前time scale的影响。这意味着我们的FPS可能是错误的,除非time scale设置为1。但我们可以使用另外一个字段unscaledDeltaTime来得到没有经过缩放的时间增量。

现在需要UI来显示FPS,就用UGUI吧。创建一个Canvas,其中包含一个Panel,然后包含一个Text对象。可以通过GameObject/UI 子菜单添加这些内容。在添加Canvas时,还将自动添加一个EventSystem对象来处理用户输入,但我们不需要它,因此可以删除它。

(UI对象的层次)

使用默认画布设置,设置为pixel-perfect。

该面板用于创建FPS标签的半透明黑色背景。这样,它将永远是可读的。把它放在窗户的左上角。将它的锚设置在左上角,这样无论窗口的大小如何,它都保持在原地。将其枢轴设置为(0,1)以便于放置。

以类似的方式将Label放置在面板内。将其改为白色粗体文本,以水平和垂直两种方式居中。微调大小,使它适合两位数字显示。

(构建UI)

现在我们需要将FPS值绑定到Label上。为此需要创建一个组件。它需要一个FPSCounter组件从其中检索值,并需要从UnityEngine.UI命名空间中对文本标签的引用来将该值分配给它。

将此组件添加到面板上并将其关联起来,直接附加到Panel上,因为这是一个整体FPS显示器,而不仅仅是Label。后面我们会包括更多的Label。

(设置显示规则)

显示组件只需每帧更新Label的text。所以先缓存一下对计数器的引用,这样我们就不需要每次调用GetComponent了。

FPS标签正在更新!但是当时我们设计它的时候只想展示2位数,所以一旦帧率超过每秒99的时候,显示上就会有问题。所以逻辑上收紧显示值,任何超过99的表现无论如何都足够好了。

(可以看到帧率了)

看起来已经完成了预期的表现,但是有一个很小的问题。现在每帧都在创建一个新的String对象,该对象将在下一个更新中被丢弃。这会污染托管内存,从而触发垃圾收集器。虽然这对桌面应用来说不是什么大问题,但对于内存不足的设备来说,这就更麻烦了。它还污染了我们的分析器数据,这是比较烦人的,需要想办法解决。

(临时的string造成的性能开销)

有办法能摆脱这些临时的string吗?回想一下,FPS的显示值可以是0到99之间的任意整数。那其实就是100个不同的字符串。为什么不创建一次性创建所有这些字符并重复利用它们呢?

通过一个固定数组缓存可能需要的每个数字的字符串,现在已经能够消除所有临时字符串分配!

4 帧平均每秒

更新每个帧的FPS值有一个不好的副作用。当帧率不稳定时,label显示会不断波动,因此很难得到有用的读数。所以需要减少标签浮动更新的频率,但是这样的话,又不是及时反馈帧的浮动变化。

一个可能的解决方案是平均帧速率,平滑突然变化造成的影响,产生较少的抖动值。现在来调整下FPSCounter,使其在可配置的帧范围内完成此操作。将此值设置为1,但它与不平均的那个值完全不相同,因此它实际上是可选的。

(配置帧率)

将属性名从FPS更改为AverageFPS,因为这是对它现在表示的值的更好的定义和描述。你可以使用IDE重构名称,也可以手动更新显示组件以使用新名称。

除此之外,还需要一个缓冲区来存储多个帧的FPS值,再加上一个索引,这样我们就知道将下一个帧的数据放在哪里了。

初始化此缓冲区时,请确保FrameRange至少为1,并将索引设置为0。

Update方法变得有些复杂。它首先初始化缓冲区(如果需要的话),或者是因为我们刚刚开始,或者是因为FraRange已经改变了。不管如何,它都需要先初始化,再更新缓冲区,然后才能计算平均FPS。

更新缓冲区是通过在当前索引中存储当前FPS来完成的,该索引会递增。

如果这样的话,很快就会填满整个缓冲区。所以在增加新值之前,可以放弃最旧的值。所以,可以将所有的值都转换成一个位置,平均值并不关心值所处的顺序。所以我们可以将索引包装回数组的开头。这样,一旦缓冲区被填慢,我们总是用最新的值去覆盖最老的值。

计算平均值比较简单,就是将缓冲区中的所有值相加,再除以值的数量。

现在平均帧率可以正常显示了,在合理的帧范围内,这个表现会减少抖动,让展示变的平滑。但其实还可以做得更好。

由于现在有来自多个帧的数据,我们还可以在这个范围内公开最高和最低的FPS。这会给出更多的信息,而不仅仅是平均水平。

我们可以一边计算,一边找到这些值。

FPSDisplay组件现在可以绑定另外两个Label。

将两个Label添加到UI中,并将它们全部关联起来。把最高的FPS放在顶部,最低的FPS在底部,平均FPS在中间。

(更多的信息展示,更少的抖动)

5 给文本上色

作为FPS标签的最后一步,可以给它们上上色。这可以通过将颜色与FPS值相关联来实现。这样的关联可以用自定义结构表示。

由于FPSDisplay是使用此结构的唯一工具,因此我们将struct定义直接放在该类中,并将其设置为私有,这样它就不会出现在全局命名空间中。使其可序列化,以便由Unity编辑器编辑。

添加这些结构的数组,以便配置FPS标签的着色。我们通常会为它添加一个public字段,但是现在不能加,因为结构本身是私有的。所以,也要将数组设置为私有,并赋予它SerializeField属性,以便Unity在编辑器中公开并保存它。

继续,添一些颜色!确保至少有一个条目,按从最高到最低的FPS顺序,最后一个条目为0 FPS。

(颜色配置)

在将颜色应用到Label之前,通过引入一个单独的显示方法来重构Update方法,该方法负责调整单个Label。

通过遍历数组找到正确的颜色,直到满足颜色的最小FPS为止。然后设置颜色并跳出循环。

为什么我的Label消失了?

因为该条目的颜色将其所有四个通道设置为零。这包括控制不透明度的alpha通道。如果你没修改改alpha通道,得到就是完全透明的Label。

(带颜色的FPS展示)

完成!现在享受一下你的帧率变慢吧!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 壹种念头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档