University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 6
- Zach Corse
- LinkedIn: https://www.linkedin.com/in/wzcorse/
- Personal Website: https://wzcorse.com
- Twitter: @ZachCorse
- Tested on: Windows 10, i7-6700HQ @ 2.60GHz 32GB, NVIDIA GeForce GTX 970M (personal computer)
In this project, I use Vulkan to implement a grass simulator and renderer. I use compute shaders to perform physics calculations on Bezier curves that represent individual grass blades. Since rendering every grass blade on every frame is fairly inefficient, I also use compute shaders to cull grass blades that don't contribute to a given frame.
- Gravity, Wind, and Recovery Forces Calculated in Compute Shader
- Updated Tesselation Shaders
- Grass Blade Culling
- Distance
- Frustum
- Orientation
This project is an implementation of the paper, Responsive Real-Time Grass Rendering for General 3D Scenes.
Grass blades are represented as Bezier curves while performing physics calculations and culling operations. Each Bezier curve has three control points.
v0: the position of the grass blade on the geomtryv1: a Bezier curve guide that is always "above"v0with respect to the grass blade's up vector (explained soon)v2: a physical guide for which we simulate forces on
We also store per-blade characteristics that will help us simulate and tessellate our grass blades correctly.
up: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on atv0- Orientation: the orientation of the grass blade's face
- Height: the height of the grass blade
- Width: the width of the grass blade's face
- Stiffness coefficient: the stiffness of our grass blade, which will affect the force computations on our blade
We can pack all this data into four vec4s, such that v0.w holds orientation, v1.w holds height, v2.w holds width, and
up.w holds the stiffness coefficient.
Without physics calculations in the compute shader, grass is rendered as straight blades after tesselation and fragment shading.
Gravity is added in the usual way: gE = vec3(0, -9.8, 0)
We determine the contribution of gravity with respect to the front facing direction of the blade, f,
as a term called the "front gravity". Front gravity is computed as gF = (1/4) * ||gE|| * f.
The total gravity on the grass blade is g = gE + gF.
Recovery corresponds to the counter-force that brings our grass blade back into equilibrium. This is derived in the paper using Hooke's law.
In order to determine the recovery force, we compare the current position of v2 to its original position before the
simulation started, iv2. At the beginning of our simulation, v1 and v2 are initialized to be a distance of the blade height along the up vector.
Once we have iv2, we can compute the recovery forces as r = (iv2 - v2) * stiffness.
Wind is calculated as vec(v_x, 0, v_z) * sin(time) (a sinusoidal force along a particular wind direction).
Total force is calculated as tF = (gravity + recovery + wind) * deltaTime.
I followed section 5.2 of the paper referenced to determine the corrected final positions for v1 and v2 (these corrections ensure that grass doesn't pass below ground plane and maintains a proper length).
- Orientation: Blades whose normals are perpendicular to the look vector are culled. We can't see these!
- Frustum: Blades that are outside the viewing frustum are culled.
- Distance: Blades that are sufficiently far from the camera (less than a pixel in size) are culled. Additionally, more blades are culled as function of distance from the camera (blades are binned into discrete intervals).
We see in the below graph that as the number of grass blades increases, our performance (in FPS) decreases (Note: x-axis is log scale).
As expected, each successive culling operation improves performance. We see the biggest performance boost with orientation culling.




