Version: 5.5
グラフィックスパフォーマンスの最適化
最適なパフォーマンスのためのキャラクターモデリング

ドローコールバッチング

オブジェクトをスクリーンに描画するためには、グラフィックス API (OpenGL や Direct3D 等) に対してドローコールが必要です。グラフィックス API はドローコールごとに非常に多くの処理をするため、ドローコールは高負荷で、CPU 側のパフォーマンスオーバーヘッドの原因になります。これは大抵、ドローコール間に状況の変更 (例えばマテリアルの切り替えなど) が発生することに起因し、グラフィックスドライバーで高負荷な検証と 変換処理の原因となります。

Unity では、それに対処するために 2 つのテクニックが使用されます。

  • 動的バッチ処理: かなり小さいメッシュ用に、その頂点を CPU 上に変換して多数の類似したものを群にし、ひとまとめにして描画します。
  • 静的バッチ: 静的 (つまり、動かない) オブジェクトを大きなメッシュと結合して、それを高速でレンダーします。

手動でオブジェクトを統合するのに比べ、ビルトインのバッチにはいくつかの利点があります (もっとも注目するのは、オブジェクトがまだ、個別に抜粋できることです)。しかし、いくつかの欠点もあります (静的バッチはメモリーとストレージのオーバーヘッドを生じさせ、動的バッチはいくつかの CPU オーバーヘッドを生じさせます)。

バッチに必要なマテリアル設定

まとめてバッチ処理できるのは、同じマテリアルを共有しているゲームオブジェクトに限られます。そのため、より効果的にバッチ処理するには、できる限り多くの異なるオブジェクト間でマテリアルを共有するようにします。

もし、テクスチャだけが違う 2 つの同じマテリアルがある場合、これらのテクスチャを合わせて 1つの大きなテクスチャにできます。この処理は、しばしばテクスチャのアトラス化と呼ばれます (Wikipedia の Texture atlases 参照)。いったんテクスチャを同じアトラスにまとめると、1 つのマテリアルとして使うことができます。

共有しているマテリアルプロパティーにスクリプトを通してアクセスする必要がある場合は、以下に注意してください。Renderer.material を変更するとマテリアルの複製を作成します。そうでなく、マテリアルを共有したいのであれば、Renderer.sharedMaterial を使います。

シャドウキャストのレンダリングにおいては、仮にそれらのマテリアルが異なっている場合でも、バッチングする事ができます。 Unity のシャドウキャストは、マテリアルが異なっていても、シャドウパスが必要とするマテリアルの値が同じであれば、ダイナミックバッチングを利用する事が可能です。例えば、たくさんの木枠のマテリアルではそれぞれ異なるテクスチャを使うことができますが、シャドウキャスターはテクスチャを考慮せずにレンダリングします。このような場合、互いにバッチングすることが可能です。

動的バッチ

同じマテリアルを共有していて他の条件を満たせば、Unity は自動的に動いているオブジェクトをバッチングします。動的バッチは自動的に処理されるので、何かの手間が必要というわけではありません。

  • 動的なオブジェクトのバッチは 頂点ごとに ある程度オーバーヘッドがあります。ですので、バッチはトータル頂点数が 900 以下のメッシュでしか適応されません。
    • シェーダーが頂点位置や法線や一つの UV 情報を使っていたら、300 頂点までになります。一方で頂点位置、法線、UV0、UV1、タンジェントを使っていたら 180 頂点までになります。
    • 注意: 属性数の制限は今後変更されることがあります。
  • Transform にミラーリングを含む場合は、オブジェクトはバッチ処理されません。例えば、+1 スケールのゲームオブジェクト A と –1 スケールのゲームオブジェクト B は一緒にバッチ処理できません。
  • 違うマテリアルのインスタンスを使用すると、たとえ基本的に同じものであっても、ゲームオブジェクトが一緒にバッチ処理されない原因になります。シャドウキャスターのレンダリングは例外です。
  • ライトマップを持つゲームオブジェクトには追加のレンダラーパラメーターである Lightmap Index と Offset/Scale が含まれます。一般的に、動的にライトマップされたゲームオブジェクトがバッチ処理されるためには、ライトマップ上で正確に同じ位置を参照する必要があります。
  • マルチパスのシェーダーはバッチ処理を妨げます。
    • Unity のシェーダーはほとんどすべて、フォワードレンダリングでいくつかのライトをサポートし、効果的に追加のパスを加えます。「追加のピクセルごとのライト」のためのドローコールはバッチ処理されません。
    • 旧 Deferred (ライト pre-pass) レンダリングパスは、動的バッチを無効にします。なぜなら、旧 Deferred (ライト pre-pass) レンダリングパスは、オブジェクトを 2回描かなければならないからです。

CPU 上ではオブジェクトの頂点は全てワールド空間の座標で動いているため、 ドローコールよりも処理負荷が小さい場合のみ、有効となります。ドローコールが実際どの程度の負荷になるかは、多くの要素に依存しますが、使われているグラフィックス API によるところが大きいです。例えば、コンソールやアップルの Metal のような現代的な API では通常、ドローコールの処理負荷がかなり低く、動的バッチは全く効果的ではありません。

静的バッチ

静的バッチ処理では、静止していて同じマテリアルを共有するジオメトリであれば、どんなサイズのものにでもドローコールを減らすことができます。静的バッチ処理は CPU 上で頂点の変換を行わないため動的バッチ処理よりも低負荷ですが、メモリを多く消費します。

静的バッチを利用するには、そのオブジェクトが静的で、ゲーム内で移動、回転、スケールを行わないということを明示的に設定する必要があります。そのためには、 インスペクターの Static チェックボックスをオンにします。

静的バッチを使うと、合成したジオメトリ情報を保存しておくための余分なメモリが必要になります。静的バッチの前に、複数のオブジェクトで同じジオメトリを共有しているのであれば、オブジェクト毎にそのジオメトリのコピーが生成されます(エディター上でもランタイムでも)。このため、いつでも有効な方法とは言えません。つまり場合によっては、メモリ使用量を小さいまま維持するために、静的バッチを避けて、レンダリングのパフォーマンスを犠牲にしなければならないこともあるでしょう。例えば、密集した森林において、木を静的にすると、メモリにはかなりの負担になるかもしれません。

内部的には、静的バッチは各静的オブジェクトの位置座標をワールドスペースに変換し、それらから頂点+インデックスの巨大なバッファを作成しています。その後、同じバッチ内にある可視のゲームオブジェクトについては、ステートの変化がない場合、一連の“低負荷”なドローコールが割り当てられます。技術的に言うと“3D API draw calls”は節約できませんが、それらオブジェクト間で発生するステート変化は節約されます (これこそが計算量の多い部分です)。バッチはたいていのプラットフォームで 64k 頂点数と 64k インデックスに制限されます (OpenGLES では 48k インデックス、macOS では 32k インデックス)。

ヒント

現在、メッシュレンダラートレイルレンダラーラインレンダラーパーティクルシステムスプライトレンダラー だけがバッチ処理可能です。つまり、スキンしたメッシュ、クロス、その他のタイプのレンダリングコンポーネントはバッチ処理できません。

半透明シェーダーでは大抵、透明度を正しくするために、オブジェクトを後方から前方の順番でレンダリングする必要があります。Unity は最初に、この描画順をオブジェクトに指示し、その後、バッチングを試みます。しかし描画順を厳密に適用しなければいけないので、大抵の場合、不透明オブジェクトに比べるとバッチングが少なくなります。

互いに接近しているオブジェクトを手動で合成するのも、ドローコールバッチングにはとても効果的な方法です。例えば、引き出しがたくさんある静的な戸棚は、 3D モデリングアプリケーションか Mesh.CombineMeshes を使って、単一のメッシュにまとめてしまう事がよくあります。

グラフィックスパフォーマンスの最適化
最適なパフォーマンスのためのキャラクターモデリング