Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Restore renderer's instantiated material to shared material

Discussion in 'General Graphics' started by Radu392, Feb 5, 2019.

  1. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    210
    Hi, here's the situation. I have a base building game and when you place down a building, the material of that building is changed so that it plays a nice effect while it's constructing. After it's done doing that, I want to put the renderer's material back to the shared material to take advantage of batching. But NONE of the following do what I want:

    object.material = object.sharedMaterial;
    object.material = originalMaterial;
    object.sharedMaterial = originalMaterial;

    When I look in the editor, the material field still shows the (Instance) next to the material's name. The only way I've found is by dragging myself the originalMaterial into the renderer's material field. I would assume one of those lines of code above would do that, but none do.

    This is extremely important as the draw calls for having hundreds of instantiated materials shoots up to 3000. All I want is to get rid of the instantiated material and tell the object to use the sharedMaterial again. Is that possible?
     
  2. Smaika

    Smaika

    Joined:
    Apr 28, 2014
    Posts:
    14
    Same problem with me, did you find anything?
     
  3. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    210
    Hey, yeah I found something but it's a hacky way to do it.

    I've found that only this works:

    public void reset() {
    mr = GetComponent<MeshRenderer> ();
    Destroy (mr.material);
    mr.sharedMaterial = originalMaterial;
    mr.material = originalMaterial;
    }
    You have to assign the original material yourself in the editor, you can't just say originalMaterial = mr.sharedMaterial right before destroying either.
    It doesn't work with all the materials in the material array, I already tried. I guess that's better than nothing for now
     
  4. Smaika

    Smaika

    Joined:
    Apr 28, 2014
    Posts:
    14
    Well thanks, it did work but it's wired it won't work with material array. I hope someone from unity team could clarify why or how to work around that. Or is it a bug?
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
    Use a material property block instead. Otherwise you're creating materials that don't get cleaned up automatically by Unity. See the documentation on Renderer.material:
    https://docs.unity3d.com/ScriptReference/Renderer-material.html
    Material property blocks instead act as a list of override values which you can easily clear after you're done with them, returning the material to its original state since you're not actually modifying it. So instead of doing something like this:
    rend.material.SetFloat("_MyVar", myVarValue);

    Do this:
    Code (csharp):
    1. // material property blocks are a class, make one on awake and keep it around
    2. private MaterialPropertyBlock _matBlock;
    3. void Awake()
    4. {
    5.     _matBlock = new MaterialPropertyBlock();
    6. }
    7.  
    8. // then in your function to modify your material
    9. rend.GetPropertyBlock(_matBlock); // only needed if multiple script components are going to be setting override values
    10. _matBlock.SetFloat("_MyVar", myVarValue);
    11. rend.SetPropertyBlock(_matBlock);
    12.  
    13. // to reset the material to default, use this
    14. rend.SetPropertyBlock(null);
    You can even set unique properties per material index if need be, otherwise it overrides all materials the renderer is using:
    https://docs.unity3d.com/ScriptReference/Renderer.SetPropertyBlock.html

    It's also used for instanced meshes as a way to set instanced properties.
    https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.html
     
    Zelarm and Johan_Valectric like this.
  6. drcrck

    drcrck

    Joined:
    May 23, 2017
    Posts:
    328
    All you need to do is
    Code (csharp):
    1.  
    2. object.material = null;
    3. object.sharedMaterial = originalMaterial;
    4. Destroy(tempMaterial);
    5.  
     
    Last edited: Feb 8, 2019
    triple_why likes this.
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
    That will cause a potential memory leak. You need to destroy the material too.
     
  8. drcrck

    drcrck

    Joined:
    May 23, 2017
    Posts:
    328
    He's already doing it, check out the 3rd post
    I'm addressing the issue of reverting the sharedMaterial, nothing else

    PS Because someone might google this thread in a few years and I'm sure he'd like to see a concrete solution for his problem, not a bunch of general advices "how to write good code". It's really annoying to face such threads.
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
    The original author might already be doing it, but people reading this thread later are just going to go to the last post with code and copy it. Your example doesn't include the destroy, thus will cause a memory leak.
     
    acnestis likes this.
  10. drcrck

    drcrck

    Joined:
    May 23, 2017
    Posts:
    328
    Okay I added it, now the last post with code (=to be copypasted) is yours and it doesn't include Destroy :D
     
    bgolus likes this.