University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5
-
Alex Fu
Tested on: Windows 10, i7-10750H @ 2.60GHz, 16GB, GTX 3060 6GB
Rendering a large amount of grass that is subject to physical law at real-time speed. Reference: Responsive Real-Time Grass Rendering for General 3D Scenes
A blade can be represented as a Bezier curve with three points and two directions:
V0, V1, and V2 are the Bezier control points. The direction and up-vector, together with V0 can define the blade's world space position and orientation.
The initial representation of all blades is generated by the CPU. A blade is subjected to three forces: gravity, recovery, and wind. The compute shader will update the Bezier control points with certain laws and do the culling. Then tessellation shaders will generate the primitives based on the Bezier control points.
By editing the vector Wi
inside the compute shader we can define various wind fields:
65536 blades. The resolution is 640x480.
Click each embedded video to see how FPS varies as the camera moves around. After introducing several culling algorithms a huge improvement in performance can be seen.
Without optimization | With optimization |
---|---|
no_optimization.mp4 |
all_together.mp4 |
It is meaningless to draw blades that won't be captured by the camera. So we can cull the blades with camera frustum:
vec4 ndc = camera.proj * camera.view * vec4(v0, 1);
ndc /= (ndc.w+1.0);
if (ndc.x > 1.0 || ndc.x < -1.0 || ndc.y > 1.0 || ndc.y < -1.0 || ndc.z > 1.0 || ndc.z < -1) return;
frustum_culling.mp4
We can see FPS is increasing when the camera is zooming in.
Since our grass is represented by a quad thus no thickness, a blade is hard to see if its orientation is almost parallel to the camera's. So we can cull them:
vec3 camPos = vec3(camera.view[3][0], camera.view[3][1], camera.view[3][2]);
if (abs(dot(fwd, normalize(v0-camPos))) > 0.9) return;
However, this only brings slight improvement.
orientation_culling.mp4
We don't need to render all the grass when the camera is far away. So we can control the number of blades regarding distance:
float z = length(v0 - camPos);
int level;
if (z < 8.0)
level = 1;
else if (z < 16.0)
level = 2;
else if (z < 32.0)
level = 3;
else if (z < 64.0)
level = 4;
else
level = 5;
if ((idx+1) % level != 0) return;
This increase the FPS when camera zooms out. However, the improvement doesn't meet the expectation.
density_control.mp4
If a blade is far away it doesn't need a high tesselation level. So in the tesselation control shader we can adjust the tessellation level regarding distance:
int level;
if (z < 4.0)
level = 16;
else if (z < 8.0)
level = 12;
else if (z < 16.0)
level = 8;
else if (z < 32.0)
level = 4;
else
level = 2;
gl_TessLevelInner[0] = level;
gl_TessLevelInner[1] = level;
gl_TessLevelOuter[0] = level;
gl_TessLevelOuter[1] = level;
gl_TessLevelOuter[2] = level;
gl_TessLevelOuter[3] = level;
This significantly increases the FPS.