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. Dismiss Notice

Question Do I have to copy materials/shaders?

Discussion in 'Scripting' started by XJonOneX, Jun 13, 2023.

  1. XJonOneX

    XJonOneX

    Joined:
    Dec 19, 2017
    Posts:
    69
    I'm not at my dev computer atm. If I have several game objects with the same material, and I want to change the color via code, is that going to affect all objects with that material? Same for shader parameters? If I change parameters on a shader, does it affect all objects?

    Do I need to copy materials and shaders in code so they're unique to an object that I want to change?
     
  2. Lars-Steenhoff

    Lars-Steenhoff

    Joined:
    Aug 7, 2007
    Posts:
    3,449
  3. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Material is an intermediate object that serves as a pseudo-wrapper around a shader. It contains a shader ID as well as the parameters that are relevant to it. From the editor, objects will have whatever material instance that you have assigned manually.

    Programmatically however, MeshRenderer has two properties, material, and sharedMaterial. Reading will always get you the reference to the actual assigned instance (*), but writing is different because
    material
    assigns a new instance.

    As the
    material
    docs says
    Contrast this with
    sharedMaterial

    * Edit: This part is incorrect, reading
    material
    would internally replace the assigned material with a new clone (see next post). Which also means that the other part of that sentence is slightly incorrect, but the whole point is that using
    material
    removes the possibility of cross-contamination of other materials in the scene / project (but also sometimes needlessly produces new instances, so there's a trade off).
     
    Last edited: Jun 14, 2023
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I believe reading from
    .material
    will also immediately create a new instance. As it states:
     
    lordofduct and orionsyndrome like this.
  5. XJonOneX

    XJonOneX

    Joined:
    Dec 19, 2017
    Posts:
    69
    Awesome. Thanks guys!
     
  6. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Does it? I interpret that sentence backwards for some reason. It returns the first instantiated Material assigned to the renderer. Where does it indicate a clone? The docs... Gotta love 'em.

    Edit: I guess you're right. They just make sure it's not the one that was assigned. But I'm not sure what happens after that. If you do a loop of hundred read iterations would it pop hundred materials into existence? Probably just the first one, but now I'm curious.

    Edit2:
    Hmm, well if you never assign it back, then it is hundred materials. But by now I have answered myself all there is to it, because you're likely expected to use sharedMaterial if you want to avoid it. Kids, don't read materials in loops!
     
  7. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    The 'instantiated' part implies its a clone. I agree it could be worded better.

    And no, it only makes a new one the first time you access it.

    And gotta remember to clean up the instantiated material once you're done with it. Most people forget that, or don't even know about that. I blew the minds of a whole modding community with this information as none of them had actual first hand experience with Unity.
     
    orionsyndrome likes this.
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Ah you mean it produces a swap with the new instance internally? That's good info.
     
    spiney199 likes this.
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    It feels like a wrapper on .sharedMaterial that looks to see if .sharedMaterial is a disk asset reference and if so, it instances it AND begins using it rather than returning the original Asset.

    When you assign any Material (instance OR asset) to .material it takes that new reference into both .material and .sharedMaterial.

    Thus you actually can re-link the .sharedMaterial to point back at an arbitrary disk asset.

    At least that's my best black-box re-imagining it based on the fine details of the behavior.

    Please feel free to point out any blind spots in my experiment below.

    Also note the numbers being positive vs negative and see the docs for GetInstanceID()...

    Screen Shot 2023-06-13 at 5.13.33 PM.png

    The code:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class NewMTL : MonoBehaviour
    6. {
    7.     // @kurtdekker
    8.     // drop this on a default cube or sphere you can watch
    9.     // study the log output
    10.     // this will PAUSE, you must UN-pause
    11.     IEnumerator Start ()
    12.     {
    13.         var rndrr = GetComponent<Renderer>();
    14.  
    15.         var originalMat = rndrr.sharedMaterial;
    16.  
    17.         Debug.Log( "o=" + originalMat.GetInstanceID());
    18.  
    19.         var instanceMat = rndrr.material;
    20.  
    21.         Debug.Log( "i=" + instanceMat.GetInstanceID());
    22.  
    23.         var JinstanceMat = rndrr.material;
    24.  
    25.         Debug.Log( "j=" + JinstanceMat.GetInstanceID());
    26.  
    27.         Debug.LogWarning( "BROKE - press PAUSE"); Debug.Break(); yield return null;
    28.  
    29.         instanceMat.color = Color.green;
    30.  
    31.         Debug.LogWarning( "BROKE - press PAUSE"); Debug.Break(); yield return null;
    32.  
    33.         rndrr.material = originalMat;
    34.     }
    35. }
     
    orionsyndrome likes this.
  10. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Hm, ok.

    Kurt I haven't run your test yet, but I've been reading and it appears that the
    material
    getter behaves defensively only when there is another MeshRenderer that is referring to the same material (and also when this is the original instance, the one that was set in the editor).

    So it's not that writing to material would create a clone, as I naively thought; the idea is that you can't even refer to the original instance, because it's safe-guarded from the get go. And so it pops (and internally assigns) a safe replacement, the one that isn't referred by anything. And from that point on you can read that material a million times without producing a defensive copy.

    But now I'm not sure when, why, and how should one use Destroy to safely clean up the material.
    If we get a fresh clone from reading
    material
    , the new material Object is made on the C++ side, and we get to modify its shadow copy on the C# side. The only scenario in which we want to Destroy the material is when we want to Destroy a game object with a MeshRenderer that refers to a material that was specifically cloned for it.

    But now I'm not sure why Unity doesn't do this automatically on removing this MeshRenderer? If they can count the references already (to produce a clone when it's needed), then this has to be the safest and the best place to do it?

    If they don't do it, we have no means to tell whether the Material actually needs destroying. So you got to call
    material
    again, so that this destruction doesn't affect anything else by accident. However, in the worst case, this would produce another instance which would immediately get destroyed. If this is the case, it's such a weird design...

    I think it makes sense to build an automation around this. A proxy service that handles material matching and destroying, and knows whether to access MR by material or sharedMaterial.
     
  11. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I don't think it's that complicated. If you access the
    .material
    property at all, you need to destroy the material you get when its no longer needed.

    So even if you might modify the material, you might as well just get the instance and always destroy that instance when you're done with it.
     
    orionsyndrome likes this.
  12. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Yes, I agree, in everyday usage, I've never noticed any problems that would warrant a service around this. I also tend to use
    sharedMaterial
    whenever I can. Thankfully I was always careful about
    material
    , that's one of those things you pick up early.

    But this notion that you're producing a memory leak, just from accessing a getter, makes me feel uncomfortable. That simply violates every basic principle of OOP. 15 years later, and I'm still discovering weird design choices in Unity...

    And the wording in the docs doesn't help and feels deliberately vague. I don't know if people can even perceive how much energy is spent navigating this minefield, instead of making tools and games in the direct way possible. It's almost like all of this gives weight and value to 3rd party solutions. Also makes any form of practical know-how extremely salable.

    Knowing Unity is almost like martial arts, you dedicate years, hone your skills, let go pigeon spies to collect information. I believe in 2023 someone should be able to read a short digest in one go and lean on to standard programming principles and simple mnemonics to guide them through without any gotchas or exceptions in the vein of "well, if you read from this property, you need to clean up later".
     
    Last edited: Jun 14, 2023
    Kurt-Dekker likes this.