There are different ways to do grass in games. My favorite way is to place orthogonal quads per batches of grass (cross trees), batch them and distribute them smartly. The setup is cost efficient, convenient for lighting calculations, also it enables for alot of visual fidelity, but it is not truly “volumetric”. Here is another method which I wanted to give a try. Brute force uses several stacks of triangles on top of each other to create volumetric grass. I will not go into details of their implementation since their blog post is very clear. What I wanted to try out was to have the triangles generated in a geometry shader. Here is my code for this gif on Github: https://github.com/IRCSS/Geometry-Grass-Shader
So what is a geometry shader? It is an optional stage in the graphic pipeline, which the programmer may or may not decide to include for the rendering of their objects. This stage comes between vertex and fragment shader (so do tons of other stages, but you get the point). The special thing about geometry shader is that it can create new geometries. The newly created geometry can be streamed down the graphic pipeline, or back to the application (CPU) for further processing. Unfortunately the streaming back to application part is not supported in Unity, which is a shame. Now days the geometry shaders are overshadowed a bit by compute shaders, since some benchmarkings suggest that the compute shaders are in some cases faster than geometry shaders doing the same job.
The good thing about the geometry shader is that it creates the extra triangles directly on the GPU memory. Alot of the frame time in games is wasted on sending information from the CPU to the GPU, but we can save alot of that time by generating what we can directly in the GPU. Another advantage is that we would create only as many triangles as we really need, assuming that the underlying topology which we create our grass on is already optimized.
First part is to create the triangles above the surface of the triangles which are currently being rendered. We will just create n number of triangles, displace them in the direction of the triangle normal and append them to the triangle stream.
Pretty decent for 5 minutes of coding. I already color coded the layers for later sampling in fragment shader (look at Brute force’s blog post for details of implementation or directly in my code in Github).
Next step is to simply sample our noise texture in fragment shader, discard the fragments we don’t need, and we end up with this.
So now that this is done, here are some thoughts on the stuff I won’t implement now but are worth noting:
- For lighting we can sample neighbours of the pixel in the noise texture to determine the slope for each fragment, and from there the normals. This might be too noisy though. A better solution might be to combine this with the underlying normal of the geometry to create a slight variation to smooth shading.
- Discarding fragments causes aliasing, since MSAA is not applied on them. This is a huge pain, there are different solutions, none is perfect. If we are on deferred and are using TXAA we can use a random value between 0 → 1 to check against the noise texture instead of a fixed alpha cut. This creates a random noise which gets picked up by TXAA and improves the look. Alternatively alpha blending could be an option depending on the use.
- I took a flow map and noise from DeepspaceBanana to create the wind effect. We can use similar techniques, plus GPU collision detection to make the grass interactive to the player.
As usual thanks for reading. Here are a few blogs for further reading if you are interested:
The original blog post from Brute force: https://www.bruteforce-games.com/post/grass-shader-devblog-04
Another geometry grass shader with a different approach: https://roystan.net/articles/grass-shader.html
Intro to geometry shaders in case you are unfamiliar with them: https://jayjingyuliu.wordpress.com/2018/01/24/unity3d-intro-to-geometry-shader/
And a fun read, though slightly unrelated, placement of grass in the Witness:https://caseymuratori.com/blog_0011
You can follow me on my Twitter: IRCSS