Search Unity

Discussion Instancing new Materials instead of using Property Blocks gives more FPS

Discussion in 'Scripting' started by Klausbdl, Feb 13, 2023.

  1. Klausbdl

    Klausbdl

    Joined:
    Aug 12, 2013
    Posts:
    64
    I'm developing a game where a tower is generated at runtime. Each "floor" of this tower is a handcrafted section with platforms and whatnot (only cubes).
    For each platform (cube), I figure I could set its material to use different tiling so it matches its scale. I tested two methods, mentioned bellow.

    edit: the Render Pipeline is URP.

    This is what the tower looks like for this test, it has 30 floors, totalizing more or less 11.5k triangles (according to Unity's stats window).

    tower scene.jpg

    Each platform has a script called PropObject, which is instaced at runtime. Each of these cubes has 3 materials for each side, so I can scale each one according to its x, y and z local scale.
    Moreover, it's important to assure that this code is only executed when the platform is instanced, not every frame lol.
    On the code, "textureScale" is a Vector2 set in the inspector so I can fine tune the tiling.

    So, the first method: creating new materials, updating them and setting them to the MeshRenderer
    Code (CSharp):
    1. public void UpdateTextures()
    2.     {
    3.         // 'material' is a public variable set in the Inspector
    4.         if (material == null) return;
    5.  
    6.         // generate fresh materials for each side of the cube. 0 = front and back faces; 1 = side faces; 2 = top and bottom faces
    7.         Material[] materials = new Material[3];
    8.  
    9.         for (int i = 0; i < materials.Length; i++)
    10.             materials[i] = new Material(material);
    11.  
    12.         // update each material using the transform's scale
    13.         materials[0].mainTextureScale = new Vector2(transform.localScale.z * textureScale.x, transform.localScale.x * textureScale.y); // front and back
    14.         materials[1].mainTextureScale = new Vector2(transform.localScale.z * textureScale.x, transform.localScale.y * textureScale.y); // sides
    15.         materials[2].mainTextureScale = new Vector2(transform.localScale.x * textureScale.x, transform.localScale.y * textureScale.y); // top and bottom
    16.  
    17.         // also apply a random offset
    18.         for (int i = 0; i < materials.Length; i++)
    19.             materials[i].mainTextureOffset = new Vector2(Random.Range(0f, 1f), Random.Range(0f, 1f));
    20.  
    21.         // apply to the MeshRenderer shared materials
    22.         GetComponent<MeshRenderer>().sharedMaterials = materials;
    23.     }
    This first method gives me this stats:
    new materials copy.jpg

    For the second method: using MaterialPropertyBlocks
    Code (CSharp):
    1. private MaterialPropertyBlock[] blocks;
    2. private MeshRenderer meshRenderer;
    3.  
    4.     public void UpdateTextures()
    5.     {
    6.         if (meshRenderer == null) meshRenderer = GetComponent<MeshRenderer>();
    7.         if (blocks == null) blocks = new MaterialPropertyBlock[3];
    8.  
    9.         for(int i = 0; i < 3; i++)
    10.             blocks[i] = new MaterialPropertyBlock();
    11.  
    12.        meshRenderer.GetPropertyBlock(blocks[0], 0);
    13.         meshRenderer.GetPropertyBlock(blocks[1], 1);
    14.         meshRenderer.GetPropertyBlock(blocks[2], 2);
    15.  
    16.         Vector4 tiling0 = new Vector4(transform.localScale.z * textureScale.x, transform.localScale.x * textureScale.y, Random.Range(0f, 1f), Random.Range(0f, 1f));
    17.         Vector4 tiling1 = new Vector4(transform.localScale.z * textureScale.x, transform.localScale.y * textureScale.y, Random.Range(0f, 1f), Random.Range(0f, 1f));
    18.         Vector4 tiling2 = new Vector4(transform.localScale.x * textureScale.x, transform.localScale.y * textureScale.y, Random.Range(0f, 1f), Random.Range(0f, 1f));
    19.      
    20.         blocks[0].SetVector("_BaseMap_ST", tiling0);
    21.         blocks[1].SetVector("_BaseMap_ST", tiling1);
    22.         blocks[2].SetVector("_BaseMap_ST", tiling2);
    23.  
    24.         meshRenderer.SetPropertyBlock(blocks[0], 0);
    25.         meshRenderer.SetPropertyBlock(blocks[1], 1);
    26.         meshRenderer.SetPropertyBlock(blocks[2], 2);
    27.     }
    This second method gives me this stats:
    property block copy.jpg

    Conclusion:
    I don't know if I'm doing some horrible memory leaky or if its all good with the first method. The first (and fast) method has a few more batches than the second, but it doesn't affect the FPS at all.
    I also don't know if MaterialPropertyBlocks were supposed to be faster or slower, if I'm doing something wrong or not, but there it is. Hope we can discuss anyways to increase performance with each method and find better solutions.
     
    Last edited: Feb 13, 2023
  2. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    Which RenderPipeline is this on?
    Using MPB breaks batching on URP. So the preferred way there is multiple Material instances.
     
  3. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Huh, I'd expect for the number of SetPass calls to drop when using property blocks, as described here. The SetPass count goes up when you switch materials (or passes within a material)
     
  4. Klausbdl

    Klausbdl

    Joined:
    Aug 12, 2013
    Posts:
    64
    I'm using URP. Forgot to mention in the post, so I will edit it.
    I'm glad to hear that its the preferred way!
     
  5. Klausbdl

    Klausbdl

    Joined:
    Aug 12, 2013
    Posts:
    64
    Indeed, I must've done something wrong for the second method so it doesnt work properly, but I don't know that much to pin point what is that I did wrong.
     
  6. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    https://docs.unity3d.com/Manual/SRPBatcher.html
    Yeah for all SRP the engine uses the 'SRP batcher', which isn't compatible (yet) with MPB.
     
  7. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    Oooh, right, it works as long as the materials are based on the same shader variant!
     
  8. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    Yep.
    Really S***s the bed once you start pushing large numbers though, so I've had massive issues with it in DOTS in the past (though that was several years (and URP-versions) ago).