Hello everybody,
Recently I tried to visually upgrade the demo in order to achieve a better look for the game. I put some effort on rendering grass and vegetation, so this post will explain my approach.
A glimpse at the new grass system!
I – The unity grass system
First of all, I considered using the Unity terrain and grass engine. But this raises 2 problems:
First, I use very complex terrains in my game, with “flying” islands, holes, caves, bridges and other non convex shapes. Unity uses only “height map” terrains, which can’t easily generate grass on everyside of a flying island for example.
Second, the Unity grass system is designed to display grass on huge terrains. Thus a lot of grass batching is dynamic, resulting in quite some draw calls, which severely slows down my game.
That’s why I chose to design my own vegetation system.
II – The new vegetation system
The idea behind this new vegetation system is to be able to easily create grass fields in a few clicks, and with enough control over the artistic part. I use Blender as a my level design tool (among others), so I must be able to export the grass settings to Unity via the FBX format. I want something not too heavy filewise and quite adaptative to suit all the use cases I will face (vertical vegetation on walls, or moss below flying islands). Here’s what I ended up with:
In Blender
Because of the above reasons, I choose to use vertex colors on hand made meshes. This allows me to create custom meshes only where I want to put vegetation. Moreover, I can use the 3 color channels to create 3 different types of vegetation on a single mesh. To paint easily on each channel, I use the “Add” and “Sub” brushes with pure red, green or blue colors.
Of course, I use a special name for the mesh with specific conventions to tell Unity which kind of vegetation I want.
I can overlay many meshes on top of each other, so there is virtually no limit to the number of vegetation types I can handle.
Here’s the vertex-painted grass mesh in Blender. For this mesh I use grass in red, moss in green and some kind of falling lianas in blue.
Of course, it might be difficult to “paint” grass without actually seeing it in Blender (grass will be generated later in Unity). This is the price to pay to have light-weight file exports. But eventually, I got used to it.
If I don’t want grass everywhere, I can only set up a small mesh on the specific area I want to cover. This is very cheap with regard to the export file size.
In Unity
Then when I import the level in Unity, my level importer scripts generate vegetation with density depending on the vertex color of the custom mesh. Each grass element is generated from a prefab. The prefab is automatically snapped and rotated accordingly on the first underlying viable surface. Of course, defining a “viable” surface is not as simple as that (I have to test the kind/type of surface), but you get the idea.
Sometimes, auto snapping of grass creates weird results (grass on the sign, which is pretty cool, but not intended). So I had to add additionnal specific rules regarding the snapping process to avoid this kind of things, or, at least, explicitly tolerate them.
I also added a randomizing step, which enabled me to change rotation, or scale or whatever suits me on each prefab, thus creating variation.
A screenshot showing the 3 types of vegetation: grass on the foreground, moss on the vertical walls, and falling lianas below the floating islands.
Another screenshot of the Thief Guild island with grass.
Then, once all prefabs on all meshes are generated, I merge them by material. I pack everything in meshes with ~65000 vertices which is the limit Unity can handle. Of course, this creates several batched meshes (usually around 50, I guess I could go higher) but this drastically lowers the number of drawcalls.
III – Pros & Cons
As usual, this approach has good and bad sides:
Statically batching all grass meshes comes at the expense of not being able to dynamically show/hide the grass depending on the distance because everything is batched with no particular order or region computation. Thus every grass blade on an island is visible from any point on this island. As the islands in my game are quite small, this seems OK, at least for now. In the future, shall I need to optimize more, I think I could add some octree structure to pack grass by region, and/or use some kind of intelligent shader that would clip very far away geometry.
But, batching grass with a total random order has an advantage: I can choose to activate only half of the batched vegetation meshes, and this will divide the density by 2 everywhere. As displaying grass is still a GPU intensive task, it is quite useful to be able to dynamically lower the density to make the game run on smaller material configurations.
Another good thing is that, as grass is generated automatically via prefabs, this vegetation system can actually handle all prefabs in general, not necessarily vegetation; rocks for example. Thus, I should call this a “detail” system.
A few rocks generated with this new detail generation system.
Another advantage is that I can also manually place the grass prefab if I want. It will still benefit from the batching system. So I can use this “automatic generation system” for wide areas, and I can manually place detail on smaller or very specific areas if I want to.
I can still manually place grass tufts if I want to (on rocks here).
A simple cave with lots of lianas…
IV – Performances
At the moment, I can display 80 000 vegetation elements at a decent frame rate (~ 30 fps) on a medium configuration. Of course, it depends a lot on the number of polygons of each grass prefab. So each detail prefab must be very carefully modeled.
And because I already coded specific vegetation shaders, I still use them with this new generative system. So grass still reacts to the wind and to player moves.
So that’s all for today! I hope it wasn’t too technical. This system is still quite new, so I hope to tweak it in the next months to make it more efficient, or more adapted to my problems.
See you!
Peace!