アマゾンバナーリンク

ディスプレイ広告

スポンサーリンク

【Unity】高速化・効率化のレシピ 9選

こんにちは!ジェイです。Unityを普段使っていて、高速化を意識することはあまりないでしょう。Unity自体が大幅に効率化できるツールであり、昔と違ってその他の開発環境もどんどん良くなっています。

ですが、すべてを最適化してくれるわけではないので、プログラマーが意識すべき点について解説します。

目次

記事内広告

プロファイラを見る

まず最初にプロファイラを見ることが大事です。これを見たらどこがボトルネックになっているのかを特定できます。

window>Analysis>Profilerで開けます。

プロファイラを起動して、Recordをクリックするとデバッグモードになり、各種グラフを見れるようになります。ここで重要なのがCPU Usageです。これはCPUの使用率やフレームレートなどの情報を表示してくれます。

Rendering

緑色のグラフは描画にかかった時間を表してます。主に不透明・透明のオブジェクトの描画、ポストプロセス処理、描画処理にかかった時間などがここに表示されます。ここがボトルネックになっている場合は、描画系の処理を見直さなければならないことになります。

もし、Renderingがボトルネックになっているのであれば、次はGPU UsageとRenderingを見ます。GPU Usageはデフォルトでは隠れているので、AddProfilerからGPUを選び、GPUプロファイリングを有効にしてください。すると描画の詳細のグラフが表示されます。

GPUプロファイルとRenderingプロファイルの詳細及び対処法は、描画関連を見直すシェーダ・マテリアル関連を見直すライト・シャドウを見直すを参照してください。

Scripts

青色のグラフはスクリプト処理にかかった時間を表しています。作成したスクリプト及び、UnityビルトインスクリプトなどがScriptsに含まれています。ここがボトルネックになっている場合はスクリプト周りを見直さなければなりません。

メモリ関連を見直すコーディングを見直すを参照。

Physics

オレンジ色のグラフは物理処理にかかった時間を表します。Rigidbody、衝突判定、レイキャスト、トリガーなどがここに含まれます。このグラフは割と乱れやすい場所であるので、物理処理を多用してる場合は、注意しましょう。

物理関連を見直すを参照

Animation

水色のグラフはアニメーションにかかった時間を表しています。AnimatorやAnimationやFK,IKなどがここに含まれます。アニメーション数やボーンやIKの構造が複雑だと乱れやすくなります。

アニメーション関連を見直すを参照

GarbageCollector

木土色の様な色のグラフはガベージコレクションにかかった時間ら表しています。メモリ管理は確保と解放処理があり、基本的には自動的にやってくれますが、見直すべき場合もあります。

メモリ管理を見直すを参照

UI

紫色のグラフはUI全般にかかった時間を表しています。UIのレイアウトや描画などがここに含まれます。テキストやテクスチャなどを多用すると重くなる事が多いです。

UI関連を見直す描画関連を見直すシェーダ・マテリアル関連を見直すを参照

Vsync,Global lllmination,Other

上記以外のものは気にしなくても問題ないですが説明すると

  • Vsync リフレッシュレートとCPUを待機させている時間
  • Global lllmination 様々な照明にかかった時間で非常に重いです
  • Other それ以外にかかった時間。内訳がわからないので考えなくても大丈夫です

以上の項目を見てボトルネックが判明したら、以下の見直しをしていきます。

Qualityを見直す

そんなに重要なところではないですが、UnityのQualityを見直すだけでも動作が改善される場合があります。スマホ向けにリリースする場合には気をつけた方がよいです。

Edit>Project Settings>Qualityを選択すると以下のような画面になります。

Pixel Light Countをなるべく小さくする

もし、プロジェクト内にライトを使っていない場合は0にしてください。使っている場合は、小さくすることによってライト処理が軽くなります。

Texture Qualityを下げる

Half Res以下にするのがおすすめです。

Anisotropic Texturesをoffにする

異方性フィルタリングを扱わない場合はDisableにしましょう。

Anti Aliasingをoffにする

ジャギー(ギザギザ)が気にならなければDisableにしましょう。

Soft Particlesをoffにする

ポリゴンとの境界線にパーティクルを描画しないのなら、Soft Particleは必要ないです。

Realtime Reflection Probesをoffにする

リアルタイムに反射するレンダリング方法です。必要ないならDisableにしましょう。

Resolution Scaling Fixed DPI Factorを下げる

解像度や描画負荷についての項目で下げると負荷も下がります。

Texture Streamingをoffにする

普段は使うことはありません。offにしましょう。

Shadowsを無効にする

もし影を描画する必要がない場合は、ShadowsをDisable Shadowsにすると影の計算が省かれて軽くなります。

Shadow Resolutionを下げる

影の深度バッファ解像度の事です。

Blend Weightsを減らす

スキニングのボーンブレンド数です。スキンメッシュアニメーションが繊細でなくてよいのであれば、1Boneにしましょう。

VSync Countを無効にする

画面のリフレッシュレートに合わせて同期させるかの設定です。自分の開発するゲームにあったフレームレートを設定すべきなので、Don`t Syncにしましょう。以下のようにスクリプトからフレームレートを決められます。

Application.targetFrameRate = 60;

Particle Raycast Budgetを下げる

パーティクル衝突レイキャストの最大数で、パーティクルの品質を求めてないのであれば下げましょう。

UI関連を見直す

オブジェクトの描画処理はとても高負荷です。UI描画の負荷の軽減し、最適化するには、キャンパスの仕様とバッチングについての理解が必要です。

静的と動的なCanvasを分ける

UIキャンバスは、内容に変化がなければ描画負荷はあまりかかりません。Unityでは無駄を省くために同じ結果の計算を毎回するような事はしない仕様になってます。逆に変化がある場合は、キャンパス内のすべてが再構築されます。

そして、変化のないキャンパスを静的キャンパスとして、変化の多い動的キャンパスに分けています。 こうして、動的な部分だけ再計算できます。

テクスチャをまとめる

バッチングとは、まとめて描画することです。UIは同一マテリアルかつ同一テクスチャのオブジェクトをまとめて描画することで効率化をしてます。

このために、同じテクスチャをたくさん描画しても、描画不可はあまり変わりません。しかし、逆にマテリアルやテクスチャが変化するとバッチングは効かなくなります。 このため、最適化するには、なるべく同一マテリアルかつ同一テクスチャする必要があります。しかし、マテリアルは共通化できても、テクスチャを共通化するのは通常はできません。

UnityにはSprite Atlasという機能があり、これによりテクスチャを1つにまとめます。このおかげで、複数テクスチャが1つのテクスチャになるので、あとはマテリアルの変更をしない限り、1度のバッチでまとめて描画することができるようになるので、上手く活用しましょう。

UIはこの2つを意識するだけで十分最適化できます。

アニメーション関連を見直す

アニメーションの負荷はボーンのスキニング負荷と同じようなものです。なので、スキニング負荷を抑えるのを意識なければなりません。

ボーンの数を減らす

これが一番楽な方法ですが、スキニング負荷が減るので、かなりの効果があります。なので、モデルのボーン構造が重要となります。例えば、カメラの範囲内でない所にボーンが入っていると、その分無駄な処理が発生しまするので、モデルのポリゴン削減だけでなく、ボーンの削減も同時に考えましょう。

IKをなるべく使わない

IK(インバースキネマティクス)は負荷の高い処理です。ボーンの先からボーンの根元まで、再帰処理で計算を繰り返すので、通常のスキニングより処理負荷が高いです。なので、どうしてもIKが必要な場面以外は、IKをFKに焼いてIKを無くした方が良いです。

画面内のオブジェクトのみアニメーションさせる

画面外にあるモデルをアニメーションさせるのは、非常に無駄な処理ですので、画面内のモデルのみアニメーションすべきです。この設定は簡単にでき、Skinned Mesh RendererのUpdate When Offscreenをoffにするだけです。デフォルトでoffになっていますが、確認しておきましょう。

GPUにスキニングさせる

スキニングは通常CPUが担当します。GPUで計算させるとなかなかしんどいコーディングが必要になるからです。しかし、Unityではチェックボックス1つでGPUにスキニングさせられます。元々GPUは行列演算などの処理に特化しているので、高速化が望めます。

プレイヤー設定のOther SettingsにGPU Skinningという項目があるので、オンにすると自動的にGPUスキニングになります。

もしかしたら、逆にGPU負荷が高くなりすぎて処理が遅くなってしまうことがあります。CPUとGPUの負荷を見てちょうど良いバランスで設定するようにしましょう。

物理関連を見直す

物理関連を最適化するには、物理エンジン特性を理解する必要があります。

Rigidbody, Collider, Jointは最低限にする

まず、Unityは物理エンジンとしてPhysXを採用しています。物理エンジンは基本的に物理世界を内部で作り、RigidbodyやColliderを登録し、毎フレームシミュレーションを行い、計算結果を返します。

レイキャストは個別に受け付けていますが、UnityのコンポーネントであるRigidbodyやColliderは、内部でPhysXのRigidbodyとColliderと結びついています。

様々な計算をしてくれる物理エンジンですが、当然のことながら、かなり重いです。RigidbodyやColliderが増えると計算量が増えますし、中でもRigidbodyは重いです。

なので、無駄に物理系のコンポーネントを増やすのは控えましょう。複雑な計算が必要でなく、自前で簡単に計算できるようであれば、そうした方が当然軽くなります。

なるべく動かさない

RigidbodyやColliderの数が多くても負荷がかからない様にする方法があります。それは、動かさないことです。

物理エンジンはstaticもしくは、動かないと判断したオブジェクトはsleep状態に入り無駄な物理処理を行わない様になるので、なるべく物理オブジェクトは動かさない様にしましょう。

具体的には、UseGravtyをfalseにしたり、Iskinematicをtrueにするなどで動かなくなります。

シーンの途中でRigidbodyやColliderを追加しない

シーンの開始ではなく、途中でAddComponentで、RigidbodyやColliderを追加したくなる場合もあるかもしれませんが控えましょう。

Unityコンポーネントとして追加だけでなく、物理エンジンにも初期登録処理を行うので、かなり高負荷な処理になります。

対策としては以下のように、RigidbodyとColliderを途中で追加するのではなく、シーンの初期化に無効にしておいて

GetComponent<Rigidbody>().isKinematic = true;
GetComponent<Collider>().enabled = false;

RigidbodyやColliderを使いたい時に有効にします。

GetComponent<Rigidbody>().isKinematic = false;
GetComponent<Collider>().enabled = true;

精度を下げて軽くする

Unityの物理エンジンの精度はプロジェクト設定から変更することができます。Edit>Project Settings>Qualityから、物理エンジンの設定ができます。
基本的にはいじらない方が良いですが、精度を細かく調整したい場合は、以下の4つを設定しましょう。

Sleep Threshold

剛体がスリープ状態に入る運動エネルギーの閾値でデフォルトは0.005です。この値を下回った剛体はスリープ状態に入り止まります。この値を上げると物理オブジェクトが、スリープ状態に入りやすくなるので負荷が下がります。

Default Contact Offset

コライダー同士が衝突したと判定する距離です。デフォルトは0.01です。この値を上げると衝突判定した距離が離れるので、負荷が下がります。

Default Solver Iterations

ジョイントや重なり合った剛体同士の物理的な、相互作用を行うソルバーの処理数です。デフォルトは6で、この値を上げると不安定な物理挙動が少なくなりますが、この値も、物理挙動が変に見えない程度に下げましょう。

Fixed Timestep

この項目はProject Settings>Timeにあります。Time.fixedDeltaTimeの値であり、物理シミュレーションのフレームレートを表します。デフォルトは0.02で、物理シミュレーションは50fpsであることが分かります。

60fpsのゲームを想定しているのであれば0.02のままで良いですが、30fpsのゲームの場合は、無駄な物理シュミレーションを避けるために、0.04に設定してあげるのがいいでしょう。

メモリ関係を見直す

メモリ管理はC#ではGCCがあるので、自動的に確保や解放をしてくれますし、Unityでは特に意識する事は少ないです。しかしUnity内部では、Instantiateはmallocと同じでヒープに動的に領域を確保します。

Destroyを行うとDisposeが走り、使用されなくなったヒープが一定サイズ以上に達すると動的にGCが走ってしまいます。スタック領域やヒープ領域などの特性を理解する事がメモリ管理には必要な知識になります。

確保されたメモリを認識し、減らすことを心掛ける

メモリ周りを見直す前に、自分のゲームがどの程度メモリを使っているのか認識しましょう。スマホ向けであれば、メモリ容量2GBに対して1GBを超えるような大きなメモリを確保すると、動作が不安定になったりします。

プロファイラのMemoryを選択すると以下の様な表示がされます。これを見てメモリにどれくらいのリソースが確保されているか確認できます。

上記のUsed Totalが全体でどの位のメモリを使っているかを示しています。この値が大きかったり乱高下していたりするとGCでメモリの確保や解放が、頻繁に発生してる事になります。

その他の項目はメモリの内訳となってるので、極端に多い部分があったら見直しましょう。

プール・キャッシュを多用する

メモリはなるべくシーンの開始時に一気に確保・解放し、シーン中はできる限り確保・解放の回数を減らしたほうが負荷が減ります。そのために、プールやキャッシュなど、最初にデータを必要な分だけ用意しておいて、使い回すことによってGCCが発生する頻度と負荷を減らす事ができます。

アセットのアンロード

アセットのアンロードをするとメモリリークをなくせるので、メモリ領域を増やす事ができます。

描画関係を見直す

Unityの中で恐らく一番重いであろう処理は描画です。描画周りの最適化は、シェーダの特性を知っていれば知っている程最適化することができます。ぜひ今一度見直しましょう。

描画に関する用語

描画に関する用語はたくさんあります。プロファイルのGPUとRenderingでも記載されている用語は知っておきましょう。以下はUnityの描画を最適化するのに必要なものです。

中には、マテリアルやシェーダも含むので、詳細は シェーダ・マテリアル関連を見直す を参照してください。

Opaque

不透明描画を表しますが、基本的な描画はこれに当てはまります。

Transparent

透明描画を指す。不透明描画よりコストがかかります。

Post Process

画面全体のエフェクトを指す。高負荷なので、なるべく使わない方がよいです。

Batch

同一マテリアルである等のバッチング条件を満たした結合メッシュ単位の描画処理を指す。バッチ数を減らすこと自体が描画負荷を下げる事になります。

Set Pass Call

マテリアルの設定値をシェーダ側に伝える処理を指す。シーン上のマテリアル数がここに影響してくる。このコール数が少ないほどパフォーマンスが上昇します。

Triangles

所謂ポリゴン数で、頂点シェーダのパフォーマンスに影響するので、Trianglesが少ないほどパフォーマンスが良くなります。

Vertices

頂点数の事で、頂点シェーダのパフォーマンスに影響するので、少ない方が良いです。

オクルージョンカリングを利用する

カリングとは見えない場所を描画しないようにする機能です。オクルージョンカリングは、オブジェクトの後ろに隠れてるオブジェクトの描画を省く事によってドローコール数を下げて、負荷を下げることができます。これと似たような原理で、フラスタムカリングというものがあり、カメラの外のオブジェクトは描画しないというものです。

フラスタムカリングは意識しなくてもやってくれますが、オクルージョンカリングを自動にするには、設定する必要があります。手順は以下です。

  • シーン上で動かないオブジェクトを選択して、staticのとなりの▽を選ぶ
  • 選択したオブジェクトが遮蔽物になるか遮蔽されるものになるかを決める
  • インスペクタ上で遮蔽物はOcculuder Static、遮蔽されるものはOccludee Staticを選ぶ

指定が終えたら、Window>Rendering>Occlusion Cullingを選択します。するとオクルージョンカリング設定が開かれるので、そのままBakeでオクルージョンエリアを作成しましょう。これで、カメラに隠れたものは描画対象外になります。動くものには適用されないので、動かないとわかっているオブジェクトにだけ設定してあげましょう。

ポストプロセスをなるべく使わない

ポストプロセスは、アンチエイリアスやブラー、ブルームといった描画が終わった後のエフェクト効果を指します。これらはすべてピクセルシェーダで行われるので、非常に高価な処理となります。1ピクセル単位で処理が行われてると思ってください。

モデルにLODを適用し無駄な頂点シェーダ処理を省く

モデルの描画には基本的に頂点シェーダーとピクセルシェーダーがあります。頂点シェーダーはモデルの頂点の数だけ処理を実行し、ピクセルシェーダーはピクセルの数だけ処理を実行します。

通常のシーンでは、ピクセルシェーダーの方が負荷が大きい場合が大きいですが、カメラからモデルの距離が遠くなれば、ピクセルの数が減り負荷も減りますが、頂点シェーダーはモデルの頂点数の分だけ処理をするので負荷は下がりませんので、無駄な処理になります。

そこで、有効な対策がモデルにLevel Of Detailを適用することです。LODはカメラからの距離によってモデルのメッシュを適切に使い分ける事によって、カメラとモデルの距離が離れるとポリゴン数の少ないモデルを使うようにします。この機能によって無駄な頂点シェーダーがなくなるので負荷も軽減されます。

使い方は、LODを適用したいゲームオブジェクトのコンポーネントにLOD Groupを追加し、それぞれの距離に応じたメッシュを指定してあげれば大丈夫です。メッシュを複数用意する手間がかかりますが確実に負荷を軽減することができます。

シェーダ・マテリアル関連を見直す

描画は、内部でマテリアルデータを使ってシェーダプログラムを実行する事をいい、マテリアルやシェーダの使い方も非常に重要になります。

マテリアルを扱う上で一番大事なのは、マテリアルを可能な限り統一することです。共通のマテリアルを使うことでBatchがまとまり、マテリアル切り替えによるSetPass Callが減るからです。つまり、シェーダプログラムの実行回数が減るので負荷も減ります。

マテリアルはシェーダに渡すパラメータを指し、シェーダはそのままシェーダプログラムの事を指します。ドローコール(シェーダプログラムを実行する事)はパラメータ、メッシュ、シェーダが変わる度に呼び出すので、パラメータを減らし、メッシュをまとめて、シェーダの切り替えも少なくすると最適化することができます。テクスチャを1つにまとめた方がよいのもこのためです。

インスタンシングを使う

Batchを少なくする事(メッシュをまとめる事)が大事だと先ほど述べましたが、Unityは自動的にはメッシュをまとめてくれません。
Unityはdynamic batchを有効にすると、まとめる事の出来るメッシュインデックス配列を動的につなげる事で1つのバッチにします。メッシュは同一メッシュでなくても構いません。この処理はCPU上で行われます。

一方、インスタンシングを有効にすると、GPU内に同一メッシュのtransformリストを生成し、このリストでシェーダーを実行する事で、1バッチで描画しているようにします。この処理はGPU内部のみで、行われるためCPU負荷がかかりません。バッチはCPUでインデックスを動的につなげるので、CPU負荷がかかりやすいので注意してください。

インスタンシングするのは簡単で、以下のようにマテリアルのチェックボックスにチェックを入れればいいだけです。

ビルトインシェーダなら、Enable GPU Instancingにチェックを入れましょう。

ただし前提条件として、同一マテリアルのオブジェクトが複数あることが必要です。1つしかないオブジェクトなのに、インスタンシングを有効にすると、インスタンシングの準備コード分無駄な処理を行う事になるので、逆にパフォーマンスが落ちるので注意が必要です。

自作シェーダの無駄を取り除く

自作シェーダを使用している場合、シェーダのパフォーマンスは描画パフォーマンスに直結しますので、無駄な処理をしていないかぜひ見直してください。

精度を下げる

floatは高精度で使いたくなりますが、シェーダでfloatが必要な場合は意外と少ないです。基本的に以下の様に指定するのがおすすめです。

頂点データや法線データ, 計算関連:half
色データ:fixed

if,forを展開する

GPUはifやforが苦手なのでなるべく使わないようにしましょう。具体的には以下のようにしてください。

if:lerpやstepで代用
for:unrollして展開してしまう

ピクセルシェーダの計算を頂点シェーダに持ってくる

頂点シェーダに対してピクセルシェーダは、計算量が多いことがほとんどです。頂点シェーダはモデルの頂点数依存ですが、ピクセルシェーダは画面解像度依存だからです。

特にライティングは頂点シェーダでできることが多いので、見た目に影響が出ない程度に意識しましょう。

高価な関数をなるべく使わない

シェーダでは以下の処理は特に重いです。1ピクセルに対し1回以上下記の関数を使わない方がよいです。

処理の重い関数:pow, exp, log, cos, sin, tan

なるべくモバイルに最適化されたビルトインシェーダを使う

Unityにはモバイルに最適化されたシェーダが用意されています。基本的にモバイル用のシェーダーは軽いので、そちらを使うと負荷が下がります。

ライト・シャドウを見直す

ライトとシャドウはどちらも重い処理です。

無駄にライトを増やさない

シーン上のライトの数をなるべく増やさないようにするのも大事です。ライト数が増えると当然、シェーダ処理する回数が増えて、負荷も増えます。

ForwardAddのないシェーダであれば影響しませんが、ForwardAddのあるシェーダは影響するので、特に注意が必要です。

シャドウキャスト、レシーブシャドウは最小限に

影の影響を受ける必要のないオブジェクトはレシーブシャドウをoffにして、影を発生させる必要のないシーンではシャドウキャストをoffにしましょう。影を全く使わないのであれば、ライトのShadow TypeをNo Shadowsにしてしまいましょう。

コーディングを見直す

シーンの途中でInstantiate、Destroyを使わない

シーンの途中でInstantiate、Destroyを使わないようにしましょう。メモリの確保、解放が何度も発生しGCC上での処理が重くなります。

なのでゲームオブジェクトの追加はシーン中にはしないで、シーン開始時に必要な分だけ用意して、必要な時にgameObject.SetActive(true);にして、必要がなくなった時はgameObject.SetActive(false)としましょう。

キャッシュを多用する

GetComponent、FindObject系はUpdate中に呼び出さないようにしましょう。AwakeやStartなどで初期化時に一度だけ呼んで使いまわした方がよいです。

高価な関数・計算を認識する

シェーダ・マテリアル周りを見直す でも説明しましたが高価な関数や計算はシェーダでもスクリプトでも変わりません。Unityマニュアルにもこう書いてあります。

超越関数 (Mathf.Sin、Mathf.Pow など)、除算、平方根は、すべて乗算の時間の 100 倍ほどかかります。大きなスケールで考えると大した時間ではないですが、それらを各フレームで何千回も呼び出すと、それは積もって大きくなります。

除算は逆数を掛けるという処理に代替できますし、長さの比較はmagnitude同士ではなくsqrMagnitude同士で比較した方が高速なので、特にループ内などでは、高価な計算をしないように注意しましょう。

まとめ

高速化は見た目とのトレードオフである場合が多いです。どちらを取った方が良いか自分の環境に合わせて、適切な高速化を意識するのがよいです。

  • 不必要なオブジェクト・コンポーネントは使わない
  • シーン中に動的生成・破棄はなるべくしない
  • 重い処理をなるべく使わない

アイコンはこんぺいとうさんからお借りしました。

+3