はじめに
ソースコードからRenderer系コンポーネントに設定されたMaterialのプロパティを設定する場合、主に以下の三つの方法があります。
-
sharedMaterials
プロパティやsharedMaterial
プロパティで取得したMaterialにプロパティを設定する -
materials
プロパティやmaterial
プロパティで取得したMaterialのコピーにプロパティを設定する -
Renderer
クラスが持つSetPropertyBlock
メソッドを使用して設定する
一つ目の方法は、Materialのコピーは発生しないためメモリ使用量は一番少なくなります。**しかし、同じMaterialを参照している他のRendererにも変更が反映されてしまうため、使用できる場面は限られます。**この理由から、sharedMaterials
系プロパティで取得したMaterialに対するプロパティの変更は公式ドキュメントで非推奨とされています1。
二つ目の方法は、同じMaterialを参照している他のRendererが存在する場合にMaterialが丸ごとクローンされるため、他のRendererに変更が反映されてしまうことはありません2。Materialがクローンされる分メモリの使用量は多くなります。
三つ目の方法は、Material自体のプロパティを変更することなくプロパティを上書きする方法です。Material全体をクローンする必要がないので、メモリ使用量の面で二つ目の方法よりも効率的になります3。少数のパラメーターを変更するだけの場合は、この方法を使用することが公式ドキュメントで推奨されています3。
本記事では三つ目の方法について扱います。
SetPropertyBlock
メソッドには、Renderer単位で設定するper-rendererな設定方法とMaterial単位で設定するper-materialな設定方法の二種類が存在します3。これらの設定方法を併用した場合にどうなるのか、PropertyBlockが設定されているかどうかを取得するHasPropertyBlock
メソッドはこれらの設定方法によって挙動が変わるのか、そのあたりが少しわかりづらく感じました。
本記事ではそのあたりについて調査してまとめています。
検証環境
- Unity 2020.3.12f1
まとめ
急いでいる人向けにまとめを最初に書いておきます。
per-renderとper-materialの両方で設定した場合は、per-materialが優先されます。優先の判断はMaterial単位で行われます。
HasPropertyBlock
メソッドはper-renderかper-materialかに関わらず、PropertyBlockが設定されていればtrue
になります。
per-renderの設定 | per-materialの設定 | 反映される方 | HasPropertyBlock() |
---|---|---|---|
してない | してない | なし | false |
した | してない | per-render | true |
してない | した | per-material | true |
した | した | per-material | true |
挙動詳細
以下の四つの場合について調べました。
- なにも設定しない場合
- per-renderで設定した場合
- per-materialで設定した場合
- per-renderとper-materialで同時に設定した場合
視覚的にわかりやすいよう、左半分にインデックス0のMaterial、右半分にインデックス1のMaterialを割り当てた球を用意し、それぞれの場合にプロパティの変更がどのように反映されているか例として示しています。インデックス0のMaterialも1のMaterialも標準色は白です。
なにも設定しない場合
HasPropertyBlock()
はもちろんfalse
になります。
なにも設定していないので、左右どちらも標準の白色になっています。
per-renderで設定した場合
引数にMaterialインデックスを渡さない場合は、Renderer全体のスコープでプロパティが設定されるため、すべてのMaterialに反映されます。
HasPropertyBlock()
はtrue
になります。
以下のようなコードでper-renderで赤色を設定すると、全てのMaterialに反映されるため、インデックス0と1両方のMaterialに色が反映され、全体が赤くなります。
var mb = new MaterialPropertyBlock();
mb.SetColor("_Color", Color.red);
renderer.SetPropertyBlock(mb);
per-materialで設定した場合
引数にMaterialインデックスを渡した場合は、Materialのスコープでプロパティが設定されるため、指定したインデックスのMaterialにのみ反映されます。
HasPropertyBlock()
はtrue
になります。
以下のようなコードでインデックス0のMaterialのみに緑色を設定すると、インデックス0のMaterialにのみ反映されるため、左側が緑になります。
var mb = new MaterialPropertyBlock();
mb.SetColor("_Color", Color.green, 0);
renderer.SetPropertyBlock(mb);
per-renderとper-materialで同時に設定した場合
per-materialが優先して使われます。
この時に、優先度の判断はプロパティごとではなくMaterialごとに行われます。
つまり、per-renderとper-materialの両方を設定している状態で、per-renderで定義されていてper-materialで定義されていないプロパティがある場合、そのプロパティはper-materialのPropertyBlockが参照されて定義されていないものとして扱われます。
HasPropertyBlock()
はtrue
になります。
以下のようなコードでper-renderで赤を、インデックス0のMaterialに緑色を設定すると、インデックス0のMaterialにはper-renderとper-materialの両方が設定されますが、per-materialが優先されるため左側が緑になります。インデックス1のMaterialにはper-renderのみが設定されているので、右側が赤になります。
{
var mb = new MaterialPropertyBlock();
mb.SetColor("_Color", Color.red);
mb.SetFloat("_Metallic", 1);
renderer.SetPropertyBlock(mb);
}
{
var mb = new MaterialPropertyBlock();
mb.SetColor("_Color", Color.green);
renderer.SetPropertyBlock(mb, 0);
}
おしまい