I've read Unity3D draw call batching documentation.

I understood it, and I want to use it (or something similar) in order to optimize my application.

My situation is the following:

  • I'm drawing hundreds of 3d buildings. Each building can be represented using a Mesh (or a SubMesh for each building, but I don't thing this will affect performances)
  • Each building can be textured with several combinations of texture patterns(walls, windows,..). Textures are stored into an Atlas for optimizaztion (see Texture2d.PackTextures)
  • Texture mapping and facade pattern generation is done in fragment shader. The shader can be the same (except for few values) for all buildings, so I'd like to use a sharedMaterial in order to optimize parameters passed to the GPU.

The main problem is that, even if I use an Atlas, share the material, and declare the objects as static to use static batching, there are few parameters(very fews, it could be just even a float I guess) that should be different for every draw call.

I don't know exactly how to manage this situation using Unity3D. I'm trying 2 different solutions, none of them completely implemented.

Solution 1

  1. Build a GameObject for each building building (I don't like very much the overhead of a GameObject, anyway..)
  2. Prepare each GameObject to be static batched with StaticBatchingUtility.Combine.
  3. Pack all texture into an atlas
  4. Assign the parent game object of combined batched objects the Material (basically the shader and the atlas)
  5. Change some properties in the material before drawing an Object

The problem is the point 5. Let's say I have to assign a different id to an object before drawing it, how can I do this?

  • If I use a different material for each object I can't benefit of static batching.
  • If I use a sharedMaterial and I modify a material property, all GameObjects will reference the same modified variable

Solution 2

  1. Build a Mesh for every building (sounds better, no GameObject overhead)
  2. Pack all textures into an Atlas
  3. Draw each mesh manually using Graphics.DrawMesh
  4. Customize each DrawMesh call using a MaterialPropertyBlock

This would solve the issue related to slightly modify material properties for each draw call, but the documentation isn't clear on the following point:

Does several consecutive calls to Graphic.DrawMesh with a different MaterialPropertyBlock would cause a new material to be instanced?

Or Unity can understand that I'm modifying just few parameters while using the same material and is able to optimize that (in such a way that the big atlas is passed just once to the GPU)?


1 Answer 1

  1. Textures are stored in vram and don't get deleted even between frames, draw calls just bind these textures to the texture units. Changing a texture has always the same cost, regardless of the texture size. Each state change is expansive. DrawMesh looks pretty good, it should sort all meshes using this material and limit the state changes to the property block and transformations. I expect it to not create a new material, it is a little bit lower than that and only set the MaterialPropertyBlock's properties in addition to the material. Still, unity's batching will no longer work which means one draw call peer subject.
  2. As soon as you want to change any material properties per instance static and dynamic batching will not be possible, at least unity's batching methods look that way. Still, if you meshes contain submeshes that may use shared materials, unity should combine these at least.

I don't think unity will give you access to the low level rendering APIs to implement real hardware instancing eg using per instance vertex streams? If the amount of data is really low, you can implement your own static batching, by cloning the meshes and adding the data to each vertex, eg as color attribute, then unity's static batching should work. What a waste of memory... :-)

  • \$\begingroup\$ thanks for your answer, very useful. I'll try passing my arguments to each mesh as vertex color attributes. I read also that with Graphic.DrawMesh occlusion culling won't work. Despite the big overhead of creating hundreds of GameObjects only to display static meshes and the overhead of setting vertices colors just to pass a single parameter, I think it could be the way to go. \$\endgroup\$
    – Heisenbug
    Commented Dec 13, 2012 at 9:38
  • \$\begingroup\$ just curious to know. In your company do you use Unity3D or a different engine? \$\endgroup\$
    – Heisenbug
    Commented Dec 13, 2012 at 11:31
  • \$\begingroup\$ Currently we use the Havok Vision Engine. Previously we used Ogre. \$\endgroup\$
    – Archy
    Commented Dec 13, 2012 at 11:35
  • \$\begingroup\$ You can pass data into the mesh via uv2, normal, tangents also. \$\endgroup\$ Commented Sep 25, 2013 at 20:01
  • \$\begingroup\$ "Changing a texture has always the same cost, regardless of the texture size." - I think that there's a cost for moving the texture from VRAM to the GPU buffer, which could make your app be texture-bandwidth limited: http.developer.nvidia.com/GPUGems/gpugems_ch28.html. \$\endgroup\$
    – jjmontes
    Commented May 4, 2014 at 17:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .