Unity を検索

モバイルゲームのパフォーマンスを最適化しよう:専門家が語るグラフィックスとアセットに関するヒント

2021年8月3日 カテゴリ: テクノロジー | 12 分 で読めます
Skeletons
Skeletons

Integrated Success チームは、Unity の顧客が抱える複雑な技術的問題をサポートします。シニアソフトウェアエンジニアで構成されるこのチームに、モバイルゲームの最適化に関する知見を聞かせてもらいました。

当社の Accelerate Solutions チームはソースコードを熟知しており、無数に存在する Unity の顧客みんながエンジンを最大限に活用できるようサポートしています。このチームは、クリエイターのプロジェクトに深く入り込み、パフォーマンスを最適化することで、スピード、安定性、効率性を向上させるポイントを見つけ出します。 Unity のエンジニアたちがモバイルゲームの最適化に関する知見のシェアを始めるとすぐに、元々予定していた 1 本のブログ記事に収まりきらないほどの素晴らしい情報があることに気づきました。そこで私たちは、彼らの膨大な知識を e ブックとしてまとめ(ダウンロードはこちら)、そこに収録されている 75 以上の実用的なヒントの一部を紹介する一連のブログ記事を作成することにしました。

最適化に関する本シリーズの最終回となるこの記事では、アセット、プロジェクト構成、グラフィックスのパフォーマンスを向上させる方法に焦点を当てます。ゲームの最適化については、プロファイリング、メモリ、コードアーキテクチャ、物理、UI、オーディオについての過去の記事をご覧ください。また、無料の e ブックをダウンロードすると、これらのすべてのトピックに関する内容に触れることができます。

プロジェクトの構成

モバイルパフォーマンスに影響を与えるプロジェクト設定はいくつかあります。

加速度センサーの周波数を下げる、または無効にする

Unity では、携帯電話の加速度センサーからのデータを 1 秒間に数回プールしています。アプリケーションで使用していない場合は無効にしたり、パフォーマンスを向上させるために周波数を下げたりします。

Editor
モバイルゲームで Accelerometer Frequency を使わない場合は、この項目が無効になっていることを確認する。

不要なプレイヤー設定や品質設定を無効にする

Player の設定で、Auto Graphics API をサポートされていないプラットフォームでは無効にして、過剰なシェーダーバリアントの生成を防ぎます。古い CPU をアプリケーションでサポートしない場合は、Target Architectures からその CPU を外してください。

Quality の設定で、必要のない Quality のレベルを無効にします。

不要な物理演算を無効にする

ゲームが物理演算を使用していない場合は、Auto SimulationAuto Sync Transforms のチェックを外してください。チェックを付けていてもアプリケーションを遅くするだけで、目に見えた効果はありません。

適切なフレームレートを選択する

モバイルプロジェクトでは、バッテリー消費やサーマルスロッティングの対策を考えつつ、ちょうどいいフレームレートを定める必要があります。デバイスの限界に挑戦して 60fps を出すより、30fps での運用で妥協することを検討したほうがいいこともあります。Unity のモバイルプラットフォームのデフォルトは 30fps です。

また、Application.targetFrameRate で実行時にフレームレートを動的に調整することもできます。例えば、ゆっくりとしたシーンや比較的静かなシーンでは 30fps 以下に落とし、ゲームプレイではより高い fps に設定するよう指定することができます。

大きな階層構造を避ける

階層構造は小分けにしましょう。ゲームオブジェクトを階層化する必要がない場合は、親子関係を単純化しましょう。階層構造を小さくしておけば、シーン内のトランスフォームの更新でマルチスレッドの恩恵を受けることができます。階層構造が複雑になると、不必要なトランスフォームの計算が発生し、ガベージコレクションのコストが増大します。

階層構造の最適化に関するベストプラクティスや、こちらの Unite 講演を見て、トランスフォームに関するベストプラクティスを学びましょう。 

トランスフォームは 2 回ではなく、1 回だけ動かす

トランスフォームを動かす時は、Transform.SetPositionAndRotation を使って、位置と回転の両方を一度に更新するようにします。これにより、トランスフォームを 2 回修正するために起きるオーバーヘッドを回避できます。

実行時にゲームオブジェクトに対して Instantiate を使う必要がある場合、簡単な最適化として、インスタンス化の際に親子関係の設定と位置の設定を行うというものがあります。

GameObject.Instantiate(prefab, parent);

GameObject.Instantiate(prefab, parent, position, rotation);

Object.Instantiate の詳細については、スクリプティング API のドキュメントをご覧ください。

Vsync は有効になっているものと思う

モバイル端末ではハーフフレームがレンダリングされません。エディターで Vsync を無効にしても(Project Settings > Quality)、ハードウェアレベルでは Vsync は有効になります。GPU が十分な速度で画面をリフレッシュできない場合は、現在のフレームが保持され、事実上、fps が低下します。 

アセット

アセットパイプラインは、アプリケーションのパフォーマンスに大きな影響を与えます。経験豊富なテクニカルアーティストなら、アセットのフォーマット、仕様、インポート設定を定義し、それを運用することで、スムーズなプロセスを実現させます。

デフォルトの設定に頼らないようにしましょう。プラットフォーム固有のオーバーライドタブを使用して、テクスチャやメッシュジオメトリなどのアセットを最適化します。設定を誤ると、ビルドサイズが大きくなったり、ビルド時間が長くなったり、雑なメモリの使い方になってしまったりします。プリセット機能を使って、特定のプロジェクトを強化するベースライン設定をカスタマイズすることを検討してください。

このトピックについてさらに詳しく知りたい方は、アートアセットのベストプラクティスに関するガイドや、Unity Learn の「Arm と Unity 共同提供:モバイルアプリケーションのための 3D アートの最適化」コースをチェックしてください。

テクスチャを正しくインポートする

メモリのほとんどはテクスチャに使われるので、テクスチャのインポート設定は重要です。一般的には、以下のガイドラインに従うようにしてください。

  • 最大サイズを下げる:視覚的に問題のない結果が得られる最小の設定を使用しましょう。これは非破壊的な処理で、かつテクスチャメモリをすぐに減らすことができる対策です。
  • テクスチャサイズを 2 の累乗(POT)にする:Unity では、モバイルのテクスチャ圧縮形式(PVRTC や ETC)を使う時にテクスチャの寸法を POT にする必要があります。
  • テクスチャをアトラスにまとめる:複数のテクスチャを 1 つのテクスチャにまとめることでドローコールを減らし、レンダリングを高速化することができます。 Unity のスプライトアトラスや、サードパーティ製の TexturePacker を使用してテクスチャアトラスを作りましょう。
  • Read/Write Enabled オプションを無効にする:このオプションを有効にすると、CPU と GPU の両方のアドレス可能なメモリにコピーが作成され、テクスチャのメモリフットプリントが 2 倍になります。ほとんどの場合、このオプションは無効にしておくことをおすすめします。実行時にテクスチャを生成している場合は、Texture2D.Apply で、makeNoLongerReadabletrue に設定することで、このオプションを強制的に無効にします。
  • 不要なミップマップを無効にする:ミップマップは、2D スプライトや UI グラフィックスなど、画面上で一定の大きさを保つテクスチャには必要ありません(カメラからの距離が変化する 3D モデルには、ミップマップを有効にしておきます)。
Editor screenshot
テクスチャのインポート設定を適切に行うことで、ビルドサイズを最適化することができる。

テクスチャを圧縮する

同じモデル、同じテクスチャーを使った 2 つの例を考えてみましょう。左側の設定は、右側の設定に比べて約 8 倍のメモリを消費しますが、画質はそれほど向上しません。

Screenshot
非圧縮のテクスチャは、より多くのメモリを必要とする。

iOS と Android の両方で Adaptive Scalable Texture Compression(ATSC)を使いましょう。現在開発中のゲームの大半は、ATSC 圧縮に対応した最小スペックのデバイスをターゲットにしています。

ただし、以下の場合は例外です。

  • A7 チップ搭載デバイスやそれ以前のデバイスを対象とした iOS ゲーム(例:iPhone 5、5S など)。この場合は PVRTC を使ってください。
  • 2016 年以前のデバイスを対象とした Android ゲーム。この場合は ETC2 (Ericsson Texture Compression)を使用してください。

PVRTC や ETC などの圧縮フォーマットでは十分な品質が得られない場合や、ターゲットプラットフォームで ASTC が完全にサポートされていない場合は、32 ビットテクスチャではなく 16 ビットテクスチャを試してみてください。

プラットフォーム別の推奨テクスチャ圧縮フォーマットについては、マニュアルを参照してください。

メッシュインポート設定を調整する

テクスチャと同様に、メッシュも慎重にインポートしないと過剰なメモリを消費します。メッシュのメモリ消費を最小限に抑えるためには、以下に挙げることを実行するようにしてください。

  • メッシュの圧縮:圧縮率を高くすることで、ディスクスペースの使用量を減らすことができます(ただし、実行時のメモリ使用量の削減効果はありません)。なお、メッシュの量子化をした結果メッシュが不正確になる可能性があるので、モデルに合わせて圧縮レベルを調整して試すようにしてください。
  • Read/Write の無効化:このオプションを有効にするとメモリ内のメッシュが複製され、メッシュのコピーがシステムメモリに 1 つ、GPU メモリに 1 つ保持されるようになります。ほとんどの場合、これを無効にしたほうがよいでしょう(Unity 2019.2 以前では、このオプションはデフォルトでチェックされています)。
  • リグとブレンドシェイプの無効化:スケルトンやブレンドシェイプのアニメーションを必要としないメッシュの場合は、可能な限りこれらのオプションを無効にしてください。
  • 法線と接線を無効にする:メッシュのマテリアルに法線や接線が必要ないことが確実な場合は、これらのオプションのチェックを外しておくと、さらにリソースを節約できます。
Editor screenshot
メッシュのインポート設定を確認する。

ポリゴン数を確認する

解像度の高いモデルはメモリ使用量が多く、GPU の処理時間も長くなる可能性があります。背景のジオメトリに 50 万ポリゴンも必要でしょうか。DCC パッケージの中のモデルで、削れるものがないか検討しましょう。カメラ視点で見えないポリゴンを削除したり、高密度のメッシュの代わりにテクスチャや法線マップで細かいディテールを表現できないか検討したり、工夫できる部分があります。

AssetPostprocessor 使ってインポート設定を自動化する

AssetPostprocessor を使って、アセットをインポートする際にスクリプトを実行することができます。モデル、テクスチャ、オーディオなどを読み込む前後に、設定をカスタマイズするよう促されます。

Addressable アセットシステムを使う

Addressable アセットシステムは、コンテンツを管理するためのシンプルな方法を提供します。この統一されたシステムでは、アセットバンドルを「アドレス」またはエイリアスごとに、ローカルのパスまたはリモートのコンテンツデリバリーネットワーク(CDN)から非同期的に読み込みます。

Editor screenshot

コード以外のアセット(モデル、テクスチャ、プレハブ、オーディオ、さらにはシーン全体)をアセットバンドルに分割すれば、ダウンロードコンテンツ(DLC)として分離することができます。

その後、Addressables を使用して、モバイルアプリケーションの小規模な初期ビルドを作成します。Cloud Content Delivery を使えば、ゲームのコンテンツをホストし、ゲームの進行に合わせてプレイヤーに配信することができます。

Screenshot
Addressable アセットシステムを使って、「アドレス」ごとにアセットを読み込むことができる。

こちらをクリックして、Addressable アセットシステムを使って煩わしいアセット管理を単純化する方法をご覧ください。

グラフィックスと GPU の最適化

フレームごとに、Unity はレンダリングすべきオブジェクトを決定し、ドローコールを作成します。ドローコールとは、オブジェクト(三角形など)を描画するためのグラフィックス API への呼び出しであり、バッチとは、まとめて実行するドローコールのグループです。

プロジェクトが複雑になってくると、GPU のワークロードを最適化するパイプラインが必要になります。 ユニバーサルレンダーパイプライン(URP) は、現状シングルパスのフォワードレンダラーを使用して、高品質なグラフィックスをモバイルプラットフォームに提供しています(将来のリリースでは、ディファードレンダリングが可能になる予定です)。家庭用ゲーム機や PC で使われている物理ベースのライティングやマテリアルを、スマートフォンやタブレットでも使うことができます。

以下のガイドラインは、グラフィックスを高速化するのに役立ちます。

ドローコールをバッチにまとめる

描画するオブジェクトをバッチにまとめておくことで、バッチ内の各オブジェクトの描画に必要な状態変化を最小限に抑えることができます。これにより、オブジェクトをレンダリングするための CPU コストが削減され、パフォーマンスの向上につながります。Unity では、複数のオブジェクトをより少ないバッチにまとめるテクニックがいくつか存在します。

  • 動的バッチ処理:小さなメッシュの場合、Unity は CPU 上で頂点をグループ化して変換した後、一度にすべてを描画することができます。注:このテクニックはポリゴン数が十分に小さいメッシュ(頂点アトリビュートが 900 以下、頂点数が 300 以下)に対してのみ使うようにしてください。Dynamic Batcher はこれより大きなメッシュをバッチ処理しないため、大きなメッシュに対して有効にすると、毎フレームバッチ処理する小さなメッシュを探すようになり、CPU 時間を浪費することになります。
  • 静的バッチ処理:動かないジオメトリについては、Unity は同じマテリアルを共有するメッシュのドローコールを減らすことができます。動的バッチ処理よりも効率的ですが、より多くのメモリを使用します。
  • GPU インスタンシング: 同一のオブジェクトが多数ある場合、グラフィックスハードウェアを使用してより効率的にバッチ処理を行う手法です。
  • SRP バッチ処理:Advanced 設定から、SRP Batcherユニバーサルレンダーパイプラインのアセットに対して有効にします。これにより、シーンにもよりますが、CPU のレンダリング時間を大幅に短縮することができます。
Editor screenshot
上記のバッチ処理に関するテクニックを利用するためにゲームオブジェクトを整理する。

フレームデバッガーを使う

フレームデバッガーを使うと、各フレームが個別のドローコールによってどのように構成されているかが分かります。これは、シェーダーのプロパティのトラブルシューティングを行い、ゲームがどのようにレンダリングされるかを分析するのに役立つ非常に貴重なツールです。

Editor
フレームデバッガーでは、各フレームをそれぞれのステップに分けて表示する。

フレームデバッガーを使うのが初めての方は、こちらの初心者向けチュートリアルをご覧ください。

動的ライトを多用しない

モバイルアプリケーションには、あまり多くの動的ライトを追加しないようにすることが重要です。動的メッシュにはカスタムシェーダーによるエフェクトやライトプローブ、静的メッシュにはベイク済みライトを使うなどの代替手段を検討してください。

URP とビルトインパイプラインそれぞれにおけるリアルタイムライトの限界については、こちらの機能比較表を参照してください。

シャドウを無効にする

シャドウキャストは、MeshRenderer とライトごとに個別に無効にすることができます。可能な限りシャドウを無効にして、ドローコールを減らします。 

また、キャラクターの下に置いた単純なメッシュや四角形にぼかしたテクスチャを適用して、影に見せかけた表現をすることもできます。それ以外にも、カスタムシェーダーでブロブシャドウを作るという方法があります。

Editor screenshot
シャドウキャスティングを無効にして、ドローコールを減らす。

ライティング情報をライトマップにベイクする

グローバルイルミネーション(GI)を使用して、静的なジオメトリにドラマチックなライティングを付加しましょう。オブジェクトを Contribute GI でマークすることで、高品質なライティングをライトマップの形で保存することができます。

Contribute GI を有効にする。

ベイクされたシャドウとライティングは、実行時のパフォーマンスを低下させることなくレンダリングできます。プログレッシブ CPU/GPU ライトマッパーは、グローバルイルミネーションのベイクを高速化することができます。

 

Editor screenshot
ライトマップの設定(Windows > Rendering > Lighting Settings)とライトマップのサイズを調整して、メモリ使用量を制限する。

マニュアルガイドとライティングの最適化に関するこちらの記事を参考にして、Unity でライトマッピングを始めてみましょう。

ライトレイヤーを使う

複数のライトを使用する複雑なシーンでは、レイヤーでオブジェクトを分離し、各ライトの影響を個別のカリングマスクに絞り込みます。 

Editor screenshot
レイヤーでは、ライトの影響を個別のカリングマスクに限定することができる。

動くオブジェクトにはライトプローブを使う

ライトプローブは、高品質なライティング(直接光、間接光)を提供しながら、シーン内の何もない空間のライティング情報をベイクして保存します。球面調和関数を使っているので、動的ライトに比べて計算が非常に速いです。

Editor screenshot
ライトプローブは、背景にある動的なオブジェクトを照らす。

Level of Detail(LOD)を使う

オブジェクトが遠くに移動すると、Level of Detail は、GPUのパフォーマンスを向上させるために、よりシンプルなマテリアルとシェーダーで、よりシンプルなメッシュを使用するように、オブジェクトを調整または切り替えます。

Editor screenshot
LOD グループを使用したメッシュの例。
Screenshot
さまざまな解像度でモデル化されたソースメッシュ。

オクルージョンカリングで隠れたオブジェクトを消す

他のオブジェクトの後ろに隠れているオブジェクトも裏でレンダリングされ、リソースを消費します。こうしたオブジェクトはオクルージョンカリングで消しましょう。 

カメラの視野外の錐台カリングは自動で行われますが、オクルージョンカリングはベイク工程で行われます。オブジェクトを Static Occluders または Occludees としてマークし、Window > Rendering > Occlusion Cullingダイアログでベイクするだけオクルージョンカリングの設定が行えます。すべてのシーンで必要というわけではありませんが、カリングによってパフォーマンスが向上するケースも多数あります。

詳しくは、「オクルージョンカリングの使用方法」チュートリアルをご覧ください。

モバイルのネイティブ解像度を使わない

スマホやタブレットの進化に伴い、新しい機種では高解像度化が進んでいます。 

Screen.SetResolution(width, height, false) を使用して出力解像度を下げ、パフォーマンスをいくらか回復させましょう。複数の解像度でプロファイルを取ることで、画質と速度の最適なバランスを見つけることができます。

カメラの使用を制限する

カメラはそこにあるだけで、何か意味のある仕事をしているかどうかに関わらずオーバーヘッドを発生させます。レンダリングに必要な Camera コンポーネントだけを使うようにしましょう。ローエンド寄りのモバイルプラットフォームでは、カメラ 1 つあたり最大で 1ms の CPU 時間を消費することもあります。

シェーダーをシンプルに保つ

ユニバーサルレンダーパイプラインには、モバイルプラットフォーム向けに最適化された軽量の Lit および Unlit シェーダーがいくつか含まれています。実行時のメモリ使用量に劇的な影響を与えるので、シェーダーのバリエーションはできるだけ少なくするようにしてください。URP のデフォルトシェーダーでは物足りないという方は、シェーダーグラフを使ってマテリアルの見た目をカスタマイズすることができます。シェーダーグラフを使ってシェーダーを視覚的に構築する方法はこちらをご覧ください。

 

Screenshot
シェーダーグラフでカスタムシェーダーを作成する。

オーバードローとアルファブレンディングを出来るだけ抑える

不要な透明や半透明の画像は描かないようにしましょう。モバイルプラットフォームでは、オーバードローやアルファブレンディングの影響を大きく受けます。ほとんど見えないような画像やエフェクトが重ならないようにします。RenderDoc グラフィックデバッガーを使ってオーバードローのチェックを行えます。

ポストプロセッシングエフェクトを制限する

グローなどのポストプロセッシングエフェクトを全画面にかけると、パフォーマンスが劇的に低下します。タイトルのアートディレクションに使いたくなるかもしれませんが、慎重に検討した上で使うようにしてください。

Editor screenshot
モバイルアプリケーションでのポストプロセッシングエフェクトをシンプルに保つ。

Renderer.material に気を付ける

スクリプトで Renderer.material にアクセスすると、マテリアルが複製され、新しいコピーへの参照が返されます。これにより、すでにマテリアルが含まれている既存のバッチが壊れることになります。バッチされたオブジェクトのマテリアルにアクセスしたい場合は、代わりに Renderer.sharedMaterial を使うようにしましょう。

SkinnedMeshRenderer を最適化する

スキンドメッシュのレンダリングにはコストがかかります。SkinnedMeshRenderer を使用しているすべてのオブジェクトが、本当にそれを必要としているか確認しましょう。時々アニメーションする程度のゲームオブジェクトの場合は、BakeMesh 関数を使ってスキンドメッシュを固定し、実行時にはよりシンプルな MeshRenderer に切り替えます。

リフレクションプローブを最小化する

リフレクションプローブを使うとリアルな反射表現が可能になりますが、バッチのコストが非常に高くなります。実行時のパフォーマンスを向上させるために、低解像度のキューブマップ、カリングマスク、およびテクスチャ圧縮を活用しましょう。

モバイルゲームのパフォーマンス向上に関するガイド全体をダウンロードする

このブログ記事は、モバイルパフォーマンス最適化シリーズの最終回となります。ヒントとコツの完全なリストを見てみたい方は、こちらのページからフル版の e ブックをダウンロードしてご覧ください。 

Ebook

e ブック(英語)をダウンロードする

Integrated Support サービスについてもっと知りたい、あるいはチームにエンジニアと直接やりとりする窓口や、専門家のアドバイスやプロジェクトのベストプラクティスガイダンスを提供したいとお考えの方は、こちらのページでご案内している Unity のサクセスプランをご検討ください。

お探しのものは見つかりましたか?

私たちは、お客様のUnityアプリケーションを可能な限りパフォーマンスの高いものにするお手伝いをしたいと考えています。もっと知りたい最適化のトピックがありましたら、ぜひコメントで教えてください。 

2021年8月3日 カテゴリ: テクノロジー | 12 分 で読めます
関連する投稿