Using The Geometry Shader In Unity To Generate Countless Of Grass On GPU

This is an English translation of a blog I wrote in Chinese in 2017. Although the original blog was a few years ago, I found this article very interesting, so I decided to translate it into English.

You can find the Github Repo Link at the end of this Post.

When displaying a realistic rural scene in a game, the rendering of grass is essential, and when it comes to efficient rendering of grass, many people will think of Chapter 7 of GPU Gems, “Chapter 7. Rendering Countless Blades of Waving Grass” [1].

Now many mobile games are known as “next-generation” or even some PC games still adopt this solution more or less. However, this article will not discuss too much about this solution. Instead, most of the following content is about how to use the Geometry Shader to render countless grass on the GPU.

Grass Objects That Look Like Stars

In the traditional way, the model data is transferred from the CPU to the GPU, and then the GPU renders the grass objects based on the data. When rendering large-scale grass, the model details of a single grass are often ignored. If the model of a single grass is too detailed, rendering a large piece of grass needs to transfer a lot of vertexes, resulting in a decrease in performance. (of course, GPU-Instancing is a good solution, but I mainly talk about the Geometry Shader in this post).

Therefore, a solution for rendering large grasslands often needs to meet the following conditions:

  • The vertices of a single grass can not be too many, it is best to use a quad to represent a grass
  • Viewed from different angles, the grass must appear dense
  • The arrangement of grass cannot be too regular, otherwise, it will be unnatural

As a result, the classic structure when rendering grass, star-structure appeared.

ref: Chapter 7. Rendering Countless Blades of Waving Grass

In this way, the simple star-structure not only satisfies the low vertices number of a single grass but also makes it appear dense when viewed from different angles. It is also very simple to let the grass move with the wind. You only need to find the top vertices based on the UV information of the vertices and move the vertices according to your own rules.

if (o.uv.y > 0.5)  
{  
    float4 translationPos =  
        float4(sin(\_Time.x \* \_TimeFactor \* Pi ), 0, sin(\_Time.y \* \_TimeFactor \* Pi ), 0);  
    v.vertex += translationPos \* \_StrengthFactor;  
}

Many games still use this solution when rendering grass.

A Chinese Mobile Game In 2017
A Chinese PC Game In 2017

However, you have also seen that although this solution is simple, it is not natural. When looking down from above, all the patches can be seen clearly, so this solution is not what I want.

More Realistic Blades Of Grass

The solution I want is to be able to render in real-time on a large scale, and the blade of each grass can be swayed with the wind. In this regard, there have been some explorations in the industry, such as “Rendering Grass Terrains in Real-Time with Dynamic Lighting”[2] on Siggraph 2006, and Edward Lee’s paper “REALISTIC REAL-TIME GRASS RENDERING” [3].

This article mainly implements the effect of GPU generating endless grass swaying in the wind according to the description in Edward Lee’s paper.

Here, I mainly used the Geometry Shader[4] introduced after Direct3D 10 to implement the logic of creating individual grass blades on the GPU. Each blade has 3 types of composition according to LOD, which requires 1 quad, 3 quads, and 5 quads respectively.

ref: Edward Lee

The position of each grass is randomly determined by the CPU. Since the input of GS(Geometry Shader) is a primitive (point, line, or triangle) instead of a vertex, we need to create a point type primitive according to a random position calculated in the CPU as the root position of the grass.

Ok, next, make a blade of the grass on the GPU through a root position.

\[maxvertexcount(30)\]  
void geom(point v2g points\[1\], inout TriangleStream<g2f> triStream)  
{  
    float4 root = points\[0\].pos;

Although the position of the grass is random, we obviously also hope that the height and width of the blade itself are somewhat random.

float random = sin(UNITY\_HALF\_PI \* frac(root.x) + UNITY\_HALF\_PI \* frac(root.z));  
        \_Width = \_Width + (random / 50);  
        \_Height = \_Height +(random / 5);

After setting the blade attributes, we can create new vertices based on these attributes to simulate the appearance of the blade.

As you can see in the image above, 12 different vertices are needed to form a grass blade, but since no index is used here, a total of 30 vertices are finally output to form 5 quads.

According to this image, we can also easily calculate the position of each vertex according to the position of the root. At the same time, we can also find that the UV coordinates corresponding to even vertices are (0, v), and the UV coordinates corresponding to odd vertices are (1, v), therefore, we can easily calculate the UV coordinates corresponding to each vertex.

Finally, if we want to calculate real-time light, we also need to obtain the normal information of the vertices, which is unified as (0, 0, 1) for simplicity.

for (uint i = 0; i < vertexCount; i++)  
        {  
            v\[i\].norm = float3(0, 0, 1);  
  
            if (fmod(i , 2) == 0)  
            {   
                v\[i\].pos = float4(root.x - \_Width , root.y + currentVertexHeight, root.z, 1);  
                v\[i\].uv = float2(0, currentV);  
            }  
            else  
            {   
                v\[i\].pos = float4(root.x + \_Width , root.y + currentVertexHeight, root.z, 1);  
                v\[i\].uv = float2(1, currentV);  
  
                currentV += offsetV;  
                currentVertexHeight = currentV \* \_Height;  
            }  
  
            v\[i\].pos = UnityObjectToClipPos(v\[i\].pos);  
  
        }

In this way, a mesh of the blade is created on the GPU.

Next, we need to process the texture of the grass blades to render the blades that meet our expectations. Here I used the processing method mentioned in the GPU Gem article:

ref: Chapter 7. Rendering Countless Blades of Waving Grass

That is, the color of the blades can be processed with only a single texture that represents the color of the blades, such as the texture I used:

The specific outline of the grass blade is provided by another texture. However, instead of using alpha blend here, we use alpha to coverage, because blend will have some display order problems when dealing with overlapping grass blades. As for how to use alpha to coverage, you can refer to SL-Blend(https://docs.unity3d.com/Manual/SL-Blend.html).

SubShader  
            Tags{ "Queue" = "AlphaTest" "RenderType" = "TransparentCutout" "IgnoreProjector" = "True" }  
  
            Pass  
                AlphaToMask On

So, now we only need to simply sample textures in the Fragment Shader.

half4 frag(g2f IN) : COLOR  
    {  
        fixed4 color = tex2D(\_MainTex, IN.uv);  
        fixed4 alpha = tex2D(\_AlphaTex, (IN.uv));  
  
        return float4(color.rgb, alpha.g);  
    }

Generate Countless Of Grass

With the blades, we can consider how to generate terrain and grass-covered on the ground. For the natural and undulating contour of the ground, we can dynamically create a ground grid based on a heightmap.

Since the upper limit of Unity’s mesh vertices is 65000 (Note: at that time, since 2017.3 Unity supports 32-bit index buffer.), I decided to make the ground mesh size 250 * 250:

for (int i = 0; i < 250; i++)  
    {  
        for (int j = 0; j < 250; j++)  
        {  
            verts.Add(new Vector3(i, heightMap.GetPixel(i, j).grayscale \* 5 , j));  
            if (i == 0 || j == 0) continue;  
            tris.Add(250 \* i + j);   
            tris.Add(250 \* i + j - 1);  
            tris.Add(250 \* (i - 1) + j - 1);  
            tris.Add(250 \* (i - 1) + j - 1);  
            tris.Add(250 \* (i - 1) + j);  
            tris.Add(250 \* i + j);  
        }  
    }          
    ...  
    Mesh m = new Mesh();  
    m.vertices = verts.ToArray();   
    m.uv = uvs;  
    m.triangles = tris.ToArray();

In this way, a natural and real ground mesh is created.

Then let’s generate grass on the ground. The so-called generating grass is nothing more than we need to generate some vertices, as the root position of the blade of grass to the previously completed GS.

It should be noted here that the default mesh implementation of Unity is triangle, not point. (you can find more: https://forum.unity.com/threads/invoking-geometry-shader-for-every-vertex-of-a-mesh.338494/)

Therefore, the method of creating a mesh that records the location of grass roots is slightly different from the previous method of creating the ground.

m.vertices = verts.ToArray();  
        m.SetIndices(indices,MeshTopology.Points, 0);  
  
        grassLayer = new GameObject("grassLayer");   
        mf = grassLayer.AddComponent<MeshFilter>();  
        grassLayer.AddComponent<MeshRenderer>();

After it is created, you can see that the locations of grass roots are randomly distributed on the ground, with millions of them.

Apply the GS to the mesh that records the position of the grass roots.

Wow, countless grass appeared!

Wind Simulation

Although the standing grass looks much better than the previous star-structure grass, the still and neat blades are still very unnatural after all. Therefore, we want to move the grass to simulate the effect of wind.

The idea is to use the trigonometric function to make the blade of the grass sway, at the same time provide the initial phase for the trigonometric function according to the root position of the grass and then add some randomness in it to make the effect more natural.

wind.x += sin(\_Time.x + root.x);  
wind \*= random;  
        ...

However, in order to more realistically simulate the effect of wind, obviously different parts of the blade are affected by the wind differently.

The closer to the top of the blade, the greater the influence of the wind.

Therefore, in the logic of GS generating new vertices, the influence of wind on the position of the vertices is added. The higher the vertices are, the greater the influence is.

Such a more realistic countless grass effect is implemented!

The GitHub Repo

You can get the simplified version of this demo code here:

chenjd/Realistic-Real-Time-Grass-Rendering-With-Unity


Of course, this is not a solution used on the mobile platform, and as a demo, I did not do optimization. The main purpose is to demonstrate the function of the Geometry Shader.

Thanks for reading. I hope it’s useful.

Reference

【1】Chapter 7. Rendering Countless Blades of Waving Grass
【2】Rendering Grass Terrains inReal-Time with Dynamic Lighting
【3】REALISTIC REAL-TIME GRASS RENDERING
【4】Programming Guide for Direct3D 11


Subscribe To Jiadong Chen's Blog

Avatar
Jiadong Chen
Cloud Architect/Senior Developer

Cloud Architect at Company-X | Microsoft MVP, MCT | Azure Certified Solutions Architect & Cybersecurity Architect Expert | Member of .NET Foundation | Packt Author ㅣ Opinions = my own.

comments powered by Disqus

Related