今回は、Unityプロジェクトの最適化のヒントを紹介する連載の第2回目です。より少ないリソースでより高いフレームレートで動作させるためのガイドとしてご利用ください。これらのベスト・プラクティスを試したら、シリーズの他のページもぜひご覧ください:
Unityのグラフィックツールは、モバイルからハイエンドコンソール、デスクトップまで、さまざまなプラットフォームで、あらゆるスタイルで最適化されたグラフィックを作成できます。このプロセスは通常、あなたのアーティスティックな方向性とレンダーパイプラインに依存するので、始める前に、利用可能なレンダーパイプラインを確認することをお勧めします。
レンダーパイプラインを選択する際には、これらの点を考慮してください。パイプラインの選択と同時に、レンダリングパスも選択する必要がある。
レンダリングパスは、ライティングとシェーディングに関連する特定の一連の操作を表します。レンダリングパスの決定は、アプリケーションのニーズとターゲットハードウェアに依存します。
前方レンダリングパス
フォワードレンダリングは ユニバーサル・レンダー・パイプライン(URP)と ビルトイン・レンダー・パイプライン.フォワードレンダリングでは、グラフィックスカードがジオメトリを投影し、それを頂点に分割する。これらの頂点はさらに断片(ピクセル)に分解され、スクリーンにレンダリングされ、最終的な画像が作られる。
パイプラインは、各オブジェクトを1つずつグラフィックスAPIに渡す。フォワード・レンダリングはライトごとにコストがかかるので、シーン内のライトの数が多ければ多いほど、レンダリングに時間がかかります。
Built-in PipelineのForward Rendererは、オブジェクトごとに別々のパスで各ライトを描画します。複数のライトが同じGameObjectに当たると、重なり合った領域が同じピクセルを2回以上描画する必要があるため、大幅なオーバー描画が発生する可能性があります。オーバードローを減らすには、リアルタイムライトの数を最小限にする。
ライト1つにつき1パスをレンダリングするのではなく、URPはオブジェクトごとにライトをカリングする。これにより、ライティングをシングルパスで計算できるようになり、ビルトインレンダリングパイプラインのフォワードレンダラーに比べて描画呼び出しが少なくなります。
Built-in Render Pipeline、URP、High Definition Render Pipeline(HDRP)もDeferred Shadingレンダリングパスを使用します。Deferred Shadingでは、ライティングはオブジェクトごとに計算されません。
代わりに、Deferred Shadingは、ライティングのような重いレンダリングを後の段階に延期し、2つのパスを使用します。最初のパスでは Gバッファジオメトリパスとも呼ばれ、UnityはGameObjectをレンダリングします。このパスでは、数種類の幾何学的プロパティを取得し、テクスチャのセットに格納する。
Gバッファーテクスチャーには以下のようなものがある:
- 拡散色とスペキュラー色
- 表面の滑らかさ
- 咬合
- ワールドスペース法線
- 発光+アンビエント+反射+ライトマップ
2番目のパス(ライティングパス)では、UnityはGバッファに基づいてシーンのライティングをレンダリングします。各ピクセルを反復処理し、個々のオブジェクトではなくバッファに基づいて照明情報を計算することを想像してほしい。Deferred Shadingで影を落とさないライトを追加しても、Forwardレンダリングと同じパフォーマンスヒットは発生しません。
レンダリングパスの選択は、それ自体が最適化というわけではありませんが、プロジェクトの最適化に影響を与える可能性があります。このセクションの他のテクニックとワークフローは、選択するレンダーパイプラインとパスによって異なります。
HDRPとURPの両方が、シェーダー作成のためのノードベースのビジュアル・インターフェースであるShader Graphをサポートしています。シェーダープログラムの経験がないユーザーでも、複雑なシェーディング効果を作成できる。
現在、Shader Graphでは150以上のノードが利用可能です。さらに、APIを使って独自のカスタムノードを作ることもできる。
シェーダーグラフの各シェーダーはマスターノードから始まり、グラフの出力を決定します。ビジュアルインターフェイス内でノードと演算子を追加したり接続したりして、シェーダロジックを構築します。
シェーダーグラフはその後、レンダーパイプラインのバックエンドに渡される。最終的な結果は、HLSLやCgで書かれたものと機能的に似ているShaderLabシェーダーになります。
シェーダーグラフの最適化は、従来のHLSLやCgシェーダーに適用されるのと同じルールの多くに従います。重要なのは、シェーダーグラフの処理が多ければ多いほど、アプリケーションのパフォーマンスに影響を与えるということです。
CPUに縛られている場合、シェーダーを最適化してもフレームレートは向上しませんが、モバイルプラットフォームではバッテリー寿命が向上するかもしれません。
GPUを使用している場合は、Shader Graphを使用してパフォーマンスを向上させるためのガイドラインに従ってください:
使用されていないノードを削除する:必要な変更以外は、デフォルトを変更したり、ノードを接続したりしないでください。シェーダーグラフは、未使用の機能を自動的にコンパイルします。可能であれば、値をテクスチャにベイクする。例えば、ノードを使用してテクスチャを明るくする代わりに、テクスチャアセット自体に余分な明るさを適用します。
可能な限り小さいデータ形式を使用する:プロジェクトで可能であれば、Vector3の代わりにVector2を使うか、精度を下げる(例えば、floatの代わりにhalf)ことを検討してください。
数学の演算を減らす:シェーダー演算は1秒間に何度も実行されるので、可能な限り数学演算子を最適化するようにしてください。論理的な分岐を作るのではなく、結果の融合を目指す。定数を使用し、ベクトルを適用する前にスカラー値を組み合わせる。最後に、インスペクタに表示する必要のないプロパティをインライン・ノードに変換します。こうした段階的な強化はすべて、フレーム予算の助けになる。
プレビューのブランチグラフが大きくなると、コンパイルが遅くなることがある。現在プレビューしたい操作のみを含む、独立した小さなブランチで、ワークフローを簡素化できます。そして、望む結果が得られるまで、この小さなブランチをさらに素早く反復する。ブランチがマスターノードに接続されていない場合は、プレビューブランチをグラフに残すことができます。Unityは、コンパイル時に最終出力に影響しないノードを削除する。
手動で最適化する: 経験豊富なグラフィックス・プログラマーでも、シェーダー・グラフを使って、スクリプトベースのシェーダー用の定型コードを書くことができます。Shader Graphアセットを選択し、コンテキストメニューからCopy Shaderを選択します。新しいHLSL/Cgシェーダーを作成し、コピーしたシェーダーグラフを貼り付けます。これは一方通行の操作だが、手動の最適化によってさらなるパフォーマンスを引き出すことができる。
グラフィックス設定(Edit > Project Settings > Graphics)にあるAlways Included Shadersというリストから、未使用のシェーダーをすべて削除します。アプリケーションのライフタイムに必要なシェーダーをここに追加する。
Shader compilation pragma ディレクティブを使用して、シェーダーのコンパイルを各ターゲットプラットフォームに適合させます。次に、シェーダーキーワード(またはシェーダーグラフキーワードノード)を使用して、特定の機能を有効または無効にしたシェーダーバリアントを作成します。
シェーダーバリアントは、プラットフォーム固有の機能には便利ですが、ビルド時間とファイルサイズを増加させます。シェーダーバリアントがビルドに含まれないようにすることもできます。
まず エディターログを解析します。次に、Compiled shaderと Compressed shaderで始まる行を探します。
このログの例では、以下の統計情報を示している:
TEST Standard (Specular setup)」シェーダのコンパイル時間:31.23秒
d3d9(内部プログラム合計:482、ユニーク:474)
d3d11(内部プログラム合計:482、ユニーク:466)
メタル(総内部プログラム:482、ユニーク:480)
glcore(内部プログラム合計:482、ユニーク:454)
d3d9の「TEST Standard (Specular setup)」シェーダーを1.04MBから0.14MBに圧縮。
d3d11の「TEST Standard (Specular setup)」シェーダーを1.39MBから0.12MBに圧縮。
メタルの「TEST Standard (Specular setup)」シェーダーを2.56MBから0.20MBに圧縮。
glcoreの「TEST Standard (Specular setup)」シェーダーを2.04MBから0.15MBに圧縮。
これらの統計は、シェーダーについていくつかのことを教えてくれる:
- これは、#pragma multi_compileと shader_featureにより、482種類に拡張される。
- Unityは、ゲームデータに含まれるシェーダーを、圧縮されたサイズのほぼ合計に圧縮します:0.14+0.12+0.20+0.15 = 0.61 MB.
- 実行時、Unityは圧縮されたデータをメモリ(0.61 MB)に保持し、現在使用しているグラフィックスAPIのデータは圧縮されないままです。例えば、現在のAPIがMetalの場合、2.56MBとなる。
ビルド後 プロジェクト監査人(experimental)はEditor.logを解析して、プロジェクトにコンパイルされたすべてのシェーダ、シェーダキーワード、シェーダバリアントのリストを表示することができます。また プレイヤーログを解析することもできます。これは、アプリケーションが実際にコンパイルされ、実行時に使用されたバリアントを示します。
スクリプト可能なシェーダーストリッピングシステムを構築し、バリアントの数を減らすために、この情報を使用する。これにより、ビルド時間、ビルドサイズ、実行時のメモリ使用量を改善することができる。
このプロセスの詳細については、スクリプト可能なシェーダーバリアントのストリッピングの記事をお読みください。
アンチエイリアシングは、ギザギザのエッジを減らし、スペキュラーエイリアシングを最小限に抑えることで、よりシャープな画質に貢献します。
ビルトインレンダリングパイプラインでフォワードレンダリングを使用している場合、 マルチサンプル・アンチエイリアス(MSAA)が 品質設定で使用できます。MSAAは高品質のアンチエイリアシングを実現するが、コストが高くつく。ドロップダウンメニューのMSAAサンプル数という設定は、レンダラーが効果を評価するために使用するサンプル数を定義します(なし、2倍、4倍、8倍)。URPまたはHDRPでフォワード・レンダリングを使用している場合、MSAAを有効にすることができます。 URPアセットまたは HDRPアセットでMSAAを有効にすることができます。
あるいは、後処理としてアンチエイリアスを加えることもできる。これはカメラコンポーネント(アンチエイリアスの下)に表示され、いくつかのオプションがあります:
- 高速近似アンチエイリアス(FXAA)は、ピクセル単位でエッジを滑らかにします。これは、最もリソースを消費しないタイプのアンチエイリアスです。最終的な画像がわずかにぼやける。
- サブピクセルモルフォロジカルアンチエイリアス(SMAA)は、画像の境界に基づいてピクセルをブレンドする。FXAAよりもはるかにシャープな結果を提供し、フラット、カートゥーン風、またはクリーンなアートスタイルに適しています。
HDRPでは、追加オプションで、カメラの後処理アンチエイリアシングでFXAAとSMAAを使用することもできます:
- テンポラル・アンチエイリアシング(TAA)は、ヒストリーバッファのフレームを使ってエッジを滑らかにする。これはFXAAよりも効果的に機能するが、機能するためにはモーションベクトルが必要である。TAAはアンビエントオクルージョンと ボリューメトリックも改善できる。一般的にFXAAよりも高品質だが、余分なリソースを必要とし、時折ゴーストアーチファクトが発生することがある。
ライティングを作るのに最も手っ取り早い選択肢は、フレームごとに計算する必要がないものだ。ライトマッピングを使えば、静的なライティングをリアルタイムで計算する代わりに、一度だけベイクすることができます。
を使用して静的ジオメトリに劇的な照明を追加します。 グローバルイルミネーション(GI)を使用します。オブジェクトのContribute GIオプションをチェックして、高品質のライティングをライトマップの形で保存します。
ライトマップされた環境を生成するプロセスは、シーンにライトを配置するよりも時間がかかるが、次のような重要な利点がある:
- 1ピクセルあたり2個のライトで2~3倍高速に動作
- グローバルイルミネーションによるビジュアルの向上。リアルな直接照明と間接照明の計算が可能で、ライトマッパーは結果のマップを滑らかにし、ノイズを除去します。
- ベイクドシャドウとライティングは、リアルタイムライティングとシャドウによるパフォーマンスへの影響を受けずにレンダリングされます。
より複雑なシーンでは、長いベイク時間が必要になることもある。ハードウェアが プログレッシブGPUライトマッパー(プレビュー中)をサポートしている場合、このオプションを使用すると、CPUの代わりにGPUを使用してライトマップ生成を劇的に高速化できます。
Unityでライトマッピングを始めるには、このガイドに従ってください。
リフレクション・プローブはリアルな反射を作り出すことができる反面、ロットの点でコストがかかります。そのため、パフォーマンスへの影響を最小限に抑えるために、以下の最適化のヒントをお試しください:
- 実行時のパフォーマンスを向上させるために、低解像度のキューブマップ、カリングマスク、テクスチャ圧縮を使用します。
-:フレームごとの更新を避けるため、 。
- もしType:URPではリアルタイム 。可能な限りEvery Frameを避けるようにしよう。リフレッシュモード リフレッシュモードそして タイムスライスの設定を調整し、更新レートを下げる。また、スクリプト経由オプションを使用してリフレッシュを制御し、カスタムスクリプトからプローブをレンダリングすることもできます。
- もしType:HDRPではリアルタイム 、オンデマンドモードを選択する必要がある。Project Settings > HDRP Default Settingsで Frame Settingsを変更することもできます。
- パフォーマンスを向上させるために、Real-time Reflectionの品質と機能を下げる。
メッシュ・レンダラーとライトごとにシャドウ・キャスティングを無効にできる。ドローコールを減らすため、可能な限りシャドウを無効にする。また、キャラクターの下にあるシンプルなメッシュや四角形にぼかしたテクスチャを適用して、偽の影を作ることもできます。そうでなければ、カスタムシェーダーを使ってブロブシャドウを作ることができる。
特に、ポイントライトのシャドウを有効にするのは避けてください。シャドウのあるポイントライトは、ライトごとに6つのシャドウマップパスを必要とします。ダイナミックな影がどうしても必要な場合は、ポイントライトをスポットライトに置き換えることを検討してください。ダイナミックシャドウを避けられるなら、キューブマップを ライト.クッキーを使用してください。
複数のライトを追加するのではなく、簡単なトリックを適用できる場合もある。例えば、リムライティング効果を与えるためにカメラに直接光を当てるライトを作成する代わりに、リムライティングをシミュレートするシェーダを使用します(HLSLでの実装についてはサーフェスシェーダの 例を参照してください)。
多数のライトがある複雑なシーンでは、レイヤーを使ってオブジェクトを分離し、各ライトの影響を特定のカリングマスクに限定します。
ライトプローブは、高品質の照明(直接照明と間接照明の両方)を提供しながら、シーン内の空きスペースに関する焼き付け照明情報を保存します。球面ハーモニクスを使用しており、ダイナミックライトに比べて計算が速い。これは、通常ベイクド・ライトマッピングを受けられない動くオブジェクトに特に有効です。
ライトプローブは静的メッシュにも適用できる。メッシュレンダラーコンポーネントで、グローバルイルミネーションを受け取るドロップダウンメニューを探し、ライトマップから ライトプローブに切り替えます。
目立つレベルのジオメトリにはライトマッピングを使い続けますが、細かい部分のライティングにはライトプローブに切り替えます。ライトプローブのイルミネーションは適切なUVを必要としないので、メッシュをアンラップする余分なステップを省くことができます。また、プローブはライトマップテクスチャを生成しないため、ディスク容量も削減できます。
詳しくは、ライトプローブを使ったスタティックライティングの記事と、Unityで信じられるビジュアルを作るを参照してください。
これまでで最も包括的なガイドの1つで、PCとコンソール向けにゲームを最適化する方法について、80以上の実用的なヒントを集めている。サクセスとアクセラレート・ソリューションズの専門エンジニアが作成したこれらの詳細なヒントは、Unityを最大限に活用し、ゲームのパフォーマンスを向上させるのに役立ちます。