#1 - Early Landscape Grass
In this first week of production, I have worked primarily on the landscape, and the grass that is scattered on it. As the majority of the frame is taken up by the landscape, I figure it will be good to have a decent-looking base from which to start placing the final assets into, and will help me to get a better idea how it will all come together in the end.
In the past, I have tried more "traditional" approaches to grass, using alpha-tested billboards arranged like in this pack here:
With UE5, nanite, modern hardware, and inspired by this article about the performance of "next-gen grass" found here, I decided to try a more poly-heavy approach.
Instead of using blades of grass baked to a plane, and relying on alpha masking (an overdraw-heavy, and therefore costly approach), I instead opted to try having each blade of grass be it's own low-poly mesh.
Performance tests from Nils Arenz's project seem to show that the performance hit of this approach is negligible, and can actually be much more performant than the billboard method, simply from reducing quad overdraw.
In addition, each blade can be deformed independently, which can look better than entire strips of grass moving at once.
Grass Mesh
This is possibly the laziest method in fairness, as the blades of grass literally look like this:
Two 4-face meshes with simple deform modifiers on. The only special thing (which isn't special really, as you do it with the billboard method as well), is that the normals have been pointed upward, to help them shade more uniformly.
I then had to create a cluster of these blades with random scale and orientation. This was a good excuse to learn how Blender's Geometry Nodes work.
I created a GeoNode setup that scatters points on faces, then scatters meshes from a collection on those points.
The result was this:
15 minutes of work, to save myself maybe 2 minutes of duplicating and rotating blades of grass.
The benefit though, is that I can reuse this for any foliage I might need to create clusters of in the future.
Right now, the node setup has a handful of options including:
- Density of objects to scatter,
- Maximum Rotation Variation,
- Max/Min Scale Variation,
- A random seed, to quickly vary the results.
By adjusting these values, and by changing the shape of the base geometry, I can create bigger or smaller clusters, or clusters of any shape:
In Engine
In engine, with a fairly straightforward grass material, the result was this:
The grass material currently has the following features:
- SimpleGrassWind node, to give it some quick wind motion.
- PerInstanceFade, which fades the grass out at a distance. This helps somewhat in removing the hard edge at the end of the grass draw distance.
- Colours driven by a "Runtime Virtual Texture", which is sampling the colour of the underlying terrain.
In future, I'd like to add a panning "shimmer" effect that runs across the landscape, along with a more complex wind effect that makes the grass move more like wind across long grass does in real life.
I'd also like to try and get in more variety of grass, perhaps some dense clusters that are taller, and some per-instance colour variation.
I also notice that the grass isn't shading quite how I'd like, so I have some investigation to do here. The way each blade of grass is shading is currently very obvious. On steeper slopes, the shading completely breaks down. I think this is something to do with how the normals have been edited, either in Blender, on import into UE5, or in the material itself.
I currently have the grass spawning out to a distance of 20,000, with 3 levels of LOD which reduce geometry and disable certain shader features (at this point, just the wind effect) at greater distances.
Performance-wise, I couldn't notice a difference in framerate between this method, and a pack of similar, probably more optimised billboard meshes at the same density. Even at double the render distance, really no noticeable impact. I did notice an increase in drawcalls, something that Nils Arenz points out in his article, but UE5's static mesh instancing seems to be handling it perfectly well, even on quite a large scale.
Comments
Post a Comment