オブジェクトをスクリーンに描画するためには、グラフィックス API (OpenGL や Direct3D 等) に対してドローコールが必要です。グラフィックス API はドローコールごとに非常に多くの処理をするため、ドローコールは高負荷で、CPU 側のパフォーマンスオーバーヘッドの原因になります。これは大抵、ドローコール間に状況の変更 (例えばマテリアルの切り替えなど) が発生することに起因し、グラフィックスドライバーで高負荷な検証と 変換処理の原因となります。
Unity では、それに対処するために 2 つのテクニックが使用されます。
手動でゲームオブジェクトをマージするのに比べ、ビルトインのバッチ処理にはいくつか利点があります。特に、ゲームオブジェクトはビルトインのバッチ処理をしても個々にカリングすることが可能です。ただし、欠点もあります。静的バッチ処理では、メモリとストレージのオーバーヘッドを招きます。そして、動的バッチ処理では、いくらかの CPU オーバーヘッドが発生します。
注意 動的バッチ処理はグラフィックスのジョブと互換性がありません (スタンドアロンの Player Settings を参照)。グラフィックス関連のプロパティーが有効になっている場合、スタンドアロンのビルドでは動的バッチ処理は無効になります。
まとめてバッチ処理できるのは、同じマテリアルを共有しているゲームオブジェクトに限られます。そのため、より効果的にバッチ処理するには、できる限り多くの異なるオブジェクト間でマテリアルを共有するようにします。
もし、テクスチャだけが違う 2 つの同じマテリアルがある場合、これらのテクスチャを合わせて 1つの大きなテクスチャにできます。この処理は、しばしばテクスチャのアトラス化と呼ばれます (Wikipedia の Texture atlases 参照)。いったんテクスチャを同じアトラスにまとめると、1 つのマテリアルとして使うことができます。
共有しているマテリアルプロパティーにスクリプトを通してアクセスする必要がある場合は、以下に注意してください。Renderer.material を変更するとマテリアルの複製を作成します。そうでなく、マテリアルを共有したいのであれば、Renderer.sharedMaterial を使います。
シャドウキャストのレンダリングにおいては、仮にそれらのマテリアルが異なっている場合でも、バッチングする事ができます。 Unity のシャドウキャストは、マテリアルが異なっていても、シャドウパスが必要とするマテリアルの値が同じであれば、ダイナミックバッチングを利用する事が可能です。例えば、たくさんの木枠のマテリアルではそれぞれ異なるテクスチャを使うことができますが、シャドウキャスターはテクスチャを考慮せずにレンダリングします。このような場合、互いにバッチングすることが可能です。
同じマテリアルを共有していて他の条件を満たせば、Unity は自動的に動いているオブジェクトをバッチングします。動的バッチは自動的に処理されるので、何かの手間が必要というわけではありません。
CPU 上ではオブジェクトの頂点は全てワールド空間の座標で動いているため、 ドローコールよりも処理負荷が小さい場合のみ、有効となります。ドローコールが実際どの程度の負荷になるかは、多くの要素に依存しますが、使われているグラフィックス API によるところが大きいです。例えば、コンソールやアップルの Metal のような現代的な API では通常、ドローコールの処理負荷がかなり低く、動的バッチは全く効果的ではありません。
静的バッチ処理では、静止していて同じマテリアルを共有するジオメトリであれば、どんなサイズのものにでもドローコールを減らすことができます。静的バッチ処理は CPU 上で頂点の変換を行わないため動的バッチ処理よりも低負荷ですが、メモリを多く消費します。
静的バッチを利用するには、そのオブジェクトが静的で、ゲーム内で移動、回転、スケールを行わないということを明示的に設定する必要があります。そのためには、 インスペクターの Static チェックボックスをオンにします。
静的バッチを使うと、合成したジオメトリ情報を保存しておくための余分なメモリが必要になります。静的バッチの前に、複数のオブジェクトで同じジオメトリを共有しているのであれば、オブジェクト毎にそのジオメトリのコピーが生成されます(エディター上でもランタイムでも)。このため、いつでも有効な方法とは言えません。つまり場合によっては、メモリ使用量を小さいまま維持するために、静的バッチを避けて、レンダリングのパフォーマンスを犠牲にしなければならないこともあるでしょう。例えば、密集した森林において、木を静的にすると、メモリにはかなりの負担になるかもしれません。
内部的には、静的バッチは各静的オブジェクトの位置座標をワールドスペースに変換し、それらから頂点+インデックスの巨大なバッファを作成しています。その後、同じバッチ内にある可視のゲームオブジェクトについては、ステートの変化がない場合、一連の“低負荷”なドローコールが割り当てられます。技術的に言うと“3D API draw calls”は節約できませんが、それらオブジェクト間で発生するステート変化は節約されます (これこそが計算量の多い部分です)。バッチはたいていのプラットフォームで 64k 頂点数と 64k インデックスに制限されます (OpenGLES では 48k インデックス、macOS では 32k インデックス)。
現在、メッシュレンダラー、トレイルレンダラー、ラインレンダラー、パーティクルシステム、スプライトレンダラー だけがバッチ処理可能です。つまり、スキンしたメッシュ、クロス、その他のタイプのレンダリングコンポーネントはバッチ処理できません。
レンダラーは同じタイプのレンダラーとしかバッチ処理できません。
半透明シェーダーでは通常、透明度を出すためにオブジェクトを後方から前方の順番でレンダリングする必要があります。Unity は最初に、この描画順をオブジェクトに指示し、その後、それらをバッチ処理します。しかし描画順を厳密に適用する必要があるので、大抵の場合、不透明オブジェクトほど多くのバッチ処理はできません。
互いに接近しているオブジェクトを手動で組み合わせるのも、ドローコールのバッチ処理にはとても効果的な方法です。例えば、引き出しがたくさんある静的な食器棚は、 3D モデリングアプリケーションか Mesh.CombineMeshes を使って、単一のメッシュにまとめることは理にかなった方法といえるでしょう。
2017–10–26 限られた 編集レビュー で修正されたページ
グラフィックスジョブと互換性のない動的なバッチ処理に関するノートは 2017.2に追加