Unity を検索

取り上げているトピック
シェア

Is this article helpful for you?

Thank you for your feedback!

皆様は Unity に正確なフレームレートを維持させたり、さらには(一般的にGenLockとして知られる)外部のクロックソースに従わせたりできないかと思われたことはありませんか?本記事では、Unity がネイティブレベルでフレームレートをどのように維持しているかをご説明するとともに、それを厳密に制御するためにユーザーコードを追加する方法をご紹介します。これは、Unity と他の機器との同期が不可欠となる放送スタジオなどでは、特に重要な知識です。

通常、デフォルトの状態の Unity プロジェクトは、プロジェクトをできるだけ速く実行しようとします。フレームは、基本的にディスプレイデバイスのリフレッシュレートの制約を受けながらも、可能な限り速くレンダーされます(V Sync Count プロパティの説明 をご参照ください)。最も簡単にフレームレートの制御を開始する方法は、QualitySettings.vSyncCount を明示的に設定し、ディスプレイデバイスのリフレッシュレートに適した間隔でレンダリングが行われるようにすることです(例えば、60Hz のディスプレイの場合、vSyncCount=2 と設定すると Unity が 30 FPS でレンダーするようになり、ディスプレイと同期される形になります)。しかし、この方法だとディスプレイのリフレッシュレートの約数に制約されるため、十分に精度の高い制御にはならないかも知れません。

上記の次に簡単なソリューションは、QualitySettings.vSyncCount=0 を設定し、Application.targetFrameRate を使用してディスプレイのリフレッシュレートから独立したフレームレートをターゲットとして設定する方法です。この設定を行うと、レンダリングループがこのレートの近似値に抑制されます(ただし、Unity のレンダリングがディスプレイと同期された形で行われなくなるのでティアリングが発生する可能性があることにご注意ください)。これは、CPU 資源の必要以上の消費を避けるために低コストな方法で行われます。このアプローチのマイナス面は、すべてのユースケースで必要な精度が確保されるとは限らないことです。

しかし、コルーチンによって精度を向上させることが可能ですので、ご心配には及びません。Unity 組み込みのフレームレートスロットリングに頼るのではなく、それをスクリプトコード経由でご自分で制御していただくことができます。そのためには、Unity が出来るだけ高速で実行しようとするように設定する必要があります。QualitySettings.vSyncCount=0 を設定し、Application.targetFrameRate を非常に高い値に設定した上で、WaitForEndOfFrame コルーチンを使用して指定の時点まで次のフレームがレンダーされないようにすることで、目標レートぴったりまで落とすことができます。これを正確に行うためには、Thread.Sleep の組み合わせを使用して CPU 資源を無駄に消費することなく低コストで Unity のレンダリングループを遅延させた上で、最後の数ミリ秒で、次のフレームを開始させたい正確なタイミングを確認しながら 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 の信号が 60 FPS で実行されている場合、captureFramerate を 60 に設定すれば、(実際に経過した時間の長さに関わらず)ひとつのフレームのレンダリングと次のフレームのレンダリングの間にゲーム時間のぴったり 60 分の 1 秒を経過させるように Unity に命令が送られます。Unity 2019.2 ベータ版では、Time.captureDeltaTime を使用することで、キャプチャーフレームレートに浮動小数点値を設定できるようになっています。これより古いバージョンの Unity では、外部の信号が整数ベースのレートで実行されていない場合(59.94 FPS になっているなど)は、各フレームのレンダリングごとに captureFramerate を変えることで、時間の進行に関して目標の平均レートを実現することができます。

上記のトピックを実際に試した概念実証のための Unity のサンプルプロジェクトが GitHub プロジェクトページからご入手いただけます。具体的には ForceRenderRate.cs に、WaitForEndFrame コルーチンを使用したフレームレートの正確な制御の例が提供されています。外部の GenLock のエミュレートよりも複雑な例は GenLockedRecorder.cs でご確認いただけます。Unity はネイティブではベンダー別の GenLock 実装には対応していませんが、後者の例はこの機能を提供するサードパーティー SDK の統合を開始するための足掛かりとしてお役立ていただけます。

(注)この記事で紹介したすべてのテクニックは、Unity のスタンドアロンプレイヤービルドの一部として使用された場合に最も安定したフレームレートを実現します。これらは Unity エディターの再生モードでも機能しますが、ときどき一時的な変動が起こる可能性もあります。

2019年6月3日 カテゴリ: Engine & platform | 4 分 で読めます

Is this article helpful for you?

Thank you for your feedback!

取り上げているトピック
関連する投稿