How to compare 2 materials on different objects?

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 "==".

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.


It's always false for me. I assign material on both objects by a script (just using serialized link to material asset).

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.


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).

1 Like


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.


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.

1 Like

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.
9050320--1250497--2023-06-01_16-00-00.png
Here is the code that demonstrates weird property behaviour:

using UnityEngine;
using TMPro;

public class MaterialsComparisonProblem : MonoBehaviour
{
    [SerializeField] Material sourceMaterial; // material that we assign for comparison
    [Space]
    [SerializeField] TextMeshProUGUI textResult1;
    [SerializeField] TextMeshProUGUI textResult2;
    [Space]
    [SerializeField] GameObject object1;
    [SerializeField] GameObject object2;
    [SerializeField] GameObject object3;
    [SerializeField] GameObject object4;

    Material newMaterialOnObject1;
    Material newMaterialOnObject2;
    void Start()
    {
        MeshRenderer meshRendererOnObject1 = object1.GetComponent<MeshRenderer>();
        MeshRenderer meshRendererOnObject2 = object2.GetComponent<MeshRenderer>();
        MeshRenderer meshRendererOnObject3 = object3.GetComponent<MeshRenderer>();
        MeshRenderer meshRendererOnObject4 = object4.GetComponent<MeshRenderer>();

        meshRendererOnObject1.sharedMaterial = sourceMaterial;
        meshRendererOnObject2.sharedMaterial = sourceMaterial;
        meshRendererOnObject3.sharedMaterial = sourceMaterial;
        meshRendererOnObject4.sharedMaterial = sourceMaterial;

        string materialName = meshRendererOnObject3.material.name; // here's the cause of strange behaviour while comparing. Now it won't equal meshRendererOnObject4.sharedMaterial

        textResult1.text = $"Does sharedMaterials on object1 and object2 equal? {((meshRendererOnObject1.sharedMaterial == meshRendererOnObject2.sharedMaterial) ? true : false)}"; // yes
        textResult2.text = $"Does sharedMaterials on object3 and object4 equal? {((meshRendererOnObject3.sharedMaterial == meshRendererOnObject4.sharedMaterial) ? true : false)}"; // no
    }
}


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?
9050350--1250512--2023-06-01_16-16-42.png 9050350--1250509--2023-06-01_16-16-48.png 9050350--1250506--2023-06-01_16-18-21.png 9050350--1250503--2023-06-01_16-20-00.png

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.


I've read this and attached screenshot with what it says: [quote]
Modifying material will change the material for this object only.
[/quote] I'm not modifying anything, i'm calling .name property with get request. Modifying property is set method.

The first lines states:
[quote]
Returns the first instantiated Material assigned to the renderer.
[/quote]

Note the bolded word. If you read from it, a copy of the material is instantiated.

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:
[quote]
If the material is used by any other renderers, this will clone the shared material and start using it from now on.
[/quote]

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...)

1 Like


I agree with you. Let's consider it as something strange but as is. My mind just wasn't ready for this:smile:

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!