在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官方平台(Unity-GreaterChina),转载此文目的在于传递更多信息,版权归原作者所有。

推荐阅读