Aragami2 has very rich vegetation with a lot of trees, plants, and forests. We needed the trees to look nice and real so they had to move with the wind and have subsurface scattering (SSS) so that they felt right. Plants are also an important part of the game since the player will be hiding on them all the time, so they needed to move with the player and enemies as well as being performant.
SSS is not known for being performance-friendly, so I had to come up with a solution that looks right and doesn’t consume too much. What I ended up with is a similar way of how Frostbite2 approximates SSS but cheaper because we don't need thickness since all leaves are flat!
The basic principle is fairly simple, a SSS is just the light of one side of the object that scatters inside and comes out by the other side right? so that is exactly what the shader does, we can approximate the light of the opposite side by checking what the light on the lit side is.
Trees move with the wind, in this video, you can see an exaggerated example of how the wind system works.
It is based on a compute shader that generates the wind data, direction, strength, and so on. With this, the shader then creates three kinds of movements, rotation, bending, and also a bit of jittering on the leaves to make them feel more real.
In the game, the player can hide inside tallgrass, it's one of the main mechanics of the game. Also, there are around 20 to 50k plants per level.
Before I got the project all the plants were placed one by one in the scene, so I had plenty of room to investigate methods.
This is what I did:
For this system, I had to take in mind that It had to be performant but also work on all consoles (Switch, Xbox One, Xbox X/S, ps4 neo/standard, and ps5).
With this premise, I tried the following:
Using geometry shaders to make each individual plant paint a 4x4 plant batch:
This approach was a complete failure, we could definitely draw more plants, but the performance was still too bad, mostly because the mesh needed to be reconstructed on each pass(lit and shadows).
Using instantiation to instantiate the geometry shader plants:
This method did not fix anything since the problem was with the meshes being build at each pass, but we saw an improvement thanks to instantiation.
Using a compute shader to generate a mesh with all the plants of a level:
With this approach, we got closer to what we ended up having, this method was way more performant but had the problem that the generated mesh was so huge that we ran out of Vram on old-gen consoles, also the performance was good, but not as good as it could be since to add wind and interaction we had to generate the mesh each frame.
Finally, the method that I used was the following:
At the beginning run a compute shader that reads all triangles in a mesh and generates a point on the center of each one.
Each frame read from the buffer with all the points and choose which ones to paint from another compute shader
instantiate a plant on each chosen point
To make the system performant I had to split the generation and the choice of what points to paint on two different compute shaders to be able to discard points outside of the frustum, and also add lodding to discard 50% of the points when further from the camera, each frame. Also, this allows me to modify the indirect arguments buffer from the compute shader and only paint the exact plants.
At the end we got the vegetation to cost 1ms on the ps4 when rendering 20k plants.
The player will spend a lot of time inside these cover plants on Aragami 2, we needed to make them beautiful but also interactive, this way the player can easily tell where he is and also see the plants react to other entities walking over them.
To do so I render only the interactive actors (player and enemies) in a separate texture where those entities write their position and more important the eight. This way the plants can read from that texture and know what direction they need to move, the best part is that since the entities render their height the plants can correctly react to the player jumping on them
Lastly, interactions need to be performant, and having a camera dedicated to that system does not seem to be the best way to go, considering it needs to render all the level to cover all the plants in it.
The solution to this was not making the camera render all the level, only a few meters away from the player, this way we only render a very little amount of objects since there will be only the player and not many enemies, then with the known position of the camera we can reconstruct the real positions of the content in the interaction image
To make the wind on the plants I split the wind affectance as batch wind and per-vertex wind. This way the artists can make the plants seem more rigid and move as a whole or be more flaccid, even combine the two modes to make the wind more interesting!