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
78 changes: 73 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,78 @@ Vulkan Grass Rendering

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

* (TODO) YOUR NAME HERE
* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab)
* Wayne Wu
* [LinkedIn](https://www.linkedin.com/in/wayne-wu/), [Personal Website](https://www.wuwayne.com/)
* Tested on: Windows 10, AMD Ryzen 5 5600X @ 3.70GHz 32GB, RTX 3070 8GB (personal)

### (TODO: Your README)
## Background

*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.
This project implements the simulation and rendering of grass in real-time using Vulkan based on the paper: [Responsive Real-Time Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf).

https://user-images.githubusercontent.com/77313916/139769346-15b2f672-7666-4ccd-a473-662d80589295.mp4


The three main components of the project are the following:
1. **Simulation**: Apply forces to the grass to provide dynamics.
2. **Culling**: Removing visually unimportant grass blades to improve performance.
3. **Rendering**: Tessellation and shading of the grass.

## Simulation
Three types of natural forces are simulated per blade as part of the physical model.
The simulation is performed in the compute shader.

### Gravity
Gravity is the first type of force applied. In addition to the normal downward gravity force, an artificial gravity force is also added that pushes the blade towards the face-forward direction in order to get the bending affect. However, since gravity is the only force applied at the moment, all the blades will just fall to the ground as shown below.

![](img/gravity.gif)

### Recovery
Once the recovery force is applied, which in essence models the grass blades as mass-spring systems, the blades will be kept from falling to the ground. This produces a more realistic grass-look as below:

![](img/recovery.gif)

### Wind
The wind force can be arbitrarily generated to add liveliness to the grass. The image is based on the following function:

```wind = windStrength * sin(totalTime) * vec3(1,0,1) * noise(v0)```

Adding all the forces together, we get the final result below:

![](img/wind.gif)

## Culling
To reduce the number of blades that need to be rendered, three culling operations are performed in the compute shader.

### Orientation
Orientation culling removes blades that have a width direction parallel to the view direction. In the image below, we can see that as the camera is oriented, some blades disappear at a specific viewing angle. (Try focusing on one specific blade to see the effect).

![](img/orientation.gif)

### View-Frustum
View-frustum culling removes blades that are outside of the view frustum. Different from the paper, instead of adding a small tolerance value, a weight factor is used instead to shrink or expand the clip space. For example, the image below has a weight factor of `0.8`. Additionally, no bound-test is performed for the z component in clip space.

![](img/frustum.gif)

### Distance
Finally, the distance culling operation removes blades based on the distance of the blade from the camera (projected on to the ground plane). The blade distance is discretized into a distance level bucket, in which a certain percentage of blades will be culled. The further the distance level is from the camera, the more blades will be culled.

![](img/distance.gif)

## Rendering
The grass is rendered using its own graphics pipeline in Vulkan, on top of the pipeline that renders the ground with texture. Each blade data is processed as a vertex by the Vertex Shader, and the data (e.g. v0, v1 etc.) is passed directly from the Tessellation Control Shader to the Tessellation Evaluation Shader, in which the actual curve interpolation is performed. The tessellation level is set to 10 arbitrarily for sufficient visual quality. Finally, a simple shading model is applied in the Fragment Shader for ambient and diffuse lighting effects.

## Performance Analysis

### Number of Blades
The implementation was tested at different number of blades with and without culling operations. As expected, we see that as the number of blades increases (by a factor of 2), the FPS decreases accordingly.
The maximum number of blades tested was 2e24 with culling operations and 2e20 without culling operations. Any number above those will produce unstable results.

![](img/bladesperformance.png)

### Effects of Culling
To understand the effect of each culling operation, each operation is applied exclusively for performance measurement.
It is important to note that the result is dependent on the camera view and the parameters applied. For example, a camera view that puts everything inside the view-frustum will not benefit from the view-frustum culling. In this particular scene, the distance culling has the greatest performance boost.

Test Scene | Result
:-------------------------:|:-------------------------:
![](img/cullingtestscene.png) | ![](img/cullingperformance.png)
Binary file added img/bladesperformance.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/cullingperformance.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/cullingtestscene.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/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/frustum.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/recording.mp4
Binary file not shown.
Binary file added img/recovery.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.
7 changes: 5 additions & 2 deletions src/Blades.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ 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);
// NOTE: VK_BUFFER_USAGE_STORAGE_BUFFER_BIT specifies that the buffer can be used in a VkDescriptorBufferInfo suitable
// for occupying a VkDescriptorSet slot either of type VK_DESCRIPTOR_TYPE_STORAGE_BUFFER or VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC.

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 << 17;
constexpr static float MIN_HEIGHT = 1.3f;
constexpr static float MAX_HEIGHT = 2.5f;
constexpr static float MIN_WIDTH = 0.1f;
Expand Down
2 changes: 1 addition & 1 deletion src/Camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Camera::Camera(Device* device, float aspectRatio) : device(device) {
r = 10.0f;
theta = 0.0f;
phi = 0.0f;
cameraBufferObject.viewMatrix = glm::lookAt(glm::vec3(0.0f, 1.0f, 10.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
cameraBufferObject.viewMatrix = glm::lookAt(glm::vec3(0.0f, 5.0f, 10.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
cameraBufferObject.projectionMatrix = glm::perspective(glm::radians(45.0f), aspectRatio, 0.1f, 100.0f);
cameraBufferObject.projectionMatrix[1][1] *= -1; // y-coordinate is flipped

Expand Down
1 change: 1 addition & 0 deletions src/Instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ Device* Instance::CreateDevice(QueueFlagBits requiredQueues, VkPhysicalDeviceFea
throw std::runtime_error("Failed to create logical device");
}

// NOTE: Retrieving the queue handles for each queue family. Will use the queues to submit graphics commands
Device::Queues queues;
for (unsigned int i = 0; i < requiredQueues.size(); ++i) {
if (requiredQueues[i]) {
Expand Down
Loading