GPUの最適化

はじめに

グラフィックス技術の進化に対する需要により、グラフィックスのボトルネックに遭遇することはほぼ確実です。これらの一部は CPU 側で発生する可能性があり、たとえば、Godot エンジン内でのオブジェクトのレンダリング準備のための計算などでも発生します。CPU上のボトルネックは GPU に渡す命令を分類するグラフィックスドライバーや、これらの命令の転送でも発生する可能性があります。そして最後に、GPU 自体でもボトルネックは発生します。

レンダリングでボトルネックが発生する場所は、ハードウェアによって大きく異なります。特にモバイル GPU では、デスクトップでは簡単に実行できるシーンでも問題が発生する可能性があります。

GPU のボトルネックを理解して調査することは、CPU の状況とは少し異なります。これは多くの場合、GPU に与える命令を変更することによってのみ間接的にパフォーマンスを改善できるからです。また、測定を行うことがより困難な場合があります。多くの場合、パフォーマンスを測定する唯一の方法は、各フレームのレンダリングに費やされる時間の変化を調べることです。

ドローコール、ステート変更、API

注釈

次のセクションはエンドユーザーには関係ありませんが、後のセクションに関連する背景情報を理解するのに役立ちます。

Godot はグラフィックス API (Vulkan、OpenGL、OpenGL ES、WebGL など) を介して GPU に命令を送信します。関連する通信とドライバーのアクティビティは、特に OpenGL、OpenGL ES、WebGL では、かなりコストがかかる可能性があります。ドライバーと GPU の最適な方法でこれらの命令を実行できれば、パフォーマンスは大幅に向上できます。

OpenGL のほぼすべての API コマンドでは、GPU が正しい状態で実行されることを確認するために、ある程度の検証が必要です。一見単純なコマンドでも、舞台裏で大量の加工処理が発生する可能性があります。したがって、目標はこれらの命令を最小限に減らし、類似のオブジェクトをできるだけグループ化して、一緒にレンダリングできるようにするか、これらのコストのかかるステート変更を最小限に抑えることです。

2Dバッチ処理

In 2D, the costs of treating each item individually can be prohibitively high - there can easily be thousands of them on the screen. This is why 2D batching is used. Multiple similar items are grouped together and rendered in a batch, via a single draw call, rather than making a separate draw call for each item. In addition, this means state changes, material and texture changes can be kept to a minimum.

3Dバッチ処理

3D でもドローコールとステート変更を最小限に抑えることを目指しています。ただし、複数のオブジェクトを 1 回の描画呼び出しにまとめるのは難しくなります。3D メッシュは数百または数千の三角形で構成される傾向があり、大規模なメッシュをリアルタイムで結合するのは非常に高コストです。メッシュあたりの三角形の数が増えると、結合にかかるコストがすぐにメリットを上回ります。より優れた代替手段は、事前にメッシュを結合 (互いに関連した静的メッシュ) することです。これはアーティストが行うことも、アドオンを使用して Godot 内でプログラム的に行うこともできます。

There is also a cost to batching together objects in 3D. Several objects rendered as one cannot be individually culled. An entire city that is off-screen will still be rendered if it is joined to a single blade of grass that is on screen. Thus, you should always take objects' locations and culling into account when attempting to batch 3D objects together. Despite this, the benefits of joining static objects often outweigh other considerations, especially for large numbers of distant or low-poly objects.

3D 固有の最適化の詳細については、 3Dパフォーマンスの最適化 を参照してください。

シェーダーとマテリアルの再利用

Godot レンダラーは、既存のレンダラーとは少し異なります。GPU の状態変化を可能な限り最小限に抑えるように設計されています。StandardMaterial3D は、同様のシェーダーを必要とするマテリアルの再利用に優れています。カスタム シェーダーを使用する場合は、可能な限り再利用するようにしてください。Godot の優先順位は次のとおりです。

  • マテリアルの再利用: シーン内の異なるマテリアルが少ないほど、レンダリングは高速になります。シーンに大量のオブジェクト (数百または数千) がある場合は、マテリアルを再利用するか、アトラスを使用してテクスチャの変更量を減らしましょう。

  • シェーダーの再利用: マテリアルを再利用できない場合は、少なくともシェーダーを再利用してみてください。注: StandardMaterial3D は、パラメータが異なっていても、同じ構成 (チェックボックスで有効または無効にされる機能) のシェーダーを自動的に再利用します。

たとえば、シーンに 20,000 個のオブジェクトがあり、それぞれに 20,000 個の異なるマテリアルがある場合、レンダリングは遅くなります。同じシーンに 20,000 個のオブジェクトがあり、使用するマテリアルが 100 個だけの場合、レンダリングは非常に速くなります。

ピクセル計算コスト vs 頂点計算コスト

モデル内のポリゴン数が少ないほどレンダリングが速くなると聞いたことがあるかもしれません。これは実際には相対的なものであり、多くの要因に影響されます。

現代の PC やコンソールでは、頂点の計算コストは低くなっています。GPU はもともと三角形のみをレンダリングしていました。つまり、すべてのフレームで次のようになります:

  1. すべての頂点は CPU によってトランスフォーム処理される必要がありました (クリッピングを含む)。

  2. すべての頂点をメインメモリから GPU メモリに送信する必要がありました。

現在、これらすべてが GPU 内で処理されるため、パフォーマンスが大幅に向上しています。3D アーティストは、ポリゴン数のパフォーマンスについて誤った認識を持っていることがよくあります。これは 3D DCCソフト (Blender、3ds Max など) では、ジオメトリを編集するために CPU メモリ内に保持する必要があり、実際のパフォーマンスが低下するためです。ゲーム エンジンは GPU に大きく依存するため、多くの三角形をより効率的にレンダリングできます。

モバイル デバイスでは、話は異なります。PC やコンソールの GPU は、電力網から必要なだけの電力を引き出すことができる強力なモンスターです。モバイル GPU は小さなバッテリーに制限されているため、電力効率を高める必要があります。

モバイル GPU は効率を上げるために、オーバードロー を回避しようとします。オーバードローは画面上の同じピクセルが何回もレンダリングされるときに発生します。建物がいくつかある町を想像してください。GPU は描画するまで何が見えていて何が隠れているかを知りません。たとえば家が描画された後、その前に別の家が描画されることがあります (つまり、同じピクセルに対して 2 回のレンダリングが行われたことになります)。PC の GPU は通常、この点をあまり気にせず、ハードウェアにピクセル プロセッサを追加してパフォーマンスを向上させます (これにより、消費電力も増加します)。

モバイル デバイスでは電力を多く使うことはできないため、画面をグリッドに分割する タイル ベースレンダリング と呼ばれる手法が採用されています。各セルには描画された三角形のリストが保持され、深度によって並べ替えられるため、オーバードロー を最小限に抑えることができます。この手法によりパフォーマンスが向上し、電力消費が削減されますが、頂点のパフォーマンスが低下します。その結果、描画処理できる頂点と三角形の数は少なくなります。

さらにタイルベースのレンダリングは、画面の小さな領域に多くのジオメトリを持つ小さなオブジェクトがある場合に問題が生じます。これにより、モバイル GPU は 1 つの画面タイルに多大な負荷をかけることになり、他のすべてのセルはフレームを表示する前にタイルの完了を待たなければならないため、パフォーマンスが大幅に低下します。

まとめると、モバイルでは頂点の数を気にする必要はありませんが、画面の小さな部分に頂点が集中しないようにする 必要があります。キャラクター、NPC、乗り物などが遠くにある場合 (つまり小さく見える場合) は、代わりに小さい詳細レベル (LOD) モデルを使用します。また、デスクトップ GPU でも画面上のピクセル サイズよりも小さい三角形は避けることをお勧めします。

以下のケースで必要な追加の頂点処理にも注意してください:

  • スキニング (骨格アニメーション)

  • モーフ (シェイプキー)

  • 頂点ライティング (モバイルでは一般的)

ピクセル/フラグメントシェーダーとフィルレート

頂点処理とは対照的に、フラグメント (ピクセル単位) のシェーディングのコストは、長年にわたって劇的に増加しています。画面解像度は増加しており、古い 640×480 VGA 画面の面積は 307,200 ピクセルですが、4K 画面の面積は 8,294,400 ピクセルとなり、面積は 27 倍です。また、フラグメント シェーダーの複雑さも爆発的に増加しています。物理ベースのレンダリングでは、フラグメントごとに複雑な計算が必要になります。

プロジェクトがフィルレート制限を受けているかどうかは、非常に簡単なテストで確認できます。V-Sync をオフにして、1 秒あたりのフレーム数が制限されないようにし、大きなウィンドウで実行した場合と非常に小さなウィンドウで実行した場合の 1 秒あたりのフレーム数を比較します。シャドウを使用する場合は、同様にシャドウマップのサイズを小さくするとメリットが得られることもあります。通常、小さなウィンドウを使用すると FPS がかなり増加します。これはフィルレートがある程度制限されていることを示しています。一方、FPS がほとんどまたはまったく増加しない場合は、ボトルネックは別の場所にあります。

フィルレートが制限されているプロジェクトでは、GPU が行う作業量を減らすことでパフォーマンスを向上させることができます。これを行うには、シェーダーを簡素化するか (StandardMaterial3D を使用している場合は、コストのかかるオプションをオフにします)、使用するテクスチャの数とサイズを減らします。またシェーディングされていないパーティクルを使用する場合は、マテリアルで頂点シェーディングを強制してシェーディングコストを削減することを検討してください。

参考

Variable rate shading がサポートされているハードウェアでは、最終画像のエッジの鮮明さに影響を与えずにシェーディング処理コストを削減できます。

モバイルデバイスをターゲットにする場合は、合理的に使用できる最もシンプルなシェーダーの使用を検討してください。

テクスチャの読み取り

フラグメントシェーダーのもう 1 つの要素は、テクスチャの読み取りコストです。特に1つのフラグメントシェーダーで複数のテクスチャを読み取る場合は、コストのかかる操作です。またフィルタリングによってさらに速度が低下する可能性があることも考慮してください (ミップマップを使ったトリリニアフィルタリングと平均化)。テクスチャの読み取りは電力消費の面でもコストが高く、モバイルでは大きな問題となります。

サードパーティのシェーダーを使用する場合、または独自のシェーダーを作成する場合は、テクスチャの読み取りをできるだけ少なくするアルゴリズムを使用するようにしてください。

テクスチャ圧縮

Godotは、3Dモデルをインポートする際にテクスチャを Video RAM (VRAM) 圧縮することができます。Video RAM 圧縮は保存時のサイズの点で PNG や JPG ほど効率的ではありませんが、大きなテクスチャを描画する場合のパフォーマンスが大幅に向上します。

これはテクスチャ圧縮の主な目的がメモリと GPU 間の帯域幅の削減であるためです。

3Dでは、オブジェクトの形状は、テクスチャよりもジオメトリによるところが大きいので、普通は圧縮しても見分けはつきません。2Dの場合、圧縮はテクスチャ内に描かれた形状も変化させてしまうため、違いがより目立つようになります。

注意すべき点として、Androidデバイスでは透過のあるテクスチャの圧縮はサポートされていません(非透過のみ)ので、気をつけてください。

注釈

3Dの場合でも、「ピクセルアート」テクスチャではVRAM圧縮を無効にする必要があります。解像度が低いとパフォーマンスが大幅に向上するわけでもなく、描画の見た目に悪影響を与えるためです。

ポストプロセッシングとシャドウ

ポストプロセスエフェクトとシャドウも、フラグメントシェーディングのアクティビティの観点からはコストがかかる可能性があります。常にさまざまなハードウェアでこれらの影響をテストしてください。

シャドウマップの書き込みと読み取りの両方において、 シャドウマップのサイズを小さくするとパフォーマンスが向上します 。さらにシャドウのパフォーマンスを向上させる最善の方法は、できるだけ多くのライトとオブジェクトのシャドウをオフにすることです。小さいまたは遠いオムニライト/スポットライトでは、多くの場合、シャドウを無効にしても視覚的な影響はわずかです。

半透明とブレンド

半透明なオブジェクトは、レンダリング効率に関して特に問題があります。不透明なオブジェクト (特に 3D) は、基本的に任意の順序でレンダリングでき、Z バッファによって最前面のオブジェクトのみがシェーディングされます。半透明なオブジェクトまたはブレンドされるオブジェクトは異なります。ほとんどの場合、Z バッファに依存できず、正しく見えるようにするには「描画の順序」を考慮し、後ろから前へレンダリングする必要があります。

半透明なオブジェクトは、他の半透明なオブジェクトがその上に描画される場合でも、すべてのアイテムを描画する必要があるため、フィルレートに特に悪影響を及ぼします。

不透明なオブジェクトは、これを行う必要はありません。最初に Z バッファにのみ書き込み、次に特定のピクセルの最前面にあるオブジェクトのフラグメントに対してのみフラグメントシェーダを実行することができます。

複数の半透明オブジェクトが重なり合う場合に特にコストがかかります。通常これらのフィルレート要件を最小限に抑えるには、半透明領域をできるだけ小さくする方がよいでしょう。特にフィルレートが非常にコストがかかるモバイルではそうです。実際多くの状況では、より複雑な不透明なジオメトリをレンダリングする方が、半透明を使用して「ごまかす」よりも速くなることがあります。

マルチプラットフォームのアドバイス

複数のプラットフォームでリリースすることを目指している場合は、すべてのプラットフォーム、特にモバイルで 早期に 、そして 頻繁に テストしてください。デスクトップでゲームを開発し、最後の瞬間にモバイルに移植しようとすると、大惨事を招くことになります。

一般的にゲームは最低限の共通基準で設計し、その後より強力なプラットフォーム向けにオプションの機能強化を追加する必要があります。たとえばデスクトップ プラットフォームとモバイル プラットフォームの両方をターゲットにする場合は、両方に互換性レンダラーを使用するとよいでしょう。

モバイル / タイルベースレンダラー

上で説明したように、モバイルデバイスの GPU はデスクトップの GPU とは大きく異なる方法で動作します。ほとんどのモバイルデバイスはタイルベースレンダラーを使用します。タイルベースレンダラーは画面を超高速キャッシュ メモリに収まる通常サイズのタイルに分割し、メインメモリへの読み取り/書き込み操作の回数を減らします。

ただし欠点もあります。タイルベースレンダリングでは、特定のテクニックの実行がはるかに複雑になり、コストも高くなります。異なるタイルでのレンダリング結果や、以前の操作の結果が保持されることに依存するタイルは、非常に遅くなる可能性があります。シェーダー、ビューポートテクスチャ、および後処理のパフォーマンスをテストするときは、細心の注意を払ってください。