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
280 changes: 50 additions & 230 deletions README.md

Large diffs are not rendered by default.

Binary file added bin/Debug/vulkan_grass_rendering.exe
Binary file not shown.
Binary file added bin/Debug/vulkan_grass_rendering.ilk
Binary file not shown.
Binary file added bin/Debug/vulkan_grass_rendering.pdb
Binary file not shown.
Binary file added bin/Release/vulkan_grass_rendering.exe
Binary file not shown.
Binary file added img/FPS_vs_Culling.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/FPS_vs_Grass.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/grass_forces.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_straight.gif
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.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode
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::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
3 changes: 3 additions & 0 deletions src/Blades.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ class Blades : public Model {
VkDeviceMemory numBladesBufferMemory;

public:
VkDescriptorSet bladesDescriptorSet;
VkDescriptorSet grassDescriptorSet;

Blades(Device* device, VkCommandPool commandPool, float planeDim);
VkBuffer GetBladesBuffer() const;
VkBuffer GetCulledBladesBuffer() const;
Expand Down
154 changes: 150 additions & 4 deletions src/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "Blades.h"
#include "Camera.h"
#include "Image.h"
#include "BufferUtils.h"

static constexpr unsigned int WORKGROUP_SIZE = 32;

Expand Down Expand Up @@ -198,6 +199,38 @@ 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 allBlades = {};
allBlades.binding = 0;
allBlades.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
allBlades.descriptorCount = 1;
allBlades.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
allBlades.pImmutableSamplers = nullptr;

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

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

std::vector<VkDescriptorSetLayoutBinding> bindings = { allBlades, cullBlades, drawBlades };

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

void Renderer::CreateDescriptorPool() {
Expand All @@ -216,6 +249,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 , 3 },
};

VkDescriptorPoolCreateInfo poolInfo = {};
Expand Down Expand Up @@ -320,6 +354,43 @@ 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
// 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
VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout };
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = layouts;

auto& bladeGroups = scene->GetBlades();

for (auto& bladeGroup : bladeGroups) {
// Allocate descriptor sets
if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, &bladeGroup->grassDescriptorSet) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate descriptor set");
}

// Configure the descriptors to refer to buffers
VkDescriptorBufferInfo modelBufferInfo = {};
modelBufferInfo.buffer = bladeGroup->GetModelBuffer();
modelBufferInfo.offset = 0;
modelBufferInfo.range = sizeof(ModelBufferObject);

std::array<VkWriteDescriptorSet, 1> descriptorWrites = {};
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = bladeGroup->grassDescriptorSet;
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pBufferInfo = &modelBufferInfo;
descriptorWrites[0].pImageInfo = nullptr;
descriptorWrites[0].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 +431,71 @@ 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
VkDescriptorSetLayout layouts[] = { bladesDescriptorSetLayout };
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = layouts;

auto& bladeGroups = scene->GetBlades();

for (auto& bladeGroup : bladeGroups) {
// Allocate descriptor sets
if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, &bladeGroup->bladesDescriptorSet) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate descriptor set");
}

// Configure the descriptors to refer to buffers
VkDescriptorBufferInfo bladesBufferInfo = {};
bladesBufferInfo.buffer = bladeGroup->GetBladesBuffer();
bladesBufferInfo.offset = 0;
bladesBufferInfo.range = sizeof(Blade) * NUM_BLADES;

VkDescriptorBufferInfo culledBladesBufferInfo = {};
culledBladesBufferInfo.buffer = bladeGroup->GetCulledBladesBuffer();
culledBladesBufferInfo.offset = 0;
culledBladesBufferInfo.range = sizeof(Blade) * NUM_BLADES;

VkDescriptorBufferInfo drawBladesBufferInfo = {};
drawBladesBufferInfo.buffer = bladeGroup->GetNumBladesBuffer();
drawBladesBufferInfo.offset = 0;
drawBladesBufferInfo.range = sizeof(BladeDrawIndirect);

std::array<VkWriteDescriptorSet, 3> descriptorWrites = {};
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = bladeGroup->bladesDescriptorSet;
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pBufferInfo = &bladesBufferInfo;
descriptorWrites[0].pImageInfo = nullptr;
descriptorWrites[0].pTexelBufferView = nullptr;

descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = bladeGroup->bladesDescriptorSet;
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pBufferInfo = &culledBladesBufferInfo;
descriptorWrites[1].pImageInfo = nullptr;
descriptorWrites[1].pTexelBufferView = nullptr;

descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[2].dstSet = bladeGroup->bladesDescriptorSet;
descriptorWrites[2].dstBinding = 2;
descriptorWrites[2].dstArrayElement = 0;
descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrites[2].descriptorCount = 1;
descriptorWrites[2].pBufferInfo = &drawBladesBufferInfo;
descriptorWrites[2].pImageInfo = nullptr;
descriptorWrites[2].pTexelBufferView = nullptr;

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

void Renderer::CreateGraphicsPipeline() {
Expand Down Expand Up @@ -716,8 +852,8 @@ 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 };
// TODO: Add the compute descriptor set layout you create to this list
std::vector<VkDescriptorSetLayout> descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, bladesDescriptorSetLayout };

// Create pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
Expand Down Expand Up @@ -885,6 +1021,13 @@ void Renderer::RecordComputeCommandBuffer() {

// TODO: For each group of blades bind its descriptor set and dispatch

auto& bladeGroups = scene->GetBlades();

for (auto& bladeGroup : bladeGroups) {
vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &bladeGroup->bladesDescriptorSet, 0, nullptr);
vkCmdDispatch(computeCommandBuffer, (NUM_BLADES + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE, 1, 1);
}

// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
throw std::runtime_error("Failed to record compute command buffer");
Expand Down Expand Up @@ -976,13 +1119,16 @@ 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);

vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &scene->GetBlades()[j]->grassDescriptorSet, 0, nullptr);

// TODO: Bind the descriptor set for each grass blades model
//vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &cameraDescriptorSet, 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
1 change: 1 addition & 0 deletions src/Renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Renderer {
VkDescriptorSetLayout cameraDescriptorSetLayout;
VkDescriptorSetLayout modelDescriptorSetLayout;
VkDescriptorSetLayout timeDescriptorSetLayout;
VkDescriptorSetLayout bladesDescriptorSetLayout;

VkDescriptorPool descriptorPool;

Expand Down
11 changes: 11 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "Camera.h"
#include "Scene.h"
#include "Image.h"
#include <iostream>

Device* device;
SwapChain* swapChain;
Expand Down Expand Up @@ -143,12 +144,22 @@ int main() {
glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback);

float time_start = glfwGetTime();
int numFrames = 0;

while (!ShouldQuit()) {
glfwPollEvents();
scene->UpdateTime();
renderer->Frame();
numFrames++;
}

float time_end = glfwGetTime();
float avg_FPS = numFrames / float(time_end - time_start);
std::cout << avg_FPS;
std::cout << "Press any key to continue..." << std::endl;
std::cin.get();

vkDeviceWaitIdle(device->GetVkDevice());

vkDestroyImage(device->GetVkDevice(), grassImage, nullptr);
Expand Down
101 changes: 96 additions & 5 deletions src/shaders/compute.comp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ struct Blade {
vec4 up;
};

layout(set = 2, binding = 0) buffer AllBlades {
Blade blades[];
} allBlades;

layout(set = 2, binding = 1) buffer CulledBlades {
Blade blades[];
} culledBlades;

layout(set = 2, binding = 2) buffer NumBlades {
uint vertexCount; // Write the number of blades remaining here
uint instanceCount; // = 1
uint firstVertex; // = 0
uint firstInstance; // = 0
} numBlades;

// TODO: Add bindings to:
// 1. Store the input blades
// 2. Write out the culled blades
Expand All @@ -36,21 +51,97 @@ struct Blade {
// uint firstInstance; // = 0
// } numBlades;

bool inBounds(float value, float bounds) {
return (value >= -bounds) && (value <= bounds);
}

void main() {
// Reset the number of blades to 0
if (gl_GlobalInvocationID.x == 0) {
// numBlades.vertexCount = 0;
numBlades.vertexCount = 0;
}
barrier(); // Wait till all threads reach this point
uint threadIdx = gl_GlobalInvocationID.x;
Blade blade = allBlades.blades[threadIdx];

float height = blade.v1.w;
float width = blade.v2.w;
float k = blade.up.w;

vec3 v0 = blade.v0.xyz;
vec3 v1 = blade.v1.xyz;
vec3 v2 = blade.v2.xyz;
vec3 vup = blade.up.xyz;

// TODO: Apply forces on every blade and update the vertices in the buffer

// gravitational force (simplified)
vec3 gE = vec3(0.0, -9.8, 0.0); // y is down
vec3 f = normalize(cross(vup, vec3(sin(blade.v0.w), 0.0, cos(blade.v0.w)))); // perp to blade
vec3 gF = 0.5 * 9.8 * f;
vec3 gravityF = gE + gF;

// recovery force
vec3 Iv2 = v0 + (vup * height);
vec3 recoveryF = (Iv2 - v2) * k;

// wind (drag) force
vec3 wind_f = vec3(4.0, 4.0, 0.0) * sin(totalTime);
float f_ori = 1 - abs(dot(normalize(wind_f), normalize(v2 - v0)));
float f_exp = dot((v2 - v0), vup) / height; // exposure
vec3 windF = wind_f * f_ori * f_exp;

// update blade
v2 += (recoveryF + gravityF + windF) * deltaTime;

// validate updates
// make sure v2 is above the ground plane
v2 = v2 - vup * min(dot(vup, v2 - v0), 0.0);

// evaluating the position of v1
// first get the component of v2 - v0 projected onto the ground plane
//float l_proj = length(v2 - v0 - vup * dot(v2 - v0, vup));
//v1 = v0 + height * vup * max(1.0 - (l_proj / height), 0.05 * max(l_proj / height, 1.0));

// now ensure that the length of the bezier curve is not longer than the length of the blade
float n = 2.0;
float L0 = distance(v0, v2);
float L1 = distance(v0, v1) + distance(v1, v2);
float L = ((2.0 * L0) + (n - 1.0) * L1) / (n + 1.0);

float r = height / L;
vec3 v1_corr = v0 + r * (v1 - v0);
vec3 v2_corr = v1_corr + r * (v2 - v1);

allBlades.blades[threadIdx].v1.xyz = v1_corr;
allBlades.blades[threadIdx].v2.xyz = v2_corr;

// TODO: Cull blades that are too far away or not in the camera frustum and write them
// to the culled blades buffer
// Note: to do this, you will need to use an atomic operation to read and update numBlades.vertexCount
// You want to write the visible blades to the buffer without write conflicts between threads

// camera position
vec4 eye = inverse(camera.view) * vec4(0.0, 0.0, 0.0, 1.0);

// orientation culling
vec3 look = normalize(eye.xyz - v0);
vec3 perp_dir = f;
if(dot(look, perp_dir) > 0.9){
return;
}

// view-frustum culling
vec4 v0_prime = camera.proj * camera.view * vec4(v0, 1.0);
float tol = 2.5;
float q = v0_prime.w + 2.5;
if( v0_prime.x < -q || v0_prime.x > q ||
v0_prime.y < -q || v0_prime.y > q ||
v0_prime.z < -q || v0_prime.z > q) {
return;
}

// distance culling
float d_proj = length(v0 - eye.xyz - vup * dot(v0 - eye.xyz, vup));
if(mod(threadIdx, 15) > floor(15.0 * (1 - (d_proj / 75.0)))){
return;
}

culledBlades.blades[atomicAdd(numBlades.vertexCount, 1)] = allBlades.blades[threadIdx];
}
Loading