Confused on Batching.

Hello,

May this is a noob question. I am wondering why can’t 2 objects be batched if they have different materials?

For example, I have 2 mats A and B, and just one property called “_Test” has different values. Objects using material A cannot be batched with objects with material B. These objects are using different meshes, so they have different unity_Object2World. Unity_Object2World is also a property. My question is: Why is batching interrupted by “_Test”, but not interrupted by “unity_Object2World”?

Unity_Object2World is a global property. However, it seems every renderer has its unque Unity_Object2World, and the value is not saved on materials. How is it implemented?

My final goal is to add a global property like Unity_Object2World. It is not saved on materials, but also has unique value for every renderer.

You batch meshes that can be sent to the GPU at the same time. There are alot of rules for this, The min requirement for this is same material. But there are more rules. More so for dynamic batching and gpu instancing.

Also whats even more expensive is set pass calls. Because these have to change the state in the GPU, which can be very expensive.

Batching rules: Unity - Manual: Draw call batching

The feature you may be looking for is the MaterialPropertyBlock, which allows you to have unique values per renderer. Tutorial / Comparison of Different Batching Techniques .

Why can’t unity pass materials’ property like Unity_Object2World? In this way , even different materials will not break batching.

Depends on what you mean by “batching”. In Unity, batching usually refers to static and dynamic batching. As I described in the post @Lo-renzo linked to , batching combines multiple meshes into a single pre-transformed into world space mesh. Any connection to the original meshes and their transform matrices is gone at this point, it’s now a single mesh with a single material on it. When it needs to be rendered Unity just has to tell the GPU to use the single material, and then render the mesh. There’s only one unity_ObjectToWorld matrix used on a batched mesh, any batched mesh, and it’s an identity matrix: no scale, no rotation, no translation. It assumes the mesh is being rendered placed at the center of the world, because it is.

There’s no point in batching two meshes with different materials in this way because Unity would have to send the material settings to the GPU, render the part of the batched mesh with that material, then send the second material settings, and render the other part of the mesh. Which is exactly what it has to do to render two meshes anyway.

Understand the most expensive part here for the GPU is having to tell it “hey, we need to change some material settings”.

Now if you wanted to, you could have an array of transform matrices you set on the material, and each vertex of the mesh could store the index to the transform matrix. And you can have other arrays for other material properties use the same index. This is actually what Keijiro’s Kvant Spray does (though instead of arrays the transform matrices and material properties are stored in textures).

So why doesn’t Unity do this by default?

Because it requires completely custom shaders to use. Ones that wouldn’t work on a normal mesh.

The advantage of Unity’s batching approach is the original material that was on the individual meshes “just works” on the batched mesh and the original mesh with no modifications, as long as the original material didn’t require knowing the original transform matrix or original local space vertex positions. And most of the time they don’t. None of Unity’s built in shaders require knowing any of that.

Also remember the batching systems have been around for well over a decade now. The batching systems date back to fixed function shader days, meaning on some platforms it wasn’t even possible to have a shader that required that data. But this batching system would still work.

Now what you’re talking about with having multiple transform matrices or material properties that can change per mesh is called instancing. This is different than batching, and has different limitations. Those limitations exist because of how GPUs were setup to work, not because of Unity. There are commands that tell a GPU “render x of this one mesh using this one material”. The GPU then automatically passes along an index to the shader for each instance of the mesh being rendered, and you can use that to look up into an array of values. This is a more recent system in Unity, only added about 5 years ago. And it still requires custom shaders to make use of it, but they made sure to include macros to help make it easier to implement.

3 Likes

Keep in mind that GPU Instancing only works on single mesh. If same material is used on multiple meshes use static batching or dynamic batching (though dynamic batching is very limited).

edit: also SRP have new batching system btw.

The result I tested is different from what you said. For static batch, Unity sorts all meshes by materials in a list. The meshes with same material will be laid out together. Then, Unity is trying to combine all meshes no matter they shares same material or not. I think may put meshes with same material together can decrease state changes?

I skipped over some of the minutia of static batching, which gets complicated and confusing. And there’s some weirdness with what “a mesh” is. For Unity a “mesh” can have multiple materials, but if you dig into meshes more you’ll see “sub meshes” mentioned. On the GPU you can’t render a single mesh with multiple materials at once. You can only render one mesh with one render state at a time. Those sub meshes are the mesh broken up by material (or sometimes by bone influence or vertex count in the cases of complex skinned meshes or vertex counts greater than the mesh’s indices format allows). For the GPU the sub meshes are separate “meshes”. Or more specifically separate lists of triangles.

So, yes, Unity collects all the static meshes, sorts by material, and combines those into one “mesh” regardless of the material. But as far as the GPU is concerned they’re separate meshes with a single material each.

There’s additionally stuff like Unity still draws each original mesh of the same static sub mesh one at a time. This means you can hide individual meshes either by toggling the renderer component or by the mesh being occluded / frustum culled. This is done by drawing a range of triangles at a time rather than always drawing the entire mesh all at once. But since the render state, shader, material settings, and mesh aren’t changing, this is very cheap compared to rendering them as completely separate meshes.

Unity may also still change render state between different parts of a static mesh too. This may be due to things like dynamic lights, reflection probes, or shadow distances. So it doesn’t get rid of all of the state changes. It’s also possible to swap materials at runtime on static meshes if you want, or render individual static renderer components with CommandBuffer.DrawRenderer(). Doing any of these mostly removes the performance benefit of static batching compared to rendering a separate mesh normally. However it can still be a benefit overall as hopefully more than one “mesh” within the batch can be rendered at once still. It’s rare to have as many unique render states as mesh renderers in a static batch.

And none of this changes the fact the shader has no way of telling different parts of a static mesh apart, except for values that are baked into the vertex data.

3 Likes

I got it. Thank you very much.

May I ask @bgolus additional question regarding the Unity static batching:
Can the GameObject/mesh be marked as a “static”, even if is it occasionally shown/hidden, it means activated/deactivated using GameObject. SetActive(true or false)? Of course the object transforms are not animated by any means.
My user case is a historical architecture development modeling - showing/hiding various architectural elements as they were gradually built/removed in time - turrets, annexes, building wings etc…
Could not find clear answer anywhere… any hint would be appreciated!
By the way thanks bgolus for useful explanations of the computer graphics aspects and their practical impacts - not only in this particular thread!

Yes.

One minor caveat is if you’re using occlusion culling make sure not to set objects you intend to toggle to be a static occluder. The Static check box has a arrow to the left of it that has a list of things that check box toggles that you can select from individually. Though if you don’t know if you are using occlusion or not, you’re not, so don’t worry about that too much.

1 Like

Thank you very much for your quick answer, which, besides, made me to look up occlusion-related info in the manual and learn something new :slight_smile: