うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】material と sharedMaterial の違い

検証 UnityUnity2019.3.5f1

MeshRendererSpriteRenderer など Material をアタッチできるコンポーネントには
Material を取得する方法として、.material.sharedMaterial の2つが存在します

.material

呼び出したタイミングで 新しい Material を作成して返します

新しく Material を作成するため以下の問題が発生する可能性があります
* 共通の Material を使いたい場合は呼び出ししてしまうと意図した挙動をしなくなる
* バッチングが効かなくなる

正直 new Material(_spriteRenderer1.sharedMaterial); と挙動は一緒なので、
マテリアルの参照はすべて sharedMaterial でもいい気がしていますが・・・

例えば下記コードで spriteRenderer1 と spriteRenderer2 が同じ Material を参照していたとする
var mat1 = _spriteRenderer1.material; のように Material を取得すると
もともと参照していた Material ではなく 新しい Material を取得できます

その際、Inspector 上では 「元の名前 (Instance)」と表示されるので
その Material が作成されたものかどうかは名前を見ることで判別することができます

f:id:hacchi_man:20200405003322p:plain

f:id:hacchi_man:20200405004029p:plain

using UnityEngine;

public class SampleMonoBehaviour : MonoBehaviour
{
    [SerializeField]
    private MeshRenderer _spriteRenderer1;
    [SerializeField]
    private MeshRenderer _spriteRenderer2;

    private void Start()
    {
        var mat1 = _spriteRenderer1.material;
        var mat2 = _spriteRenderer2.material;
        mat1.color = Color.red;
        mat2.color = Color.blue;
    }
}

Material の作成は呼び出しの度に行われるわけではなく、初回呼び出しのタイミングのみで行われるため
複数回 .material の参照を行ったとしても一つの Material しか作成されません

そのため、下記コードを実行した場合すべての オブジェクトの色が赤色となります

f:id:hacchi_man:20200405003824p:plain

using UnityEngine;

public class SampleMonoBehaviour : MonoBehaviour
{
    [SerializeField]
    private MeshRenderer _spriteRenderer1;
    [SerializeField]
    private MeshRenderer _spriteRenderer2;
    [SerializeField]
    private MeshRenderer _spriteRenderer3;

    private void Start()
    {
        _spriteRenderer2.material = _spriteRenderer1.material;
        _spriteRenderer3.material = _spriteRenderer1.material;
        _spriteRenderer1.material.color = Color.red;
    }
}

一度 .material を呼び出したあとに .sharedMaterial を呼び出したとしても
もともとの Material が取得できるわけではなく
作成した Material が取得できてしまいます

f:id:hacchi_man:20200405005658p:plain

using UnityEngine;

public class SampleMonoBehaviour : MonoBehaviour
{
    [SerializeField]
    private MeshRenderer _spriteRenderer1;
    [SerializeField]
    private MeshRenderer _spriteRenderer2;
    [SerializeField]
    private MeshRenderer _spriteRenderer3;

    private void Start()
    {
        var mat1 = _spriteRenderer1.material;
        var mat2 = _spriteRenderer1.sharedMaterial;
        _spriteRenderer2.material = mat1;
        _spriteRenderer3.material = mat2;
        mat1.color = Color.red;
        mat2.color = Color.blue;
    }
}

.sharedMaterial

もともと参照のある Material をそのまま返却します

Material を変更すると、参照しているすべてのオブジェクトに対して変更が適応されてしまうため変更には注意が必要です

同じ Material を参照してない状態で下記コードを実行するとすべて赤色になります

f:id:hacchi_man:20200405005852p:plain

using UnityEngine;

public class SampleMonoBehaviour : MonoBehaviour
{
    [SerializeField]
    private MeshRenderer _spriteRenderer1;
    [SerializeField]
    private MeshRenderer _spriteRenderer2;
    [SerializeField]
    private MeshRenderer _spriteRenderer3;
  
    private void Start()
    {
        _spriteRenderer2.sharedMaterial = _spriteRenderer1.sharedMaterial;
        _spriteRenderer3.sharedMaterial = _spriteRenderer1.sharedMaterial;
        _spriteRenderer1.sharedMaterial.color = Color.red;
    }
}

おまけ

取得する場合はどちらの プロパティを使うかを考える必要がありますが
セットはどちらも同一の処理を呼び出しているため、どちらを利用しても問題ありません

    /// <summary>
    ///   <para>Returns the first instantiated Material assigned to the renderer.</para>
    /// </summary>
    public Material material
    {
      get
      {
        if (!this.IsPersistent())
          return this.GetMaterial();
        Debug.LogError((object) "Not allowed to access Renderer.material on prefab object. Use Renderer.sharedMaterial instead", (Object) this);
        return (Material) null;
      }
      set
      {
        this.SetMaterial(value);
      }
    }
 
    /// <summary>
    ///   <para>The shared material of this object.</para>
    /// </summary>
    public Material sharedMaterial
    {
      get
      {
        return this.GetSharedMaterial();
      }
      set
      {
        this.SetMaterial(value);
      }
    }