diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ba7471 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/build +/bin diff --git a/CMakeLists.txt b/CMakeLists.txt index 860a279..9d5a7e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,6 @@ ELSE(USE_D2D_WSI) find_package(XCB REQUIRED) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_XCB_KHR") ENDIF(USE_D2D_WSI) - # Todo : android? ENDIF(WIN32) # Set preprocessor defines diff --git a/README.md b/README.md index 20ee451..53e59d4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,55 @@ Vulkan Grass Rendering ================================== -**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5** +https://github.com/user-attachments/assets/a1ca7be8-5007-4135-999c-f40fa03c1052 -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +> University of Pennsylvania, CIS 5650: GPU Programming and Architecture, Project 3 - CUDA Path Tracer +> * Michael Mason +> + [Personal Website](https://www.michaelmason.xyz/) +> * Tested on: Windows 11, Ryzen 9 5900HS @ 3.00GHz 16GB, RTX 3080 (Laptop) 8192MB -### (TODO: Your README) +This is a small Vulkan project that implements a grass simulation. -*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. +The implementation is based on [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 & Wimmer. + +## Physics Sim / Forces + +### Gravity + Recovery + +![Recording 2024-10-31 at 22 18 32](https://github.com/user-attachments/assets/96a571f0-5c65-4d2d-bc6f-f72b95c32ce9) + +### Wind + +![Recording 2024-10-31 at 22 20 15](https://github.com/user-attachments/assets/1cb92ce7-4e38-4c13-bf8f-5f9b2eff712d) + +## Culling Methods + +### Orientation + +![Recording 2024-10-31 at 22 24 01](https://github.com/user-attachments/assets/2515cba0-8f11-4f7b-bb7b-0d3ad1076ec5) + +### Frustum + +![Recording 2024-10-31 at 22 26 36](https://github.com/user-attachments/assets/9fbfd865-eecf-4029-ad20-36e0261bd344) + +### Distance + +![Recording 2024-10-31 at 22 28 39](https://github.com/user-attachments/assets/7fd976c0-e2a6-47be-a788-72a9ee0b03fe) + +## Performance Analysis + +*Lower is Better* + +![No Culling, Orientation, Frustum and Distance](https://github.com/user-attachments/assets/5de8f739-49cc-4c7d-829b-6b7c59614f9d) + +The graph above represents the effect of different culling methods for rendering grass in Vulkan. We measured miliseconds per frame against number of grass blades. + +Culling by Orientation: this represents the culling occurs when grass blades (which are infinitely flat) are perpendicular to the camera. In the shader, we cull those grass blades within a certain threshold of 90 degrees + +Culling by Frustum: This represents culling that occurs when grass blades are outside of the camera frustum. In the shader we cull those blades. + +Culling by distance: This represents culling grass blades that are of a certain distance. After 10 units, every 10th blade is culled. After 40 units, every 2nd blade is culled and after 50 units, all blades are culled. + +Without any culling, rendering times increase sharply with blade count, reaching 178.32 milliseconds at 4,194,304 blades. Orientation Culling, which removes blades perpendicular to the camera, provides moderate improvement, reducing the time to 123.23 milliseconds at the highest count. Frustum Culling, which discards blades outside the camera's view, achieves a similar but slightly less effective reduction to 156.2 milliseconds. Distance Culling, however, proves to be the most impactful, reducing the frame time to 75.89 milliseconds by progressively removing distant blades, especially effective in large fields where far-off details contribute minimally to visual quality. + +While distance culling appears to be the winner here, be mindful that the tests done are dependant on the camera's position and orientation to the grass. It's likely more tests are needed to accurately measure performance of culling. diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..38518b0 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -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(), getBladesBufferSize(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory); + BufferUtils::CreateBuffer(device, getBladesBufferSize(), 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); } diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..f5ed1a2 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 << 16; // 1 < 13 constexpr static float MIN_HEIGHT = 1.3f; constexpr static float MAX_HEIGHT = 2.5f; constexpr static float MIN_WIDTH = 0.1f; @@ -84,5 +84,8 @@ class Blades : public Model { VkBuffer GetBladesBuffer() const; VkBuffer GetCulledBladesBuffer() const; VkBuffer GetNumBladesBuffer() const; + static constexpr size_t getBladesBufferSize() { + return NUM_BLADES * sizeof(Blade); + } ~Blades(); }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aea02fe..54fa1ee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,8 +43,6 @@ foreach(SHADER_SOURCE ${SHADER_SOURCES}) ExternalTarget("Shaders" ${fname}.spv) add_dependencies(vulkan_grass_rendering ${fname}.spv) endif(WIN32) - - # TODO: Build shaders on not windows endforeach() target_link_libraries(vulkan_grass_rendering ${ASSIMP_LIBRARIES} Vulkan::Vulkan glfw) diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..4bad6e2 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -21,6 +21,7 @@ Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* c CreateModelDescriptorSetLayout(); CreateTimeDescriptorSetLayout(); CreateComputeDescriptorSetLayout(); + CreateGrassDescriptorSetLayout(); CreateDescriptorPool(); CreateCameraDescriptorSet(); CreateModelDescriptorSets(); @@ -195,9 +196,56 @@ void Renderer::CreateTimeDescriptorSetLayout() { } 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 inputGrass = {}; + inputGrass.binding = 0; + inputGrass.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + inputGrass.descriptorCount = 1; + inputGrass.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + VkDescriptorSetLayoutBinding outputGrass = {}; + outputGrass.binding = 1; + outputGrass.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + outputGrass.descriptorCount = 1; + outputGrass.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + VkDescriptorSetLayoutBinding numBlades = {}; + numBlades.binding = 2; + numBlades.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBlades.descriptorCount = 1; + numBlades.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + std::vector bindings = { inputGrass, outputGrass, numBlades }; + + 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::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 bindings = { uboLayoutBinding }; + + // 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, &grassDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } } void Renderer::CreateDescriptorPool() { @@ -215,7 +263,8 @@ 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 + // Blades (compute) + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3 * static_cast(scene->GetBlades().size())} }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -318,8 +367,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 + grassDescriptorSets.resize(scene->GetModels().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(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 descriptorWrites(grassDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetModels().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(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -358,8 +442,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 + 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(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 descriptorWrites(3 * computeDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + // input grass + VkDescriptorBufferInfo inputGrassBufferInfo = {}; + inputGrassBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + inputGrassBufferInfo.offset = 0; + inputGrassBufferInfo.range = scene->GetBlades()[i]->getBladesBufferSize(); + + // culled grass + VkDescriptorBufferInfo culledGrassBufferInfo = {}; + culledGrassBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledGrassBufferInfo.offset = 0; + culledGrassBufferInfo.range = scene->GetBlades()[i]->getBladesBufferSize(); + + // num blades (indirect draw call buffer) + 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 = &inputGrassBufferInfo; + + 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 = &culledGrassBufferInfo; + + 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; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -600,7 +746,7 @@ void Renderer::CreateGrassPipeline() { rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; // TODO: use VK_POLYGON_MODE_FILL rasterizer.lineWidth = 1.0f; rasterizer.cullMode = VK_CULL_MODE_NONE; rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; @@ -654,7 +800,7 @@ void Renderer::CreateGrassPipeline() { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout }; + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, grassDescriptorSetLayout }; // Pipeline layout: used to specify uniform values VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -716,8 +862,7 @@ 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 }; + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout }; // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -883,7 +1028,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 + for (size_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); + } + + vkCmdPipelineBarrier(computeCommandBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 0, nullptr); // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -931,19 +1081,19 @@ void Renderer::RecordCommandBuffers() { renderPassInfo.clearValueCount = static_cast(clearValues.size()); renderPassInfo.pClearValues = clearValues.data(); - std::vector barriers(scene->GetBlades().size()); + // ?? It doesn't seem like any of these sync barriers do ANYTHING to fix the grass flickering + + // 1. vkCmdPipelineBarrier(commandBuffers[i], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 0, nullptr); + + // 2. + /*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].sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; barriers[j].srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; barriers[j].dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT; - barriers[j].srcQueueFamilyIndex = device->GetQueueIndex(QueueFlags::Compute); - barriers[j].dstQueueFamilyIndex = device->GetQueueIndex(QueueFlags::Graphics); - barriers[j].buffer = scene->GetBlades()[j]->GetNumBladesBuffer(); - barriers[j].offset = 0; - barriers[j].size = sizeof(BladeDrawIndirect); } - vkCmdPipelineBarrier(commandBuffers[i], VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, 0, 0, nullptr, barriers.size(), barriers.data(), 0, nullptr); + vkCmdPipelineBarrier(commandBuffers[i], VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, 0, barriers.size(), barriers.data(), 0, nullptr, 0, nullptr);*/ // Bind the camera descriptor set. This is set 0 in all pipelines so it will be inherited vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 0, 1, &cameraDescriptorSet, 0, nullptr); @@ -962,7 +1112,7 @@ void Renderer::RecordCommandBuffers() { vkCmdBindIndexBuffer(commandBuffers[i], scene->GetModels()[j]->getIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); // Bind the descriptor set for each model - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 1, 1, &modelDescriptorSets[j], 0, nullptr); + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 1, 1, &modelDescriptorSets[j], 0, nullptr); // Draw std::vector indices = scene->GetModels()[j]->getIndices(); @@ -975,14 +1125,13 @@ 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 + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + + 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 @@ -1041,8 +1190,6 @@ void Renderer::Frame() { Renderer::~Renderer() { vkDeviceWaitIdle(logicalDevice); - // TODO: destroy any resources you created - vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); vkFreeCommandBuffers(logicalDevice, computeCommandPool, 1, &computeCommandBuffer); @@ -1050,13 +1197,15 @@ Renderer::~Renderer() { vkDestroyPipeline(logicalDevice, grassPipeline, nullptr); vkDestroyPipeline(logicalDevice, computePipeline, nullptr); + vkDestroyPipelineLayout(logicalDevice, computePipelineLayout, nullptr); vkDestroyPipelineLayout(logicalDevice, graphicsPipelineLayout, nullptr); vkDestroyPipelineLayout(logicalDevice, grassPipelineLayout, nullptr); - vkDestroyPipelineLayout(logicalDevice, computePipelineLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, grassDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..b4d4cfc 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -19,6 +19,7 @@ class Renderer { void CreateModelDescriptorSetLayout(); void CreateTimeDescriptorSetLayout(); void CreateComputeDescriptorSetLayout(); + void CreateGrassDescriptorSetLayout(); void CreateDescriptorPool(); @@ -56,12 +57,16 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout grassDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + std::vector grassDescriptorSets; + std::vector computeDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/Scene.cpp b/src/Scene.cpp index 86894f2..b37f007 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -1,6 +1,8 @@ #include "Scene.h" #include "BufferUtils.h" +#include + 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); vkMapMemory(device->GetVkDevice(), timeBufferMemory, 0, sizeof(Time), 0, &mappedData); diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..ee15de3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -116,7 +116,7 @@ int main() { grassImageMemory ); - float planeDim = 15.f; + float planeDim = 40.f; float halfWidth = planeDim * 0.5f; Model* plane = new Model(device, transferCommandPool, { @@ -159,7 +159,11 @@ int main() { delete blades; delete camera; delete renderer; + + // this base code SUCKS delete swapChain; + vkDestroySurfaceKHR(instance->GetVkInstance(), surface, nullptr); + delete device; delete instance; DestroyWindow(); diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..5b85d9d 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -21,10 +21,20 @@ struct Blade { vec4 up; }; -// TODO: Add bindings to: -// 1. Store the input blades -// 2. Write out the culled blades -// 3. Write the total number of blades remaining +layout(set = 2, binding = 0) buffer InputGrass { + Blade inputBlades[]; +}; + +layout(set = 2, binding = 1) writeonly buffer CulledGrass { + Blade culledBlades[]; +}; + +layout(set = 2, binding = 2) buffer NumBlades { + uint vertexCount; + uint instanceCount; + uint firstVertex; + uint firstInstance; +} numBlades; // 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 @@ -40,17 +50,180 @@ bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +mat4 rot(vec3 axis, float angle) +{ + axis = normalize(axis); + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, + oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +#define TOLERANCE -0.0 + +bool inFrustum(vec3 p) { + vec4 pHom = vec4(p, 1); // homogenous form + vec4 pNor = camera.proj * camera.view * pHom; + float h = pNor.w + TOLERANCE; + + bool vx = p.x > -h && p.x < h; + bool vy = p.y > -h && p.y < h; + bool vz = p.z > -h && p.z < h; + + return vx && vy && vz; +} + +float hash( in ivec2 p ) // this hash is not production ready, please +{ // replace this by something better + // 2D -> 1D + int n = p.x*3 + p.y*113; + // 1D hash by Hugo Elias + n = (n << 13) ^ n; + n = n * (n * n * 15731 + 789221) + 1376312589; + return -1.0+2.0*float( n & 0x0fffffff)/float(0x0fffffff); +} + +float noise( in vec2 p ) +{ + ivec2 i = ivec2(floor( p )); + vec2 f = fract( p ); + + // cubic interpolant + vec2 u = f*f*(3.0-2.0*f); + + return mix( mix( hash( i + ivec2(0,0) ), + hash( i + ivec2(1,0) ), u.x), + mix( hash( i + ivec2(0,1) ), + hash( i + ivec2(1,1) ), u.x), u.y); +} + + +#define HACK false +#define CULL true + void main() { // Reset the number of blades to 0 if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + numBlades.vertexCount = 0; + numBlades.instanceCount = 1; + numBlades.firstVertex = 0; + numBlades.firstInstance = 0; } barrier(); // Wait till all threads reach this point + Blade b = inputBlades[gl_GlobalInvocationID.x]; + + vec3 v0 = b.v0.xyz; + vec3 v1 = b.v1.xyz; + vec3 v2 = b.v2.xyz; + vec3 up = b.up.xyz; + float orientation = b.v0.w; + float height = b.v1.w; + float width = b.v2.w; + float stiffness = b.up.w; + + // TODO: Apply forces on every blade and update the vertices in the buffer - // 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 + // gravity + + mat4 rotation = rot(up, orientation); + vec3 f = (rotation * vec4(0, 0, 1, 0)).xyz; + + // gravity magnitude/direction + vec4 D = vec4(0, -0.1, 0, 1); + vec3 gEnvironment = normalize(D.xyz) * D.w; // global gravity force + vec3 gFront = 0.25f * length(gEnvironment) * f; + + vec3 gravity = gEnvironment + gFront; + + // recovery + vec3 iv2 = v0 + (up * height); // initial position of v2 + vec3 recovery = (iv2 - v2) * stiffness; + + // wind + float n = noise(v0.xz); + vec3 windDir = 4. * sin(n + vec3(totalTime)) * cos(n + vec3(totalTime)); + float fd = 1 - length(dot(normalize(windDir), normalize(v2 - v0))); + float fr = dot((v2 - v0), up) / height; + vec3 wind = windDir * (fd * fr); + + // correct position + + // Not sure why delta time doesn't work, but using deltaTime makes stuttering movement + if (HACK) { + b.v2.xyz += (gravity + recovery + wind) * 0.001; + } + else { + //v2 += (gravity + recovery + wind) * deltaTime; + v2 += (gravity + recovery + wind) * deltaTime; + + v2 = v2 - up * min(dot(up, (v2 - v0)), 0.); + float lproj = length(v2 - v0 - up * dot((v2 - v0), up)); + v1 = v0 + height * up * max(1. - (lproj / height), 0.05 * max(lproj / height, 1)); + + float L0 = distance(v2, v0); + float L1 = distance(v1, v0) + distance(v2, v1); + float L = 0.25 * (2 * L0 + 2 * L1); + float r = height/L; + + v1 = v0 + r * (v1 - v0); + v2 = v1 + r * (v2 - v1); + + inputBlades[gl_GlobalInvocationID.x].v1.xyz = v1; + inputBlades[gl_GlobalInvocationID.x].v2.xyz = v2; + + } + + // -- Culling -- + + if (CULL) + { + // -- cull based on blade orientation (in relation to camera) -- + vec3 cameraPos = (inverse(camera.view) * vec4(0, 0, 0, 1)).xyz; + + // project camera direction onto grass plane + // direction and distance to camera + vec3 dirCamera = v0 - cameraPos - up * dot(v0 - cameraPos, up); + vec3 dirBlade = normalize((rotation * vec4(1, 0, 0, 0)).xyz); // direction of blade along it's width + + if (0.9 < abs(dot(normalize(dirCamera), dirBlade))) { + return; + } + + // -- cull based on the camera's frustum -- + + // approximate mid point of grass blade + vec3 m = .25 * v0 * .5 * v1 * .25 * v2; + + if (!inFrustum(v0) && !inFrustum(m) && !inFrustum(v1)) { + return; + } + + // --cull based on distance -- + + if (length(dirCamera) > 50.0) { + return; + } + else if (length(dirCamera) > 40.0 && gl_GlobalInvocationID.x % 2 == 0) { + return; + } + else if (length(dirCamera) > 10.0 && gl_GlobalInvocationID.x % 10 == 0) { + return; + } + + } + + uint index = atomicAdd(numBlades.vertexCount, 1); + if (HACK) { + culledBlades[index] = b; + } + else + { + culledBlades[index] = inputBlades[gl_GlobalInvocationID.x]; + } } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..45a891f 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,17 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in float v; layout(location = 0) out vec4 outColor; void main() { - // TODO: Compute fragment color + // Define the dark green and light green colors + vec3 darkGreen = vec3(0.0, 0.5, 0.0); // Dark green + vec3 lightGreen = vec3(0.5, 1.0, 0.5); // Light green - outColor = vec4(1.0); + // Interpolate the grass color between darkGreen and lightGreen + vec3 grassColor = mix(darkGreen, lightGreen, v); + + outColor = vec4(grassColor, 1.); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..b46c2e0 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -8,13 +8,32 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; +layout(location = 0) in vec4 v0_in[]; +layout(location = 1) in vec4 v1_in[]; +layout(location = 2) in vec4 v2_in[]; +layout(location = 3) in vec4 up_in[]; + +layout(location = 0) out vec4 v0_out[]; +layout(location = 1) out vec4 v1_out[]; +layout(location = 2) out vec4 v2_out[]; +layout(location = 3) out vec4 up_out[]; + // TODO: Declare tessellation control shader inputs and outputs +in gl_PerVertex +{ + vec4 gl_Position; +} gl_in[gl_MaxPatchVertices]; + 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 + v0_out[gl_InvocationID] = v0_in[gl_InvocationID]; + v1_out[gl_InvocationID] = v1_in[gl_InvocationID]; + v2_out[gl_InvocationID] = v2_in[gl_InvocationID]; + up_out[gl_InvocationID] = up_in[gl_InvocationID]; // TODO: Set level of tesselation // gl_TessLevelInner[0] = ??? @@ -23,4 +42,11 @@ void main() { // gl_TessLevelOuter[1] = ??? // gl_TessLevelOuter[2] = ??? // gl_TessLevelOuter[3] = ??? + gl_TessLevelInner[0] = 0; + gl_TessLevelInner[1] = 10; + + gl_TessLevelOuter[0] = 10.0; + gl_TessLevelOuter[1] = 2; + gl_TessLevelOuter[2] = 10.0; + gl_TessLevelOuter[3] = 2; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..337c22b 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -8,11 +8,51 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 v0_in[]; +layout(location = 1) in vec4 v1_in[]; +layout(location = 2) in vec4 v2_in[]; +layout(location = 3) in vec4 up_in[]; + +layout(location = 0) out float v_out; + +mat4 rot(vec3 axis, float angle) +{ + axis = normalize(axis); + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, + oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, + 0.0, 0.0, 0.0, 1.0); +} 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 + v_out = v; + + vec3 v0 = gl_in[0].gl_Position.xyz; + vec3 v1 = v1_in[0].xyz; + vec3 v2 = v2_in[0].xyz; + float width = v2_in[0].w; + float height = v1_in[0].w; + + mat4 rotation = rot(up_in[0].xyz, v0_in[0].w); + vec3 t1 = (rotation * vec4(1, 0, 0, 0)).xyz; + 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 - u * v * v; + vec3 pos = (1. - t) * c0 + t * c1; + + gl_Position = camera.proj * camera.view * vec4(pos, 1); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..2f5d880 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -6,7 +6,15 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { mat4 model; }; -// TODO: Declare vertex shader inputs and outputs +layout(location = 0) in vec4 v0; // Corresponds to Blade::v0 +layout(location = 1) in vec4 v1; // Corresponds to Blade::v1 +layout(location = 2) in vec4 v2; // Corresponds to Blade::v2 +layout(location = 3) in vec4 up; // Corresponds to Blade::up + +layout(location = 0) out vec4 v0_out; +layout(location = 1) out vec4 v1_out; +layout(location = 2) out vec4 v2_out; +layout(location = 3) out vec4 up_out; out gl_PerVertex { vec4 gl_Position; @@ -14,4 +22,9 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + v0_out = v0; + v1_out = v1; + v2_out = v2; + up_out = up; + gl_Position = model * vec4(v0.xyz, 1.); }