Blade Grass Position and Shape
Grass rendering plays a significant role in video games, adding considerable detail and depth to scenes. A prime example is the game 'Ghost of Tsushima,' which showcases an incredible world with realistically rendered grass. This project, inspired by Sucker Punch's enlightening presentation at GDC 2021 and various related papers, endeavors to render blade grass in Unity. Key features implemented include:
        Procedural rendering of grass on the GPU.
        Bezier curve-controlled grass shapes.
        Wind animation utilizing a 2D noise map.
        Terrain generation based on height maps.
        Implementation of Distance, Frustum, and Occlusion Culling.
This project leverages the capabilities of Compute Shaders and GPU Instancing to efficiently render a large number of blade grass models with fewer draw calls. In the Compute Shader, each thread is responsible for calculating the position of an individual blade of grass. Initially, blades are uniformly distributed across the terrain with a slight random offset, resulting in a uniform height and base shape for each grass blade. GPU Instancing facilitates the grouping of all grass models, allowing them to be rendered with fewer draw calls. In the vertex shader, each blade of grass is indexed into the GrassBlades buffer to obtain specific parameters for that blade. The vertices of each blade are then positioned according to a Bezier curve defined by these parameters. Additionally, each blade of grass is rotated using a random offset to enhance realism. The fragment shader then colors these procedurally generated blades of grass by interpolating between the top and bottom colors.
blade grass
blade grass
bezier curve
bezier curve
add density
add density
The wind animation is achieved through the use of scrolling 2D Perlin noise. This noise map is fed into the Compute Shader, where the position of each blade of grass is used to sample a value from the map. The variable '_Time' dictates the changes in the sampling coordinates. These sampled values, with an added offset, are then passed to the vertex shader. The noise value is incorporated into a sine-based function, which modulates various parameters of the grass. This modulation affects aspects such as the Bezier control points and the grass's facing direction, contributing to a more realistic and dynamic representation of wind effects on the grass.
Terrain Generation and Height Map
The current implementation allows for grass generation on the 'xz' plane, but not on the 'y' axis. To simulate grass covering a mountainous terrain, it's necessary to incorporate a terrain and height map. By sampling the height map, each blade of grass can receive a y-axis offset, creating the visual effect of grass situated at varying elevations. To further enhance realism and variety, a procedural terrain mesh generation function has been developed.​​​​​​​​​​​​​​
The terrain generation process begins with the creation of Perlin noise using Unity's built-in noise generation function. This noise map assigns a normalized height value to each coordinate. Subsequently, the map is utilized to generate a mesh. The stored height values are multiplied by a 'height multiplier' variable, reflecting the height variation of the terrain. The terrain's shape can be dynamically altered by adjusting control variables such as seed, lacunarity, and octaves, allowing for greater flexibility. All terrain modifications occur in the editor mode. A button in the inspector facilitates the creation of a height map in the asset folder. With this newly generated height map, the grass generator can then adapt to the updated terrain
Hierarchical-Z Map Based Occlusion Culling
With the advantages of GPU parallel computing and GPU Instancing, the scene is now capable of rendering approximately a million blades of grass while maintaining a stable FPS rate. However, as the complexity of the scene increases, there remains room for further improvement and optimization. Implementing culling strategies is essential to minimize wasted rendering resources. Grass blades that are not within the scene's view, outside the camera's frustum, or obscured by other objects in the scene should not be rendered. By applying such culling techniques, the rendering efficiency can be significantly improved, ensuring optimal performance even in more complex scenes.​​​​​​​​​​​​​​​​​​​​​
Distance culling and frustum culling are implemented by checking whether each grass blade's position falls within a specified distance threshold and lies inside the camera's frustum in clipping space. For occlusion culling, a depth texture is employed to compare the depth of each grass render position with the object depths recorded in the texture. The Unity camera is configured to generate this depth texture during runtime. Each pixel in the depth texture represents the distance of an object from the camera at that specific point. By sampling the grass position on the depth texture and comparing it to the grass's depth, it becomes straightforward to determine whether a blade of grass should be culled or not.

Depth Texture

Further Works and references
There are several prospective avenues for further optimizing the grass rendering process. One such approach involves incorporating Level of Detail (LOD) for the blade grass mesh, which would render fewer vertices at greater distances, thereby improving performance. Additionally, as suggested in the GDC talk, introducing grass clumping could significantly enhance the realism of the entire scene. Another intriguing possibility I aim to explore is the procedural generation of the blade grass mesh. This could offer more dynamic and versatile grass rendering capabilities, potentially leading to more natural and varied landscapes.

Reference about this project:
https://www.gdcvault.com/play/1027033/Advanced-Graphics-Summit-Procedural-Grass
https://www.youtube.com/watch?v=bp7REZBV4P4&t=403s
https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf
https://zhuanlan.zhihu.com/p/396979267
https://www.youtube.com/watch?v=Y0Ko0kvwfgA&t=452s
Back to Top