在Unity中实现准确的帧率
你是否想过,Unity可以遵循准确的帧率,甚至遵循外部时钟源即Genlock同步锁相呢?
本文将介绍Unity如何在本地维持帧率,以及如何添加用户代码来严格控制该过程。该功能在类似于严格同步Unity和其它设备的播音室等环境中非常重要。
通常,Unity项目会尝试着以尽可能快的速度运行。每一帧都会尽快渲染,这通常会受限于显示设备的刷新速率。
控制帧率最简单的方法是:明确设置QualitySettings.vSyncCount,使渲染过程发生的间隔和显示设备的刷新速率相关。
例如:对于频率为60Hz的显示器,设置vSyncCount=2会使Unity以30fps的帧率同步于显示器进行渲染。这样会使帧率限制为显示器刷新速率的大约数值,所以无法提供足够粒度化的控制。
另一个简单的解决方法是:设置QualitySettings.vSyncCount=0,并使用Application.targetFrameRate设置独立于显示器刷新速率的帧率目标。
通过这样的设置,Unity会将渲染循环限制为约等于该速率。但需要注意,因为此时Unity不再同步于显示器进行渲染,所以可能会出现图像撕裂现象。该方法会消耗较低性能,以避免使用不必要的CPU资源,但缺点是可能无法为每个用例实现所需的精度。
不必担心,协程可以帮助你提高精度。我们不必使用Unity内置帧率限制功能,而是通过脚本代码自己控制帧率。
为此,我们必须设置QualitySettings.vSyncCount=0,把Application.targetFrameRate设为非常大的数值,从而让Unity尽可能快的运行项目,然后使用WaitForEndOfFrame协程,通过使下一帧仅在我们允许时才开始渲染,准确的把速度减慢为目标速率。
为了准确执行操作,我们建议结合使用Thread.Sleep,从而适当的延迟Unity渲染循环,避免消耗过多CPU资源,然后在最后几毫秒内调整CPU,同时检查允许下一帧开始渲染的合适时间。
如果要协调Unity帧率和外部时间即Genlock同步锁相,我们应该在收到外部同步信号时立即跳出CPU旋转循环。
using System.Collections;
using System.Threading;
using UnityEngine;
public class ForceRenderRate : MonoBehaviour
{
public float Rate = 50.0f;
float currentFrameTime;
void Start()
{
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = 9999;
currentFrameTime = Time.realtimeSinceStartup;
StartCoroutine("WaitForNextFrame");
}
IEnumerator WaitForNextFrame()
{
while (true)
{
yield return new WaitForEndOfFrame();
currentFrameTime += 1.0f / Rate;
var t = Time.realtimeSinceStartup;
var sleepTime = currentFrameTime - t - 0.01f;
if (sleepTime > 0)
Thread.Sleep((int)(sleepTime * 1000));
while (t < currentFrameTime)
t = Time.realtimeSinceStartup;
}
}
}
最后一点,如果想要协调时间和外部时钟源,我们可能也想让Unity的内部游戏时间以相同的速度进行,而不是遵循CPU时间,那样会随着时间相对于外部时钟源发生变化。我们可以通过设置Time.captureFramerate为外部时间的相同速率来实现。
例如:如果外部的Genlock同步锁相信号运行在60fps,我们可以设置CaptureFramerate为60,这样无论现实中经过了多少时间。都会使Unity允许帧之间渲染间隔为游戏时间每秒的1/60,
在Unity 2019.2 Beta中,我们可以通过设置Time.captureDeltaTime,来设置采集帧率的浮点值。对于旧版Unity,如果外部信号不以整数速率运行,例如:速率为59.94。我们可以变化每帧渲染时的CaptureFramerate值,为时间变化实现理想的平均速率。
我们在GitHub上提供了用于验证上述概念方法的Unity项目,请访问:
https://github.com/Unity-Technologies/GenLockProofOfConcept
我们可以在ForceRenderRate.cs脚本中找到使用WaitForEndFrame协程对帧率进行准确控制的方法,也可以在GenLockedRecorder.cs脚本查看更加复杂的示例,即模仿外部Genlock同步锁相的示例。
虽然Unity不原生支持任何特定厂商的Genlock同步锁相实现,但前面提到的第二个示例是很好的起点,便于集成提供该功能的第三方SDK。
友情提醒:上述所有方法会在部分Unity Standalone Player构建时取得最稳定的帧率,虽然它们在Unity编辑器内的运行模式下有效,但你随时都有可能遇到短暂的帧率波动现象。
参考资料
同步锁相:
https://en.wikipedia.org/wiki/Genlock
V Sync Count垂直同步数文档:
https://docs.unity3d.com/Manual/class-QualitySettings.html#Other
Thread.Sleep:
https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.sleep
小结
Unity如何在本地维持帧率就介绍到这里,更多Unity功能介绍,尽在Unity Connect平台(Connect.unity.com)。
下载Unity Connect APP,请点击此处。 观看部分Unity官方视频,请关注B站帐户:Unity官方。
推荐阅读
Adaptive Performance:高品质画面呈现与流畅稳定帧率的二者兼得
使用Unity 2019.1中的Timeline Signals
Unite 2019 | Unity中的实时光线追踪技术剖析
使用Unity 2019.1中的Timeline Signals
有奖调查
Unity Hub 2.0中文版以及Unity中文版文档已发布,请参与有奖调查,反馈你的意见,以便我们进一步改进。更多详情,请访问此处。
喜欢本文,请点击“在看”