Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
560 changes: 560 additions & 0 deletions .gitignore

Large diffs are not rendered by default.

95 changes: 89 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,95 @@
Vulkan Grass Rendering
==================================

**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5**
**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 4**

* (TODO) YOUR NAME HERE
* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab)
* Haoquan Liang
* [LinkedIn](https://www.linkedin.com/in/leohaoquanliang/)
* Tested on: Windows 10, Ryzen 7 5800X 8 Core 3.80 GHz, NVIDIA GeForce RTX 3080 Ti 12 GB

### (TODO: Your README)
# Overview
This project is a grass simulator and renderer using Vulkan API. It is based on [Responsive Real-Time Grass Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf), which allows us to render fields of grass of arbitrary shapes and spatial alignment efficiently and realistically.
Outside learning grass simulation, one of the main goal of this project is to about the classical rendering pipeline of Vulkan (vertex->tesselation control->tesselation->fragment->compute) and how tesselation and level of details works.
![demo](img/demo.gif)

*DO NOT* leave the README to the last minute! It is a crucial part of the
project, and we will not be able to grade you without a good README.
# Table of Contents
* [Features](#features)
* [Force Simulation](#simulation)
* [Blades Culling](#culling)
* [Tessellation with Levels of Details (Extra Credit)](#lod)
* [Performance Analysis](#performance)
* [Reference](#reference)

# <a name="features"> Features</a>
## <a name="simulation">Force Simulation</a>
We represent the grass blade Bezier curves, where `v0` is the position of the grass blade on the geomtry, `v1` is a Bezier curve guide that is always "above" `v0` with respect to the grass blade's up vector, and `v2` is a physical guide for which we simulate forces on.
We simulate forces on grass blades while they are still Bezier curves. We apply the transformations to `v2`initially and correct for potential errors. Then, we update `v1` to maintain the appropriate length of our grass blade.
![](img/blade_model.jpg)

### Gravity Force
Given a gravity direction, `D.xyz`, and the magnitude of acceleration, `D.w`, we can compute the environmental gravity in our scene as `gE = normalize(D.xyz) * D.w`.
We then determine the contribution of the 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`.
We can then determine the total gravity on the grass blade as `g = gE + gF`.
**As shown in the following image, when a small gravity force is applied, the grass will slowly transform to touch the ground.**

![gravity](img/gravity.gif)

### Recovery Force
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 need to compare the current position of `v2` to its original position before 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`.
**As shown in the following image, when both the gravity and recovery force are applied, the grass will have the tips facing down, but it will reach a stasis (stiff) state.**

![recovery](img/stiff.png)

### Wind Force
The wind can have any direction, to make it more interesting, I made its direction `vec3(cos(totalTime * 2), 0.f, fbm1D(totalTime))` so that the grass will sway in the left-right direction, and have some random small movements on the forward direction.
Additionally, the wind has a larger impact on grass blades whose forward directions are parallel to the wind direction, which is referred to as `windAlignment` term.
With a wind direction and a wind alignment term, the total wind force (`w`) will be `windDirection * windAlignment`.
**As shown in the following image, with only wind force applied, the grass sway left and right, with some small random movements forward.**
![wind](img/wind.gif)

## <a name="culling">Blades Culling</a>
### Orientation Culling
Consider the scenario in which the front face direction of the grass blade is perpendicular to the view vector. Since our grass blades won't have width, we will end up trying to render parts of the grass that are actually smaller than the size of a pixel. This could lead to aliasing artifacts.
To remedy this, we can cull these blades! Simply do a dot product test to see if the view vector and front face direction of the blade are perpendicular. The cull threshold used is `0.9`.
**As shown in the following image, only the blades that faces the camera will be rendered.**
![orientation](img/orientation.gif)
### View-Frustum Culling
We also want to cull blades that are outside of the view-frustum, considering they won't show up in the frame anyway. To determine if a grass blade is in the view-frustum, we want to compare the visibility of three points: `v0, v2, and m`, where `m = (1/4)v0 * (1/2)v1 * (1/4)v2`.
If all three points are outside of the view-frustum, we will cull the grass blade.
**As shown in the following image, only the grass blades within the view-frustum will be rendered.**
![viewfrustum](img/viewfrustum.gif)
### Distance Culling
Similarly to orientation culling, we can end up with grass blades that at large distances are smaller than the size of a pixel. This could lead to additional artifacts in our renders. In this case, we can cull grass blades as a function of their distance from the camera.
The maximum distance used is `30` and the number of buckets to place grass blades between the camera is `10`.
**As shown in the following image, only the grass blades within the distance will be rendered.***
![distance](img/distance.gif)

### <a name="lod">Dynamic Tesselation - Levels of Details</a>
If the grass blade is very far from the camera, there is no need to generate a high tesselation level for it. We can vary the levels of details as a function of how far the grass blade is from the camera to further optimize the performance.
**As shown in the following image (rendered in wireframe mode), the blades closer to the camera have more polygons than the blades further away.**
![lod](img/LOD.png)

# <a name="performance">Performance Analysis</a>
### Varying Number of Grass Blades
**How the renderer handles varying numbers of grass blades**
As my data shows, the performance (measured in frame rate) is linearly proportional to the number of grass blades. Everytime the number of grass blades doubles, the frame rate is roughly halved.
The following chart is generated with a constant tesselation level of 20 for each blade. Since the camera position will also affect how many grass blades will be rendered, all the tests used the default camera position.
![c1](img/chart1.png)

### Improvements with Culling
**The improvements by culling using each of the three culling tests**
The view-frustum culling gives a small performance boost, which is as expected, since only a small number of blades on the edge of the image will be culled.
The distance culling gives a better performance boost. However, this result varies on the maximum distance used (30 in this case). With a smaller max distance, more blades will be culled and a better performance is expected (at the cost of possibly worse visual result).
The dynamic tesselation level doubles the performance! This makes sense, as the grass blades further away only have 1/2, 1/4, or even 1/10 of the polygons of the front blades. Since most grass blades on the back will be occuluded, and we can only see their parts, this method gives a huge performance boost at the cost of very little (non-perceivable) visual downgrade.
The orientation culling gives the most performance boost, as it quadruples the frame rate. This is also expected, since at least half of the blades will be facing left or right. Because we will most likely not see these blades any way, this is also a great method with hugh performance boost and very little visual performance cost.
With all the culling methods turned on, the frame rate is almost 12 times the original. The culling methods truly achive the performance boost purpose with small cost on the visual result.
![c2](img/chart2.png)

# <a name="reference">Refrence</a>
* [Responsive Real-Time Grass Grass Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf)
* [CIS565 Vulkan samples](https://github.com/CIS565-Fall-2017/Vulkan-Samples/tree/master/samples/5_helloTessellation)
* [Official Vulkan documentation](https://www.khronos.org/registry/vulkan/)
* [Vulkan tutorial](https://vulkan-tutorial.com/)
* [RenderDoc blog on Vulkan](https://renderdoc.org/vulkan-in-30-minutes.html)
* [Tessellation tutorial](https://ogldev.org/www/tutorial30/tutorial30.html)
Binary file modified bin/Release/vulkan_grass_rendering.exe
Binary file not shown.
Binary file added img/LOD.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/bug-fix-copy-n-paste-can-be-dangerous.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/bug.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/chart1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/chart2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/distance.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/gravity.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/orientation.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/stiff.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/viewfrustum.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/wind.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/Blades.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode
indirectDraw.firstVertex = 0;
indirectDraw.firstInstance = 0;

BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory);
BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory);
BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, bladesBuffer, bladesBufferMemory);
BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory);
BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Blades.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <array>
#include "Model.h"

constexpr static unsigned int NUM_BLADES = 1 << 13;
constexpr static unsigned int NUM_BLADES = 1 << 18;
constexpr static float MIN_HEIGHT = 1.3f;
constexpr static float MAX_HEIGHT = 2.5f;
constexpr static float MIN_WIDTH = 0.1f;
Expand Down
Loading