Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question How to compare 2 materials on different objects?

Discussion in 'Editor & General Support' started by Andrew3371, May 31, 2023.

  1. Andrew3371

    Andrew3371

    Joined:
    Mar 27, 2023
    Posts:
    23
    I try to compare 2 materials on 2 different object. It's actually the same material "sourceMaterial" assigned on 2 objects. When I get them from Mesh Renderer and compare them it's always false. So how to do it properly?

    Shared materials or just materials as a property of Mesh Renderer on 2 different objects are always "false" when using operator "==".
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,612
    If both assets are referencing the same material asset, then comparing via
    .sharedMaterial
    will return true. Though I believe if you have edited either of the material at any point during runtime and caused the renderer to create an instanced material, then this will of course cause the equality check to fail.

    Though a good question is why you might be comparing materials. Might be a situation where there's a better way to design this.
     
  3. Andrew3371

    Andrew3371

    Joined:
    Mar 27, 2023
    Posts:
    23
    It's always false for me. I assign material on both objects by a script (just using serialized link to material asset).
    Code (CSharp):
    1. string textForResult1_2 = $"Are shared materials on two different objects the same: {((meshRendererOnObject1.sharedMaterial == meshRendererOnObject2.sharedMaterial) ? true : false)}";
    Probably there is a better way. I use it to highlight object like it was selected. If it has it's own material I apply highlight-material. If object's material equals highlit-material I apply original one. I use equality to define selection state.
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,612
    Are you assigning to its
    .material
    or
    .sharedMaterial
    property? Assigning to
    .material
    will also make a new material instance. And naturally assigning to
    .sharedMaterial
    changes all objects using that material.

    It might be easier to just add/remove a specific component to indicate its selected, or just store a list of selected objects and check if it's in the list (or both).
     
    Andrew3371 likes this.
  5. Andrew3371

    Andrew3371

    Joined:
    Mar 27, 2023
    Posts:
    23
    Do you say that if I want to assing a material to the object I have to use
    sharedMaterial
    ? Yes, I assign it by using
    material
    property.

    Hmm, good one, thanks. I'll give it a try.
     
  6. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,193
    Any time you access
    material
    or
    materials
    on a renderer, that actually creates a copy of the materials assigned just to that renderer. I think that's the thing that's causing you. This is very useful when you want to customize each instance of a rendering with different material settings. But now that renderer's materials will never be == to any other materials.

    As mentioned above, if you instead use
    sharedMaterial
    or
    sharedMaterials
    , that does not create copies of the materials. So as long as you only use the "shared" materials, you should be able to test for equality. However, if you ever call
    material
    or
    materials
    , you can't expect equality anymore.

    Depending on what you're trying to accomplish, you probably want to look for some other way to compare materials so that it won't matter if a renderer creates instances (copies) of the materials. Testing the material name for a common string, or perhaps testing for the materials shader, could be done.
     
    Andrew3371 likes this.
  7. Andrew3371

    Andrew3371

    Joined:
    Mar 27, 2023
    Posts:
    23
    I've found out the weird reason why
    sharedMaterial
    on different objects becomes not equal. If you call
    get
    method of property
    meshRenderer.material.name
    then
    sharedMaterials
    become not equal. You don't have to change something or assign, just call a
    get
    method. Wow! I've spent hour to figure that out.
    2023-06-01_16-00-00.png
    Here is the code that demonstrates weird property behaviour:
    Code (CSharp):
    1. using UnityEngine;
    2. using TMPro;
    3.  
    4. public class MaterialsComparisonProblem : MonoBehaviour
    5. {
    6.     [SerializeField] Material sourceMaterial; // material that we assign for comparison
    7.     [Space]
    8.     [SerializeField] TextMeshProUGUI textResult1;
    9.     [SerializeField] TextMeshProUGUI textResult2;
    10.     [Space]
    11.     [SerializeField] GameObject object1;
    12.     [SerializeField] GameObject object2;
    13.     [SerializeField] GameObject object3;
    14.     [SerializeField] GameObject object4;
    15.  
    16.     Material newMaterialOnObject1;
    17.     Material newMaterialOnObject2;
    18.     void Start()
    19.     {
    20.         MeshRenderer meshRendererOnObject1 = object1.GetComponent<MeshRenderer>();
    21.         MeshRenderer meshRendererOnObject2 = object2.GetComponent<MeshRenderer>();
    22.         MeshRenderer meshRendererOnObject3 = object3.GetComponent<MeshRenderer>();
    23.         MeshRenderer meshRendererOnObject4 = object4.GetComponent<MeshRenderer>();
    24.  
    25.         meshRendererOnObject1.sharedMaterial = sourceMaterial;
    26.         meshRendererOnObject2.sharedMaterial = sourceMaterial;
    27.         meshRendererOnObject3.sharedMaterial = sourceMaterial;
    28.         meshRendererOnObject4.sharedMaterial = sourceMaterial;
    29.  
    30.         string materialName = meshRendererOnObject3.material.name; // here's the cause of strange behaviour while comparing. Now it won't equal meshRendererOnObject4.sharedMaterial
    31.  
    32.         textResult1.text = $"Does sharedMaterials on object1 and object2 equal? {((meshRendererOnObject1.sharedMaterial == meshRendererOnObject2.sharedMaterial) ? true : false)}"; // yes
    33.         textResult2.text = $"Does sharedMaterials on object3 and object4 equal? {((meshRendererOnObject3.sharedMaterial == meshRendererOnObject4.sharedMaterial) ? true : false)}"; // no
    34.     }
    35. }
     
  8. Andrew3371

    Andrew3371

    Joined:
    Mar 27, 2023
    Posts:
    23
    You explanation makes it more clear but I never expected making
    get
    request to instantiate something. Unity's official manual doesn't make it clear for me. Why does calling
    .name
    property intantiates something for whatever reason? Is it a good practice at all?
    2023-06-01_16-16-42.png 2023-06-01_16-16-48.png 2023-06-01_16-18-21.png 2023-06-01_16-20-00.png
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,612
    Read the documentation on MeshRenderer.material. It's perfectly described in the documentation.

    The reason for this is to prevent modification of assets during editor play mode testing.
     
  10. Andrew3371

    Andrew3371

    Joined:
    Mar 27, 2023
    Posts:
    23
    I've read this and attached screenshot with what it says:
    I'm not modifying anything, i'm calling
    .name
    property with
    get
    request. Modifying property is
    set
    method.
     
  11. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,612
    The first lines states:
    Note the bolded word. If you read from it, a copy of the material is instantiated.
     
  12. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,193
    I agree that the behavior on this one is quite subtle. I don't think it would be as big of an issue if they'd just chosen the property names better. The average person is going to see "material" and "sharedMaterial", and they'd probably use "material" because it's not obvious that they should use the other one. Maybe "sharedMaterial" should have just been "material" and the original material could have been named "materialInstance" to avoid confusion. But, it's probably a decade too late to wonder about that.

    The docs do state it pretty clearly, though:
    Still, not obvious at all until you've gotten used to how thing works.

    As for whether Unity has a lot of things like this, I'd say the answer is generally "no". This one's a bit of an odd-ball. Maybe the difference between
    gameObject.tag == x
    and
    GameObject.CompareTag(x)
    is another thing that trips people up, but not so severely. (I'm not even 100% sure whether
    gameObject.tag == x
    still generates garbage in modern versions of Unity...)
     
    Andrew3371 likes this.
  13. Andrew3371

    Andrew3371

    Joined:
    Mar 27, 2023
    Posts:
    23
    I agree with you. Let's consider it as something strange but as is. My mind just wasn't ready for this:D
     
  14. Andefob

    Andefob

    Joined:
    Sep 17, 2018
    Posts:
    99
    I bumped into this thread when I was using debugger to verify one piece of my code handling shared materials was working properly. When using the debugger, it looked like comparing two shared materials which were supposed to be equal returned false, resulting in erroneous outcome. However, when I was not using step-by-step debugger the outcome was what I wanted. I assume the reason is that when VS debugger shows the contents of a renderer in step-by-step mode, it shows what materials contain and that makes Unity actually make new instances of the shared materials.

    Inverse Heisenbug!