Is there a way to save material instances to the disk with independent properties on each instance?

I am thinking about using .material to access and change properties on instanced materials and serialized them to the disk, so that whenever the original material changes, some certain properties will stay the same on those instanced materials, without the need of manually creating material copies every time.

Because whenever using .material, Unity will warn that there is a chance for the materials to be leaked to the scene.
How to safely and properly create material instances that can be stored on the disk?

Or should I use SetMaterialProperty on awake and let it be a script component on this model prefab?

Really there’s no difference between calling Material mat = rend.material; and Material mat = new Material(rend.sharedMaterial);. There’s some additional behind the scenes logic for when accessing renderer.material creates a new material and when it gives you the existing one, as it won’t create a new material on access if it already created a copy. (Though it will create a copy every time the rend.material is assigned, so make sure you use rend.sharedMaterial = mat; for that.)

The issue with leaking is if you create a material when instantiating an object which you later destroy, and you do that a lot of times, you’re effectively creating a memory leak unless you also destroy the new material or load to a new level. If you’re modifying the prefab in the editor the same thing can technically be happening, but it’s not really a concern. You can save that new material to be an asset using AssetDatabase.CreateAsset(), though you may need to reassign the material back onto the prefab afterwards. You’d have to test that yourself, I don’t know if the asset GUID remains intact. Be mindful to give it a new unique name, as otherwise it’ll just overwrite any previous asset at the file path you give it.

This might be creating some headache for yourself though, as again this is an entirely new material with no connection back to the original material. Any modification to the “original” shader will require going through and either making all new copies, or modifying the copies with the wanted changes each time.

If you’re doing that though it means you already have a list of the properties you want to modify uniquely per prefab. At which point you could slap those into a material property block on awake and assign it to the renderer component and never have to think about it again. Which is what I would do.

The one massive caveat to all this is the URP / HDRP don’t seem to like material property blocks as they disable the SRP Batcher. I don’t know why this is the case, especially since a ton of internal Unity stuff modifies material properties using material property blocks (like using animations to modify materials!), but that’s where we are.

1 Like

I am doing this to make it a real material “instance” rather than a copy, to inherit all future new properties and changes on the base material, while decoupling these certain properties in material property block. I tested using OnValidate() and serialize the material property block, which seems work.

Does it need to use Awake to send it to renderers every time the game is started? If so, why it seems I only use serialized material property block and onValidate work already? I reboot the PC and play the game and regenerate the base material, the material property block component never gets messed up and these decoupled properties(as a script component) on it are intact.

So this leads to another question, where is the renderer’s propertyBlock info saved?

If you’re overwriting a few properties on per-objecgt basis than rather than creating material instances you might consider implementing MaterialPropertyBlock override for objects via a custom component.

Here’s an example, the principles still apply to thsi day:
https://thomasmountainborn.com/2016/05/25/materialpropertyblocks/

In this scenario your component would serialize to the disk, and you’d be able to store changes in prefabs.

I’m in the same configuration where I’d like to store a Material Instance inside of a prefab. Did you get any chance for a result ?

I’ve spent much too much time on the topic of per object shader properties in URP :smile:

If you need a material instance to animate, store a reference to the base material asset and instanciate and assign it when it is needed. As long as you don’t need it use the base asset. Storing as in serializing a unique instance inside of a prefab is impossible because prefabs do not work that way.

Inside of the editor you can make use of material variants (set material.parent but wrap that call in and #if UNITY_EDITOR, the api is editor only although it is in the runtime namespace). That way you don’t loose connection to the base material properties, that will only help with editor-usability though, in a build as with prefabs there will be no connection to the base material after instanciating.

If you want to serialize a material instance, you can do that … As bgolus said save it to disk using AssetDatabase. But then you have to assign those to your prefabs, you could do that in a pooling fashion or sth like that but that would depend a lot on the use case.

The lack of SRP batcher material property block support sucks a lot. You can spare yourself a lot of trouble and use those if you only need them temporarily on a few instances though (a few <10 more draw calls will not cause issues). MPB have the advantage of working across all materials of the renderer, even override materials from a renderer feature.

Thanks for answering. Well yeah I kinda figured out it was still impossible. I also asked on the animation forum as to find an alternative to our issue ( API for Animation panel previews and state restoration? )

What I do is I have a script that handles instantiation and restoration of the materials specifically to my use case. I was already doing so to be able to “animate” physic in unity timeline/animation dropesheet. along [executeAlways] I’m using OnEnable/Disable and other engine triggers (Enter/Exit playmode, Serialization …) to plug in the restoration.

1 Like

What I do is similar to your solution: have a component with custom per instance data, that handles creating/destroying the material instance at runtime. In my experience this is flexible and performant. You can see an adapted version of my code in this thread, can use either instantiated materials or material property blocks:

1 Like