So the previous blog details how the pivots for BeeBeeQ’s wind system are generated, in this blog I’ll go into how that was included in the mesh data and how the process was able to reduce the overall number of drawcalls and automate a manual process.
I took implementing the wind system as an opportunity to update the mesh batching in BeeBeeQ, we’d been mostly using stock Unity static batching up till this point, but there were a few pain points I wanted to resolve.
We had an extra step for static shadow casters, for each scene we had to trigger a process from the editor which gathered all the static meshes in the scene and created one giant mesh asset, with all the normal and UV data removed, all split vertices merged and a single material applied. This was done with the idea to save on static drawcalls, in the shadow pass Unity by default will render a subset of the static batch for each material when (for BeeBeeQ at least) all static shadow casting objects have the same material properties and could be rendered as a single batch. This added a pain point though as the shadow geometry could easily slip out of sync with the actual scene’s geometry when it was worked on.
We also had the sycamore tree, a prominent feature in a few levels, but a bit of a resource drain. It was made up of 20+ SkinnedMeshRenderers that allowed it to sway, but prevented it from being batched despite being almost static, another pain point for me.
Basically I wanted to solve these pain points while also cramming the wind data into the meshes, and I ended up using a PostProcessScene method to handle wind data and batching at the same time.
Starting with static batching unticked in the project settings I wrote a PostProcessScene method that found all the batching static meshes in the scene, processed and finally batched them.
Static Static Meshes
For the static (not wind affected) static meshes, each one was copied under a new parent, given a single shadow caster material and put into a “Static Shadows” layer, then all the copied meshes were batched together using StaticBatchingUtility.Batch. This had much the same affect as the previous solution, except it didnt do any stripping and since it used Unity’s native backed function was much faster and dynamic.
Wind Affected Static Meshes
For the wind affected meshes I had to add the pivot data first.
Since BeeBeeQ uses many instances of the same mesh it made sense in a lot of cases to set up the pivots by mesh reference rather than a Component on the GameObject.
I created a SciptableObject that contained a list of Meshes for each pivot strategy, then in the PostProcessScene method for each wind affected mesh I would check if it was in the SciptableObjects lists and apply the appropriate strategy, falling back to the renderer base if it wasn’t in the list. The pivot generation is done per mesh instance and in worldspace to support the batching.
The pivots were encoded into UV2 and UV3 as 3 floats for pivot position (xyz) and an extra value for time offset (w) for pivot0 and pivot1 (this meant that plants/wind affected objects could not be lightmap static but that’s not an issue for BeebeeQ).
I applied a similar strategy for batching to the wind affected objects, copies are created for shadow casting, but because the shadows need to sway as much as the visible geometry and the material has a _WindStrength property I ended up creating a shadow caster material for each material with a different _WindStrength value, less materials overall but not a single material. Finally the wind shadow casters were batchedin the “Shadows Dynamic” layer.
Finally the rest of the actually visible geometry is batched under its highest common root, as stock static batching would have done.
The result is all camera visible static geometry gets rendered as normal and does not cast shadows in any way, and a second batch of static geometry gets rendered more optimally for shadows.
_Time For One Less Thing
One interesting note is after setting this up I got loads of shadow acne on the wind affected objects when they were moving away from the light which took a while to figure out the cause thinking it must have been to do with the meshes… but no! Our custom shadow pass does a RenderWithShader() right before the VR camera, it seems Unity updates the _Time property before every Render() so it was slightly different between rendering the shadows and the frame, a custom time property sorted it.