Grass Rendering Plans
Added 2019-03-01 16:04:53 +0000 UTCAs people seem to be interested in my implementation for rendering grass, and since I don't recall having explained it anywhere public, here are some notes on how I am currently doing it, and how I intend to fix the speed issues I am having.
First, let me explain my current process for laying down patches of green, aka grass: The terrain in CoS is composed of modular tiles, each containing a script that determines if grass is going to be rendered on it or not. To mask out sections where I don't want grass to render on the specific tile, I can add another script. The script basically allows me to place down points to form a polygon that will act as the mask for the grass. The whole tile with the mask is then packed into a prefab for convenient world-building in the editor. I also have one or more global grass masks, which I can use to mask specific areas that are not defined per tile; one such example would be to mask out areas around specific world objects, like wells, houses, etc. Then, on the camera object I have another script, which checks which tiles are visible, and for the visible ones, it creates batches of materials to be fed to the drawmeshinstanced method, which draws the actual grass. This script takes care of filtering out tiles that are either too distant or just out of sight.
The actual rendering is pretty efficient, it's the management of which grass tiles should render in real-time that is the bottleneck.
To solve that bottleneck, I plan to modify the whole thing as follows: First, I will pre-calculate the grass positions for each mesh in-editor, and store them globally in an array in some GameObject, so I only need to go through one array of points instead of every single mesh. Then, I can use a frustum check to render only the specific blades of grass I need (Currently, I need to render all the blades of grass on a specific land tile, even if only a piece of it is visible). The loop that updates the array could as well be a Unity Job to speed it up considerably (a thing that was tough, if not impossible to do in the old implementation).
The editor should have a button for refreshing the array of all the points, so it doesn't impact world editing performance. It would be sensible to have that one global array somehow partitioned into sectors for update speed sake, but that may impact the performance of the eventual Job of rendering. I wonder how could I modify the array instead of recreating it... what "hook" to use to determine the Vector3s in the array are bad (deleted meshes)? Perhaps check through the whole array and delete them periodically?
Anyway, I am rambling, and probably it doesn't even matter, as I'd be updating the array manually once I build the "levels" in the editor.
So, the plan is as follows:
- Create a GameObject carrying a GrassCaching script.
- The GrassCaching script contains a public array of Vector3s.
- The script could also have settings for the grass types (i.e. materials) to render on that specific mesh (right now the array of materials together with a grass pattern is set in a script on the main camera).
- Have a menu script that updates this public array by looping through all the meshes containing a HasGrass script attached, randomly generating the individual Vector3s representing grass, while taking into consideration the local and global grass masks.
- At this point, the neighboring meshes could blend the grass types at the seams (if they differed).
- Have the grass-rendering script on the camera create batches and render the grass from the public array.
Once that is out of the way and working, I am considering replacing the simple grass billboard with a star-like (*) mesh. That should work out better on slopes, while also requiring less meshes to be drawn, hopefully increasing performance as well.