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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 3.5)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")

project(cis565_project5_vulkan_grass_rendering)
Expand Down
41 changes: 36 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,41 @@ 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)
* Qirui (Chiray) Fu
* [personal website](https://qiruifu.github.io/)
* Tested on my own laptop: Windows 11, i5-13500HX @ 2.5GHz 16GB, GTX 4060 8GB

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

*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.
<img src="/img/screenshot.png">

<img src="/img/screen-gif.gif">

### Description

In this project, I implement a tool to render and simulate behaviours of grasses in Vulkan. There are three parts in this project: rendering, simulating forces and culling. The grass blades are represented by Bezier curves and all forces are considered on three control points for blades. Furthermore, we can optimize the performance using some culling techniques.

### Technique Details

#### Forces

We have 3 kinds of forces to consider in this project: gravity, recovery and wind. They are not really physics based, we just apply the effects of them on the positions of control points. For example, the gravity has two parts: environmental gravity and front gravity. It's not physicallly precise but we can simulate the behaviours of curved blades.

#### Culling

We have 3 ways for culling so we don't need to render all grasses, which can improve the performance a lot. The first one is intuitive: our view is a frustum, all blades out of this view-frustum should not be rendered. The second is orientation culling, which means the blades who are parallal with view vector are not visible because they don't have width. The last one is if a blade is too far from the camera, we are not supposed to render it. We will discuss later how these methods can improve the performance.

### Performance Analysis
#### Number of Blades

This figure is generated with all culling methods on:
<img src="/img/p1.jpg">

The FPS decreases exponentially as the number of grass blades increases. I believe that the computational complexity is roughly `O(N)` or slightly higher. From $2^{13}$ to $2^{20}$, the number of blades increases by a factor of 128, while FPS drops from 6000 to 40 — about a 150 times decrease. This suggests that the main performance bottleneck scales linearly with the number of blades.

#### Culling Methods

The influence of 3 culling methods are shown here (# of blades: $2^{15}$):
<img src="/img/p2.jpg">

We can tell that distance culling has the most significant influence on the performance. However, it will also lead to some obvious artifacts: some grasses disapper when you move the camera. View-frustum culling doesn't improve FPS a lot, I believe the reason is most grasses are located in the frustum. If we build a bigger scene with more objects, this method is expected to have better performance.
Binary file modified bin/Release/vulkan_grass_rendering.exe
Binary file not shown.
12 changes: 1 addition & 11 deletions external/GLFW/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 3.5)

project(GLFW C)

set(CMAKE_LEGACY_CYGWIN_WIN32 OFF)

if (NOT CMAKE_VERSION VERSION_LESS "3.0")
# Until all major package systems have moved to CMake 3,
# we stick with the older INSTALL_NAME_DIR mechanism
cmake_policy(SET CMP0042 OLD)
endif()

if (NOT CMAKE_VERSION VERSION_LESS "3.1")
cmake_policy(SET CMP0054 NEW)
endif()

set(GLFW_VERSION_MAJOR "3")
set(GLFW_VERSION_MINOR "3")
set(GLFW_VERSION_PATCH "0")
Expand Down
Binary file added img/p1.jpg
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/p2.jpg
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/screen-gif.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/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 << 15;
constexpr static float MIN_HEIGHT = 1.3f;
constexpr static float MAX_HEIGHT = 2.5f;
constexpr static float MIN_WIDTH = 0.1f;
Expand Down
145 changes: 141 additions & 4 deletions src/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,37 @@ void Renderer::CreateComputeDescriptorSetLayout() {
// TODO: Create the descriptor set layout for the compute pipeline
// Remember this is like a class definition stating why types of information
// will be stored at each binding
VkDescriptorSetLayoutBinding bladesLayoutBinding = {};
bladesLayoutBinding.binding = 0;
bladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bladesLayoutBinding.descriptorCount = 1;
bladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
bladesLayoutBinding.pImmutableSamplers = nullptr;

VkDescriptorSetLayoutBinding culledBladesLayoutBinding = {};
culledBladesLayoutBinding.binding = 1;
culledBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
culledBladesLayoutBinding.descriptorCount = 1;
culledBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
culledBladesLayoutBinding.pImmutableSamplers = nullptr;

VkDescriptorSetLayoutBinding bladesInfoLayoutBinding = {};
bladesInfoLayoutBinding.binding = 2;
bladesInfoLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bladesInfoLayoutBinding.descriptorCount = 1;
bladesInfoLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
bladesInfoLayoutBinding.pImmutableSamplers = nullptr;

std::vector<VkDescriptorSetLayoutBinding> bindings = { bladesLayoutBinding, culledBladesLayoutBinding, bladesInfoLayoutBinding };

VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
layoutInfo.pBindings = bindings.data();

if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) {
throw std::runtime_error("Failed to create descriptor set layout");
}
}

void Renderer::CreateDescriptorPool() {
Expand All @@ -216,6 +247,7 @@ void Renderer::CreateDescriptorPool() {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 },

// TODO: Add any additional types and counts of descriptors you will need to allocate
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast<uint32_t>(3 * scene->GetBlades().size()) }
};

VkDescriptorPoolCreateInfo poolInfo = {};
Expand Down Expand Up @@ -320,6 +352,41 @@ void Renderer::CreateModelDescriptorSets() {
void Renderer::CreateGrassDescriptorSets() {
// TODO: Create Descriptor sets for the grass.
// This should involve creating descriptor sets which point to the model matrix of each group of grass blades

int cnt_blade = scene->GetBlades().size();
bladeDescriptorSets.resize(cnt_blade);

VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout };
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(bladeDescriptorSets.size());
allocInfo.pSetLayouts = layouts;

if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, bladeDescriptorSets.data()) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate descriptor set");
}

std::vector<VkWriteDescriptorSet> descriptorWrites(bladeDescriptorSets.size());

for (uint32_t i = 0; i < cnt_blade; i++) {
VkDescriptorBufferInfo modelBufferInfo = {};
modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer();
modelBufferInfo.offset = 0;
modelBufferInfo.range = sizeof(ModelBufferObject);

descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[i].dstSet = bladeDescriptorSets[i];
descriptorWrites[i].dstBinding = 0;
descriptorWrites[i].dstArrayElement = 0;
descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[i].descriptorCount = 1;
descriptorWrites[i].pBufferInfo = &modelBufferInfo;
descriptorWrites[i].pImageInfo = nullptr;
descriptorWrites[i].pTexelBufferView = nullptr;
}

vkUpdateDescriptorSets(logicalDevice, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}

void Renderer::CreateTimeDescriptorSet() {
Expand Down Expand Up @@ -360,6 +427,70 @@ void Renderer::CreateTimeDescriptorSet() {
void Renderer::CreateComputeDescriptorSets() {
// TODO: Create Descriptor sets for the compute pipeline
// The descriptors should point to Storage buffers which will hold the grass blades, the culled grass blades, and the output number of grass blades
int cnt_blade = scene->GetBlades().size();
computeDescriptorSets.resize(cnt_blade);

VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout };
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(computeDescriptorSets.size());
allocInfo.pSetLayouts = layouts;

if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate compute descriptor set");
}

std::vector<VkWriteDescriptorSet> descriptorWrites(computeDescriptorSets.size() * 3);

for (uint32_t i = 0; i < cnt_blade; ++i) {
VkDescriptorBufferInfo bladesBufferInfo = {};
bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer();
bladesBufferInfo.offset = 0;
bladesBufferInfo.range = NUM_BLADES * sizeof(Blade);

VkDescriptorBufferInfo culledBladesBufferInfo = {};
culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer();
culledBladesBufferInfo.offset = 0;
culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade);

VkDescriptorBufferInfo numBladesBufferInfo = {};
numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer();
numBladesBufferInfo.offset = 0;
numBladesBufferInfo.range = sizeof(BladeDrawIndirect);

descriptorWrites[3 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[3 * i + 0].dstSet = computeDescriptorSets[i];
descriptorWrites[3 * i + 0].dstBinding = 0;
descriptorWrites[3 * i + 0].dstArrayElement = 0;
descriptorWrites[3 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrites[3 * i + 0].descriptorCount = 1;
descriptorWrites[3 * i + 0].pBufferInfo = &bladesBufferInfo;
descriptorWrites[3 * i + 0].pImageInfo = nullptr;
descriptorWrites[3 * i + 0].pTexelBufferView = nullptr;

descriptorWrites[3 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[3 * i + 1].dstSet = computeDescriptorSets[i];
descriptorWrites[3 * i + 1].dstBinding = 1;
descriptorWrites[3 * i + 1].dstArrayElement = 0;
descriptorWrites[3 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrites[3 * i + 1].descriptorCount = 1;
descriptorWrites[3 * i + 1].pBufferInfo = &culledBladesBufferInfo;
descriptorWrites[3 * i + 1].pImageInfo = nullptr;
descriptorWrites[3 * i + 1].pTexelBufferView = nullptr;

descriptorWrites[3 * i + 2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[3 * i + 2].dstSet = computeDescriptorSets[i];
descriptorWrites[3 * i + 2].dstBinding = 2;
descriptorWrites[3 * i + 2].dstArrayElement = 0;
descriptorWrites[3 * i + 2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrites[3 * i + 2].descriptorCount = 1;
descriptorWrites[3 * i + 2].pBufferInfo = &numBladesBufferInfo;
descriptorWrites[3 * i + 2].pImageInfo = nullptr;
descriptorWrites[3 * i + 2].pTexelBufferView = nullptr;
}

vkUpdateDescriptorSets(logicalDevice, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}

void Renderer::CreateGraphicsPipeline() {
Expand Down Expand Up @@ -717,7 +848,7 @@ void Renderer::CreateComputePipeline() {
computeShaderStageInfo.pName = "main";

// TODO: Add the compute dsecriptor set layout you create to this list
std::vector<VkDescriptorSetLayout> descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout };
std::vector<VkDescriptorSetLayout> descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout };

// Create pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
Expand Down Expand Up @@ -884,6 +1015,10 @@ void Renderer::RecordComputeCommandBuffer() {
vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 1, 1, &timeDescriptorSet, 0, nullptr);

// TODO: For each group of blades bind its descriptor set and dispatch
for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) {
vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr);
vkCmdDispatch(computeCommandBuffer, (NUM_BLADES / WORKGROUP_SIZE), 1, 1);
}

// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
Expand Down Expand Up @@ -976,13 +1111,14 @@ void Renderer::RecordCommandBuffers() {
VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() };
VkDeviceSize offsets[] = { 0 };
// TODO: Uncomment this when the buffers are populated
// vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);
vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);

// TODO: Bind the descriptor set for each grass blades model
vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &bladeDescriptorSets[j], 0, nullptr);

// Draw
// TODO: Uncomment this when the buffers are populated
// vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect));
vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect));
}

// End render pass
Expand Down Expand Up @@ -1057,11 +1193,12 @@ Renderer::~Renderer() {
vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr);

vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr);

vkDestroyRenderPass(logicalDevice, renderPass, nullptr);
DestroyFrameResources();
vkDestroyCommandPool(logicalDevice, computeCommandPool, nullptr);
vkDestroyCommandPool(logicalDevice, graphicsCommandPool, nullptr);
}
}
3 changes: 3 additions & 0 deletions src/Renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,15 @@ class Renderer {
VkDescriptorSetLayout cameraDescriptorSetLayout;
VkDescriptorSetLayout modelDescriptorSetLayout;
VkDescriptorSetLayout timeDescriptorSetLayout;
VkDescriptorSetLayout computeDescriptorSetLayout;

VkDescriptorPool descriptorPool;

VkDescriptorSet cameraDescriptorSet;
std::vector<VkDescriptorSet> modelDescriptorSets;
VkDescriptorSet timeDescriptorSet;
std::vector<VkDescriptorSet> bladeDescriptorSets;
std::vector<VkDescriptorSet> computeDescriptorSets;

VkPipelineLayout graphicsPipelineLayout;
VkPipelineLayout grassPipelineLayout;
Expand Down
32 changes: 32 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,40 @@ int main() {
glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback);


// The code for printing fps is from :
// https://github.com/TianhongZhou/Project5-Vulkan-Grass-Rendering/blob/main/src/main.cpp

double lastTitleUpdate = glfwGetTime();
double lastFrameTime = lastTitleUpdate;
int frames = 0;
double smoothMs = -1.0;

while (!ShouldQuit()) {
glfwPollEvents();

double now = glfwGetTime();
double dt = now - lastFrameTime;
lastFrameTime = now;
frames++;

double ms = dt * 1000.0;
smoothMs = (smoothMs < 0.0) ? ms : (0.9 * smoothMs + 0.1 * ms);

if (now - lastTitleUpdate >= 1.0) {
double sec = now - lastTitleUpdate;
double fps = frames / sec;

char title[160];
std::snprintf(title, sizeof(title),
"%s %.1f FPS (%.2f ms)",
applicationName, fps, smoothMs);
glfwSetWindowTitle(GetGLFWWindow(), title);

frames = 0;
lastTitleUpdate = now;
}

scene->UpdateTime();
renderer->Frame();
}
Expand Down
Loading