Question About Textures and Materials

I was wondering which method would take the least amount of work for the computer. I have a bunch of gameobjects that will change textures if they have been activated. Which method would be the better one to use, or are there any other methods which would be even better?

The first method I am changing the texture of the material that is on the gameobjects

public Texture2D defaultTexture;
public Texture2D activeTexture;
public GameObject[] myGameobjects;

for (int i = 0; i < myGameobjects.Length; ++i) {
    myGameobjects[i].transform.renderer.material.mainTexture = activeTexture;
}

The second method I am changing the actual material on the gameobjects

public Material defaultMaterial;
public Material activeMaterial;
public GameObject[] myGameobjects;

for (int i = 0; i < myGameobjects.Length; ++i) {
    myGameobjects[i].transform.renderer.material = activeMaterial;
}

Unity handles material references in a really odd way. Before giving you my opinion follow me and let’s step into some code together! Yay!

public class MaterialTest : MonoBehaviour
{
    public GameObject firstObject, secondObject;
    public Material SomeOtherMaterial;

    private void Start()
    {
        LogId(firstObject.renderer.sharedMaterial); //Id[74]
        LogId(secondObject.renderer.sharedMaterial); //Id[74]
        //Same Ids, as expected

        LogId(firstObject.renderer.material); //Id[-44682]
        LogId(secondObject.renderer.material); //Id[-44684]
        //Different Ids, .material property clones material when first accessed

        LogId(firstObject.renderer.sharedMaterial); //Id[-44682]
        LogId(secondObject.renderer.sharedMaterial); //Id[-44684]
        //Bet you didn't expect that!

        LogId(SomeOtherMaterial); //Id[-29346]
        firstObject.renderer.material = SomeOtherMaterial;
        LogId(firstObject.renderer.sharedMaterial); //Id[-29346]
        //Same as SomeOtherMaterialId - also, you've lost reference to the previously cloned material and caused a leak

        LogId(firstObject.renderer.material);//Id[-44686]
        //Now it's been cloned again and have caused yet another leak. Excelsior!

        LogId(firstObject.renderer.sharedMaterial);//Id[-44686]
        //.sharedMaterial references the same newly cloned material
    }

    private void LogId(Material material)
    {
        Debug.Log(string.Format("Id[{0}]", material.GetInstanceID()));
    }
}

What I’ve concluded is that the renderer internally references a single collection of materials and the .material(s) / .sharedMaterial(s) properties expose 2 ways of accessing them. When getting .material(s) properties: the component will first check if this is the first time its referenced material(s) are being accessed and, if so, will clone them. Getting .sharedMaterials(s) will simply return the internal references it currently has. Setting either .material(s) or .sharedMaterial(s) will simply set the internal references the same way and prime the next .material get call to clone again.

So, to answer your question: Setting the material will technically be the most efficient as it will avoid cloning, just be aware of the consequences. If you set multiple game object render.material properties (or .sharedMaterial for that matter) to the same referenced material and then modify a property off a direct reference to that material (like the texture), it will affect the rendering of all those objects. Also, any subsequent accessing of that material via the renderer.material property will clone that material for that renderer instance which, if it had been cloned and then subsequently set to something else before, will result in a material leak.

Confusing, isn’t it?