When you access a renderer's material with the
.material getter, Unity instantiates a new copy of the material unique to this object — that's why you see an "(Instance)" added to its name.
The benefit of this is that now you can make any changes you want to this object's material, without unwanted effects on other objects that use the same material, and without inadvertently saving runtime modifications into the source material asset stored on disc / in your project's version control.
But it does come with costs:
The new material needs to be allocated, which takes a little time
The object using the new material won't batch together with other objects that used the same source material — even if two script instances both independently copied the material and made the same changes to it, Unity can't guarantee they're the same, so they won't batch together
The memory used by the new material isn't automatically garbage collected when your object is destroyed. Unity usually cleans these up on scene loads (and will log a warning in the editor about "leaking materials" if you've left some to pile up this way)
These impacts aren't drastic. If you have just a few long-lived objects that need this, you're probably safe enough with this default behaviour.
But if you need a lot of objects with material variants, or you're frequently spawning and despawning objects with a modified material, then all the extra batch splits and memory allocation can add up.
Fortunately you can control when to make copies and when to avoid/get rid of them:
You can use the
.sharedMaterial getter to take the current material as-is, without making a copy.
Doing so, you take responsibility for sorting out when you want to make a new copy with
Instantiate(material) before modifying it, so you don't modify a shared asset.
You can create your own material cache that keeps track of what material variants you've instantiated this way, and re-uses the same one if several different objects want "this base material, but blue". This way you avoid redundant allocations and preserve batching between objects using the same look.
Destroy(materialInstance) on a material copy you've created when you're done with it (say, inside your object's
OnDestroy() method, or when your cache's ref count hits zero), so you don't leak memory until the next scene load purges unused materials. (In the editor, you'd use
You can use
MaterialPropertyBlocks as a more lightweight way to make small changes to materials, like just changing a colour. This still breaks batching between versions that use different properties, but has less overhead than a whole new material, and you don't need special clean-up considerations since they're garbage collected automatically when the objects using them are destroyed.