This article is the second part of how to improve performance of my Wave Engine games. So if you do not know it yet, I recommend you read the first article before you start.
In the first article we showed how Wave Engine´s render works and how many draw calls are need to render entities. In this second article we are going to review two interesting techniques that allow you to reduce the number of draw calls to render your scenes.
Static Batching
This technique allows you to group many meshes on a single mesh so the engine will be able to render multiples entities as one. But some limitations exist that we need to know before we use it:
- It is necessary to mark your entities as Static, this means that these entities cannot be translated, rotated or scaled when running your
- All entities must share the same material instance.
To mark an entity as Static you can set the Static checkbox on Wave Editor´s Entity Details Panel:
You can also set IsStatic property to true programmatically in entity instances:
Entity box = new Entity() { IsStatic = true } .AddComponent(new Transform3D()) .AddComponent(new MaterialsMap()) .AddComponent(Model.CreateCube()) .AddComponent(new ModelRenderer()); this.EntityManager.Add(box);
Materials are shared between entities instances by default on WaveEngine, unless UseMaterialCopy is marked on the MaterialsMap component.
We are going to check the differences between using Static Batching or not using it to draw multiples entities with lights:
There are 8 draw calls to draw 3 entities with 2 lights; 3 G-Buffer pass, 2 Lighting pass and 3 forward rendering pass (one for each entity).
Now we mark all entities as Static:
There are 4 draw calls to draw 3 entities with 2 lights; 1 G-Buffer pass (only one for all entities), 2 Lighting pass and 1 forward rendering pass (only one for all entities).
Now all three entities are batched and drawing like a single entity with 1 G-Buffer pass and 1 forward rendering pass, reducing the overhead on render frames.
In the first test we are batching multiples instances of the same mesh so it is easy to share their materials but if we want to batch different mesh entities with different textures then we need to group all textures in an atlas and use it as a single material for all entities.
Note. Maximum atlas texture size supported on Wave Engine is 2048×2048 pixels.
It is important to highlight that Static Batching groups multiple meshes on a batch, batches are limited to 65.535 indices (21.845 triangles). So if your Static entities exceed the batch limitation, it will create multiples batches.
Dynamic Batching
This technique allows the engine to group multiples meshes into one too, but this technique can also batch animated entities. However, this technique is much more restrictive than Static Batching.
Requisites that you need to know:
- All entities can be animated so you can translate, rotate and scale these entities while running your game.
- All entities must have a limited number of vertices so this technique only works with small meshes *.
- All entities must share the same material instance.
- Is not necessary that you do anything because the engine will make dynamic batching automatically when it is possible.
(*) This table shows the maximum number of vertices depending on vertex format used:
Vertex Format | Stride | Max. Vertices |
VertexPosition | 12 bytes | 533 |
VertexPositionColor | 16 bytes | 400 |
VertexPositionTexture | 20 bytes | 320 |
VertexPositionColorTexture | 24 bytes | 266 |
VertexPositionNormal | 24 bytes | 266 |
VertexPositionNormalColor | 28 bytes | 228 |
VertexPositionDualTexture | 28 bytes | 228 |
VertexPositionNormalTexture | 32 bytes | 200 |
VertexPositionNormalColorTexture | 36 bytes | 177 |
VertexNormalMapping | 56 bytes | 114 |
VertexPositionNormalTangentTexture | 56 bytes | 114 |
VertexNormalMappingLightMap | 64 bytes | 100 |
VertexPositionNormalTangentColorDualTexture | 68 bytes | 94 |
SkinnedNormalMappedVertex | 68 bytes | 94 |
SkinnedVertex | 68 bytes | 94 |
When you import an FBX mesh from an external 3D tool (3DstudioMax, Blender, Cinema4D, Maya…) then the most common vertex format is VertexPositionNormalTangentTexture so the maximum vertex limit per mesh is 114 vertices.
If you want to know which VertexFormat your model is using, you can check it with the following code in your scene:
protected override void CreateScene() { this.Load(WaveContent.Scenes.MyScene); … } protected override void Start() { base.Start(); var box = this.EntityManager.Find("woodenBox"); var model = box.FindComponent<Model>(); var vertexFormat = model.InternalModel.Meshes[0].VertexBuffer.VertexBufferFormat; var stride = vertexFormat.Stride; }
The easiest way to know if your model fulfills the conditions is to open Asset Viewer tool from Wave Editor to see the number of vertices of your mesh.
The pallet mesh exceeds the triangles limit for VertexPositionNormalTangentTexture vertex format so Dynamic batching technique is not possible with this mesh, however if we load a simpler mesh like a wooden box, the number of vertices is lower.
Now, if we create a simple scene with three wooden box meshes:
There are 4 draw calls to draw 3 entities with 2 lights; 1 G-Buffer pass, 2 Lighting pass and 1 forward rendering pass (only one for every entity). So the engine applied dynamic batching automatically with this mesh without any additional work.
TIP: If your game doesn’t use dynamic lights and you are using a VertexPositionNormalTangentTexture format, open the Asset Viewer and uncheck GenerateTangeSpace option and then your model will use VertexPositionNormalTexture vertex format, so your model could be up to 200 vertices instead of 114.
In conclusion, Static batching and Dynamic batching are two important techniques that you must know to improve your games´ performance and this can make a big difference.