circle-loader
by
237/ 0

前書き

ますます多くのモバイルゲームが「3A」に向うにつれて、スマホの各性能も「3A」の後ろ盾になるために改善されています。チップ上のすべてのトランジスターの性能を掘り下げるほどでした。しかし、「高得点低性能」のスマホを目の前にすると、「泣きたいけど涙が出ない」という無力感が常にあるのではないか――フレームレートを高く保ち、画面の質を確保する必要もあります。両立したい場合、解像度をを下げるのが最も速い方法だと思います。

デバイスの解像度を調整するというと、Screen.SetResolution メソッドによく知っている人が多いでしょう、が、この調整はグローバルでハードウェア レベルの調整であり、3D と UI のレンダリング ターゲットを個別に調整することはできません。もちろん、SRPパイプラインの出現により、すでに3DカメラとUIカメラの解像度の個別調整が実現できており、UWAでも関連記事が紹介されています(参考文献9参照)。

本日の記事では、Unity と Unreal 両方がともに提供する動的解像度ソリューションについて説明します。これにより、単一のレンダリング ターゲットを動的にスケーリングして、GPU の負荷を軽減できます。

3D と UI の別々のレンダリングに関しては、次のような解決策を考えた人がいるでしょう。RTに3D レンダリングし、そして最終的に 3D のRT を最終的な RT にBlit します。このソリューションは、Unity が提案する動的解像度ソリューションとは異なるのでしょうか? それとも、新しいボトルに入った古いワインですか?

次に、(Unity を主に) 動的解像度の原理とその適用シナリオについて説明します。

従来の 3D と UI の分離方式

上の図のように、シーンをレンダリングするときにビューポート サイズ (Viewport) を調整し、レンダリングを画面外のレンダー ターゲットの一部に制限してから、シーンのレンダー ターゲットのコンテンツを最終RTまでBlit するのが基本的な原理です。たとえば、レンダー ターゲットのサイズは (1920, 1080) ですが、ビューポートの原点は (0, 0) でサイズは (1280, 720) の場合があります。

この実装には、次の問題がある可能性があります。

1.Blit のパフォーマンスの損失, この操作はリアルタイムであってはなりません. 一般に, ゲームが初期化された後または特定のシーンに入る前に一度設定されます。これは低頻度の操作であり、リアルな “リアルタイム” 調整をすることはできません。

2. レンダリング パイプラインによって制限される場合がある
デフォルトのレンダリング パイプラインである場合、最終的なブリット操作のタイミングを選択する必要があります。これは、通常、ゲームには後処理ステージがあり、このステージをうまく利用してブリットを実行する必要があるためです。CommandBuffer を使用して、ビューポートの変更と後処理操作をカメラのさまざまなレンダリング ステージに挿入できます。
SRPのレンダリングパイプライン(Unity 2018以降のバージョン)であれば、Blitを処理する機会を独自に持つことができますが、もちろん、この操作は高頻度の操作ではありません。

マニュアル

Unity の公式ドキュメントを参照して、まず動的解像度を使用するプロセスを見てみましょう。

まず、1 つのことを確認する必要があります。動的解像度を有効にするための前提条件は、GPU バウンドです。 そのため、GPU の各フレームの実行時間をリアルタイムで取得することによって決定されます。

  • ゲームでフレームがドロップするのは、過度の GPU 負荷が原因ですか?
  • レンダー ターゲットのスケーリング係数

レンダリング ターゲットは、スケーリング係数に従って動的にスケーリングされます。 このプロセスでは、レンダリング ターゲットの解像度が変更されたときに GPU メモリが再割り当てされないようにする必要があります。そうしないと、Screen.SetResolution と同じになります (画面がちらつきます)。

図に示すように、動的にズームする必要があるカメラを確認します。

PlayerSettings で「Enable Frame Timing Stats」をチェックします。

FrameTimingManager.CaptureFrameTimings() と FrameTimingManager.GetLatestTimings の 2 つのインターフェイスを介して CPUTime と GPUTime を取得し、倍率を自分で判断します。

最後に ScalableBufferManager.ResizeBuffers(m_widthScale, m_heightScale) を呼び出してスケーリングを設定します

プラットフォームのサポート

Unity の公式ドキュメントには次のように書かれています。

理解度に関係しているのかもしれません。上記の説明を見て混乱しています。OpenGLESは動的解像度、組み込みのレンダリングパイプラインとURPなど互換性をサポートしておらず、それでは、URPの下のOpenGLESプラットフォームである場合はどうなりますか? サポートするか、サポートしないか。

いずれにせよ、まずは疑念を脇に置いて、動的解像度の実現原理を探ってみましょう。

原理探索

公式ドキュメントの利用プロセスをたどり、Unity のソースコードの内部に触れて、なぜ OpenGLES が支持されているのかを見てみましょう。 ソースコードの部分が絡むので、結論はここで言います。

ズーム RT はプラットフォームに依存します。OpenGLES はズーム RT を作成できません。その理由については後で説明します。
動的解決の原理はVulkanのメモリエイリアシング(Memory Aliasing)機能
メモリーエイリアシング
Vulkanの説明については、1を参考してください。

DirectX 12 や Vulkan などの最新のグラフィックス API を使用すると、ユーザーはメモリの場所を定義し、割り当てられた GPU リソースを手動で作成されたヒープに配置できます。 これにより、メモリ部分が完全にオーバーラップするテクスチャとバッファを作成できます。 これが、OpenGLES が動的解決をサポートしない理由です。OpenGLES は、より効率的なメモリ管理を実現できるように下位レベルの API を開かないためです。

ゲームの典型的なフレームを例にとると、いくつかのジオメトリをラスタライズし、シェーディングを実行し、一連の後処理を実行します。 ここでの各ステージの出力はテクスチャまたはバッファに書き込まれ、フレームで他のステージで使用されます。ただし、特定のステージで生成されたリソースは、後処理など、他のいくつかのステージでのみ使用される場合があります: ブルームによって生成された出力は、次のステージのトーン マッピングでのみ使用され、フレーム内の他の場所では使用されません。が必要です。 リソースの有効な有効期間は短い可能性がありますが、事前に割り当てられ、フレーム全体でメモリを占有する可能性が高いことがわかります。

Unity の RenderTexture.GetTemporary は、RenderTexture オブジェクト プールを内部的に維持します。 ただし, この方法は後処理段階にのみ適用されます. 異なるフォーマットとサイズのリソースは再利用できないためです. 後処理は通常フルスクリーンパスです. 読み書きされるテクスチャは通常同じ属性を持ちます.処理のみ 2 つの RT を繰り返し交互に使用することで実現できます (これについては、後の URP の章で説明します)。

オブジェクト プールは本質的に高レベルのメモリ エイリアスであり、開発者はメモリ管理に注意を払う必要はありませんが、最新のグラフィックス API (DX12 および Vulkan) は、基礎となるメモリ エイリアスを実装できるメモリ管理インターフェイスを提供します。 メモリ エイリアシングは、異なる変数が同じアドレスを指すことを意味します。つまり、複数のリソースが同時に同じメモリ領域に格納されます。時間的に重複しない大きなリソースが多数ある場合、これらのリソースは同じアドレスに割り当てられます。メモリー。 オブジェクト プールと比較して、メモリ エイリアスはメモリ使用量をさらに削減できます。これは、最下層がバイトの集まりであるため、リソースのタイプ、形式、サイズなどを考慮する必要がないためです。 具体的な図を次の図に示します。


まとめ
上記の分析から、Unity の動的解決の実現の原理、つまり、Vulkan が提供するメモリ管理インターフェイスを使用して、最下層でメモリの効率的な再利用を実現することが大まかに理解できました。 このようにして、基本的にパフォーマンスの低下なしに、ゲーム内で効率的かつリアルタイムに解像度を調整できます。

URP の実装

URP の前身である LWRP がまだプロジェクト チームによって使用されていることを考慮して、最初に LWRP について簡単に見てみましょう。

LWRP
簡単に言えば、カメラのレンダー ターゲットを再作成することで実現されます。 セットアップ中に、最初に RequiresIntermediateColorTexture 関数を入力して、新しい RT を作成するかどうかを決定します. その中に変数 isScaledRender があります。スケーリングが必要な場合は、パスを入力して RT を作成します:

m_CreateLightweightRenderTexturesPass

public void Setup(ScriptableRenderer renderer, ref RenderingData renderingData)
{
    ...

    bool requiresRenderToTexture = ScriptableRenderer.RequiresIntermediateColorTexture(ref renderingData.cameraData, baseDescriptor);

    RenderTargetHandle colorHandle = RenderTargetHandle.CameraTarget;
    RenderTargetHandle depthHandle = RenderTargetHandle.CameraTarget;

    if (requiresRenderToTexture)
    {
          colorHandle = ColorAttachment;
          depthHandle = DepthAttachment;

          var sampleCount = (SampleCount)renderingData.cameraData.msaaSamples;
          m_CreateLightweightRenderTexturesPass.Setup(baseDescriptor, colorHandle, depthHandle, sampleCount);
          renderer.EnqueuePass(m_CreateLightweightRenderTexturesPass);
    }

    ...
}

public static bool RequiresIntermediateColorTexture(ref CameraData cameraData, RenderTextureDescriptor baseDescriptor)
{
     if (cameraData.isOffscreenRender)
          return false;

     bool isScaledRender = !Mathf.Approximately(cameraData.renderScale, 1.0f);
     bool isTargetTexture2DArray = baseDescriptor.dimension == TextureDimension.Tex2DArray;
     bool noAutoResolveMsaa = cameraData.msaaSamples > 1 && !SystemInfo.supportsMultisampleAutoResolve;
     return noAutoResolveMsaa || cameraData.isSceneViewCamera || isScaledRender || cameraData.isHdrEnabled ||
           cameraData.postProcessEnabled || cameraData.requiresOpaqueTexture || isTargetTexture2DArray || !cameraData.isDefaultViewport;
}

URP

Unity 2019.3.0a バージョンから、LWRP は正式に URP にアップグレードされ始めました。 URP は主に 2 つのフォルダーに分けられます。1 つは個別に抽出されて HDRP と共有される基本的なコア ライブラリ コアであり、もう 1 つは URP 自体に共通です。


URP の各バージョンのコードを調べた結果、Unity がレンダー ターゲットの管理機能に注目 (提供) し始めたのは、Core RP ライブラリ バージョン 10.2 (Unity バージョン 2020.2.0b に対応) になってからでした。

前の章の「原則の調査」から、レンダリング ターゲットの管理がレンダリング パイプラインの重要な部分であること、また、新しいレンダリング テクスチャがまったく同じプロパティと解像度を使用する場合にのみ、RenderTexture がメモリを再利用できることもわかっています。

これらレンダリング テクスチャ メモリ割り当ての問題を解決するために、Unity の SRP (URP&HDRP) では RTHandle システムが導入されています。 このシステムは、RenderTexture の上位にある抽象化レイヤーで、レンダリング テクスチャをより適切に管理できます. 詳細な紹介については、リファレンス 8 を参照してください. ここでは簡単に紹介します.


上のスクリーンショットの列挙に示されているように、SRP は「ハードウェア」と「ソフトウェア」の 2 つの動的解像度を実現します。現在のビューポートのソフト実装です。 ハードウェアの動的解像度が現在のプラットフォームをサポートしていない場合、RTHandle システムは自動的にソフトウェアの動的解像度に切り替えます。 それだけでなく、最新の URP バージョンは RTHandle に基づくダブル バッファリングも実装しています. 興味がある場合は、URP ソース コードに移動して RenderTargetBufferSystem を表示できます。

応用

その過程で、「動的解像度」についての理解も深まりました.「動的解像度」について話すとき、私たちが話しているのは、ハードウェアレベルで実現される実際の動的解像度です。最新のグラフィックス API のメモリ エイリアシングを使用すると、FPS を特定のレベルに維持するために、GPU によるフレーム ドロップが発生したときに、GPU メモリを再割り当てすることなくレンダリング ターゲットの解像度を動的に調整できます (グラフィックス API のメモリ エイリアシングを使用)。

ただし、デバイスの互換性を考慮すると、ほとんどのゲームでサポートされているプラ​​ットフォームは Vulkan ではなく OpenGLES しかないため、残念ながら動的解像度は役に立ちません。 最後の手段として、さまざまなレンダリング パイプラインについて、採用できるソリューションの簡単な説明を次に示します。

  • デフォルトのレンダリング パイプライン – Unity 2017 以前のバージョン
    この記事の「従来の 3D と UI の分離スキーム」で紹介したスキームを使用し、CommandBuffer を使用してビューポートを適切なタイミングで動的に調整できますが、頻繁に使用することはできません。
    LWRP——Unity 2018~Unity 2019.3.0a
    URP——Unity 2019.3.0a12+~Unity 2020.2.0b8+
    URP の前身である LWRP にはまだ完成されていない機能がたくさんありますが、3D と UI の個別のレンダリングを既に実現しており、デフォルトのレンダリング パイプラインよりも柔軟です。 しかし、それはまだより良い RT 管理を提供するものではありません.効率的な RT 管理システムをカスタマイズするには、URP を参照する必要があります.
    URP – Unity 2020.2.0b12+

上記のように、Unity は SRP の Core RP ライブラリのバージョン 10.2 まで、比較的完全な RT 管理システムを提供していませんでした。必要に応じて参照できます。