Search code examples
unity-game-engineshadershader-graph

Updating a material's property in Edit Mode


I have created a simple color swap shader, and then a material from it. Now, I would like to see the effect in action in Edit Mode.

However, I receive the following error:

Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead.

I do not want to update the sharedMaterial, because I want to be able to set different colors for different game objects, and using that property would change the value in the base material, which I do not want.

What do I need to change to address the error, please?

Here's my script:

[ExecuteInEditMode]
public class ColorSwap : MonoBehaviour
{
    [SerializeField] private Color color;

    private SpriteRenderer spriteRenderer;

    private void Awake()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    void Start()
    {
        spriteRenderer.material.SetColor("color", color);
    }
}

Solution

  • renderer.material is interesting. The first time you reference it, Unity will create a copy of the first sharedMaterial on the renderer, and then replace that first sharedMaterial with its own instance.

    If the material is used by any other renderers, this will clone the shared material and start using it from now on.

    ...

    This function automatically instantiates the materials and makes them unique to this renderer. It is your responsibility to destroy the materials when the game object is being destroyed

    Unity - Scripting API: Render.material

    The creation of that copy is why you get a warning- you now have a new Material asset, and that asset has to be saved somewhere, otherwise how would the scene reference it?


    Instead of creating new materials, you can try using a MaterialPropertyBlock. This lets you modify a material parameter for a single renderer, without the need to create new materials:

    [ExecuteInEditMode]
    public class ColorSwap : MonoBehaviour
    {
        [SerializeField] private Color color;
    
        private SpriteRenderer spriteRenderer;
        private MaterialPropertyBlock mpb = new();
    
        private void Awake()
        {
            spriteRenderer = GetComponent<SpriteRenderer>();
             
            // Copy the existing material properties into the MaterialPropertyBlock
            // to ensure that the default values are properly handled
            spriteRenderer.GetPropertyBlock(mpb);
        }
    
        void Start()
        {
            // Set the "color" property for this object.
            mpb.SetColor("color", color);
            spriteRenderer.SetPropertyBlock(mpb);
        }
    }
    

    If you want to remove these MaterialPropertyBlock overrides, just pass null as the argument to SetPropertyBlock. Optionally, you can use GetPropertyBlock again to make sure the default values are up-to-date:

    void ClearMaterial() {
        // Clears any overrides
        spriteRenderer.SetPropertyBlock(null);
    
        // Put the defaults back into "mpb"
        // Note: ??= is OK since MaterialPropertyBlock does not inherit from UnityEngine.Object 
        spriteRenderer.GetPropertyBlock(mpb ??= new());