サンプル - ビルボード平面の作成
ドローコールバッチング

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

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

どこにグラフィックスのコストがかかるか

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

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

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

あまり一般的ではないボトルネック:

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

CPU 最適化

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

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

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

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

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

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

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

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

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

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

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

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

最速なのは、ライティングの計算をしない事です。そのためには、フレーム毎にライティングを計算するのをやめて、静的なライティングを一度に “ベイク” して ライトマップ を作成します。ライトマップを適用した環境を製作するのは、単に Unity 上でライトをシーンに置くよりも多少時間がかかります。 その一方で

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

多くの場合、複数の余分なライトを追加する代わりに、シェーダーおよびコンテンツでのテクニックがあります。例えば、 Rim Lighting 効果を得るためにカメラに直接を光を差し込むライトの代わりに専用の Rim Lighting の計算をシェーダーに直接追加します。(詳しくは サーフェイスシェーダーの例 を参照してください。)

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

Forward Rendering パスの詳細も参照してください。

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

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

During rendering, Unity finds all lights surrounding a mesh and calculates which of those lights affect it most. The settings on the Quality window are used to modify how many of the lights end up as pixel lights, and how many as vertex lights. Each light calculates its importance based on how far away it is from the mesh and how intense its illumination is - and some lights are more important than others purely from the game context. For this reason, every light has a Render Mode setting which can be set to Important or Not Important; lights marked as Not Important have a lower rendering overhead.

例としてカーレースのゲームで、プレイヤーの車が暗闇の中でヘッドライトをオンにして走っているとします。ヘッドライトはもっとも重要な光源となるため、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 など) はとても負荷が高く、可能な場合は使用を避けてください。適切な場合は、複雑な数学的計算の代替案としてルックアップテクスチャの使用を検討してください。

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

浮動小数点の精度

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

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

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

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

参照

サンプル - ビルボード平面の作成
ドローコールバッチング