プロファイリング
モバイル用の最適化に関する実用ガイド

最適化

PC と同様に iOS および Android などのモバイルプラットフォームにはさまざまなレベルのパフォーマンスを持ったデバイスがあります。デバイスによってレンダリング能力の差が 10 倍にもなります。 スケーリングを行う簡単な方法としては

  1. ベースラインのデバイスで問題なく実行できることを確認する
  2. 高いパフォーマンスを持ったデバイスでは見映えをあげる
    • 解像度
    • ポストプロセッシング
    • MSAA
    • Anisotropy(異方性)
    • シェーダー
    • Fx /パーティクル密度を有効化/無効化

GPU へのフォーカス

グラフィックスパフォーマンスはフィルレート、ピクセル数、ジオメトリの複雑さ(頂点カウント)により制約されます。これらのレンダラーをカリングする方法を見つけることができれば、これらすべてを削減することができます。Unity は自動的に視錐台の外にあるオブジェクトをカリングするため、オクルージョンカリングがここでは役立つかもしれません。

モバイルではフィルレートの制約(フィルレート = 画面ピクセル数 x シェーダーの複雑さ x オーバードロー)があり、複雑すぎるシェーダーがもっとも良くある問題です。このため Unity に付属するシェーダーを使用します。自身で設計するときは、できるだけシンプルにします。もし可能であれば、ピクセルシェーダーを頂点シェーダーに移行してシンプルにします。

Quality Settings で Texture Quality を下げることでゲームが速くなる場合、メモリ帯域幅により制限されている可能性が高いと言えます。その場合は、テクスチャを圧縮する、ミップマップを使用する、テクスチャサイズを削減する、などを行います。

LOD (Level of Detail) - によりオブジェクトが遠ざかるにつれてシンプルにしたり完全に削除したりします。

グッドプラクティス

モバイル GPU は発熱量、電力、騒音に大きな制限があります。このためデスクトップ部品と比較してモバイル GPU は帯域幅が少なく、ALU パフォーマンスが低く、テクスチャリングパワーが劣ります。GPU のアーキテクチャはできる限り小さい帯域幅で少量の電力を使用するように最適化されてます。

Unity は OpenGL ES 2.0 に最適化されていて、GLSL ES (HLSL と類似) シェーディング言語を使用します。ビルトインシェーダーはほとんど HLSL (Cg とも知られる) で書かれています。これはモバイルプラットフォームでは GLSL ES にクロスコンパイルされます。直接 GLSL を記述することもできますが、GLSL->HLSL の変換ツールが現在ないため OpenGL 関連のプラットフォームに制限されます(例えばモバイルおよび Mac )。HLSL で float/half/fixed 型を使用すると GLSL ES では highp/mediump/lowp 精度表現になります。

上手な使用法のチェックリストを次に示します。

  1. マテリアル数をできるだけ少なくします。これにより Unity のバッチングが容易になります。
  2. 個別テクスチャよりもテクスチャアトラス(サブ画像を含む大きな画像)を使用します。これらのほうがロードが高速であり、ステートの変更が少なく、バッチングと親和性があります。
  3. テクスチャアトラスや共有マテリアルを使用する場合 Renderer.sharedMaterialRenderer.material の代わりに使用します。
  4. フォワードレンダリングされたピクセルライトは負荷が高いです。
    • 可能なかぎりリアルタイムライトを使わずにライトマッピングを使用してください。
    • quality settings で pixel light count を調整します。つまり、ディレクショナルライトのみがピクセル単位であるべきで、その他はすべて頂点単位とすべきです。ゲームによってどちらにするか判断します。
  5. Quality Settings の Render Mode of Lights で試行錯誤して正しい優先度となるようにします。
  6. カットアウト(アルファテスト)シェーダーは本当に必要な場合に限定します。
  7. 画面で使用する透明度(アルファブレンド)を最小限にします。
  8. 複数のライトが特定のオブジェクトを照らす状況を避けるようにします。
  9. シェーダーパスの全体的な数を削減します(シャドウ、ピクセルライト、反射)。
  10. レンダリングの順番は重要です。一般的には、
    • 完全に不透明なオブジェクトはおおむね手前から奥の順
    • アルファテストのオブジェクトはおおむね手前から奥の順
    • skybox
    • アルファブレンドのオブジェクト(必要であれば奥から手前の順)
  11. モバイルでポストプロセッシングは負荷が高いため、慎重に使用します。
  12. パーティクル:オーバードローを削減し、できる限りシンプルなシェーダーを使用します。
  13. メッシュのダブルバッファーが毎フレーム更新されます。
void Update (){
  // メッシュを切り替えます
  bufferMesh = on ? meshA : meshB;
  on = !on;
  bufferMesh.vertices = vertices; // メッシュを変更
  meshFilter.sharedMesh = bufferMesh;
}

シェーダーの最適化

フィルレートにより制限されているか判断するのは容易です。画面解像度を下げるとゲームは速くなりますか? もしそうであれば、フィルレートにより制限されています。

次の手法によってシェーダーの複雑さを軽減します。

  • アルファテストのシェーダーを避けます。代わりにアルファブレンドのものを使用します。
  • シンプルで最適化されたシェーダーのコードを使用します( Unity の Mobile シェーダーと同様に)。
  • シェーダーで負荷の高い math 関数の使用を避けます (pow、exp、log、cos、sin、tan、その他)。代わりに事前計算されたルックアップテクスチャを使用することを考えてください。
  • できる限り低い精度の型を(float, half, fixedin Cg) 使用してベストのパフォーマンスを得ます。

CPU へのフォーカス

ゲームが、ピクセル処理時に GPU によって制限されてる場合がしばしばあります。結果的に、特にマルチコアモバイル CPU で、未使用の CPU 処理能力が余ることになります。このため GPU の作業を代わりに CPU に処理させると効果的です (Unity はこれをすべて自動的に行います)。メッシュスキニング、小さいオブジェクトのバッチング、パーティクルジオメトリの更新などがこれに該当します。

これらはやみくもに行うのではなく、慎重に行われるべきです。ドローコールがネックではない場合、バッチングはカリングの効率を下げるだけでなく、ライトにより影響を受けるオブジェクト数を増やしてしまうので、パフォーマンスに悪影響を与えます。

グッドプラクティス

  • FindObjectsOfType (および Unity の getter プロパティー全般) は非常に遅いため慎重に使用してください。
  • 動かないオブジェクトの静的なプロパティーを有効にすることにより、静的バッチングなどの内部最適化を行うことができます。
  • CPU サイクルを大量に消費してオクルージョンカリングとより効果的なソートを行ってください (Early Z-cull を活用するため)。

物理計算

物理計算は重い CPU 処理を伴います。エディタープロファイラーによりプロファイリングできます。もし CPU 上で物理計算の時間がかかりすぎている場合、

  • Time.fixedDeltaTime (Project settings -> Time にて) を微調整してできるだけ大きくします。もしゲームが遅い動作のものであれば、素早いアクションを求められるゲームより fixedupdate を少なくするのがよいでしょう。素早いアクションを求められるゲームは計算がより頻繁に必要です。そのため、 fixedDeltaTime をより小さな値にしないと衝突が正しく動作しない可能性があります。
  • Physics.solverIterationCount (Physics Manager)。
  • できるかぎりクロスオブジェクトを少なくします。
  • Rigidbody の使用を必要な場合のみに制限します。
  • プリファレンスメッシュコライダーでプリミティブコライダーを使用します。
  • スタティックコライダー(すなわち Rigidbody のないコライダー)はパフォーマンス影響が甚大であるため絶対に動かさないこと。プロファイラーで Static Collider.Move と表示されますが実際の処理は Physics.Simulate にあります。もし必要であれば RigidBody を追加して isKinematic に true をセットします。
  • Windows 上で NVidia の AgPerfMon プロファイリングツールを使用して必要に応じてより詳細が得られます。

Android

GPU

これは一般的によく使われるモバイルアーキテクチャです。これは PC/コンソールで異なるベンダーであり、通常の GPU と異なる GPU アーキテクチャです。

  • ImgTec PowerVR SGX - タイルベース、デファード:すべてを小さなタイル(16x16) としてレンダリングし、表示されるピクセルのみシェードします。
  • NVIDIA Tegra - Classic: すべてをレンダリング
  • Qualcomm Adreno - タイル: すべてをタイルでレンダリングし、大きなタイルとして(256k として) 処理します。
  • ARM Mali Tiled: すべてをタイルとして(16x16 として) 処理し、レンダリングします。

異なるレンダリングのアプローチを検討してゲームをそれにあわせて設計します。ソートには特別に注意します。開発サイクルの早い段階でサポートする最低限のローエンドデバイスを決めます。ゲームを設計するときにプロファイラーを有効にしてテストします。

プラットフォーム固有のテクスチャ圧縮を使用します

関連資料

画面解像度

Android バージョン

iOS

GPU

PowerVR アーキテクチャ(タイルベースデファード)のみに注意が必要です。

  • ImgTec PowerVR SGX。タイルベース、デファード: すべてをタイルとしてレンダリングし、表示されるピクセルのみシェードします。

つまり、

  • ミップマップはそれほど必要ではありません。
  • アンチエイリアスおよび Aniso (異方性)は十分軽く、iPad 3 では必要ない場合もあります。

また欠点として、

  • もしフレーム毎の頂点データ(頂点数 × 頂点シェーダー後の必要ストレージ)がドライバーによって割り当てられた内部バッファーを超えると、シーンは “分割” される必要がありパフォーマンスが消費されます。ドライバーがそれ以降、さらに大きなバッファーを割り当てるか、頂点数を減らす必要があるかもしれません。これは iPad 2 (iOS 4.3) において、かなり複雑なシェーダーを持つ場合に 10 万頂点くらいで顕著になります。
  • タイルベースデファードレンダリング( TBDR )ではより多くのトランジスタをタイリングおよびデファード部分に割り当てる必要があり、概念的には “生のパフォーマンス” に割り当てられるトランジスタが少なくなります。TBDR で GPU タイミングを得ることは難しく、プロファイリングが難しくなります。

関連資料

画面解像度

iOS バージョン

ダイナミックオブジェクト

アセットバンドル

  • アセットバンドルはある制限までデバイス上にキャッシュされます。
  • エディター API を使用して作成します。
  • 読み込みに WWW API を使用します。 WWW.LoadFromCacheOrDownload またはリソースとして AssetBundle.CreateFromMemory や AssetBundle.CreateFromFile を使用します。
  • AssetBundle.Unload を使用してアンロードします。バンドルはアンロードしますが、ただしそれからロードしたアセットを維持するオプションがあります。また、シーンの中で参照している場合でも、ロードされたすべてのアセットをキルできます。
  • Resources.UnloadUnusedAssets はシーンで参照されていないすべてのアセットをアンロードするので、必要のないアセットへの参照を忘れずにキルしてください。パブリックおよび静的変数は、ガベージコレクトされません。
  • Resources.UnloadAsset は、メモリから特定のアセットをアンロードします。必要に応じて、ディスクから再ロードできます。

iOS 上でアセットバンドルの同時ダウンロード数の制限はありますか?(例えば、同時に(または毎フレームで) 10 個を超えるアセットバンドルを安全にダウンロードできますか?)

ダウンロードは OS により提供される非同期 API により実装されているため、ダウンロードのためにいくつのスレッドを作成する必要があるかを OS が判断します。複数の同時ダウンロードを起動するとき、デバイスがサポートできる帯域幅の合計および空きメモリ量を考慮する必要があります。各同時ダウンロードにより一時バッファが割りあてられるため、メモリが不足しないように注意します。

リソース

  • アセットは、ビルドする際に Unity によって認識される必要があります。
  • Unity がバイナリデータとして認識できるように任意の生のバイトファイルにファイル拡張子 .bytes を加えます。
  • Unity がテキストアセットとして認識できるように、任意のテキストフ ​​ ァイルにファイル拡張子 .txt を加えます。
  • リソースはビルド時にプラットフォームの形式に変換されます。
  • Resources.Load()

問題チェックリスト

  • 適切に圧縮されていないテクスチャ
  • それぞれのケースにさまざまなソリューションがありますが、圧縮すべきでないことが確かな場合を除いては、必ずテクスチャを圧縮するようにしてください。
  • ETC/RGBA16 - android のデフォルトですが、GPU ベンダーに応じて調整することができます。最善のアプローチは、可能な限り ETC を使用するようにします。アルファテクスチャはアルファであるために 1 つチャンネルのある 2 つの ETC ファイルを使用できます。
  • PVRTC - 多くの場合に適している iOS 用のデフォルト値
  • テクスチャのピクセル Get/Set を有効にする - フットプリントを倍増します。Get/Set が必要な場合を除き、チェックを外します。
  • ランタイムにテクスチャを JPEG/PNG 画像からロードすると、非圧縮になります。
  • 大きいサイズの mp3 ファイルはロードする時に非圧縮であるとマークされます。
  • シーンを追加でロードする場合
  • メモリ内にクリーンされずに残留した未使用のアセット
  • ランダムにクラッシュする時は、開発キットまたは 2GB のメモリのデバイス (iPad 3 など)上で試してみてください。

ランダムなクラッシュの場合は、コンソールには何も表示されない場合があります。

  • 高速なスクリプト呼び出しとストリッピングは、iOS のランダムなクラッシュを引き起こすことがあります。それらを除いて試してみてください。
プロファイリング
モバイル用の最適化に関する実用ガイド