Material still creating separate instances

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:

rend = GetComponent<MeshRenderer>();

        Material test = rend.sharedMaterial;
        //Destroy(rend.material);
        rend.material = null;
        rend.sharedMaterial = test;

        //rend.material = null;
        //rend.sharedMaterial = null;
        //Destroy(rend.material);
        //rend.material.shader = objList.shaderDefault; // NO
        //rend.sharedMaterial = objList.shapeDefault;
        //rend.sharedMaterial = Instantiate(objList.shapeDefault);

        //myPropBlock = objList.propBlock;
        myPropBlock = new MaterialPropertyBlock();
        myPropBlock.SetColor("_Color", color);
        rend.SetPropertyBlock(myPropBlock);
        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:

Shader "Custom/TestShader"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 200
        CGPROGRAM

        // Uses the physically based standard lighting model with shadows enabled for all light types.
        #pragma surface surf Standard fullforwardshadows

        // Use Shader model 3.0 target
        #pragma target 3.0

        // added this later? Still not changing anything
        #pragma multi_compile_instancing

        sampler2D _MainTex;
        struct Input
        {
            float2 uv_MainTex;
        };
        half _Glossiness;
        half _Metallic;
        UNITY_INSTANCING_BUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
        UNITY_INSTANCING_BUFFER_END(Props)
        void surf(Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

Which is the third attempt of making my own shader, that I just stuck with since I was able to force dynamic batching:
9360455--1309052--material_ins.jpg
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:
9360455--1309055--material_nonins.jpg
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.

Try

rend = GetComponent<MeshRenderer>();  // cache in a member variable
myPropBlock = new MaterialPropertyBlock(); // cache in a static variable
rend.GetPropertyBlock(myPropBlock);
myPropBlock.SetColor("_Color", color);
rend.SetPropertyBlock(myPropBlock);

Remove any rend.material references because this property makes a copy of the material.

1 Like

Thanks for the reply, I just tried that, and still it’s creating instances:
9360590--1309097--material_01.jpg
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.

oof! just noticed you said make that static, sorry. one sec

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?

1 Like

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:

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.

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…

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.

Ok, so maybe it was from trying to set all of that within the Awake(), as testing:

if (Input.GetKeyDown(KeyCode.T))
        {
            for (int i = 0; i < playableItems.Count; i++)
            {
                Items item = playableItems[i];
                item.rend.material = null;
                item.rend.sharedMaterial = null;
                item.rend.sharedMaterial = objList.customMat;
            }
        }

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!:

1 Like