diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/README.md b/README.md index 20ee451..45209cd 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,97 @@ 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) +* Hanlin + * [LinkedIn](https://www.linkedin.com/in/hanlin-sun-7162941a5/) +* Tested on: Windows 11, i7-12700H @ 2.30GHz 32GB, NVIDIA RTX 3070Ti -### (TODO: Your README) +# Overview -*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. +![Overview](img/overview.gif) + +This project involved implementing a physical grass simulation rendered in Vulkan, based on the 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) by Jahrmann and Wimmer. There were three major components to this project, including rendering the grass, applying forces to control grass movement, and culling blades of grass to improve rendering frame rate when possible. + +# Features +## Grass Blade Structure +A model for the grass blades was presented in the paper, which uses Bezier curves to represent the shape of a blade. Bezier curves are also used to represent grass blades for rendering in this project. The model is replicated below: + +![](img/blade_model.jpg) + +Each Bezier curve has three control points: +* `v0`: the position of the grass blade on the geomtry +* `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 + +Additional information is passed into the compute shader. +* `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at `v0` +* Orientation: the orientation of the grass blade's face (as an angle offset from the x axis) +* 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 + +This data is packed into four `vec4`s, such that `v0.w` holds orientation, `v1.w` holds height, `v2.w` holds width, and `up.w` holds the stiffness coefficient. + +# Modeling Forces + +## Gravity +Gravity drags down the tips of the blades, making them move towards the ground. Alone, gravity would cause all the grass to fall, so a counter force is needed to balance them. This is discussed next. + +![Gravity](img/gravity.png) + +## Restorative +After applying gravity force, adding the restoring force pushes the blade of grass back up, allowing it to "bounce" back up when the gravity push the blade down. Depending on the stiffness of the blade, the recovery can be slow or fast.(Here only gravity applied) + +![Recover](img/recover.gif) + +## Wind +After applying gravity and restore force, adding simple wind force can make it swing from side to side.Wind force is based on the cosine (along x) and sin (along z) of the current time, which makes the grass blade move in a circular fashion. + +![Wind](img/wind.gif) + +# Optimization +Three optimizations were added to increase the frame rate. Some were more effective than others, as discussed in the performance analysis section. + +## Orientation Culling +If the viewing angle is perpendicular to the thin edge of the blade of grass, it will not be rendered. To view it's effect I suggest focusing on the singlr blade and check it's visibility when camera rotate. + +![Orientation Cull](img/orientationCull.gif) + +## Frustum Culling +If the grass blade world position is outside of the camera view frustum, then it will be culled. It is a little bit difficult to view that effect since the render pipeline will automatically cull the object outside the window(but if not using frustum culling, they will still be rendered and will cause performance lost). So I output the FPS value, by moving the camera forward you can see that the FPS become higher(Only frustum culling was opened), which means less blades was rendered. That result proves that blades outside the camera frustum was culled and this algorithm works. + +![Frustum Cull](img/FrustumCull.gif) + +## Distance Culling +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. + +![Distance Cull](img/DistanceCull.gif) + +# Performance Analysis +All tests below were performed with the camera situated above the plane, at an angle. `r = 10.0, theta = 0.f, phi = 0.0`. + +## Impact of Blades +As expected, increasing the number of blades decreases the frame rate. This is because more blades needs to be computed, and more blades to send through the rendering pipeline. Adding more than 65536 blades causes us to not be able to see any of the blades anyways, so figures are only helpful in seeing the FPS hit. The graph below shows how the FPS decreases exponentially as number of blades is increased. From the graph, we can see that enable all forms of culling gives us a nearly 2.5x boost in most cases. + +![NumBlades_FPS](img/NumBlade_FPS.png) + +## Optimization + +From these paragraphes we can see that the distance culling has the most significant effect, and view_frustum culling has the minimum effect, and the orientation culling was in the middle. +And what's more, the orientation culling and view_frustum culling FPS was very close to the original FPS. I guess doing culling process will also need computation, but after these culling method, only few of them was culled, so the effect was not that clear. + +But the distance culling will cut off a lot of blades, which bring the most significent performance increase. + +Separate graphs are used because increasing number of blades will cause large differences in FPS magnitude, which are hard to see on a single graph. + +![NumBlades_FPS](img/16384.png) + +![NumBlades_FPS](img/32768.png) + +![NumBlades_FPS](img/65536.png) + +![NumBlades_FPS](img/131072.png) + +# References +* Getting camera eye from view matrix + * Invert to find camera space in terms of world space and take displacement in fourth column of homogeneous matrix + * https://www.3dgep.com/understanding-the-view-matrix/ diff --git a/bin/Debug/vulkan_grass_rendering.exe b/bin/Debug/vulkan_grass_rendering.exe new file mode 100644 index 0000000..d072c63 Binary files /dev/null and b/bin/Debug/vulkan_grass_rendering.exe differ diff --git a/bin/Debug/vulkan_grass_rendering.pdb b/bin/Debug/vulkan_grass_rendering.pdb new file mode 100644 index 0000000..17be5cd Binary files /dev/null and b/bin/Debug/vulkan_grass_rendering.pdb differ diff --git a/img/131072.png b/img/131072.png new file mode 100644 index 0000000..246a46e Binary files /dev/null and b/img/131072.png differ diff --git a/img/16384.png b/img/16384.png new file mode 100644 index 0000000..2560981 Binary files /dev/null and b/img/16384.png differ diff --git a/img/32768.png b/img/32768.png new file mode 100644 index 0000000..273af0f Binary files /dev/null and b/img/32768.png differ diff --git a/img/65536.png b/img/65536.png new file mode 100644 index 0000000..fb702a6 Binary files /dev/null and b/img/65536.png differ diff --git a/img/DistanceCull.gif b/img/DistanceCull.gif new file mode 100644 index 0000000..ce92a60 Binary files /dev/null and b/img/DistanceCull.gif differ diff --git a/img/FrustumCull.gif b/img/FrustumCull.gif new file mode 100644 index 0000000..4149f25 Binary files /dev/null and b/img/FrustumCull.gif differ diff --git a/img/NumBlade_FPS.png b/img/NumBlade_FPS.png new file mode 100644 index 0000000..01e18a9 Binary files /dev/null and b/img/NumBlade_FPS.png differ diff --git a/img/gravity.png b/img/gravity.png new file mode 100644 index 0000000..b67cc03 Binary files /dev/null and b/img/gravity.png differ diff --git a/img/orientationCull.gif b/img/orientationCull.gif new file mode 100644 index 0000000..ff0f7f1 Binary files /dev/null and b/img/orientationCull.gif differ diff --git a/img/overview.gif b/img/overview.gif new file mode 100644 index 0000000..2085da7 Binary files /dev/null and b/img/overview.gif differ diff --git a/img/recover.gif b/img/recover.gif new file mode 100644 index 0000000..d6fe8f7 Binary files /dev/null and b/img/recover.gif differ diff --git a/img/wind.gif b/img/wind.gif new file mode 100644 index 0000000..640f786 Binary files /dev/null and b/img/wind.gif differ diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..6b97bfd 100644 --- a/src/Blades.h +++ b/src/Blades.h @@ -4,7 +4,7 @@ #include #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; diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..e7ed416 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -195,9 +195,44 @@ void Renderer::CreateTimeDescriptorSetLayout() { } void Renderer::CreateComputeDescriptorSetLayout() { - // TODO: Create the descriptor set layout for the compute pipeline + // DONE: 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 + + //Check the document + //Input Blades + VkDescriptorSetLayoutBinding inputGrassBladeLayoutBinding = {}; + inputGrassBladeLayoutBinding.binding = 0; + inputGrassBladeLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + inputGrassBladeLayoutBinding.descriptorCount = 1; + inputGrassBladeLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + inputGrassBladeLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding cullBladesLayoutBinding = {}; + cullBladesLayoutBinding.binding = 1; + cullBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + cullBladesLayoutBinding.descriptorCount = 1; + cullBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + cullBladesLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding numBladesLayoutBinding = {}; + numBladesLayoutBinding.binding = 2; + numBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBladesLayoutBinding.descriptorCount = 1; + numBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numBladesLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { inputGrassBladeLayoutBinding, cullBladesLayoutBinding, numBladesLayoutBinding }; + + // Create the descriptor set layout + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(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() { @@ -215,7 +250,9 @@ void Renderer::CreateDescriptorPool() { // Time (compute) { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, - // TODO: Add any additional types and counts of descriptors you will need to allocate + // DONE: Add any additional types and counts of descriptors you will need to allocate + // Blades compute buffers + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,static_cast(3*scene->GetBlades().size())}, }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -318,8 +355,47 @@ void Renderer::CreateModelDescriptorSets() { } void Renderer::CreateGrassDescriptorSets() { - // TODO: Create Descriptor sets for the grass. + // DONE: Create Descriptor sets for the grass. // This should involve creating descriptor sets which point to the model matrix of each group of grass blades + grassDescriptorSets.resize(scene->GetBlades().size()); + + VkDescriptorSetLayout layouts[] = {modelDescriptorSetLayout}; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(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"); + } + + // Configure the descriptors to refer to buffers + + std::vector descriptorWrites(grassDescriptorSets.size()); + for (uint32_t i = 0; i < scene->GetBlades().size(); i++) + { + //Pick up each blade + VkDescriptorBufferInfo bladeBufferInfo = {}; + // bladeBufferInfo.buffer = scene->GetBlades()[i]->GetBladeBuffer(); + bladeBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + bladeBufferInfo.offset = 0; + bladeBufferInfo.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 = &bladeBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -358,8 +434,78 @@ 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 + // DONE: 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 + + // 3 attribute + computeDescriptorSets.resize(scene->GetBlades().size()); + + //Describe the descriptor set + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(computeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + //Allocate descriptor + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) + { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) + { + VkDescriptorBufferInfo grassBladeBufferInfo = {}; + grassBladeBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + grassBladeBufferInfo.offset = 0; + grassBladeBufferInfo.range = NUM_BLADES * sizeof(Blades); + + VkDescriptorBufferInfo culledBladeBufferInfo = {}; + culledBladeBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladeBufferInfo.offset = 0; + culledBladeBufferInfo.range = NUM_BLADES * sizeof(Blades); + + VkDescriptorBufferInfo numBladeBufferInfo = {}; + numBladeBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladeBufferInfo.offset = 0; + numBladeBufferInfo.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 = &grassBladeBufferInfo; + 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 = &culledBladeBufferInfo; + 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 = &numBladeBufferInfo; + descriptorWrites[3 * i + 2].pImageInfo = nullptr; + descriptorWrites[3 * i + 2].pTexelBufferView = nullptr; + + } + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -716,8 +862,8 @@ void Renderer::CreateComputePipeline() { computeShaderStageInfo.module = computeShaderModule; computeShaderStageInfo.pName = "main"; - // TODO: Add the compute dsecriptor set layout you create to this list - std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout }; + // DONE: Add the compute dsecriptor set layout you create to this list + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout }; // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -883,7 +1029,14 @@ 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 + // DONE: For each group of blades bind its 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); + //Check document + vkCmdDispatch(computeCommandBuffer,NUM_BLADES/WORKGROUP_SIZE,1,1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -932,6 +1085,7 @@ void Renderer::RecordCommandBuffers() { renderPassInfo.pClearValues = clearValues.data(); std::vector barriers(scene->GetBlades().size()); + for (uint32_t j = 0; j < barriers.size(); ++j) { barriers[j].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; barriers[j].srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; @@ -975,14 +1129,14 @@ void Renderer::RecordCommandBuffers() { for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) { VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() }; VkDeviceSize offsets[] = { 0 }; - // TODO: Uncomment this when the buffers are populated - // vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); - - // TODO: Bind the descriptor set for each grass blades model + // DONE: Uncomment this when the buffers are populated + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + // DONE: 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)); + // DONE: Uncomment this when the buffers are populated + vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); } // End render pass @@ -1041,7 +1195,7 @@ void Renderer::Frame() { Renderer::~Renderer() { vkDeviceWaitIdle(logicalDevice); - // TODO: destroy any resources you created + // DONE: destroy any resources you created vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); vkFreeCommandBuffers(logicalDevice, computeCommandPool, 1, &computeCommandBuffer); @@ -1058,6 +1212,11 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + // =============== Destroy added descriptorSetLayout ========================= + + vkDestroyDescriptorSetLayout(logicalDevice,computeDescriptorSetLayout,nullptr); + // =============== End ======================= + vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); vkDestroyRenderPass(logicalDevice, renderPass, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..58a33de 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,6 +56,9 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + // =================== Add ====================== + VkDescriptorSetLayout computeDescriptorSetLayout; + // =================== End ====================== VkDescriptorPool descriptorPool; @@ -63,6 +66,11 @@ class Renderer { std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + //=================== Add ======================= + std::vector grassDescriptorSets; + std::vector computeDescriptorSets; + //=================== End ======================= + VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; VkPipelineLayout computePipelineLayout; diff --git a/src/Scene.cpp b/src/Scene.cpp index 86894f2..9f22b93 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -1,5 +1,8 @@ #include "Scene.h" #include "BufferUtils.h" +#include + +#define FRAME_UPLIMIT 30 Scene::Scene(Device* device) : device(device) { BufferUtils::CreateBuffer(device, sizeof(Time), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, timeBuffer, timeBufferMemory); @@ -24,6 +27,9 @@ void Scene::AddBlades(Blades* blades) { } void Scene::UpdateTime() { + //compute FPS + static unsigned frameCount = 0; + static float frame_end_time = 0.0f; high_resolution_clock::time_point currentTime = high_resolution_clock::now(); duration nextDeltaTime = duration_cast>(currentTime - startTime); startTime = currentTime; @@ -31,6 +37,17 @@ void Scene::UpdateTime() { time.deltaTime = nextDeltaTime.count(); time.totalTime += time.deltaTime; + frameCount++; + frame_end_time += duration_cast(nextDeltaTime).count(); + + if (frameCount == FRAME_UPLIMIT) + { + float fps= frameCount * 1000 / frame_end_time; + + std::cout << "FPS: " << fps << std::endl; + frameCount = 0.0f; + frame_end_time = 0.0f; + } memcpy(mappedData, &time, sizeof(Time)); } diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..c323c92 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -4,6 +4,10 @@ #define WORKGROUP_SIZE 32 layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; +#define OrientationCull 1 +#define ViewFrustumCull 1 +#define DistanceCull 1 + layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; @@ -21,7 +25,7 @@ struct Blade { vec4 up; }; -// TODO: Add bindings to: +// DONE: Add bindings to: // 1. Store the input blades // 2. Write out the culled blades // 3. Write the total number of blades remaining @@ -29,12 +33,24 @@ struct Blade { // The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call // This is sort of an advanced feature so we've showed you what this buffer should look like // -// layout(set = ???, binding = ???) buffer NumBlades { -// uint vertexCount; // Write the number of blades remaining here -// uint instanceCount; // = 1 -// uint firstVertex; // = 0 -// uint firstInstance; // = 0 -// } numBlades; + + +layout(set=2,binding=0)buffer GrassBlades +{ + Blade grassBlades[]; +}grassBlades; + +layout(set=2,binding=1)buffer CulledBlades +{ +Blade culledBlades[]; +}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; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); @@ -43,14 +59,124 @@ bool inBounds(float value, float 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 - // TODO: Apply forces on every blade and update the vertices in the buffer + + uint threadIdx = gl_GlobalInvocationID.x; + Blade blade = grassBlades.grassBlades[threadIdx]; + + // DONE: Apply forces on every blade and update the vertices in the buffer + float orient = blade.v0.w; + float height = blade.v1.w; + float width =blade.v2.w; + float stiffness = blade.up.w; + + vec3 v0_vec = blade.v0.xyz; + vec3 v1_vec = blade.v1.xyz; + vec3 v2_vec = blade.v2.xyz; + vec3 up_vec = blade.up.xyz; + + vec3 orient_vec = normalize(vec3(cos(orient), 0.f, sin(orient))); + vec3 frontface_vec = normalize(cross(orient_vec, up_vec)); + // DONE: Apply forces on every blade and update the vertices in the buffer + + //Gravity + vec4 gE=vec4(0.f,-1.f,0.f,5.f); + vec3 force_gE=normalize(gE.xyz)*gE.w; + vec3 force_gF=0.25*length(force_gE)*frontface_vec; + vec3 force_g=force_gF+force_gE; + + //Recovery force + vec3 v2_init = v0_vec+normalize(up_vec)*height; + vec3 force_r = (v2_init-v2_vec)*stiffness; + + // Wind Force + vec3 wind_dir =vec3(cos(4*totalTime),0.f,sin(5*totalTime)); + float fd = 1-abs(dot(normalize(wind_dir),normalize(v2_vec-v0_vec))); + float fr=dot(v2_vec-v0_vec,up_vec)/height; + float wind_alignment = fd*fr; + vec3 force_w=wind_dir*wind_alignment; + + //Translate v2 as a result of forces + //vec3 tv2_vec = (force_g+force_r+force_w)*deltaTime; + + vec3 tv2_vec = (force_g+force_r+force_w)*deltaTime; + v2_vec +=tv2_vec; - // TODO: Cull blades that are too far away or not in the camera frustum and write them + //Ensure v2 does not push into the ground + v2_vec -= up_vec*min(dot(v2_vec-v0_vec,up_vec),0.0f); + + //Adjust v1 as the result of new v2 position + float I_proj = length(v2_vec-v0_vec-up_vec*dot(v2_vec-v0_vec,up_vec)); + v1_vec = v0_vec + height * up_vec * max(1.f - I_proj / height, 0.05f * max(I_proj / height, 1.f)); + + bool cull=false; + //DONE: Cull blades that are too far away or not in the camera frustum and write them // to the culled blades buffer + float n = 2.f; // Degree of Bezier curve + float L0 = length(v2_vec - v0_vec); + float L1 = length(v2_vec - v1_vec) + length(v1_vec - v0_vec); + float L_blade = (2.f * L0 + (n - 1.f) * L1) / (n + 1.f); + + float r = height / L_blade; + v1_vec = v0_vec + r * (v1_vec - v0_vec); + v2_vec = v1_vec + r * (v2_vec - v1_vec); // 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 + + //Cull Method + + vec3 cameraPos = inverse(camera.view)[3].xyz; + + #if OrientationCull + //Check orientation + float angle_camera_blade = dot(frontface_vec,normalize(v0_vec-cameraPos)); + if(angle_camera_blade>0.9) + { + cull=true; + } + else{ + cull=false; + } + #endif + + #if ViewFrustumCull + float tol=20.f; + vec3 md = 0.25*v0_vec+0.5*v1_vec+0.25*v2_vec; + mat4 VP=camera.proj*camera.view; + + vec4 v0_ndc = VP*vec4(v0_vec,1.f); + vec4 md_ndc = VP*vec4(md,1.f); + vec4 v2_ndc=VP*vec4(v2_vec,1.f); + + float v0_lim=v0_ndc.w+tol; + float v2_lim=tol; + float md_lim=tol; + + bool keep_v0=(inBounds(v0_ndc.x,v0_lim))&& + (inBounds(v0_ndc.y,v0_lim)); + bool keep_md=(inBounds(md_ndc.x,md_lim))&& + (inBounds(md_ndc.y,md_lim)); + bool keep_v2=(inBounds(v2_ndc.x,v2_lim))&& + (inBounds(v2_ndc.y,v2_lim)); + + cull=cull||(!keep_v0&&!keep_md&&!keep_v2); + + #endif + + #if DistanceCull + float d_proj = length(v0_vec-cameraPos-up_vec*dot(up_vec,(v0_vec-cameraPos))); + float maxDistance=20.f; + int cullLevel=6; + cull=cull||(mod(threadIdx,cullLevel)>floor(cullLevel*(1.f-d_proj/maxDistance))); + #endif + grassBlades.grassBlades[threadIdx].v1.xyz = v1_vec; + grassBlades.grassBlades[threadIdx].v2.xyz = v2_vec; + + if(!cull) + { + culledBlades.culledBlades[atomicAdd(numBlades.vertexCount,1)]=grassBlades.grassBlades[threadIdx]; + } } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..efbbefd 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -6,12 +6,15 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare fragment shader inputs +// DONE: Declare fragment shader inputs +layout(location=1)in float param_height; layout(location = 0) out vec4 outColor; void main() { - // TODO: Compute fragment color - - outColor = vec4(1.0); + // DONE: Compute fragment color + vec4 lightYellow=vec4(250.f,255.f,11.f,255.f)/255.f; + vec4 orange=vec4(245.f,122.f,22.f,255.f)/255.f; + vec4 mergeCol =mix(lightYellow,orange,1-param_height); + outColor = mergeCol; } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..eb63309 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -8,19 +8,32 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation control shader inputs and outputs +// DONE: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4 in_v0[]; +layout(location = 1) in vec4 in_v1[]; +layout(location = 2) in vec4 in_v2[]; +layout(location = 3) in vec4 in_up[]; + +layout(location = 0) out vec4 out_v0[]; +layout(location = 1) out vec4 out_v1[]; +layout(location = 2) out vec4 out_v2[]; +layout(location = 3) out vec4 out_up[]; void main() { // Don't move the origin location of the patch gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; - // TODO: Write any shader outputs + // DONE: Write any shader outputs + out_v0[gl_InvocationID] = in_v0[gl_InvocationID]; + out_v1[gl_InvocationID] = in_v1[gl_InvocationID]; + out_v2[gl_InvocationID] = in_v2[gl_InvocationID]; + out_up[gl_InvocationID] = in_up[gl_InvocationID]; - // TODO: Set level of tesselation - // gl_TessLevelInner[0] = ??? - // gl_TessLevelInner[1] = ??? - // gl_TessLevelOuter[0] = ??? - // gl_TessLevelOuter[1] = ??? - // gl_TessLevelOuter[2] = ??? - // gl_TessLevelOuter[3] = ??? + //DONE: Set level of tesselation + gl_TessLevelInner[0] = 8.0; + gl_TessLevelInner[1] = 8.0; + gl_TessLevelOuter[0] = 8.0; + gl_TessLevelOuter[1] = 8.0; + gl_TessLevelOuter[2] = 8.0; + gl_TessLevelOuter[3] = 8.0; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..46cb287 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -8,11 +8,40 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation evaluation shader inputs and outputs +// DONE: Declare tessellation evaluation shader inputs and outputs +layout(location=0) in vec4 in_v0[]; +layout(location=1)in vec4 in_v1[]; +layout(location=2)in vec4 in_v2[]; +layout(location=3)in vec4 in_up[]; + +layout(location =0)out vec3 nor; +layout(location=1)out float param_height; void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; - // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + + // DONE: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + vec3 v0 = in_v0[0].xyz; + vec3 v1 = in_v1[0].xyz; + vec3 v2 = in_v2[0].xyz; + float orient = in_v0[0].w; + float width = in_v2[0].w; + vec3 t1=normalize(vec3(cos(orient),0.f,sin(orient))); + + //Create Bezier curve + vec3 a =v0+v*(v1-v0); + vec3 b=v1+v*(v2-v1); + vec3 c=a+v*(b-a); + vec3 c0=c-width*t1; + vec3 c1=c+width*t1; + vec3 t0 = normalize(b-a); + vec3 n=normalize(cross(t0,t1)); + + float t = u + 0.5 * v - u * v; + vec3 p = (1 - t) * c0 + t * c1; + gl_Position = camera.proj * camera.view * vec4(p, 1.f); + + param_height = v; } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..8af5fb0 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,11 +7,25 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { }; // TODO: Declare vertex shader inputs and outputs +layout(location = 0) in vec4 in_v0; +layout(location = 1) in vec4 in_v1; +layout(location = 2) in vec4 in_v2; +layout(location = 3) in vec4 in_up; + +layout(location = 0) out vec4 out_v0; +layout(location = 1) out vec4 out_v1; +layout(location = 2) out vec4 out_v2; +layout(location = 3) out vec4 out_up; + out gl_PerVertex { vec4 gl_Position; }; void main() { - // TODO: Write gl_Position and any other shader outputs + // DONE: Write gl_Position and any other shader outputs + out_v0 = in_v0; + out_v1 = in_v1; + out_v2 = in_v2; + out_up = in_up; }