- Home /
Instanced material ignoring value changes.
So, I'm working with some objects in my scene that use dynamic materials which are modified at runtime. However, in an attempt to clean up the code some, things have stopped working. I used to instantiate the materials as needed, modify them and set them. That worked fine, but without proper garbage collection that leads to a reasonable memory leak.
Logically speaking, I should only have to instantiate the material once per object, and modify the instance from there. However, all of my attempts to do this lead to Unity simply ignoring any changes at runtime. I have even debugged the values as they change, and they are changing within the material, there is just no visible change. If I modify the values at start, regardless of order, they work. After start, however, anything I do is ignored unless I instantiate another copy, which should not be the case.
void Start()
{
//This works
Material mat = Instantiate(PosterImage.material);
mat.SetFloat("_EffectAmount", 0f);
PosterImage.material = mat;
//This also works
PosterImage.material = Instantiate(PosterImage.material);
PosterImage.material.SetFloat("_EffectAmount", 0f);
}
Any combination of setting the value and the material in any order within start works perfectly, but if I try to modify that in any way afterwards, it sets the value and does not change anything physically in scene. I would try MaterialPropertyBlocks, but the objects do not have a renderer as they are canvas based. Worst case scenario I will have to go back to instantiating and manually cleaning up the materials, but that is really not something that should be necessary. Is there something I am missing, or is this a limitation of Unity.
Edit: I suppose it helps to mention that the PosterImage variable is an Image, from UnityEngine.UI, so it is governed by a CanvasRenderer as opposed to the standard renderer, as stated above.
Can you post an example of code that does not work?
If I have instantiated it beforehand, any combination of references and resetting do not work, even though the references are updating.
void UpdateTexture()
{
//This works
$$anonymous$$aterial mat = Instantiate(PosterImage.material);
mat.SetFloat("_EffectAmount", 0f);
PosterImage.material = mat;
//This does not work
PosterImage.material.SetFloat("_EffectAmount", 0f);
//Neither does this, assu$$anonymous$$g I store the instance at the start
$$anonymous$$atInst.SetFloat("_EffectAmount", 1f);
PosterImage.material = $$anonymous$$atInst;
//If I debug the value after modifying it, the value HAS changed
//However, it does not change how things are rendered at all
//Unless I re-instantiate and re-set the material
Debug.Log(PosterImage.material.GetFloat("_EffectAmount"));
//This is the only way currently to make it work cleanly
$$anonymous$$aterial mat = Instantiate(PosterImage.material);
Destroy(PosterImage.material);
mat.SetFloat("_EffectAmount", 1f);
PosterImage.material = mat;
}
Answer by Adam-Mechtley · Nov 22, 2016 at 08:45 PM
Have you tried setting the float on the materialForRendering property instead of the material property?
So, upon testing that appears to work just fine, without any memory leaking. The odd part is that according to the docs: "By default it's the same as Graphic.material" so it should really have already been working. What is the purpose of the material value if Unity decides to render a different material? Oh well, that's a mystery for another day. Thanks.
If you have any I$$anonymous$$aterial$$anonymous$$odifier objects (e.g., $$anonymous$$asks) acting on the component, then the material actually submitted to the renderer may not be the same as the default one specified.
It should also be noted that while using this method, you still need to instantiate the material at start, or else they will continue to edit the base material. This may not always be an issue, but it is far from ideal, so save yourselves the headaches and instance those materials in advance.
Answer by Bunny83 · Nov 22, 2016 at 06:04 PM
I don't quite get your problem. Whenever you acutally use renderer.material, Unity will automatically create an instance of the original material just for this object. Unity does this "once" for each object. There's no need to instantiate the material manually. If you want to access the shared material (which doesn't auto-instantiate the material on access) you have to use "sharedMaterial".
If the value in the material has actually changed but doesn't affect the actual object, make sure the object is not marked as static because chances are high that the object has been statically batched. Batching in general could be a problem here.
What exactly is "PosterImage"?
ps: Materials are not automatically garbage collected when you loose all references to it. All objects derived from UnityEngine.Object can be recovered by using FindObjectsOfType. They are automatically removed when you change the scene or when you call Resources.UnloadUnusedAssets.
So when you no longer need a dynamically created Material you should Destroy it.
PosterImage is an Image object, from UnityEngine.UI. It is therefore governed by a CanvasRenderer. I am aware that the materials are not garbage collected, so I've got to destroy them on my own as I go to avoid needing Resources.UnloadUnusedAssets. I've not marked any of the objects in question as static, since they are dynamically generated and modified at runtime.
Well, that turns the question into something completely different ^^. $$anonymous$$ost things that are rendered in Unity use some sort of renderer. However the new UI system works a bit different.
Good that your comment / question update lead $$anonymous$$to properly answer your question ^^.