A Custom Foliage System
As everyone knows, Unity’s terrain / foliage system isn’t exactly modern. Hence, I’ve been doing everything I can to avoid the horrors of these systems. The first step in that direction was to build my own foliage system.
Cell Based Data
First off, a simple explanation of Ardenfall’s world-data structure is needed. In short, the world is split into Cells, some in a gridded form (like most of the world) but can also be on their own (a dungeon, for example). Each cell has its own Scene file, data file, and foliage file. (And of course, other data)
This allows for automated streaming (never load what you don’t need) and is also git-friendly – it’s now possible for multiple people to work on the same map, just by editing different cells. This data is automatically loaded and unloaded, making it easy for the foliage system to simply grab the current cells and do whatever it needs to do.
Foliage cell data is also split into subcells, just a simple array of classes. This is to make rendering faster and smarter.
Culling in Unity is incredibly easy. First, you need to calculate the frustrum planes:
frustrumPlanes = GeometryUtility.CalculateFrustumPlanes(camera);
And then when you want to see if a certain Bounds is inside the camera, just do a AABB check:
I utilize this by giving each foliage instance has its own bounding box, which is easily calculated by grabbing the bounds of the mesh. All these bounds add up to a sub cell’s bounding box, and the subcell’s bounding box adds up to a cell’s bounding box.
And so, when it’s time to render, it goes through all loaded cells and checks distance, and then checks culling for each cell. And then from within each subcell, it once again culls. And then in every instance, they’re culled again. This results in only culling what is needed. In other words, if an entire cell that consists of thousands of trees is completely off screen, it is instantly discarded on the first step, very cheaply. I also implemented a per-foliage culling feature, but this seemed to not help, in fact the extra computation on the main thread that this feature created seemed to be worse than just rendering the entire subcell on the GPU.
However, when culling is done like this, a new problem arises: shadows! One solution is to always render shadows, but I’ve found that shadows seems to be much more computationally intensive than the mesh itself. My solution was to basically extend the bounds of the mesh for shadow rendering: so if these bounds are on camera, only render shadows… if not, render nothing.
Instancing in Unity is incredibly easy, it’s quite fantastic. All you need to do is run Graphics.DrawMeshInstanced, plugging in the mesh, submesh index, materal, matricies, and so on.
Note that the material used must support instancing. Don’t worry, most do – you just need to check that little box.
If you have multiple submeshes, don’t worry – just run it multiple times, with a different submesh index.
Lod calculation is quite easy – for each foliage asset (tree type) I have, I just have a list of LODS. And then, for each subcell, the system simply calculates the distance from the camera to said subcell, and uses that to pick what LOD to use. It then uses Graphics.DrawMeshInstanced to render this LOD mesh. In other words, a LOD isn’t applied per-instance, but per-subcell. This results in faster render times (since the number of distance checks are kept at a minimum), with very little difference to the outcome. The only thing to be wary about is the subcell/cell size – too big is unnecessary, and too small and you will lose the “exactness” of the LOD distances.
Shadows are quite expensive, but I’ve found they are so fantastically beautiful I just *had* to make it so as many people could play the game with fancy shadows. I haven’t done much yet, but I have added a simple shadow imposter system.
A shadow imposter is basically a simpler mesh that is used to only render shadows, while the actual visible mesh does not render any shadows. This results in a much more efficient render, as often complexities in meshes are unneeded when rendering shadows anyways. Trees are no exception. I’ve found I can create some nice shadows with only 1/4th of the number of verts:
Of course, it’s not perfect – this was mostly a test to see how far I could take it, and it especially lacks leaves in the top, so any shadows casted onto other tree’s often look glitchy. Regardless, it works for now and is speedy fast.
Collision is automatically generated as the player walks through the game. The generation is run every second, but of course this can be modified. How collision works is quite simple – collider sizes are saved per instance, and then as the player walks through the world, colliders are created or destroyed. There’s a simple pool system implemented, since destroying and creating a bunch of gameobjects all the time is unnecessary.
This works especially well with my game, which has very little physics, and since NPC’s use navmeshes, collision is pretty much only used for the player.
Currently the navmesh does not generate with the collision trees, I still need to figure out how to design that. One simple way would be to create all collision objects during edit mode before navmesh creation, although this seems quite ridiculous and expensive.
What would a foliage system be without grass? Not a foliage system, I suppose. Grass is rendered using a mesh of vertices that are used as grass positions. This results in incredibly fast rendering, and very pretty results. However, currently my grass shader hasn’t been set up for deferred lighting (aka “infinite” number of light sources), so it’s quite glitchy.
I plan on writing an article on the grass when I get that working. Perhaps I’ll release the shader while I’m at it.
Here are some ideas and thoughts on the future of this system:
Occlusion Culling: Currently a cell, subcell, or instance is only culled when it is offscreen. But what if it’s hidden by other objects, such as terrain or a wall? This is where occlusion culling comes in. Unity has this feature built in, and it looks like it’s somewhat easily accessible using their CullingGroup API. I’m not sure if my system gains much from this (how often will a subcell be hidden by other meshes?), but if so I am certainly interested.
Runtime Removal/Addition: Technically adding the function to add or remove foliage during runtime would be quite easy, since the data is quite accessible. However I’ll only look into this if I decide I need it.
Threaded rendering: The actual rendering of the meshes is handled on the GPU, but the system that figures out what cells / subcells to render is all run on the main thread. Using Unity 2018’s new C# Job system may help with this!
Release of the Foliage System
I’ve thought about releasing this code, perhaps on github, however some key things need to be done before this is possible: mainly, making it so it doesn’t require my cell system. Also, there is a lot to be done – cleaning up code, making the GUI usable, and adding features. If I did update it, I cannot promise I’d keep it up to date. Regardless, it is a possibility in the future if enough people are interested.
This post was mirrored from IndieDB