Change texture on an instance of a prefab without leaking materials into scene?

I’ve searched, and searched. sharedMaterial and all that, but I still don’t get it.

  • I have a prefab called BuildingSign, and I have about 100 of them instantiated in my scene.
  • I have a different texture assigned to each, so I wrote a script that takes the Texture as an argument, and assigns it to the sign, also making the height/width ratio correct.
  • I wanted to see the signs in the editor, so I ran it from a Validate() function in a script with [ExecuteInEditMode).

It works, I can see each sign, correctly in editor or standalone. But it screams at me about leaking materials in the scene.

If I try to use sharedMaterial, all of the signs take on the same texture. No bueno.

Also tried setting a reference to the material and changing that reference.

Each sign throws:
Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead.
UnityEngine.Renderer:get_material ()

Latest version:

using UnityEngine;

//[ExecuteInEditMode]
public class ApplyTextureToChildren : MonoBehaviour
{
    [Tooltip("The texture to apply to all children.")]
    public Texture texture;

    public bool isDoublesided = true;

    private float cutoutThreshold = 0.5f;

    Material material;

    private void OnValidate()
    {
        ApplyTexture();
    }

    private void ApplyTexture()
    {
        if (texture == null)
        {
            return;
        }

        foreach (Transform child in transform)
        {
            Renderer renderer = child.GetComponent<Renderer>();
            if (renderer != null)
            {
                material = renderer.material;

                material.mainTexture = texture;

                //Brighten it up!
                material.SetTexture("_EmissionMap", texture);
                material.SetColor("_EmissionColor", Color.white);
                // Set render mode to Cutout
                material.SetFloat("_Mode", 1); // Set render mode to Cutout
                material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                material.SetInt("_ZWrite", 1);
                material.EnableKeyword("_ALPHATEST_ON");
                material.DisableKeyword("_ALPHABLEND_ON");
                material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                material.renderQueue = 2450;

                if (material.HasProperty("_Cutoff"))
                {
                    material.SetFloat("_Cutoff", cutoutThreshold);
                }

                // renderer.material = material;

                float aspectRatio = (float)texture.width / texture.height;
                Vector3 scale = child.localScale;
                scale.z = scale.x / aspectRatio;
                child.localScale = scale;
            }
        }

        // Hide the back if single sided
        transform.GetChild(1).SetActive(isDoublesided);
    }

    void OnDestroy()
    {
        Destroy(material);
    }
}

Try create a new material and copy the renderer material properties instead

Can also keep a list with the created materials and destroy them after the preview is not needed anymore

@nasos_333 Yeah, tried that. Tried both:

Material material = renderer.material;

and

Material material = new Material(renderer.material);

Also tried both with sharedMaterial. getting the material caused a leak, and getting the shared material still shared it.

Funny thing is, I’ve done this before, and I swear it worked by just setting a reference to the material and changing the reference, or creating a new material, modifying it, then applying the new material to the renderer. But now, everything I do leaks into the scene. Odd.

Ultimately, I managed to get it to work. Not sure why this attempt succeeded when all other’s failed, but here’s the code, if it helps anyone else.

using System;
using UnityEngine;

[ExecuteInEditMode]
public class ApplyTextureToChildren : MonoBehaviour
{
    [Tooltip("The texture to apply to all children.")]
    public Texture texture;

    public bool isDoublesided = true;

    private float cutoutThreshold = 0.5f;

    Material material;

    private void OnValidate()
    {
       // Make sure we're not working on the actual Prefab, but an instance.
        if (isActiveAndEnabled)
            ApplyTexture();
    }

    private void ApplyTexture()
    {
        if (texture == null)
        {
            return;
        }

        foreach (Transform child in transform)
        {
            Renderer renderer = child.GetComponent<Renderer>();
            if (renderer != null)
            {
                try
                {
                    // material = renderer.material;
                    if (renderer.sharedMaterial)
                    {
                        material = new Material(renderer.sharedMaterial);
                    }
                    else
                    {
                        material = new Material(renderer.material);
                    }

                    material.mainTexture = texture;

                    //Brighten it up!
                    material.SetTexture("_EmissionMap", texture);
                    material.SetColor("_EmissionColor", Color.white);
                    // Set render mode to Cutout
                    material.SetFloat("_Mode", 1); // Set render mode to Cutout
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                    material.SetInt("_ZWrite", 1);
                    material.EnableKeyword("_ALPHATEST_ON");
                    material.DisableKeyword("_ALPHABLEND_ON");
                    material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                    material.renderQueue = 2450;

                    if (material.HasProperty("_Cutoff"))
                    {
                        material.SetFloat("_Cutoff", cutoutThreshold);
                    }

                    if (material) renderer.material = material;
                }
                catch (Exception e)
                {
                    Debug.LogError("ApplyTextureToChildren::Validate: An error occurred while Applying the texture [" + texture.name + "] to GameObject [" + transform.name + "].");
                }

                float aspectRatio = (float)texture.width / texture.height;
                Vector3 scale = child.localScale;
                scale.z = scale.x / aspectRatio;
                child.localScale = scale;
            }
        }

        // Hide the back if single sided
        transform.GetChild(1).SetActive(isDoublesided);
    }

    void OnDestroy()
    {
        Destroy(material);
    }
}