Lerping material of a GameObject causes massive performance issues

I have a GameObject (let’s call it groupOfModels) whose children are all buildings (gameobjects), who also have children gameobjects (windows, doors, etc.). I am Lerping the materials of each of these buildings to white. It works, but the problem is that it causes massive performance issues when changing the scene, all of which can be traced back to GC.DeleteUnusedAssets in the Profiler.

Here is my code:

void InitializeBuildingFade(Renderer[] group)
{
    lerp = 0f;

    childrenMaterials = new Material[group.Length][];
    newMaterials = new Material[group.Length][];
    // We need to use a for loop to copy values into the arrays
    for (int i = 0; i < group.Length; i++)
    {
        //.sharedMaterials refers to the original source material, not the instance
        childrenMaterials[i] = group[i].sharedMaterials;
        //.materials refers to the current instance of material
        newMaterials[i] = group[i].materials;
    }
}

void BuildingFade(Renderer[] group)
{
    lerp = Mathf.MoveTowards(lerp, 1, 0.2f * Time.deltaTime);

    for (int i = 0; i < group.Length; i++)
    {
        for (int j = 0; j < childrenMaterials[i].Length; j++)
        {
           // lerp to the whiteMaterial
                newMaterials[i][j].Lerp(childrenMaterials[i][j], whiteMaterial, lerp);
        }
        // we can't lerp the materials immediately from the renderer because renderer.material.lerp
        //only works for the first instantiated material, and many of our objects have more than
        //one material. So we have to set all the materials this way.
        group[i].materials = newMaterials[i];

    }
}

InitializeBuildingFade is called only once. BuildingFade is called in Update for around 5 seconds. The argument for InitializeBuildingFade is simply groupOfModels.GetComponentsInChildren<Renderer>().

Anybody got any ideas as to how I can improve the performance, or of a better way to do this?

Thanks!

Do all these GameObjects share the same shader and properties ?
if so , why not just create a single MaterialPropertyBlock that contains the alpha value , and pass THAT to the materials ?
if not , specify your setup and what’s shared and unique between the renderers

Edit:

For textures it’s really the same approach , you can group your buildings by the texture they use and create a MaterialPropertyBlock per <Color,Texture> combination for example , the decision is yours since you have the complete context.
As for other solutions , there are MANY and they will depend on how much you’re willing to research/experiment , here are some suggestions :

  • Merge stuff as much as possible , don’t have your building fragmented into multiple parts if possible
  • you can merge the buildings that have the same (material,color) combination into one mesh and render them once like that
  • You can mark you buildings as static meshes if you don’t need to move them
  • You can use one material , and one mesh for all your buildings , and put the dynamic stuff into the vertex color or UVs , very typical optimization for game assets
  • You can as (you mentioned) do occlusion culling of LODs
  • Instanced rendering with Graphics.DrawInstanced

Many solutions exist , so just experiment and try them out

Accessing .Materialand .Materials(as opposed to the “shared” variants) creates new copy instances of the Material that you are now liable to Destroy. Your script isn’t Destroy()-ing them, so it’s leaking them instead.

In the Memory Profiler those are called out as Leaked Dynamically & Runtime Created Assets. On scene switch the Asset GC (Resources.UnloadUnusedAssets) has to clean those out but that’s about the only time it gets called for you so you might as well crash OutOfMemory before then if memory pressure is high enough.

You could also reduce the scene switch time by Destroying the old material instances each frame before setting the new ones, but really, the better approach would be the MaterialPropertyBlock mentioned by @EtherealPineapple.

Pictures of the materials I am using.

And here is one of them in the inspector:

And here is an example of a GameObject group whose materials I want to change: