【Unity】ドローコールを減らすためにいま僕たちができること
ドローコールを減らすことは持続的なゲームプレイにとって重要です。ドローコールを減らす方法を模索し、ゲーム体験を向上させることでより多くの人にゲームを遊んでもらえるようにしましょう。
……まるで温室効果ガスの排出を減らすかのような感覚でドローコールを減らそうとスローガンを掲げましたが、ゲーム画面を描画するためのコストは低いに越したことはありません。
ドローコールとは、グラフィクスAPIに対して描画のお願いをすることで、この処理がまた重いんです。CPU内で完結するならともかく、GPUまで巻き込んで行う処理ですから何度もお願いするとそれだけ負荷が大きくなります。
このページではそんなドローコールを減らす方法についてできることから考えていきましょう。
環境
macOS 11.1 Big Sur
Unity2019.4.4f1
ドローコールとは
ドローコール(Draw Call)とは、その名の通りドローをコールすること。すなわち画面描画の処理を呼び出すことです。
画面の描画はGPUを使ってどのピクセルにどんな色を描画するかの計算を通して行われます。
ソフトウェア側からは直接GPUに「これ描いて!」とお願いするのではなく、グラフィックスAPI(OpenGLとかDirectXとか)を使って描画の処理を呼んでいます。というのも、ソフトウェア側からGPUに命令を出すとなると、異なるGPUに対して同じ画面が表示されるようにたくさんの処理を自分で実装しないといけなくなります。例えばスマートフォン向け、特にAndroidなら端末ごとの構成は幅広くなってくるので、これを自分で実装するとなると……想像するだけで胃が痛くなるでしょ? 私も書いていて胃が痛くなってきました。
そこでグラフィックスAPIとして描画のための処理を定義しておくことで、それを呼び出すだけでGPUに対する処理(ドライバへの命令を含む)を任せちゃうことができます。便利。
ここではざっくりと説明しましたが、ドローコールの解説については以下のサイトがとても分かりやすいのでこちらをご覧いただくと良いかもしれません。
ドローコールの負荷について
ドローコールはCPUからグラフィックスAPIに対して行われます。その先にはドライバだったりGPUがいるわけですが、グラフィックスAPIを介してCPUからGPUに命令をする際には、情報の検証や変換などの処理が入ります。(事前にCPUが情報をまとめる処理もあるので、毎フレームでたくさんの処理を行っています)
命令の数が少なければこうした部分での影響も少なくなりますが、何度も何度も処理を呼び出しているとその分負荷も大きくなってしまいます。すると、CPUの処理時間がかさみ、なんだかゲームがカクカクして……そう、FPSが下がってしまうんですね。
なので、ドローコールはなるべく減らしてもらうのがパフォーマンス面で大切なんです。
ドローコールバッチングとは
Unityではなるべくドローコールを減らすために、ドローコールバッチングが行われています。バッチング(またはバッチ処理)とは、Batchの言葉にあるように「まとめる」こと。ドローコールをなるべくひとまとめにすることで、負荷の大きいドローコールの呼び出し回数を減らそうとします。
例えば「河原の石を100個集める」という目的があったとして、1回河原に行って100個の石をまとめて袋に入れて帰ってくるのと、河原に行って1個石を拾って帰ってくるのを100回繰り返すのとでは、最終的に手元に石が100個あったとしても労力が変わります。まとめて拾ってきた方が楽ですよね。
ドローコールに関しても同じようなことが言えて、処理はひとまとめにして呼び出し回数を減らした方が負荷を減らせます。
観察: バッチ数が与える影響
たくさんのゲームオブジェクトがシーン内に存在する時に、バッチ数の大小でどうなるかを観察します。
例えば以下の画像はシーン内に200*200の合計4万個のオブジェクトを作成した時の統計情報ウィンドウです。「Batches」の項目はドローコールのバッチ数を表していて、その数なんと9万ほどになっています。バッチ数が多いということは、ドローコールをまとめきれておらず、ドローコールが何度も呼ばれている状態です。
Profilerウィンドウを確認すれば以下のように15FPSのラインを突き抜けるようにレンダリングの処理時間が積み上がっています。
試しにゲームの実行中にCombineMeshesを使ってメッシュを結合してみます。すると以下のように「Batches」の項目を減らすことができました。
あれだけ壁のようにそり立っていたCPU時間も以下のようにだいぶマシに。ピーク時に30FPSのラインまでいっているものもありますが、概ね60FPS以上を維持できています。
ここでサンプルとして挙げたのはパーリンノイズを使って地形の高さを決めて配置するサンプルです。メッシュの結合まで一連の流れになっているので時間があればさらっとご覧あれ。
ドローコールを減らすUnityのバッチング処理
上の例ではユーザ側でメッシュを結合しましたが、Unityではバッチング処理を自動的に行ってくれる機能もあります。
動的バッチ処理(Dynamic Batching)と静的バッチ処理(Static Batching)があります。動的バッチ処理の方は小さいメッシュに関してゲーム実行中にUnity側で自動的にドローコールをまとめてくれるもので、静的バッチ処理はメッシュの大きさによらず動かないメッシュについてドローコールをまとめてくれるものです。
この2つのバッチ処理に関しては、PlayerSettingsの中の『Other Settings』の項目で実行するかどうかを設定できます。『Dynamic Batching』の方は、PC向けのプラットフォーム設定ではデフォルトだとチェックが入っていなかったので、使用する場合はチェックを入れるようにします。
動的バッチ処理はドローコールの負荷との兼ね合いで使うかどうかを決めた方が良いかもしれません。グラフィックスAPIによってはドローコールの処理負荷が低いこともあるので、こうした場合はバッチ処理のメリットがないことも。
例えば上で紹介した200*200のオブジェクトであれば、動的バッチングを有効にすることでバッチ数(Batches)は1384まで減らせました。右側に「Saved by Batching」といった形でどれだけバッチ数を減らせたかが記載されていて、1384 + 89595 = 90979となり、何もしなかったケースのバッチ数と一致します。
ただし、Profilerウィンドウを確認すると相変わらず15FPSより低いので、このケースではメッシュを結合しちゃった方がメリットが大きいようですね。「SetPass Calls」の値や「Shadow casters」の値は減っていないので、このケースではパフォーマンス面では大幅な改善というわけではないようでした。(厳密にいうと、Renderingにかかる時間は動的バッチ処理なしで110ms、ありで93msだったので多少の改善はあります)
もう少し現実的な数のオブジェクト数で、かつ同じマテリアルを使っている状況では動的バッチ処理も有効な手段なので、状況に合わせて選択できるとグッド。なお、動的バッチングが行われるにはいくつかの条件があるので、Unityのマニュアルの以下のページもあわせてご覧ください。
静的バッチ処理の方は何気なくいつもやっているかもしれませんが、インスペクターウィンドウの右上にある「Static」のチェックを入れることで静的バッチ処理の対象になります。
動かさないメッシュに関してはこちらを設定しておくことでドローコールを減らすことができます。ただし、メモリの消費量は大きくなるので、こちらもシーンの状況に合わせて選択するようにしてください。
ドローコールを減らすためにいま僕たちができること
ドローコールを減らすためには、ドローコールの処理をまとめるようにしましょう。まとめるためにはUnityのバッチング処理である動的バッチ処理、静的バッチ処理を活用します。
また、事前にまとめておいたメッシュを使用したり、あるいはスクリプトからCombineMeshesを使ってメッシュをまとめる方法もあるので、こちらを使ってドローコールを減らすのも有効です。
ドローコールが減ればCPUの処理負荷が減り、CPUの処理負荷が減ればフレームレートが維持され、快適なゲーム環境を提供することができます。
せっかく作ったあなたのゲーム世界の素晴らしさを伝えるためにも、ドローコールに目を向けて調整してみてください。
サンプルとして紹介したパーリンノイズ関係やそれに関連するメッシュ関係の記事は以下のページでまとめています。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【Unity】メインのプロジェクトを壊さないために別のプロジェクトを用意しておこう 2021.01.05
-
次の記事
【Unity】パーリンノイズを組み合わせてもうちょっと地形に変化をつける 2021.01.07
コメントを書く