Version: 2021.1
言語: 日本語
OpenGL コア
ドローコールバッチング

グラフィックスパフォーマンスの最適化

パフォーマンスが良いことは、多くのゲームにとって重要なことです。ここでは、ゲームのレンダリングのスピードを最高にするための簡単なガイドラインを紹介します。

グラフィックスのコストが高い部分を見つける

ゲームのグラフィカルな部分は、コンピューターの 2 つのシステム、GPU と CPU に主に影響を与えます。どんな最適化であっても最も重要なことは パフォーマンスの問題がどこにあるか を見つけることです。なぜなら、GPU と CPU では最適化の内容がかなり異なるからです (相反する内容になる場合もあります。例えば、良くあることとして CPU の最適化による GPU の処理の増加、またはその逆など)。

よくあるボトルネックとそれをチェックする方法は以下のとおりです。

  • GPU は、しばしば フィルレート またはメモリ帯域幅で制限されています。
    • ディスプレイ解像度を低くしてゲーム実行します。ディスプレイ解像度を低くすることによってゲームの実行が速くなる場合は、GPU のフィルレートにより制限されている可能性があります。
  • CPU は多くの場合、レンダリングする必要があるバッチ数によって制限されます。

あまり一般的ではないボトルネックは以下の通りです。

  • GPU が処理する頂点数が多すぎる場合。どれだけの頂点数であれば “問題ない” かは GPU および頂点シェーダーの複雑さに依存します。一般的に、典型的な指標はモバイルで 10万以下、PC で数百万以下です。ただし、最適化を行ってこの数字をできるだけ低く抑えることが推奨されます。
  • CPU が処理する頂点数が多すぎる場合。スキンメッシュ、クロスシミュレーション、パーティクル、その他のゲームオブジェクトやメッシュが考えられます。上記に関しては、一般的に、ゲームのクオリティを落とさないでこの数をできるだけ低く抑えることが推奨されます。これを行う方法は、後述の CPU の最適化 を参照してください。
  • GPU、CPU でレンダリングが問題でない場合は、その他の問題 (例えば、スクリプトや物理演算) の問題が考えられます。Unity プロファイラー を使って調べてください。

CPU 最適化

画面のオブジェクトをレンダリングするために CPU は多くの処理を行っています。どのライトがオブジェクトに影響するか判断する、シェーダーとシェーダーパラメーターの設定、グラフィックスドライバーに描画コマンドを送信する、その後、グラフィックドライバーはグラフィックスカードへ送信するコマンドを準備する、などです。

これらすべての “オブジェクトごとの” CPU 使用はリソースがかかるため、可視のオブジェクトが多いと CPU の処理が堆積する可能性があります。例えば 1000 個の三角形がある場合、CPU にとっては、それぞれの三角形で 1 つのメッシュ (加算すると 1000 メッシュになります) を処理するよりも、すべての三角形が 1 つのメッシュにまとまっているほうがよほど簡単です。この 2 つのシナリオの GPU の負担はほとんど変わりませんが、CPU が (1 つのオブジェクトではなく) 1000 のオブジェクトをレンダリングする負荷は、著しく高くなります。

可視のオブジェクトの数を減らします。CPU の処理量を削減する方法は以下のとおりです。

  • 近いオブジェクトを手動または Unity の ドローコールバッチング を使用してまとめます。
  • 別々のテクスチャをより大きなテクスチャアトラスにまとめるなどして、オブジェクト内のマテリアル数を減らします。
  • オブジェクトが複数回レンダリングされる原因となるリフレクション、シャドウ、ピクセルライトなど、を使用しない。

オブジェクトをまとめて、各メッシュに少なくとも数百の三角形が含まれるようにして、メッシュ全体で マテリアル を 1 つだけ使用するようにします。重要なことですが、マテリアルが共通でない 2 つのオブジェクトをまとめてもパフォーマンスはまったく向上しません。複数のマテリアルが必要となる最も一般的な理由は 2 つのメッシュが同じテクスチャを使用していないということです。CPU パフォーマンスを最適化するには、共通のテクスチャをもつオブジェクトをまとめるよう気を付けてください。

Forward Rendering パス でピクセルライトを多く使用する場合、後述するようにオブジェクトをまとめることが無意味な場合があります。これについては、後述のライティングパフォーマンスを参照してください。

OnDemandRendering による CPU の最適化

OnDemandRendering を使用してアプリケーションのレンダリング速度を制御することで、CPU のパフォーマンスを向上させます。

以下のような場合には、フレームレートを下げたほうがよいでしょう。

  • アプリケーションのエントリーポイントや一時停止メニューなどのメニュー。メニューは比較的シンプルなシーンが多く、最高速度でレンダリングする必要はありません。メニューを低いフレームレートでレンダリングすることで、電力消費を抑えたり、デバイスの温度が上昇して CPU の周波数が抑えられるのを防ぐことができます。
  • チェスなどのターン制ゲーム。プレイヤーは、他のユーザーがコマを進めるのを待ったり、自分の手を考えたりして過ごします。アクティビティが少ない時間帯は、フレームレートを下げることで、不要な電力消費を防ぎ、バッテリーの寿命を延ばすことができます。
  • 自動車の UI など、コンテンツがほとんど静止しているアプリケーション。

レンダリング速度を調整することで、電力使用量やデバイスの熱を管理し、バッテリー駆動時間の最大化や CPU のスロットリングを防ぐことができます。特に、Adaptive Performance パッケージ と共に使うと特に効果的です。フレームが頻繁にレンダリングされなくても、アプリケーションは通常のペースでスクリプトにイベントを送信します (例えば、レンダリングされていないフレームの間に入力を受け取る場合があります)。入力の遅れを防ぐには、OnDemandRendering.renderFrameInterval = 1 を入力の間に呼び出すことで、動きやボタンなどが反応しているように表示することができます。

スクリプト、物理、アニメーションなどの処理が非常に多く、レンダリングが少ない状況では、この API を使用するメリットはありません。アプリケーションのビジュアルは、電力使用量への影響を最小限に抑えながらも、もたつく可能性があります。

ノート: VR アプリケーションは OnDemandRendering をサポートしていません。すべてのフレームをレンダリングしないと、頭の動きに合わせて映像が同期しなくなり、動きによる酔いのリスクが高まる可能性があります。

GPU - モデルジオメトリの最適化

モデルのジオメトリを最適化するには 2 つの基本的ルールがあります。

  • 必要以上の数の三角形を使用しない
  • UV マッピングの継ぎ目とハードエッジ (2 重化した頂点) をできるかぎり少数に抑える

通常、グラフィックハードウェアが処理する実際の頂点数は 3D アプリケーションで表示される頂点数とは一致しないことに注意してください。モデリングアプリケーションは通常、ジオメトリ頂点数、すなわちモデルを構成する角の数、を表示します。しかし、グラフィックスカードにとっては、レンダリングの目的で 2 つ以上の論理的な頂点に分ける必要があるジオメトリ頂点があります。頂点は複数の法線、UV 座標、または頂点カラーがある場合に分割する必要があります。結果的に、Unity でカウントされる頂点数は 3D アプリケーションで表示される数よりも、常に大きくなります。

モデルのジオメトリの数は、たいてい GPU に関連がある一方で、Unity のいくつかの機能は例えばメッシュスキンのように CPU でモデルを処理します。

Unity 以外の 3D アプリケーションでアセットを作成する際のパフォーマンスを向上させるためのヒントは、最適なパフォーマンスのためのモデル作成 を参照してください。

ライティングパフォーマンス

最速の方法は、常に、計算が不要なライティングを行うことです。そのためには、フレーム毎にライティングを計算するのをやめて、ライトマップ を使用して静的なライトを 1 回だけ “ベイク” します。ライトマップを適用して環境を製作する処理は、単に Unity のシーンにライトを配置するよりも多少時間がかかります。 その一方で 以下の利点があります。

  • はるかに早く実行されます (2 つのピクセルライトでは 2–3 倍)。
  • グローバルイルミネーションおよびライトマッパーのベイクにより結果を滑らかにできるため、外見が著しく改善されます。

多くの場合、複数の余分なライトを追加する代わりに、簡単なコツを適用できます。例えば、 Rim Lighting 効果のためにカメラに向かって直接を光を差し込むライトの代わりに専用の Rim Lighting の計算をシェーダーに直接追加します(詳しくは サーフェスシェーダーの例 を参照してください)。

フォワードレンダリングのライト

フォワードレンダリングパスの詳細 も参照してください。

動的なピクセル単位のライティングでは、影響する全てのピクセルにレンダリング作業が追加されるので、オブジェクトをマルチパスでレンダリングする事になります。モバイルや ローエンド PC の GPU など処理能力の低いデバイスでは、オブジェクトを複数の ピクセルライト で照らすことは避け、毎フレームライティングを計算する代わりに、ライトマップを使用して静的なオブジェクトをライティングしてください。動的な頂点単位のライティングも、頂点をトランスフォームすると顕著にコストが増加します。複数のライトでオブジェクトを照らすことは、なるべく避けるようにしてください。

様々なピクセルライトで照らすために、十分距離を置いているメッシュをまとめることは避けてください。ピクセルライトを使用するとき、各メッシュはそれを照らすピクセルライトの数だけレンダリングされなければなりません。とても距離のある 2 つのメッシュをまとめると、まとめたオブジェクトの有効サイズが増加します。レンダリングのときには、ひとまとめにしたオブジェクトのあらゆる部分を照らしているピクセルライトすべてが考慮されます。そのため、必要なレンダリングパスの数は増加します。一般的に、まとめられたオブジェクトをレンダリングするために必要なパスの数は、離れたオブジェクトそれぞれのパスの合計数で、メッシュをまとめても何の意味もありません。

レンダリング中、Unity はメッシュの周囲のすべてのライトを見つけ、メッシュに最も影響するライトを計算します。Quality ウィンドウの設定が、ピクセルライトとして使用するライトの数と、頂点ライトとして使用するライトの数の変更に使用されます。各ライトは、メッシュからの距離、ライトの強さに基づいて重要度が計算されます。ライトの中には、純粋にゲームのコンテキストのために他よりもずっと重要なものがあります。このため、すべてのライトには Render Mode 設定があり、Important または Not Important に設定することができます。 Not Important に設定されたライトのレンダリングオーバーヘッドは低くなります。

例としてカーレースのゲームで、プレイヤーの車が暗闇の中でヘッドライトをつけて走っているとします。ヘッドライトはもっとも重要な光源となるため、Render Mode はおそらく Important に設定されます。逆に、ゲームの中で重要性がやや劣るライト (他の車のバックライト、街灯柱、等) およびピクセルライトにしてもビジュアル効果が改善されないライトがあります。それらのライトの Render ModeNot Important に無難に設定することで、効果が薄いところに無駄なレンダリングコストを消費することを避けます。

ピクセル単位のライティングの最適化は CPU と GPU の両方を節約します。CPU が処理するドローコールが減り、GPU が処理する頂点数と、余分なオブジェクトレンダリングでラスタライズするピクセル数が減ります。

GPU - テクスチャの圧縮とミップマップ

テクスチャのサイズを小さくするには圧縮テクスチャを使用します。 これにより、読み込み時間を短縮し、メモリフットプリントを縮小し、レンダリングパフォーマンスを大幅に向上できます。圧縮テクスチャは、圧縮されていない 32 ビット RGBA テクスチャに必要なメモリ帯域幅のわずかな部分しか使用しません。

ミップマップテクスチャ

3D シーンで使用されるテクスチャでは、常に Generate Mip Maps を有効にしてください。ミップマップテクスチャは GPU を有効にし、小さな三角形では低解像度を使用します。圧縮テクスチャにより GPU がレンダリングの際に転送されるテクスチャデータの量が制限されるのと同様です。

このルールで唯一の例外となるのはテクセル(テクスチャピクセル)がレンダリングされた画面ピクセルに 1 : 1 でマッピングする、すなわち UI 要素または 2D ゲームのときです。

LOD およびレイヤーごとのカリング距離

カリングオブジェクトは、オブジェクトを非表示にすることに関与しています。CPU と GPU のロードを削減する効果的な方法の 1つです。

多くのゲームでは、プレイヤー体験の質を損ねずにこれを行う手っ取り早くて効果的な方法は、小さなオブジェクトを大きなオブジェクトと比べてより積極的に無視することです。例えば、小さな岩や砂塵は遠い距離では見えなくして、大きな建物は表示させたままにします。

様々な方法でこれを行うことができます。

  • Level Of Detail システムの利用

  • カメラのレイヤーごとのカリング距離を手動で設定

  • 小さなオブジェクトは 別のレイヤー に置き、Camera.layerCullDistances スクリプト関数を使用してレイヤーごとのカリング距離を設定します。

リアルタイムシャドウ

リアルタイムシャドウは効果的ですが、パフォーマンスにかなり影響を与えるもので、CPU での余分なドローコールと、GPU で追加の処理を要します。詳細については、Light のトラブルシューティングとパフォーマンス を参照してください。

GPU - パフォーマンスの高いシェーダーを書くヒント

異なる種類のプラットフォームには、かなり大きなパフォーマンス能力の違いがあります。ハイエンドの PC GPU はグラフィックスとシェーダーの面で、ローエンドのモバイル GPU に比べ、ずいぶん多くを処理できます。個々のプラットフォームに関しても同じことが言えます。GPU が速いと、GPU が遅いものに比べ、十数倍速く処理を行います。

モバイルプラットフォームおよびローエンド PC での GPU パフォーマンス は、多分あなたの開発機より遥かに遅いでしょう。ローエンド GPU 機器でよいパフォーマンスを得るために、シェーダーを手動で最適化して計算量およびテクスチャ読み込みを削減することを推奨します。例えば、Unity のいくつかのビルトインシェーダーは「モバイル」と同等の仕様があり、より高速です。ただし、制限事項や近似があります。

以下は、モバイルやローエンド PC のグラフィックスカードのためのガイドラインです。

複雑な数値計算

超越関数 (powexplogcossintan など) はとても負荷が高く、可能な場合は使用を避けてください。可能な場合は、複雑な数学的計算の代替案としてルックアップテクスチャの使用を検討してください。

独自の操作 (normalizedotinversesqrt など) を書くことを避けてください。Unity のビルトインオプションは、ドライバがより優れたコードを生成できることを確証しています。アルファテスト (discard) の操作がしばしばフラグメントシェーダーを遅くすることに気を付けてください。

浮動小数点の精度

浮動小数点数の変数の精度 (floathalffixed) が、 デスクトップ GPU ではほとんど無視される一方で、 モバイル GPU でよいパフォーマンスを得ることがかなり重要になっています。 詳しくは、シェーダーのデータタイプと精度 を参照してください。

シェーダーパフォーマンスの詳細については シェーダーを書く場合のパフォーマンスのヒント を参照してください。

ゲームを高速化する際のチェックリスト

  • PC 向けでは頂点数 20 万未満、フレーム毎では 300 万より少なくなるようにします (ターゲットとする GPU のスペックに依存します)。
  • ビルトインシェーダーを使用する場合、 Mobile または Unlit のカテゴリーから選択する。モバイルでないプラットフォームでも動作しますが、より複雑なシェーダーの簡素化され、近似化されたバージョンです。
  • シーンにおける異なるマテリアルの数を低く抑えます - 異なるオブジェクト間でマテリアルをできるかぎり共有します。
  • 動かないオブジェクトに対して、Static を設定して、ドローコールバッチング のような内部の最適化を許容します。
  • ジオメトリに影響する ピクセルライト (完璧にディレクショナルな) を複数でなく、 1つだけ設定するようにします。
  • 動的なライティングを使用するより、ライティングをベイクするようにします。
  • 圧縮テクスチャ形式を可能な場合は使用する、その他の場合は 32 ビットよりも 16 ビットを選択します。
  • 可能な限りフォグの使用を避けます。
  • 遮蔽物が沢山ある複雑で静的なシーンの場合には、オクルージョンカリング を使用して表示するジオメトリの量とドローコールを削減します。オクルージョンカリングを念頭に置きながら、レベルの設計をします。
  • Skybox を使用して遠くのジオメトリを “本物に見せかけます”。
  • ピクセルシェーダーを使用するか、Texture Combiner を使用して、マルチパスではなく複数のテクスチャをミックスします。
  • 可能な場合は、 精度浮動小数点数の変数を使用してください。
  • powsincosなどの複雑な数値計算の使用を最小限に抑えます。
  • フラグメントごとのテクスチャを極力使用しないようにします。

参照

OpenGL コア
ドローコールバッチング