Integrated Success チームは、Unity の顧客が抱える複雑な技術的問題をサポートします。シニアソフトウェアエンジニアで構成されるこのチームに、モバイルゲームの最適化に関する知見を聞かせてもらいました。
当社の Accelerate Solutions チームはソースコードを熟知しており、無数に存在する Unity の顧客みんながエンジンを最大限に活用できるようサポートしています。このチームは、クリエイターのプロジェクトに深く入り込み、パフォーマンスを最適化することで、スピード、安定性、効率性を向上させるポイントを見つけ出します。 Unity のエンジニアたちがモバイルゲームの最適化に関する知見のシェアを始めるとすぐに、元々予定していた 1 本のブログ記事に収まりきらないほどの素晴らしい情報があることに気づきました。そこで私たちは、彼らの膨大な知識を e ブックとしてまとめ(ダウンロードはこちら)、そこに収録されている 75 以上の実用的なヒントの一部を紹介する一連のブログ記事を作成することにしました。
最適化に関する本シリーズの最終回となるこの記事では、アセット、プロジェクト構成、グラフィックスのパフォーマンスを向上させる方法に焦点を当てます。ゲームの最適化については、プロファイリング、メモリ、コードアーキテクチャ、物理、UI、オーディオについての過去の記事をご覧ください。また、無料の e ブックをダウンロードすると、これらのすべてのトピックに関する内容に触れることができます。
モバイルパフォーマンスに影響を与えるプロジェクト設定はいくつかあります。
Unity では、携帯電話の加速度センサーからのデータを 1 秒間に数回プールしています。アプリケーションで使用していない場合は無効にしたり、パフォーマンスを向上させるために周波数を下げたりします。
Player の設定で、Auto Graphics API をサポートされていないプラットフォームでは無効にして、過剰なシェーダーバリアントの生成を防ぎます。古い CPU をアプリケーションでサポートしない場合は、Target Architectures からその CPU を外してください。
Quality の設定で、必要のない Quality のレベルを無効にします。
ゲームが物理演算を使用していない場合は、Auto Simulation と Auto Sync Transforms のチェックを外してください。チェックを付けていてもアプリケーションを遅くするだけで、目に見えた効果はありません。
モバイルプロジェクトでは、バッテリー消費やサーマルスロッティングの対策を考えつつ、ちょうどいいフレームレートを定める必要があります。デバイスの限界に挑戦して 60fps を出すより、30fps での運用で妥協することを検討したほうがいいこともあります。Unity のモバイルプラットフォームのデフォルトは 30fps です。
また、Application.targetFrameRate で実行時にフレームレートを動的に調整することもできます。例えば、ゆっくりとしたシーンや比較的静かなシーンでは 30fps 以下に落とし、ゲームプレイではより高い fps に設定するよう指定することができます。
階層構造は小分けにしましょう。ゲームオブジェクトを階層化する必要がない場合は、親子関係を単純化しましょう。階層構造を小さくしておけば、シーン内のトランスフォームの更新でマルチスレッドの恩恵を受けることができます。階層構造が複雑になると、不必要なトランスフォームの計算が発生し、ガベージコレクションのコストが増大します。
階層構造の最適化に関するベストプラクティスや、こちらの Unite 講演を見て、トランスフォームに関するベストプラクティスを学びましょう。
トランスフォームを動かす時は、Transform.SetPositionAndRotation を使って、位置と回転の両方を一度に更新するようにします。これにより、トランスフォームを 2 回修正するために起きるオーバーヘッドを回避できます。
実行時にゲームオブジェクトに対して Instantiate を使う必要がある場合、簡単な最適化として、インスタンス化の際に親子関係の設定と位置の設定を行うというものがあります。
GameObject.Instantiate(prefab, parent);
GameObject.Instantiate(prefab, parent, position, rotation);
Object.Instantiate の詳細については、スクリプティング API のドキュメントをご覧ください。
モバイル端末ではハーフフレームがレンダリングされません。エディターで Vsync を無効にしても(Project Settings > Quality)、ハードウェアレベルでは Vsync は有効になります。GPU が十分な速度で画面をリフレッシュできない場合は、現在のフレームが保持され、事実上、fps が低下します。
アセットパイプラインは、アプリケーションのパフォーマンスに大きな影響を与えます。経験豊富なテクニカルアーティストなら、アセットのフォーマット、仕様、インポート設定を定義し、それを運用することで、スムーズなプロセスを実現させます。
デフォルトの設定に頼らないようにしましょう。プラットフォーム固有のオーバーライドタブを使用して、テクスチャやメッシュジオメトリなどのアセットを最適化します。設定を誤ると、ビルドサイズが大きくなったり、ビルド時間が長くなったり、雑なメモリの使い方になってしまったりします。プリセット機能を使って、特定のプロジェクトを強化するベースライン設定をカスタマイズすることを検討してください。
このトピックについてさらに詳しく知りたい方は、アートアセットのベストプラクティスに関するガイドや、Unity Learn の「Arm と Unity 共同提供:モバイルアプリケーションのための 3D アートの最適化」コースをチェックしてください。
メモリのほとんどはテクスチャに使われるので、テクスチャのインポート設定は重要です。一般的には、以下のガイドラインに従うようにしてください。
同じモデル、同じテクスチャーを使った 2 つの例を考えてみましょう。左側の設定は、右側の設定に比べて約 8 倍のメモリを消費しますが、画質はそれほど向上しません。
iOS と Android の両方で Adaptive Scalable Texture Compression(ATSC)を使いましょう。現在開発中のゲームの大半は、ATSC 圧縮に対応した最小スペックのデバイスをターゲットにしています。
ただし、以下の場合は例外です。
PVRTC や ETC などの圧縮フォーマットでは十分な品質が得られない場合や、ターゲットプラットフォームで ASTC が完全にサポートされていない場合は、32 ビットテクスチャではなく 16 ビットテクスチャを試してみてください。
プラットフォーム別の推奨テクスチャ圧縮フォーマットについては、マニュアルを参照してください。
テクスチャと同様に、メッシュも慎重にインポートしないと過剰なメモリを消費します。メッシュのメモリ消費を最小限に抑えるためには、以下に挙げることを実行するようにしてください。
解像度の高いモデルはメモリ使用量が多く、GPU の処理時間も長くなる可能性があります。背景のジオメトリに 50 万ポリゴンも必要でしょうか。DCC パッケージの中のモデルで、削れるものがないか検討しましょう。カメラ視点で見えないポリゴンを削除したり、高密度のメッシュの代わりにテクスチャや法線マップで細かいディテールを表現できないか検討したり、工夫できる部分があります。
AssetPostprocessor を使って、アセットをインポートする際にスクリプトを実行することができます。モデル、テクスチャ、オーディオなどを読み込む前後に、設定をカスタマイズするよう促されます。
Addressable アセットシステムは、コンテンツを管理するためのシンプルな方法を提供します。この統一されたシステムでは、アセットバンドルを「アドレス」またはエイリアスごとに、ローカルのパスまたはリモートのコンテンツデリバリーネットワーク(CDN)から非同期的に読み込みます。
コード以外のアセット(モデル、テクスチャ、プレハブ、オーディオ、さらにはシーン全体)をアセットバンドルに分割すれば、ダウンロードコンテンツ(DLC)として分離することができます。
その後、Addressables を使用して、モバイルアプリケーションの小規模な初期ビルドを作成します。Cloud Content Delivery を使えば、ゲームのコンテンツをホストし、ゲームの進行に合わせてプレイヤーに配信することができます。
こちらをクリックして、Addressable アセットシステムを使って煩わしいアセット管理を単純化する方法をご覧ください。
フレームごとに、Unity はレンダリングすべきオブジェクトを決定し、ドローコールを作成します。ドローコールとは、オブジェクト(三角形など)を描画するためのグラフィックス API への呼び出しであり、バッチとは、まとめて実行するドローコールのグループです。
プロジェクトが複雑になってくると、GPU のワークロードを最適化するパイプラインが必要になります。 ユニバーサルレンダーパイプライン(URP) は、現状シングルパスのフォワードレンダラーを使用して、高品質なグラフィックスをモバイルプラットフォームに提供しています(将来のリリースでは、ディファードレンダリングが可能になる予定です)。家庭用ゲーム機や PC で使われている物理ベースのライティングやマテリアルを、スマートフォンやタブレットでも使うことができます。
以下のガイドラインは、グラフィックスを高速化するのに役立ちます。
描画するオブジェクトをバッチにまとめておくことで、バッチ内の各オブジェクトの描画に必要な状態変化を最小限に抑えることができます。これにより、オブジェクトをレンダリングするための CPU コストが削減され、パフォーマンスの向上につながります。Unity では、複数のオブジェクトをより少ないバッチにまとめるテクニックがいくつか存在します。
フレームデバッガーを使う
フレームデバッガーを使うと、各フレームが個別のドローコールによってどのように構成されているかが分かります。これは、シェーダーのプロパティのトラブルシューティングを行い、ゲームがどのようにレンダリングされるかを分析するのに役立つ非常に貴重なツールです。
フレームデバッガーを使うのが初めての方は、こちらの初心者向けチュートリアルをご覧ください。
モバイルアプリケーションには、あまり多くの動的ライトを追加しないようにすることが重要です。動的メッシュにはカスタムシェーダーによるエフェクトやライトプローブ、静的メッシュにはベイク済みライトを使うなどの代替手段を検討してください。
URP とビルトインパイプラインそれぞれにおけるリアルタイムライトの限界については、こちらの機能比較表を参照してください。
シャドウを無効にする
シャドウキャストは、MeshRenderer とライトごとに個別に無効にすることができます。可能な限りシャドウを無効にして、ドローコールを減らします。
また、キャラクターの下に置いた単純なメッシュや四角形にぼかしたテクスチャを適用して、影に見せかけた表現をすることもできます。それ以外にも、カスタムシェーダーでブロブシャドウを作るという方法があります。
グローバルイルミネーション(GI)を使用して、静的なジオメトリにドラマチックなライティングを付加しましょう。オブジェクトを Contribute GI でマークすることで、高品質なライティングをライトマップの形で保存することができます。
Contribute GI を有効にする。
ベイクされたシャドウとライティングは、実行時のパフォーマンスを低下させることなくレンダリングできます。プログレッシブ CPU/GPU ライトマッパーは、グローバルイルミネーションのベイクを高速化することができます。
動くオブジェクトにはライトプローブを使う
ライトプローブは、高品質なライティング(直接光、間接光)を提供しながら、シーン内の何もない空間のライティング情報をベイクして保存します。球面調和関数を使っているので、動的ライトに比べて計算が非常に速いです。
オブジェクトが遠くに移動すると、Level of Detail は、GPUのパフォーマンスを向上させるために、よりシンプルなマテリアルとシェーダーで、よりシンプルなメッシュを使用するように、オブジェクトを調整または切り替えます。
他のオブジェクトの後ろに隠れているオブジェクトも裏でレンダリングされ、リソースを消費します。こうしたオブジェクトはオクルージョンカリングで消しましょう。
カメラの視野外の錐台カリングは自動で行われますが、オクルージョンカリングはベイク工程で行われます。オブジェクトを Static Occluders または Occludees としてマークし、Window > Rendering > Occlusion Cullingダイアログでベイクするだけオクルージョンカリングの設定が行えます。すべてのシーンで必要というわけではありませんが、カリングによってパフォーマンスが向上するケースも多数あります。
詳しくは、「オクルージョンカリングの使用方法」チュートリアルをご覧ください。
スマホやタブレットの進化に伴い、新しい機種では高解像度化が進んでいます。
Screen.SetResolution(width, height, false) を使用して出力解像度を下げ、パフォーマンスをいくらか回復させましょう。複数の解像度でプロファイルを取ることで、画質と速度の最適なバランスを見つけることができます。
カメラはそこにあるだけで、何か意味のある仕事をしているかどうかに関わらずオーバーヘッドを発生させます。レンダリングに必要な Camera コンポーネントだけを使うようにしましょう。ローエンド寄りのモバイルプラットフォームでは、カメラ 1 つあたり最大で 1ms の CPU 時間を消費することもあります。
ユニバーサルレンダーパイプラインには、モバイルプラットフォーム向けに最適化された軽量の Lit および Unlit シェーダーがいくつか含まれています。実行時のメモリ使用量に劇的な影響を与えるので、シェーダーのバリエーションはできるだけ少なくするようにしてください。URP のデフォルトシェーダーでは物足りないという方は、シェーダーグラフを使ってマテリアルの見た目をカスタマイズすることができます。シェーダーグラフを使ってシェーダーを視覚的に構築する方法はこちらをご覧ください。
不要な透明や半透明の画像は描かないようにしましょう。モバイルプラットフォームでは、オーバードローやアルファブレンディングの影響を大きく受けます。ほとんど見えないような画像やエフェクトが重ならないようにします。RenderDoc グラフィックデバッガーを使ってオーバードローのチェックを行えます。
グローなどのポストプロセッシングエフェクトを全画面にかけると、パフォーマンスが劇的に低下します。タイトルのアートディレクションに使いたくなるかもしれませんが、慎重に検討した上で使うようにしてください。
スクリプトで Renderer.material にアクセスすると、マテリアルが複製され、新しいコピーへの参照が返されます。これにより、すでにマテリアルが含まれている既存のバッチが壊れることになります。バッチされたオブジェクトのマテリアルにアクセスしたい場合は、代わりに Renderer.sharedMaterial を使うようにしましょう。
スキンドメッシュのレンダリングにはコストがかかります。SkinnedMeshRenderer を使用しているすべてのオブジェクトが、本当にそれを必要としているか確認しましょう。時々アニメーションする程度のゲームオブジェクトの場合は、BakeMesh 関数を使ってスキンドメッシュを固定し、実行時にはよりシンプルな MeshRenderer に切り替えます。
リフレクションプローブを使うとリアルな反射表現が可能になりますが、バッチのコストが非常に高くなります。実行時のパフォーマンスを向上させるために、低解像度のキューブマップ、カリングマスク、およびテクスチャ圧縮を活用しましょう。
このブログ記事は、モバイルパフォーマンス最適化シリーズの最終回となります。ヒントとコツの完全なリストを見てみたい方は、こちらのページからフル版の e ブックをダウンロードしてご覧ください。
Integrated Support サービスについてもっと知りたい、あるいはチームにエンジニアと直接やりとりする窓口や、専門家のアドバイスやプロジェクトのベストプラクティスガイダンスを提供したいとお考えの方は、こちらのページでご案内している Unity のサクセスプランをご検討ください。
私たちは、お客様のUnityアプリケーションを可能な限りパフォーマンスの高いものにするお手伝いをしたいと考えています。もっと知りたい最適化のトピックがありましたら、ぜひコメントで教えてください。