Last update: 1/14/2026
This project is implemented based on the paper:
Jahrmann, and Wimmer. Responsive real-time grass rendering for general 3D scenes, 2017
Windows 11, Intel Core i7 12700h, Nvidia RTX 3060. 16GB RAM.
Visual Studio 2022. Release Mode, app resolution 1920 x 1080
Please use git clone --recursive when cloning this repo as there are submodules which need to be cloned as well.
Cull blades that are facing orthogonal to the camera view direction to avoid rendering blade looking like a thin layer

Cull blades that are far from camera (distance projected onto the ground)
Up: Full culling. Down: Preserve a portion of grass
Perform frustum culling in Compute shader stage so blades outside the camera view frustum are rejected early.

Image comes from https://github.com/bobhansky/FrustumCullingPerformanceAnalysis
Use distance-based tessellation LOD in the TCS to reduce subdivision precisions.
The farther the camera is, the edgier the grass is, and the less triangles generate.
- Generate random grass attributes on CPU.
- Run Compute pipeline (compute shader) for physics simulation and culling. Update them on Shader Storage Buffer Objects (SSBOs).
- Start Grass Pipeline. Bind culledBladesBuffer SSBO as vertexBuffer as Vertex shader vertex buffer input.
- Tessellation control shader sets the subdivision levels (Level of Details, LOD) based on distance from grass root to camera.
- Tessellation evaluation shader populates the actual geometry.
- Fragment shader for shading.
Up: With all optimization (1,2,3,4).
Down: without any optimazation
Grass rendered count: 16384 (all blades in the test)
FPS: 709
Grass rendered count: 7640
FPS: 1110
Grass rendered count: 7640
FPS: 1035
Settings
In Case1 and Case2, In tessellation control shader, gl_TessLevelOuter is set to 7, gl_TessLevelInner is set to 5.
In Case3, gl_TessLevelOuter is interpolated from 2 to 7, gl_TessLevelInner is interpolated from 1 to 5, based on distance to camera.
Result
Phenomenon: FPS of Case1 is the least without doubt. Case3 (with LOD) should've intuitively outperformed Case2 where LOD is disabled, but in this test it didn't.
Possible reason: even though the geometry in Case3 is less complex due to LOD, the computations for LOD levels in tessellation control shader (distance evaluation, interpolation, and non-uniform tessellation levels) introduce overhead that compensates the geometry reduction benifits, and thus result in a slightly worse performance.
The grass geometry is too simple and thus might not suit for LOD. In order to see the benifit of LOD, 3 extra test cases is added below.
Grass rendered count: 16384
FPS: 247
Grass rendered count: 7640
FPS: 473
Grass rendered count: 7640
FPS: 842
Settings
In Case1.1 and Case2.1, gl_TessLevelOuter is set to 20, gl_TessLevelInner is set to 20.
In Case3.1, gl_TessLevelOuter is interpolated from 2 to 20, gl_TessLevelInner is interpolated from 1 to 20, based on distance to camera.
The purpose is to create a geometrically complex test scene. In real world application, grass doesn't need to be such delicate.
Result
Case1.1 and Case 2.1
Each Blade is formed by excessively many triangle to represent a geometrically complex model.
Case 3.1
The blade geometry is still complex when it is close to camera.
But it degrades into a simple geometry when far away, reducing the number of triangles for geometry generation and shading.










