Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project 5: Anthony Mansur #8

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build*
85 changes: 80 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,87 @@
Vulkan Grass Rendering
==================================

<img src="img\final.gif" height="500px"/>

**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)
* Anthony Mansur
- https://www.linkedin.com/in/anthony-mansur-ab3719125/

- Tested on: Windows 10, AMD Ryzen 5 3600, Geforce RTX 2060 Super (personal)

### Introduction

For this project, we use [Vulkan](https://www.vulkan.org/) to implement a grass simulator and renderer based on the research paper, [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) . We use compute shaders to perform physics calculations on Bezier curves that represent individual grass blades in our application. Since rendering every grass blade on every frame is fairly inefficient, we also use compute shaders to cull grass blades that do not contribute to a given frame. The remaining blades are then passed to a graphics pipeline. In this pipeline, we have a vertex shader that transforms our Bezier control points, tessellation shaders to dynamically create the grass geometry from the Bezier curves, and a fragment shader to shade the grass blades.

### Grass Model

<img src="img\blade_model.jpg" height="300px"/>

To render the grass, we take in as input several control points for every grass blade. These control points are:

- `v0`: the position of the grass blade on the geometry
- `v1`: a Bezier curve guide that is always "above" v0 with respect to the grass blade's up vector
- `v2`: a physical guide for which we simulate forces on
- `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at v0

These control points are four-dimensional vectors, with the fourth coordinate representing per-blade characteristics that helps us simulate and tessellate our grass blades correctly:

- **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 (used in physics simulation)

### Rendering

<img src="img\grass-types.png" height="200px"/>

Once we have passed the control points for every grass blade, we can render four different types of grass blades, as described in the research paper. As described in the introduction, our pipeline includes the tessellation shaders to render these blades as quadratic bezier curves. The result of our render is shown below:

<img src="img\no-force.gif" height="450px"/>



### Simulation

We first simulate the grass blades and then render it every frame, using the time difference between every frame as our time step for the physics simulation. The forces are applied to the control point `v2`, with the added constraint that the length of the grass blade remains constant.

The three types of forces the simulation computes are:

- **Gravity:** both the environmental and the "front" gravity
- **Recovery:** the counter-force that brings our grass blade back into equilibrium (using Hooke's law)
- **Wind:** primitive wind function that has a direction and strength and varies with respect to time and the position of the grass blade.

| Gravity + Recovery | Gravity + Recovery + Wind |
| :-----------------------------------------: | :---------------------------------------------: |
| <img src="img\no-wind.gif" height="300px"/> | <img src="img\strong-wind.gif" height="300px"/> |



### Performance Analysis

We use several culling techniques to reduce the amount of grass blades we simulate and render for performance reasons. The three different types of tests we perform are:

- **Orientation**: To minimize anti-aliasing effects, we eliminate grass blades when their front-facing direction is nearly perpendicular to the view vector
- **View-frustum**: Remove any grass blades that isn't inside our viewing frustum
- **Distance**: Reduce the number of grass blades as a function of distance from the camera



To see these effects in extreme, see the following gifs:

| Orientation test | View-Frustum test | Distance test |
| :-----------------------------------------------------: | :-------------------------------------------------: | :--------------------------------------------------: |
| <img src="img\orientation-culling.gif" height="250px"/> | <img src="img\frustum-culling.gif" height="250px"/> | <img src="img\distance-culling.gif" height="250px"/> |



We then see how much our frames per second changes as we add these culling techniques and add more blades. To perform this test, we make the plane width 100 by 100, and we change the camera position to be 20 units from the origin, with a phi angle of -35 degrees.



<img src="img\performance.png" height="400px"/>


### (TODO: Your 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.
As we can see from the performance analysis, each culling technique improves our frames per second, with the view-frustum test being the most impactful and the orientation test being the least, as expected. This trend follows as we increase the number of blades.
Binary file added bin/Debug/vulkan_grass_rendering.exe
Binary file not shown.
Binary file added bin/Debug/vulkan_grass_rendering.pdb
Binary file not shown.
Binary file modified bin/Release/vulkan_grass_rendering.exe
Binary file not shown.
Binary file added img/distance-culling.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/final.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-culling.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/grass-types.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/no-force.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/no-force.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/no-wind.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-culling.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/performance.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/strong-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_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_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 << 13; // default is 1 << 13
constexpr static float MIN_HEIGHT = 1.3f;
constexpr static float MAX_HEIGHT = 2.5f;
constexpr static float MIN_WIDTH = 0.1f;
Expand Down
163 changes: 157 additions & 6 deletions src/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* c
CreateCameraDescriptorSetLayout();
CreateModelDescriptorSetLayout();
CreateTimeDescriptorSetLayout();
CreateGrassDescriptorSetLayout();
CreateComputeDescriptorSetLayout();
CreateDescriptorPool();
CreateCameraDescriptorSet();
Expand Down Expand Up @@ -194,10 +195,63 @@ void Renderer::CreateTimeDescriptorSetLayout() {
}
}

void Renderer::CreateGrassDescriptorSetLayout() {
VkDescriptorSetLayoutBinding uboLayoutBinding = {};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.descriptorCount = 1;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
uboLayoutBinding.pImmutableSamplers = nullptr;

std::vector<VkDescriptorSetLayoutBinding> bindings = { uboLayoutBinding };

// Create the descriptor set layout
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, &grassDescriptorSetLayout) != VK_SUCCESS) {
throw std::runtime_error("Failed to create descriptor set layout");
}
}

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 inputBladesLayoutBinding = {};
inputBladesLayoutBinding.binding = 0;
inputBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
inputBladesLayoutBinding.descriptorCount = 1;
inputBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
inputBladesLayoutBinding.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 numOfBladesLayoutBinding = {};
numOfBladesLayoutBinding.binding = 2;
numOfBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
numOfBladesLayoutBinding.descriptorCount = 1;
numOfBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
numOfBladesLayoutBinding.pImmutableSamplers = nullptr;

std::vector<VkDescriptorSetLayoutBinding> bindings = { inputBladesLayoutBinding, culledBladesLayoutBinding, numOfBladesLayoutBinding };

// Create the descriptor set layout
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 +270,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 +375,44 @@ 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

// Update descriptor sets
grassDescriptorSets.resize(scene->GetBlades().size());

// Describe the desciptor set
VkDescriptorSetLayout layouts[] = { grassDescriptorSetLayout };
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(grassDescriptorSets.size());
allocInfo.pSetLayouts = layouts;

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

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

for (uint32_t i = 0; i < scene->GetBlades().size(); ++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 = grassDescriptorSets[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;
}

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

void Renderer::CreateTimeDescriptorSet() {
Expand Down Expand Up @@ -360,6 +453,57 @@ 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

computeDescriptorSets.resize(scene->GetBlades().size());

// Describe the desciptor set
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;

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

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

for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) {
VkDescriptorBufferInfo inputBladesBufferInfo = {};
inputBladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer();
inputBladesBufferInfo.offset = 0;
inputBladesBufferInfo.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);;

for (uint32_t j = 0; j < 3; j++)
{
descriptorWrites[i * 3 + j].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[i * 3 + j].dstSet = computeDescriptorSets[i];
descriptorWrites[i * 3 + j].dstBinding = j;
descriptorWrites[i * 3 + j].dstArrayElement = 0;
descriptorWrites[i * 3 + j].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrites[i * 3 + j].descriptorCount = 1;
descriptorWrites[i * 3 + j].pBufferInfo =
((j == 0) ? &inputBladesBufferInfo : (j == 1) ? &culledBladesBufferInfo : &numBladesBufferInfo);
descriptorWrites[i * 3 + j].pImageInfo = nullptr;
descriptorWrites[i * 3 + j].pTexelBufferView = nullptr;
}
}

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

void Renderer::CreateGraphicsPipeline() {
Expand Down Expand Up @@ -654,7 +798,7 @@ void Renderer::CreateGrassPipeline() {
colorBlending.blendConstants[2] = 0.0f;
colorBlending.blendConstants[3] = 0.0f;

std::vector<VkDescriptorSetLayout> descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout };
std::vector<VkDescriptorSetLayout> descriptorSetLayouts = { cameraDescriptorSetLayout, grassDescriptorSetLayout };

// Pipeline layout: used to specify uniform values
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
Expand Down Expand Up @@ -716,8 +860,7 @@ void Renderer::CreateComputePipeline() {
computeShaderStageInfo.module = computeShaderModule;
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 @@ -883,7 +1026,12 @@ void Renderer::RecordComputeCommandBuffer() {
// Bind descriptor set for time uniforms
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
// Bind each compute descriptor set and dispatch
for (int i = 0; i < computeDescriptorSets.size(); i++)
{
vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr);
vkCmdDispatch(computeCommandBuffer, ceil(NUM_BLADES / WORKGROUP_SIZE), 1, 1);
}

// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
Expand Down Expand Up @@ -976,13 +1124,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, &grassDescriptorSets[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,6 +1206,8 @@ Renderer::~Renderer() {
vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, grassDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr);

vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr);

Expand Down
Loading