My name is Robbie de Gier, I’m 24 years old and I’m a 4th year game development student. I’ve always had a certain thing for art but am not very good with my hands and a pencil. That’s why I thought it’d be a great experiment to mess around with a visual shader, and hopefully bringing out the artistic side a bit.
I’m going to make a grass/foliage shader. I’ve been inspired by games like Zelda: BoTW, Skyrim. Actually, what game does not have some sort of grass shader? Because of this reason, I feel like it could be really useful for me to understand the basics of shaders and to create something beautiful, that could potentially be re-used in future projects. I’ve looked at games such as, Skyrim, Zelda: BoTW, Farming Simulator, etc.
1.1 Starting off
1.1 The concept
I’m going to create:
- A geometry shader that represents grass.
- The grass needs to be “customizable” by adjusting the properties.
- Make use of tangent space so it can be used on any surface.
- The shader should contain a way of rendering foliage/flowers/bushes.
- Custom colours/images, colour gradient
- Distance based LOD, rendering textures in the distance.
The following image captures what I want to create perfectly:
2: Geometry Phase
2.1 Basic triangle shape
Starting off I started doing research. I want to create a geometry shader so that’s why I started learning more about how this works. I came upon an article written by “Roystan”, in his article he talks about the basics of a geometry shader and how to write this. I figured it’d be a nice place to start understanding the basics and it could be something I can build upon. In the images below you can see the very first steps that were taken towards making it look like grass.
Practically what I found is that a geometry shader takes a single primitive as input. I can then choose the amount of outputs. In the code below I declare a shader with the name “geo” that composes a single triangle.
The position of the outputs are three offsets based on the input point. On the picture below I marked one of the emitted triangles, the black dot represents the input and the red dots represent the offset. This is visible on the drawn image as well.
2.2 Understanding and implementing Tangent Space
One of the requirements that I set for myself is to implement tangent space in the shader. This is so that it can be rendered on any surface. Eventually I’m adding random values to my grass blades such as: random width, length, rotation, etc. In order for this to work on any given surface we can use the tangent space.
The image above presents what I’m trying to do perfectly. By taking the normal (N) of a input vertex (I) and by taking the “Tangent Line”, which is a line that’s set to slightly touch the curve of a plane, creating a vector on the location where it hits. By taking the cross product of these two vectors (N & T) we can calculate the Binormal (B).
The code above is doing exactly what I describe above, take the normal, take the tangent and assign Binormal with the cross product of these two vectors.
2.3 Converting to local Space
So now that we’ve calculated the Binormal we can construct a matrix that’s going to help us transform from Tangent to local space. We need this because UnityObjectToClipPos expects a vertex in local Space. This is Unity’s helper function to help transform vertices. If we now multiply the tangentToLocal matrix with the input points coordinates it should be properly aligned.
I implemented tessellation. I’ve not written the tessellation code myself and don’t own it. It’s based on an article that can be found here: https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/
If you summarize tessellation very quickly it’s technically cutting things into smaller parts. Tessellation happens early in GPU and there’s a window where we are allowed to configure things. The stage sits in between the vertex and fragment shader. So by dividing all the triangles into many more triangles, creating many more vertices, we can get a very populated grass field.
3: Customizing the grass
One of the goals of the shader is to represent grass, while a triangle is a good set up, we’re going to need a lot more to convince someone that it’s actually grass. I’m going to add a random Width & Height, as well as Random facing direction for each blade of grass and also they should randomly bend forward a bit.
3.1 Color Gradient
For the colour gradient I simply lerp the colour from one value to another. The colors can be picked from the shader’s properties. This will only happen to grass blades, not to any other textures (more later).
3.2 Used functions
The following functions were found online, the links will be in the sources below.
For all the randomness in this shader I use the “Rand” function, it returns a number between 0 and 1. This is based on a random seed, I use positions and variables like that to randomize on.
As I don’t have a too much knowledge about how to manually work with a rotation matrix and felt like this might be a bit out of scope I’m using one I found online. The function constructs a matrix and rotates it around the provided axis.
3.3 Random Bending
I do this by constructing a matrix and rotate it along it’s X-Axis. I input a random seed into the rand function , the position in this case. If we then multiply this by UNITY_PI (3.14159265359) and multiply it by 0.5 we get a random range between 0 and 90 degrees. Now all that’s left is multiplying the transformationMatrix with our newly created bending matrix to apply the bend.
3.4 Random Width & Height
Getting the random width and height implemented was fairly simple. I create a float for both of them. Then by
After all the changes made in this chapter the grass currently looks like this:
4: Flowers & Bushes
4.1 2nd Geometry shader
One of the goals of the shader is to spawn random flowers between the grass. In the first case I started experimenting with complicated geometry shapes. I tried to create a basic flower-like shape with a complicated geometry shader. I started drawing the shapes on a grid in my notebook and then tried to convert this to an actual shape in a new shader.
As visible on the above images I was struggling quite hard trying to wrap my head around all the coordinates. Eventually based on the drawings I made I started working on the shape in Unity. I got the shape to work, but after a lot of work I felt like I was being really inefficient. Below I’ll post a picture of how it turned out.
This is what my flowers looked like after messing around for a while. This does look kind of nice in a way, but at the same time it doesn’t feel like a real flower at all. Also the code required was an insane amount of triangles that I drew manually, it started to feel a bit inefficient and that’s why I stopped with this approach and started looking for a new one.
4.2 Texture Atlas for bushes in distance
At this moment, everything is just grass, the entire plans gets filled with grass endlessly. This is not good for performance and also it doesn’t look to good on a big open field. Another thing that I recently learned about is the usage of a texture atlas. Practically, the idea is to replace the grass in the distance with a quad. On top of this quad I’ll render the texture of a “bush”. Creating a zone around the player where we can see bushes.
So as you can see on the image above, the quad gets drawn when camDist is lower then _Distance, _Distance is a shader property that the user can change. camDist is the distance between the player (camera) and the object in the world, but is not really used until in a later stage, right now it’s all about camDist < _Distance. The random variable that’s get passed into the vertex output is an int that’ll help us pick a random texture from a spritesheet.
Below I’ve posted an image of the used texture for the bushes as well as the textureIndexToOffset I wrote.
As visible above the function is able to divide the textures into four different pieces quarters. Based on the textureIndex it recieves (which can be 1 through 4) it will spawn the given texture on the quad.
4.3 Distance based “LOD”
Ok now that this works properly, it’s time to do something about the thousands of bushes spawning all around the plane. At this moment the performance of the shader is pretty terrible and I’m going to do something about that.
On the image above you can see that I start using camDist now to see if I should draw a bush or not. This gradually decreases the bigger the distance gets. The second statement reduces the spawning of the bushes by 80% in general.
4.2 Spawning random flower textures
In the current version of the Shader there’s one flower texture. Instead of passing a random int to the vertex output function, I instead send a number higher than 4. This is because my texture atlas is 2×2 and the function that offSets the texture probably is based on 4 textures. In the frag function I catch this by checking if the textureIndex is equal to six. If this is the case it will render a flower instead.
5: Additional Features
Some extra features that didn’t really need an entire chapter. I’ve gathered some knowledge over the past months that I’ve tried to apply with these extra features, such as using a distortion map to simulate wind. Use the same distortion map to create color changes that move through the field and also supporting multiple planes.
For the wind I use a distortion map. The UV Coordinates are based on the input point’s position. This will help us later on when we want to render our shader on multiple planes.
5.2 Wind Color change
I’ve experimented with color change on the wind as well. Practically what I tried doing was to change the color of grass blades and bush textures based on the same distortion map that I used for the wind generation. In the GIF below you can see the result of messing around with the colors for a day or so. I did this based on a suggestion of a friend. Just a little extra outside the scope.
6: Conclusion and future
6.1 Final Result
As a final result, I have build a geometry shader that makes use of tangent space to place either grass blades or quads with bushes. The grass blades are customizable (Width, Height, Random Rotation, Random Bend, Color). The shader also spawns random flowers, this was implemented after taking a complete different turn somewhere halfway down the project. I went from another geometry shader to a flower texture build into the existing geometry shader. The shader uses a texture atlas for spawning random bushes and there is a max draw distance. I’ve implemented a “LOD-like” system that slowly decreases the amount of bushes spawning around the player and eventually coming to a full stop. It’s also compatible on multiple planes.
Here’s a picture of the final result:
6.2 For future reference
If I’d have to work on the project longer I would write another texture atlas function to support way more flower textures. I was working on this but ended up just using one texture because I broke practically everything with my new set up, so this is definitely an improvement that could be made. Also I would use the knowledge that I’ve gathered over the past few months better. I started out not knowing anything and I had to take tiny steps towards building my shader, I am proud of the product I made but there is definitely room for improvement. Another thing I could add is shadows. I could finish my wind color change feature that I started working on.
Tangent Space (link had to be shortened):
Roystan, Grass Shader Tutorial
Halisavakis, My take On Grass Shader
Jose Mariaolea, Create grass with wind effect in Unity