ミニゲームを作ってUnityを学ぶ![ひつじコレクション編]
###第12回目: パフォーマンスチューニングみたいなこと(1)
前回までの内容でひつじコレクションを遊ぶための機能は全て実装できました。
今回はゲームを公開するにあたって、描画に関する部分の重い処理を修正することでスムーズな操作性を目指していきます。
#描画処理に関係する項目
チューニングを行う前に、いま現在の描画に関するステータスを確認してみます。
- ゲームViewの右上メニューからstatsを選択
ゲームViewに重なる形でStatisticsというウインドウが表示されます。
この状態でプロジェクトを実行することで、フレーム毎の描画に関するステータスを確認することができます。
この際に注目すべき項目が上画像の赤枠部分「Batches, Tris, Verts, SetPass Calls」の4つとなります。
この4つの項目には3D・2Dオブジェクトの構造が関わっていますので、必要であれば前作のマインスイーパー記事、真ん中あたりの3Dオブジェクトの構造を確認してみてください。
###【Verts】
描画されているオブジェクトの頂点数を合計した値。
###【Tris】
描画されているオブジェクトの三角情報を合計した値。
###【Batches】
バッチ作成の回数を示します。
Unityで描画(レンダリング)が行われるとき内部ではまず最初に「CPUがシーンに存在する全てのオブジェクトについて、そのオブジェクトを描画すべきかどうか判定」します。
そして、ゲーム画面の外に存在するオブジェクトなど描画する必要がないモノを除いた1つ1つのオブジェクトについて、「このオブジェクトを描画して」という命令を蓄積していきます。
命令を用意するのに必要なオブジェクトの頂点情報などが入ったデータ群をバッチと呼び、このバッチを作成した回数がBatchesとして表示されます。
つまり描画するオブジェクトが100個あればバッチを100個作成することになりますが、このときに特定の条件をクリアしたオブジェクト同士については複数のバッチを1つにまとめて作成することができます。
このバッチをまとめる行為のことをUnityではバッチ処理と呼びます。
###【SetPass calls】
SetPass Callが呼び出された回数を示します。
実際に画面を描画する際には先ほどの「このオブジェクトを描画して」という命令を実行する前に「こういう環境で」という条件を設定しなければなりません。
つまり「こういう環境で => このオブジェクトを描画して」の流れになります。
この環境を設定する処理をSetPass Callと呼びます。
一度設定した環境は変更が加えられるまで同じ状態を保持しますが、Unityを使っていてこの環境に変更が必要なタイミングが主にマテリアルを変更する場合です。
「このマテリアルを使って => このオブジェクトを描画して」という流れです。
つまりマテリアルがシーン内に1つしかないのであれば、描画するオブジェクトがいくつあっても環境の変更が行われないため、SetPass Callsの値は1のままになります。
* 本来は他にもカメラの視錐台やライティングといった色々な要素が環境に含まれます。
###少し確認してみる
上の画像はDirectional Lightの影を描画しないように設定して背景を塗りつぶした他は何も配置していないシーンのステータスです。
オブジェクトが何もないのでBachesは0になるかと思っていたのですが、おそらく塗りつぶしている背景部分がQuadとして処理されているためBachesが1となっています。
地面に見立てて板状のオブジェクトQuadを配置しました。
Quadは4つの頂点と2つの三角情報から構成されているので先ほどの状態からVerts + 4, Tris + 2されています。
さらに地面を描画するためのバッチが作成されたことでBatchesが1つ増え、先ほどと比較してBatches, Tris, Vertsがそれぞれ2倍になりました。
このことからも背景部分がQuadとして処理されている = 現在のシーンにQuadが2枚ある状態になっていることがわかります。
背景と地面はマテリアル情報が異なるため環境の変更が必要になり、SetPass Callsが1つ増えました。
地面Quadと同じマテリアルを使ったCubeを配置しました。
cubeは単純にQuadを6枚組み合わせた構造ですので24の頂点と12の三角情報を持ちます。
先ほどと比較してVerts + 24, Tris + 12となっているのが確認できます。
どうやらバッチをまとめることはできなかったようでBatchesが1つ増えていますが、マテリアルがQuadと同じことで環境の変更が行われず、SetPass Callsは2のまま変化していません。
さらにもう1つ同じCubeを配置しました。
頂点が24、三角情報が12増えましたが、2つのCubeのバッチがひとまとめで作成されたためBatchesに変化はありません。
Setpass Callsについても環境の変更がないため数値は同じままです。
今度は異なるマテリアルをアタッチしたCubeを配置しました。
頂点と三角情報が新しいCubeの分増加し、先ほどの2つのCubeのバッチにまとめられなかった新しいバッチの作成によってBatchesが1増加しています。
また環境に変更が必要になったためSetpass Callsも1増加しました。
###画面が描画されるまでの流れ
大雑把に、1フレーム内で行われる画面表示までの流れは以下のようになります。
1: CPUがシーン内のどのオブジェクトを描画するか判定してバッチを作成し、描画命令を蓄積する。
--- 以下、蓄積された命令が全て完了するまで2~5を繰り返す
2: (環境の変化がある場合は)CPUがGPUに対して「この環境で」という条件を再設定するよう指示を出す
3: 1つのバッチについてCPUがGPUに対して描画するよう指示を出す
4: GPUが渡された頂点情報を(頂点)シェーダーの規則と環境に合わせて最終的な値に変換する
5: GPUが(フラグメント)シェーダーの規則と環境に合わせて画面内1ピクセル毎に塗る色を決定する
--- 全ての命令が完了すると画面全体について1ピクセル毎に塗る色が確定し、描画が行われる
* 1=Batches, 2=SetPass Calls
- 3のCPUからGPUへの描画命令をDraw Callと呼びます。
###プロファイラ
簡単に描画の仕組みを見てきましたが、このときに大事なのは「レンダリングはCPUとGPUがそれぞれ役割を分担していて、かつ両方が互いに同期している(タイミングを取り合っている)」ということです。
チューニングが必要な場合は問題がCPU側なのかGPU側なのか、あるいは両方なのかを判断する必要があります。
それ以前に描画ではなく他の部分が問題となっている場合もあるかもしれません。
Unityにはこういったプロジェクトのボトルネックとなっている部分を探すのに便利なプロファイラという機能があります。
参考: CPUプロファイラでパフォーマンスを改善する 前編(テラシュールブログ)
こちらでは上記の記事だけでなくプロファイラを使った様々な内容が掲載されておりますので、ご紹介させていただきます。
#描画ステータスを改善する
今回は単純に上にあげた4つの項目「Batches, Tris, Verts, SetPass Calls」を改善するためにプロジェクト内のいくつかの項目の確認と修正を行っていきます。
ちなみにスマホなどモバイル環境でフレームレート30を実現するためにはSetPass Callsを100以下に抑える必要があり、Batchesについては最近の端末スペックやUnity自体の機能向上によってそれほど重要視されないようです。
また、TrisやVertsの数値が大きいほどGPUのレンダリング処理に時間がかかるようになります。
###現在の数値
まずは現在のステータスを確認しておきます。
Batches: 785
Tris: 466.9k
Verts: 1.1M
SetPass calls: 103
許容されるSetPass Callsの値をわずかに超えています。
###LODの効果
ステージ内の障害物となるBushオブジェクトにはすでにLODというパフォーマンスと見た目を両立するための仕組みが適用されています。
まずはこちらを外してポリゴン数の異なるBushをそれぞれ配置することでLODの効果を確認します。
【高ポリゴン】
Batches: 3594
Tris: 1.4M
Verts: 3.9M
SetPass calls: 113
【中ポリゴン】
Batches: 3598
Tris: 654.1k
Verts: 1.6M
SetPass calls: 110
【低ポリゴン】
Batches: 228
Tris: 428.6k
Verts: 949.6k
SetPass calls: 113
ポリゴンがそもそも三角情報を表す言葉なため、その通りにTrisとVertsに大きな変化があることが確認できます。
また、高・中ポリゴンでは1つ1つの頂点属性数が多いためバッチ処理が行われていませんが、低ポリゴンはバッチ処理が行われていることがわかります。
この値が示すとおり、LODは低ポリゴンを主に描画し、一部のカメラに近いBushを中ポリゴン以上で描画することで見た目とパフォーマンスのバランスをとっています。
###影を描画しない
3Dを扱う場合は影の有無がパフォーマンスに大きな影響を与えます。
同じマテリアルを使ったSetPass Callsが1回で済むようなオブジェクトの構成でも、それぞれに映し出される影の形が異なることでバッチ処理の対象から外れたり環境の変更が必要になるため、レンダリングの負荷が大きく増加してしまいます。
- Directional LigheのパラメーターShadowTypeを「No Shadows」に設定
Batches: 190
Tris: 119.8k
Verts: 245.1k
SetPass calls: 64
最初に掲示した現在の数字と比較したときに、影の有無が4項目に大きな影響を与えていたことがわかります。
どうしても影を表示したい場合は必要な部分のみに絞ったり、動かないオブジェクトについては予め影を焼き付けてテクスチャのように扱う(ライトマップのベイク)といった処理が必要になりそうです。
長くなってしまいそうなので、ここで一度区切りをつけます。
この作品はユニティちゃんライセンス条項の元に提供されています