2つのRendererがMaterial Aを共有した状態から、Renderer 1のmaterialを取得する例です。Renderer 1用のコピーA′が作られ、Renderer 2はAを使い続けます。専用コピーは所有者が寿命を管理します。
図:2つのRendererがMaterial Aを共有した状態から、Renderer 1のmaterialを取得する例です。Renderer 1用のコピーA′が作られ、Renderer 2はAを使い続けます。専用コピーは所有者が寿命を管理します。(クリックで拡大)

Materialが増える操作を特定する

色を変える処理やオブジェクトの生成・破棄を繰り返すとMaterial数が増える場合は、Renderer.material、Renderer.materials、new Materialを使う場所を調べます。表示確認用のログでmaterialを取得する処理も確認対象です。この記事はUnity 2022.3 LTSを基準にしています。

1. material取得時の専用コピーを理解する

Renderer.materialは、そのRendererが使うインスタンスを返すAPIです。共有Materialを使っているRendererから取得すると、そのRenderer用のコピーが作られる場合があります。図の例ではRenderer 1だけがコピーA′へ切り替わり、Renderer 2は元のAを使い続けます。

すでに専用インスタンスを保持している同じRendererで、getterを読むたびに必ず新しいMaterialが1つ増える、という仕組みではありません。新しいRendererへの初回アクセス、Materialの再割り当て、明示的な複製と解放を追ってください。Renderer.materialsもインスタンス化を伴うため、複数スロットの参照確認にはsharedMaterialsを使う経路を検討します。

2. 参照の変更と、共有Materialの値変更を分ける

renderer.sharedMaterialへ既存Assetを割り当てる操作は、そのRendererが参照するMaterialを変更します。一方、取得したsharedMaterialにSetColorなどを呼ぶ操作は、共有するMaterial自体の値を変更し、同じMaterialを使うほかのRendererへも影響します。プロジェクトのAssetを意図せず変更しないよう、参照の取得と値の書き換えを区別します。

色違いが少数で固定なら、あらかじめ用意したMaterial Assetを使い分ける方法もあります。多数のRendererを同じMaterialへ戻す際も、すでに作った専用コピーが未解放で残らないか確認します。

3. 必要な違いに合わせて手段を選ぶ

  • 既存Assetを使う:固定された見た目なら共有参照を割り当てます。個別表示のために共有Assetの色を直接書き換えないようにします。
  • 値だけを上書きする:色やFloatなどの対応プロパティならMaterialPropertyBlockが候補です。ブロックを再利用し、実際のShaderが使うプロパティ名を確認します。URP/Litの基本色なら_BaseColorなど、Shaderに合う名前が必要です。
  • 専用Materialを持つ:固有の設定や描画状態が必要なら、コピーを作る所有者を決め、参照を保持して再利用し、不要時にDestroyします。共有Assetはこの解放対象に含めません。

MaterialPropertyBlockでは描画状態の変更は扱えません。またUnity 2022.3のSRP Batcherとは非互換です。Material数を減らせたことだけで高速化と判断せず、描画構成とCPU時間を比較します。

4. 上書きの範囲と、解放の責任を確認する

PropertyBlockを複数の処理が書く場合は、更新する所有者と対象スロットを整理します。Renderer全体とMaterialスロット別の両方にブロックがあると、そのスロットではMaterial別のブロックが使われます。色が戻る場合は、別の書き込みやスロットの優先関係も調べます。

専用コピーは所有者が不要になる時点で、参照を外すか適切なMaterialへ戻してから解放します。ObjectPoolで保持しているオブジェクトなら、無効化のたびに作り直す設計より、再利用中は同じコピーを保ち、プールの最終破棄時に解放する方針も検討します。ほかのRendererが使うコピーを先に壊さないようにします。

修正後の確認

  1. 対象Playerの同じ場面で、生成前・生成後・破棄後のMaterial数とメモリを比較します。
  2. 同じ生成・破棄を複数回行い、保持数が安定するか確認します。Destroy直後だけで判断せず、破棄が処理された後を比較します。
  3. プールが意図的に保持する分と、失われた参照のまま増える分を分けます。Editorの一時資源やAsset数も混ぜて、すべてリークと判断しないでください。
  4. Frame DebuggerとProfilerで、SRP Batcherの状態・CPU時間・見た目を確認します。

Materialの個別インスタンスがあること自体は不具合ではありません。作成数、共有範囲、寿命が設計どおりかを確認します。

参考資料