シェーダーコンパイルによるスタッターの削減
パイプラインコンパイル (一般にシェーダーコンパイルとも呼ばれる) は、エンジンが GPU を使用してあらゆる種類のコンテンツを描画できるようにするために必要なコストのかかる操作です。
Godot のシェーダーとマテリアルは、GPU で実行される前にいくつかのステップを経ます。
より正確に言えば シェーダーコンパイル では、Godot が生成した GLSL コードをシステム間で共有できる中間形式 (Vulkan を使用する場合の SPIR-V など) に変換します。ただし、この形式は GPU で直接使用することはできません。
パイプラインコンパイル は、GPU ドライバーが中間シェーダーフォーマット (シェーダーコンパイルの結果) を GPU が実際にレンダリングに使用できる形式に変換するステップです。通常ドライバーはゲームが実行されるたびにプロセスが繰り返されないように、パイプラインのキャッシュをシステムのどこかに保存します。このキャッシュはドライバーが更新されると削除されます。
パイプラインにはシェーダーコード以外の情報も含まれているため、各シェーダーには数十以上のパイプラインが存在する可能性があります。そのため、エンジンが事前にパイプラインをコンパイルするのは困難です。非常に遅くなり、大量のメモリを消費するからです。さらに、この手順はユーザーのシステムでのみ実行でき、ハードウェアとドライバーのバージョンがまったく同じでない限り、ユーザー間で結果を共有するのは非常に困難です。
Godot 4.4 より前は、オブジェクトがカメラのビュー内に表示されたときにパイプラインコンパイルを生成する以外にパイプラインコンパイルの解決策はなく、最初のプレイスルー中にのみ発生する悪名高い シェーダースタッター 、またはヒッチにつながっていました。Godot 4.4 ではパイプラインコンパイルによるスタッターを軽減するための新しいメカニズムが導入されました。
UberShader: Godot は特殊化定数を使用します。これはドライバーがライト、シャドウの品質などの一連のパラメータを中心にパイプラインのコードを最適化できるようにする機能です。特殊化定数は、不要な機能を制限してシェーダーを最適化するために使用されます。特殊化定数を変更するには、パイプラインを再コンパイルする必要があります。Ubershaders はレンダリング中にこれらの定数を変更できるシェーダーの特別なバージョンです。つまり Godot は事前に 1 つのパイプラインだけをプリコンパイルし、ゲームプレイ中にバックグラウンドでより最適化されたバージョンをコンパイルできます。これにより作成する必要があるパイプラインの量が大幅に削減されます。
パイプラインのプリコンパイル: UberShader を使用することで、エンジンはメッシュがロードされるときやノードがシーンに追加されるときなど、複数の場所でパイプラインを事前にプリコンパイルできます。パイプラインはリソースのロード プロセスの一部であるため、可能であれば、ロード画面中やゲームプレイ中に複数のバックグラウンドスレッドでプリコンパイルすることもできます。
Godot 4.4 以降、Godot は必要なパイプラインを検出し、ロード時にそれらをプリコンパイルします。この検出システムはほぼ自動ですが、ロード時にすべてのシェーダー、メッシュ、またはレンダリング機能の証拠を RenderingServer が確認することに依存しています。たとえば、ゲームの実行中にメッシュとシェーダーをロードすると、そのメッシュ/シェーダーの組み合わせのパイプラインは、メッシュ/シェーダーがロードされるまでコンパイルされません。同様に MSAA を有効にしたり、ゲームの実行中に VoxelGI ノードをインスタンス化したりすると、パイプラインの再コンパイルがトリガーされます。
パイプライン事前コンパイルのモニター
パイプラインを事前コンパイルすることは、シェーダーのスタッターを軽減するために Godot が使用する主なメカニズムですが、完璧な解決策ではありません。パイプラインのスタッターにつながる可能性のある状況を認識しておくことは非常に役立ち、回避するのは以前のバージョンに比べてかなり簡単です。より多くの検出技術が実装されるにつれて、これらの回避策は、Godot の将来のバージョンでは時間の経過とともに必要性が低くなっていく可能性があります。
Godot デバッガーはゲームによって作成されたパイプラインの量と、そのコンパイルをトリガーしたステップを追跡するためのモニターを提供します。ゲームの実行中にこれらのモニターを監視して、テストするたびにドライバーキャッシュを消去しなくても、シェーダーのスタッターの潜在的な原因を特定できます。読み込み画面以外でこれらの値が突然増加すると、誰かが自分のシステムで初めてゲームをプレイしたときに、ゲームプレイ中にヒッチとして表示されることがあります。 これらのモニターを確認して、プレーヤーのスタッターの考えられる原因を特定することをお勧めします 。テストするときはドライバー キャッシュを削除したり、より弱いシステムで実行しないと、問題を体感できない可能性があります。
デモプロジェクトのパイプラインコンパイル。
注釈
ゲームプレイ中にコンパイルされたパイプラインを確認し、どのステップがスタッターの原因になる可能性があるかを検証できます。削除されたパイプラインはこれらのモニターによって追跡されず、パイプラインはゲームプレイ中に消去され再作成される可能性があるため、これらの値は増加するだけで、減少することはないことに注意してください。
Canvas: 2Dノードを描画するときにコンパイルされます。現在のエンジンには 2D 要素のプリコンパイル機能がないため、2Dノードが初めて描画されるときにスタッターが発生します。
Mesh: 3D メッシュの読み込みと、そのプロパティから事前コンパイルできるパイプライン識別の一環としてコンパイルされます。ゲームプレイ中にメッシュが読み込まれると、スタッターが発生する可能性がありますが、バックグラウンドスレッドを使用してメッシュが読み込まれると、スタッターが軽減されます。マテリアルオーバーライドなどのノードの一部である修飾子は、このステップではコンパイルできません。
Surface: フレームが描画されようとしており、3Dオブジェクトがシーンツリーに初めてインスタンス化されたときにコンパイルされます。これにはシーンツリーに表示されないノードのコンパイルも含まれる場合があります。スタッターはノードがシーンに追加された最初のフレームでのみ発生するため、読み込み画面の直後に発生しても明らかなスタッターにはなりません。
Draw: 3Dオブジェクトを描画する必要があり、UberShader が事前にプリコンパイルされていない場合にオンデマンドでコンパイルされます。エンジンがまだカバーされていないケースをトリガーしたか、エンジンのコードに変更が加えられたため、このパイプラインをプリコンパイルできません。これが発生するとゲームプレイ中にカクカクします。これはGodot バージョン 4.4 より前と同じです。ここでコンパイルが表示された場合は、 Issue で報告してください <https://github.com/godotengine/godot/issues> 。これは UberShader システムでは発生しないはずです。報告の際は最小限の再現プロジェクトを添付してください。
Specialization: ゲームプレイ中にバックグラウンドでコンパイルされ、フレームレートを最適化します。スタッターは発生しませんが、フレームごとに多数発生するとフレームレートが低下する可能性があります。
パイプラインのプリコンパイル機能
Godot にはすべてのゲームで使用されるとは限らないレンダリング機能が多数用意されています。しかし残念ながら、パイプラインのプリコンパイルでは、特定の機能がプロジェクトで使用されているかどうかを事前に知ることはできません。これらの機能の一部は、ユーザーがシーンにノードを追加したとき、またはプロジェクトまたは環境で特定の設定を切り替えたときにのみ検出できます。パイプラインのプリコンパイル システムは、これらの機能が初めて検出されたときに追跡し、その後に作成されるメッシュまたはサーフェスに対してそれらのプリコンパイルを有効にします。
ゲームでこれらの機能を使用する場合は、アセットの大部分をロードする前に、 できるだけ早くそれらを使用するシーンを用意してください 。このシーンは非常にシンプルで、ゲームで使用する予定の機能を使用している限り、目的を果たします。必要に応じて、少なくとも 1 フレームはオフスクリーンでレンダリングすることもできます。たとえば ColorRect ノードで覆ったり、ウィンドウ境界の外側にある SubViewport を使用したりします。
またゲームプレイ中にこれらの機能のいずれかを変更すると、すぐにカクツキが発生することにも注意してください。必要な場合にのみ設定画面からこれらの機能を変更し、変更が適用されたときに読み込み画面とメッセージを挿入するようにしてください。
MSAA Level: プロジェクト設定で 3D MSAA のレベルが変更されると有効になります。残念ながら、異なるビューポートで異なる MSAA レベルが使用されていると、エンジンがプリコンパイルを実行するために一度に 1 つのレベルのみを追跡するため、スタッターが発生します。
Reflection Probes: ReflectionProbe ノードがシーン上に配置されている場合に有効になります。
Separate Specular: サブサーフェススキャタリングなどのエフェクトや、画面から直接スペキュラをサンプリングするコンポジター エフェクトを使用する場合に有効になります。
Motion Vectors: TAA、FSR2 などのエフェクトや、モーションベクターを必要とするコンポジターエフェクト (モーション ブラーなど) を使用する場合に有効になります。
Normal and Roughness: SDFGI、VoxelGI、スクリーンスペース反射、SSAO、SSIL を使用する場合、またはカスタムシェーダーまたは CompositorEffect で
normal_roughness_bufferを使用する場合に有効になります。Lightmaps: LightmapGI ノードがシーンに配置され、ノードがベイクされたライトマップを使用する場合に有効になります。
VoxelGI: VoxelGI ノードがシーン上に配置されている場合に有効になります。
SDFGI: WorldEnvironment が SDFGI をONにすると有効になります。
Multiview: XR プロジェクトで有効になります。
16/32-bit Shadows: プロジェクト設定でシャドウマップの深度精度の構成が変更された場合に有効になります。
Omni Shadow Dual Paraboloid: オムニ ライトがシャドウを落とし、デュアルパラボロイドモードを使用する場合に有効になります。
Omni Shadow Cubemap: オムニ ライトがシャドウを落とし、キューブマップモード (デフォルト) を使用する場合に有効になります。
ゲームプレイ中にカクツキが発生し、Surface ステップ中にモニターがコンパイルの突然の増加を報告する場合、機能が事前に有効になっていなかった可能性が非常に高くなります。ゲームの読み込み中にこの効果が有効になっていることを確認すると、問題が軽減される可能性があります。
インスタンシングのパイプラインプリコンパイル
ゲームでスタッターが発生する一般的な原因の 1 つは、ゲームプレイ中にのみ発生するインタラクションによって、一部のエフェクトがシーンにインスタンス化されるときです。たとえばプレイヤーがアクションを実行したときにスクリプトを通じてシーンにのみ追加されるパーティクル エフェクトがあるとします。シーンがプリロードされている場合でも、エフェクトがシーンに少なくとも 1 回追加されるまで、エンジンはパイプラインをプリコンパイルできない可能性があります。
幸いなことに Godot 4.4 以降では、シーンが完全に見えなかったり、カメラの視野外にあったりしても、シーン上で少なくとも 1 回はインスタンス化されていれば、これらのパイプラインを事前コンパイルできます。
デモ プロジェクトの 1 つで、プレイヤーにアタッチされた非表示の弾丸ノード。これによりエンジンはエフェクトのパイプラインを事前にプリコンパイルできるようになります。
ゲームプレイ中にシーンに動的に追加されるエフェクトがあり、これらのエフェクトが表示されるときにコンパイル モニターで突然の増加が見られる場合、回避策としてはエフェクトの非表示バージョンを、確実に表示される場所に添付します。
たとえばプレイヤーキャラクターが何らかの爆発を引き起こすことができる場合、その効果をプレイヤーの子として非表示のノードとしてアタッチできます。非表示のノードにアタッチされたスクリプトを無効にするか、問題を引き起こす可能性のある他のノードを非表示にしてください。これはノードで 編集可能な子 を有効にすることで実行できます。