Material instances

Hi, I need some feedback on the following situation. I already have a solution in mind but I am unsure if that is the proper way to go forward with this.

For the sake of simplicity lets say I have a cube prefab. I created a custom shader for it.
Now lets say I have two cubes and I want them both to use the same shader/material, but each cube should have different color. I have achieved this by setting in the editor a new material instance for each cube like this :

Renderer rend = _gameobject.GetComponent<Renderer>();
Material temp_Mat = new Material(rend.sharedMaterial);

temp_Mat.SetColor(shader_ID, color);


Undo.RecordObject(rend, "dd");
rend.sharedMaterial = temp_Mat;

The problem with this is that unity creates a separate draw call for each material instance. Thus for performance sake I found this alternative :

Renderer rend = _gameobject.GetComponent<Renderer>();
MaterialPropertyBlock matBlock = new MaterialPropertyBlock();

matBlock.SetColor(shader_ID, color);

Undo.RecordObject(rend, "dd");
rend.SetPropertyBlock(matBlock);

The problem with this is that on play mode the new cube colors revert back to the ones set originally in the common material. If however I set the color values inside the start method the colors will stick for the duration of play mode.

Now my question is if this approach of setting the colors on start every time a good one? It seems like a big hastle for something so simple. If I could just tell Unity to not erase the data on start…
I like that “sharedMaterial” does not erase the color data on start, but “MaterialPropertyBlock” and “SetPropertyBlock” are more efficient… so which one is the better way to go ?

1 Like

I would definitely reccommend using the property blocks instead of add multiple material instances, this problem scales with your game, the less batching the more drawcalls which isn’t that amazing for performance, setting the properties in start might seems a bit annoying, while working on it but i think it’ll pay off, when the game is actually build.
I would say that having a single frame at the start taking longer is exponentially better than having worse performance overall.

Except using material property blocks won’t really reduce the number of batches. While this doesn’t create a new material, it’ll still require a separate draw call unless you’re using a shader that is setup to use instanced rendering and the property you’re changing is an instanced property. For the built in shaders none of the exposed properties are instanced, so this would have to be a custom shader.

It is more efficient, but only because you’re not generating a new material for every object. A MaterialPropertyBlock is a much lighter class than a proper Material, as it only holds a list of properties, and not other things like keywords or the queue, which can have more substantive affects on how an object is rendered in terms of the CPU side API calls.

And yes, this is fine. You can’t tell Unity to keep it around because likely part of what makes material property blocks so efficient is the fact that they are temporary. They’re not serialized (saved by c#), so going in or out of play mode, or loading a level, etc. will all clear them from renderers. I’m not even entirely sure you can serialize the data a material property block itself, you have to create one, set the values, and apply it to a renderer every time.

@bgolus Thanks for the reply. Yeah after few days of testing around I came to similar conclusion about material property blocks, though until now I was unsure why was I getting such results.
I only recently started reading up on performance optimization and the subject seems very extensive. Could you advice me on the right direction for optimizing this situation.

  • Having lots instantiated static wall prefabs with custom shader/material attached to it. The shader regulates lots of stuff(colors, textures, booleans etc.)

Since material property blocks doesnt seem to give that much more performance than just shared materials, is there any other better approach to reduce draw calls for multiple wall prefabs ?
I tried combining the meshes of the prefabs and see if that would make any difference, but for the moment I havent seen much improvement (I just found out about this technique so I might not have implemented it correctly).
You also mentioned instanced shader properties, could those be done with shadergraph as that is what I used to create my shader ?
Also I tried GPU instancing though that didnt seem to agree with the prefabs being static.
I apologize for asking so many questions, but its rare finding someone with lots of experience on the subject and who is also willing to help.(I have seen your avatar here quite often during the years :smile:)

1 Like

If you’re using static batching, and the meshes are static, then it’s already a combined mesh. That’s what static batching does, it combines all of the meshes into one. However that doesn’t really help too much if they’re all using different materials still. We’ll come back to that.

Instancing is only really worth while if you have many tens, or hundred meshes that are exactly the same mesh & exactly the same material. You can have instanced properties that are numerical values, like colors, vectors, or floats. But you can’t change the texture.

Shader Graph doesn’t really support this at all. And there don’t seem to be plans to add support. There is the option for “hybrid instancing”, but that’s only useful in conjunction with some currently experimental rendering features (DOTS Hybrid rendering). Even then, again, you need a lot of the exact same mesh, using the exact same texture for this to make sense.

If you want to change the texture, they can’t batch.

The work arounds for this are to use texture atlases so all of the possible textures are in one big texture and use bespoke meshes with UVs assigned to the appropriate area of the atlas, along with abusing vertex colors or extra UV sets to encode material data. Or instead of an atlas you can use a Texture2DArray texture, and again use an additional bit of vertex data to store the array index.

The problem is without instancing, you must use exactly the same material & material properties for Unity to batch them, because if some property changes between two objects, the GPU has to change the settings being used for rendering between the two, and that’s the part that’s actually expensive. Static batching in fact does still render every mesh as a separate draw call, even if only one material is used! Draw calls aren’t really that expensive!

But really if you’re already using Shader Graph, that means you’re using either the URP or the HDRP. In that case hopefully you have the SRP Batcher enabled, which is very possibly faster than even static batching in cases like this. Draw calls are often treated as some big enemy, but depending on what your platform is it might not actually be a significant issue.

3 Likes

I see. I could test around with texture sprites or atlases and see what happens.

As for the draw calls, the target platform is Windows so probably the draw calls aren’t going to be such an issue either way. Still though before I started optimizing the draw calls for a simple platformer map(with just identical walls) were in the hundreds or more so I decided to finally delve into performance.

About SRP Batcher I am on Unity 2019.1 with LWRP but I still have the option to enable it even though it is in experimental stage. It did improve things allot when using material property blocks. And I found quite an interesting thing while testing. If instead of property blocks I use shared materials the draw calls stopped increasing regardless of the number of walls with different color data. This alone is the prefect situation for me. I need to test it with all textures and booleans from the shader and not just color data, but for now this option looks great. Thanks for mentioning it.

I will test more stuff for the next few days and see what happens. I was also thinking of checking how the SRP Batcher behaves on the latest Unity. It might be worth moving my project to latest Unity version if the results are even better than my experimental SRP batcher version.