Dynamic Batching and Instantiating

I am creating a project where meshes are placed procedurally using a script. However, the draw calls are hurting performance. When instantiated with a script they will not dynamically batch at all.

  • I notice that when I place the meshes manually, they batch just fine. So the mesh must be within the vertex count limit of 300.

  • All of the meshes share the same materials and textures.

  • All the instantiated meshes seem to reference the same material and not separate instances, since when the materials are modified in the editor the changes apply everywhere.

  • I am not accessing MeshRenderer.material or .materials anywhere in my script.

Static batching is not a desirable option in that these meshes are constantly being instantiated in game, and each StaticBatchingUtility.Combine() call is very costly especially since there will be very many meshes.

Combining meshes isn’t an ideal solution either for similar reasons. Combining them using a hierarchy is also very complex and could impose limitations on the project in the future (for example, LOD levels).

Static batching causes some meshes to disappear and move to (0,0,0); some of these objects are scaled (mirrored along one axis specifically) although not necessarily all of them.

On a side note, if anyone has any ideas of how to improve rendering performance when instantiating objects (especially on a large scale) then please feel free to mention them.

Thank you.

The best approach is a combination of Dynamic Batching, Static Batching and Mesh Combining since each has its own advantages and disadvantages.

Dynamic Batching

Advantages:

  • Requires no code work.
  • Doesn’t cause lag spikes when optimizing many objects.
  • Supports per-object culling.
  • Objects can move and rotate freely.
  • Objects can be created and destroyed.

Disadvantages:

  • Cannot cope with point lights or spot lights.
  • Items must be of uniform scale.

##Static Batching##
Advantages:

  • Lowers draw calls dramatically when point and spot lights are not present.
  • Lowers draw calls significantly when point and spot lights are present.
  • Supports per-object culling.
  • Objects can be destroyed.

Disadvantages:

  • Requires significant code work in order to prevent lag spikes when optimizing many objects.
  • Does not cope with point lights and spot lights as well as Mesh Combining does.
  • Items must be of uniform scale.
  • Objects cannot move after being batched.
  • Newly created objects must be re-batched inorder to be optimized.

##Mesh Combining##
Advantages:

  • Works well in all lighting conditions.
  • Works well with any scaling.

Disadvantages:

  • Requires significant code work in order to prevent lag spikes when optimizing many objects.
  • Does not support per-object culling, resulting in potentially a lot of wasted computing power processing offscreen polygons. (However, this risk is negligable if the resulting combined mesh’s bounding box takes up very little screen area.)
  • Objects cannot move after being combined, unless recombined afterwards.
  • Objects cannot be created or destroyed after being combined, unless recombined afterwards.
  • Requires memory to store the resulting combined mesh.

Here’s what I think would work best:

1)
Use Mesh Combining to combine the objects into chunks. The chunks should be big enough to fit as many objects as possible without being too big, or else it will never be culled.

  • Try to use a delayed combine, in case objects are being created, changed, or destroyed often. Wait for everything to settle for a few seconds and then combine them, otherwise every change will cause this slow combine operation to happen.
  • Detect when changes are made to the objects you want to combine. When a change occurs, delete the combined mesh and enable the original mesh renderers. Then after a pause, recombine them again.
  • Don’t do this in one big step with all the objects you want to optimize. Instead, try to perform the merging over multiple frames.
  • This resolves the issues Dynamic Batching and Static Batching have with using scaled objects.
  • This also resolves the issue Static Batching has with optimizing many objects. Since many small objects are combined into a few big ones in this step, Static Batching should work much faster.
  • This also saves a slight amount of time that’d normally be spent on culling calculations.
  • Lastly, when using Point Lights or Spot Lights, the combined meshes act as a reliable fallback when the other approaches fall short.

2)
Combine the resulting larger objects together using StaticBatchingUtility.Combine();

  • Try to use a delayed combine here as well, for similar reasons.
  • Detect when changes are made to the objects you want to combine. When a change occurs, all you have to do is call StaticBatchingUtility.Combine() a second time.
  • If there are too many objects for the static batcher to combine, consider using mulitple batchers. Batch operations should also be spread out over multple frames as they can be slow.

3)
Dynamic batching is applied automatically so no work needs to be done here. :slight_smile:

I know this is a very old post, but I found this question through google and I came up with a solution that works for me, which I want to share. I’m using this to dynamically batch 2d level sprites, so I’m not sure how this applies to you.

After dynamically creating all the sprites with this code

SpriteRenderer CreateSprite( Sprite sprite, float x, float y, float rotation )
{
	GameObject newObject = new GameObject( sprite.name );
	newObject.transform.parent = transform;
	newObject.transform.localPosition = new Vector3( x, y, 0 );
	newObject.transform.localRotation = Quaternion.Euler( 0, 0, rotation );

	SpriteRenderer renderer = newObject.AddComponent<SpriteRenderer>();

	renderer.sprite = sprite;
	renderer.sortingLayerName = "LevelRoom";

	return renderer;
}

I simply call this function on the parent gameObject. Since the sprites are all parented to the same GO, this will disable and enable all newly created sprites.

void BatchSprites()
{
	gameObject.SetActive( false );
	gameObject.SetActive( true );
}

This reduces the drawcalls from 143 to 28. I’m afraid I havn’t done any more tests with this method, it seems to work for my specific case. Hopefully it points you in a direction to get your own situations working.

Cheers