Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved Material still creating separate instances

Discussion in 'General Graphics' started by wideeyenow_unity, Sep 23, 2023.

  1. wideeyenow_unity


    Oct 7, 2020
    I'm trying to reduce my draw calls, but no matter what I try the singular material I wish to use keeps making instances, and Frame Debugger says "Cannot batch since using different materials".

    So after reading practically everything google had to offer to fix said issue, I'm still at a loss, and can't understand how to prevent new instances. I've tried using sharedMaterial, MaterialPropertyBlocks, and even a custom shader.

    A generalized example of my code:
    Code (CSharp):
    1. rend = GetComponent<MeshRenderer>();
    3.         Material test = rend.sharedMaterial;
    4.         //Destroy(rend.material);
    5.         rend.material = null;
    6.         rend.sharedMaterial = test;
    8.         //rend.material = null;
    9.         //rend.sharedMaterial = null;
    10.         //Destroy(rend.material);
    11.         //rend.material.shader = objList.shaderDefault; // NO
    12.         //rend.sharedMaterial = objList.shapeDefault;
    13.         //rend.sharedMaterial = Instantiate(objList.shapeDefault);
    15.         //myPropBlock = objList.propBlock;
    16.         myPropBlock = new MaterialPropertyBlock();
    17.         myPropBlock.SetColor("_Color", color);
    18.         rend.SetPropertyBlock(myPropBlock);
    19.         if (rend.HasPropertyBlock()) print("has property block");
    As you can see it's a mess, of trying everything I've heard suggested through other posts on this same issue.

    And even tried the Unity Manual example of making a shader, that should be appropriate:
    Code (CSharp):
    1. Shader "Custom/TestShader"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Color", Color) = (1,1,1,1)
    6.         _MainTex("Albedo (RGB)", 2D) = "white" {}
    7.         _Glossiness("Smoothness", Range(0,1)) = 0.5
    8.         _Metallic("Metallic", Range(0,1)) = 0.0
    9.     }
    11.     SubShader
    12.     {
    13.         Tags { "RenderType" = "Opaque" }
    14.         LOD 200
    15.         CGPROGRAM
    17.         // Uses the physically based standard lighting model with shadows enabled for all light types.
    18.         #pragma surface surf Standard fullforwardshadows
    20.         // Use Shader model 3.0 target
    21.         #pragma target 3.0
    23.         // added this later? Still not changing anything
    24.         #pragma multi_compile_instancing
    26.         sampler2D _MainTex;
    27.         struct Input
    28.         {
    29.             float2 uv_MainTex;
    30.         };
    31.         half _Glossiness;
    32.         half _Metallic;
    34.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
    35.         UNITY_INSTANCING_BUFFER_END(Props)
    36.         void surf(Input IN, inout SurfaceOutputStandard o) {
    37.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
    38.             o.Albedo = c.rgb;
    39.             o.Metallic = _Metallic;
    40.             o.Smoothness = _Glossiness;
    41.             o.Alpha = c.a;
    42.         }
    43.         ENDCG
    44.     }
    45.     FallBack "Diffuse"
    46. }
    Which is the third attempt of making my own shader, that I just stuck with since I was able to force dynamic batching:
    This ^^ is normal runtime of the game, which does show some of the objects being batched. I'm not sure which ones they are, since all of the ones in the hierarchy still show "Material (Instance)" on their shader/material. So I maybe assumed it's like I read in catlike codings example:
    Where he states it may be the issue of too many vertices, and Unity by default will create new instances.

    However, if I select all in-game objects, then select the material for all of them, I get:
    The shader seems to perfectly fine with batching them all. So I find it hard to believe that that's my issue.

    So is there something wrong with how I'm trying to set the sharedMaterial? Or is there some other default modification I need to disable? Any help, or clarification on any of this would be greatly appreciated, as I am down-right confused on how materials work now.
  2. c0d3_m0nk3y


    Oct 21, 2021
    Code (CSharp):
    1. rend = GetComponent<MeshRenderer>();  // cache in a member variable
    2. myPropBlock = new MaterialPropertyBlock(); // cache in a static variable
    3. rend.GetPropertyBlock(myPropBlock);
    4. myPropBlock.SetColor("_Color", color);
    5. rend.SetPropertyBlock(myPropBlock);
    Remove any rend.material references because this property makes a copy of the material.
    wideeyenow_unity likes this.
  3. wideeyenow_unity


    Oct 7, 2020
    Thanks for the reply, I just tried that, and still it's creating instances:
    Now the shader I have may be at fault at this point, so I'll have to make another standard one, and apply it to the objects being spawned in.
  4. wideeyenow_unity


    Oct 7, 2020
    oof! just noticed you said make that static, sorry. one sec
  5. c0d3_m0nk3y


    Oct 21, 2021
    The static is just to avoid constant memory allocations. You really only need one instance of a MPB.

    I'd say, try with the standard shader to see if that works. Maybe you also have to enable instancing on the material.

    However, this is kind of two questions in one.
    One question is - where do the material instances come from
    Second question is - how do I make it batch

    PS: What render pipeline are you on? Built-In, UDP, HDRP?
    wideeyenow_unity likes this.
  6. wideeyenow_unity


    Oct 7, 2020
    yeah, even making it static doesn't help, which should only be one reference to the MPB.

    Yes, that's my overall goal is to make it GPU instanced, so they dynamically batch. But before I made my own custom shader I was(trying to) using MPB, and read somewhere I needed a custom setup.

    They are set on each prefab, which the standard and custom both had GPU instanced selected. So that was why I eventually thought just to delete them off with code, but I can't get a singular reference for the material/shader. I even tried making:
    Code (CSharp):
    1. public static Material staticShapeMat;
    But that wouldn't work, as I don't think you can 'static' a material, I kept getting errors when trying to implement that.

    I'm pretty sure it's a standard 3D core, although I did select the one for mobile, as I'm trying to make a simple phone game for myself. But pretty sure it's not anything with more advanced pipelines.
  7. wideeyenow_unity


    Oct 7, 2020
    Ok that actually does work, my first attempt was a static Material in another class, and it wouldn't let me use that in the objects class. But making it a static Material in the parent, and calling a prefab reference from another class to set that static Mat does work.

    But even still with trying to set the sharedMaterial as that static Material, after deleting the material on the object, still gives the same results.

    I guess I'm just not fully sure how a sharedMaterial is supposed to be instanced, as reading a post before showed you have to call an array, then set the sharedMaterial after making the rend.material null.

    If I'm confusing you, I am sorry, I am totally confused myself, lol, my brain is buzzing..
  8. wideeyenow_unity


    Oct 7, 2020
    Ok, even using the standard shader again, still gives the same results. As I'm getting the reference from a public(inspector) set material. Which I think that itself is creating a new instance.

    So my next thought would be to get a reference directly from assets, as I don't want any 'new' instances. I can manually select all the objects in-game and replace the MeshRenderer material, with either the standard or custom shader, and everything works as intended. I just can't figure how to do that in code only.

    I'll attempt to make a manager type function, and see if iterating through a list of them and setting the sharedMaterial will work that way.. As of now I have each object calling said functions in their "Awake()", so that might be another issue, I'll continue to test.
  9. wideeyenow_unity


    Oct 7, 2020
    Ok, so maybe it was from trying to set all of that within the Awake(), as testing:
    Code (CSharp):
    1. if (Input.GetKeyDown(KeyCode.T))
    2.         {
    3.             for (int i = 0; i < playableItems.Count; i++)
    4.             {
    5.                 Items item = playableItems[i];
    6.                 item.rend.material = null;
    7.                 item.rend.sharedMaterial = null;
    8.                 item.rend.sharedMaterial = objList.customMat;
    9.             }
    10.         }
    This ^ works just fine... Wow, I can't believe the whole problem was just trying to implement this upon creation(during objects Awake)..

    oof, sorry to have bothered, this was completely user error. Thanks so much for trying to help, really was appreciated! :Cheers!:
    c0d3_m0nk3y likes this.