diff --git a/README.md b/README.md index 20ee451..dbbe0db 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,75 @@ -Vulkan Grass Rendering -================================== +# 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) +- Jinxiang Wang +- Tested on: Windows 11, AMD Ryzen 9 8945HS w/ Radeon 780M Graphics 4.00 GHz 32GB, RTX 4070 Laptop 8 GB -### (TODO: Your README) +### Features Implemented: -*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. +1. Vulkan grass render pipeline +2. Force simulation (gravity, recovery, wind) +3. Culling (orientation, view-frustum, distance) +4. Grass blade tessellation +5. Distance based tessellation + +## Forces + +| Gravity | Recovery | Wind | +| ------------------------ | ------------------------- | --------------------- | +| ![](results/gravity.gif) | ![](results/recovery.gif) | ![](results/wind.gif) | + +## Culling + +--- + +### Orientation Culling + +**Orientation culling** is calculated by the absolute value of dot product between blade's **width orientation** and **xz direction of view direction** + +| Culling Off | Threshold = 0.1 | Threshold = 0.3 | Threshold = 0.6 | Threshold = 0.9 | +| -------------------------- | -------------------------- | -------------------------- | -------------------------- | -------------------------- | +| ![](results/orient_00.gif) | ![](results/orient_01.gif) | ![](results/orient_03.gif) | ![](results/orient_06.gif) | ![](results/orient_09.gif) | + +### View-Frustum Culling + +**View-frustum culling** is determined by whether or not a blade is inside view frustum's AABB + +| Culling Off | Culling On | +| ----------------------------- | ---------------------------- | +| ![](results/viewFrus_off.png) | ![](results/viewFrus_on.png) | + +### Distance Culling + +In **distance culling**, we calculate distance between camera, and decide whether or not this blade is culled by computing **threshold < (dist - minDist) / fadeDist** + +| Culling On | +| --------------------- | +| ![](results/dist.gif) | + +## Tessellation + +A **distance based tessellation** is performed in this project. By using similar concept introduced in Distance Culling, we lerp between preset min and max edge factors. + +![](results/QuadLevels.png) + +### Developing progress + +| Setup and Bind Resources | Fixed Orientation | Add Forces and Culling | +| -------------------------- | ----------------------- | ---------------------- | +| ![](results/showgrass.png) | ![](results/bugfix.png) | ![](results/final.png) | + +### Performance Analysis + +Scene Spec: + +| Resolution | Num Blades | +| ---------- | ---------------- | +| 1080x1080 | 262144 (1 << 18) | + +Results: + +| | No Culling | Orientation Culling | View Frustum Culling | Distance Culling | +| ------------ | ---------- | ------------------- | -------------------- | ---------------- | +| FPS | 112 | 136 | 184 | 189 | +| FPS Increase | 0 | 24 | 48 | 5 | diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe index f68db3a..412d132 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/results/QuadLevels.png b/results/QuadLevels.png new file mode 100644 index 0000000..84b8e91 Binary files /dev/null and b/results/QuadLevels.png differ diff --git a/results/bugfix.png b/results/bugfix.png new file mode 100644 index 0000000..54a4b37 Binary files /dev/null and b/results/bugfix.png differ diff --git a/results/culling.png b/results/culling.png new file mode 100644 index 0000000..911f51d Binary files /dev/null and b/results/culling.png differ diff --git a/results/dist.gif b/results/dist.gif new file mode 100644 index 0000000..04414a0 Binary files /dev/null and b/results/dist.gif differ diff --git a/results/final.png b/results/final.png new file mode 100644 index 0000000..025be39 Binary files /dev/null and b/results/final.png differ diff --git a/results/gravity.gif b/results/gravity.gif new file mode 100644 index 0000000..1828db2 Binary files /dev/null and b/results/gravity.gif differ diff --git a/results/orient_00.gif b/results/orient_00.gif new file mode 100644 index 0000000..7eb39bd Binary files /dev/null and b/results/orient_00.gif differ diff --git a/results/orient_01.gif b/results/orient_01.gif new file mode 100644 index 0000000..fdbbecc Binary files /dev/null and b/results/orient_01.gif differ diff --git a/results/orient_03.gif b/results/orient_03.gif new file mode 100644 index 0000000..a81a0e0 Binary files /dev/null and b/results/orient_03.gif differ diff --git a/results/orient_06.gif b/results/orient_06.gif new file mode 100644 index 0000000..29c4802 Binary files /dev/null and b/results/orient_06.gif differ diff --git a/results/orient_09.gif b/results/orient_09.gif new file mode 100644 index 0000000..47b6d56 Binary files /dev/null and b/results/orient_09.gif differ diff --git a/results/recovery.gif b/results/recovery.gif new file mode 100644 index 0000000..8ff7c62 Binary files /dev/null and b/results/recovery.gif differ diff --git a/results/showgrass.png b/results/showgrass.png new file mode 100644 index 0000000..e2e1502 Binary files /dev/null and b/results/showgrass.png differ diff --git a/results/viewFrus_off.png b/results/viewFrus_off.png new file mode 100644 index 0000000..879b56d Binary files /dev/null and b/results/viewFrus_off.png differ diff --git a/results/viewFrus_on.png b/results/viewFrus_on.png new file mode 100644 index 0000000..a8c744e Binary files /dev/null and b/results/viewFrus_on.png differ diff --git a/results/wind.gif b/results/wind.gif new file mode 100644 index 0000000..3b600ed Binary files /dev/null and b/results/wind.gif differ diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..8c61ddd 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; 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/Camera.cpp b/src/Camera.cpp index 3afb5b8..9157086 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -8,6 +8,9 @@ #include "Camera.h" #include "BufferUtils.h" +int WIDTH = 960; +int HEIGHT = 960; + Camera::Camera(Device* device, float aspectRatio) : device(device) { r = 10.0f; theta = 0.0f; diff --git a/src/Camera.h b/src/Camera.h index 6b10747..6736bd6 100644 --- a/src/Camera.h +++ b/src/Camera.h @@ -9,6 +9,8 @@ struct CameraBufferObject { glm::mat4 projectionMatrix; }; +extern int WIDTH; +extern int HEIGHT; class Camera { private: Device* device; diff --git a/src/Instance.cpp b/src/Instance.cpp index 7f6b01c..457f644 100644 --- a/src/Instance.cpp +++ b/src/Instance.cpp @@ -3,15 +3,17 @@ #include #include "Instance.h" -#ifdef NDEBUG -const bool ENABLE_VALIDATION = false; -#else +//#ifdef NDEBUG +//const bool ENABLE_VALIDATION = false; +//#else +//const bool ENABLE_VALIDATION = true; +//#endif const bool ENABLE_VALIDATION = true; -#endif namespace { const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" + "VK_LAYER_KHRONOS_validation", + "VK_LAYER_LUNARG_monitor" }; // Get the required list of extensions based on whether validation layers are enabled diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..39fe3e6 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -9,7 +9,7 @@ static constexpr unsigned int WORKGROUP_SIZE = 32; Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* camera) - : device(device), + : device(device), logicalDevice(device->GetVkDevice()), swapChain(swapChain), scene(scene), @@ -19,6 +19,7 @@ Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* c CreateRenderPass(); CreateCameraDescriptorSetLayout(); CreateModelDescriptorSetLayout(); + CreateGrassDescriptorSetLayout(); CreateTimeDescriptorSetLayout(); CreateComputeDescriptorSetLayout(); CreateDescriptorPool(); @@ -172,6 +173,27 @@ void Renderer::CreateModelDescriptorSetLayout() { } } +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::CreateTimeDescriptorSetLayout() { // Describe the binding of the descriptor set layout VkDescriptorSetLayoutBinding uboLayoutBinding = {}; @@ -192,12 +214,49 @@ void Renderer::CreateTimeDescriptorSetLayout() { if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &timeDescriptorSetLayout) != 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 bladesLayoutBinding = { + 0, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1, + VK_SHADER_STAGE_COMPUTE_BIT, + nullptr + }; + + VkDescriptorSetLayoutBinding culledBladesLayoutBinding = { + 1, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1, + VK_SHADER_STAGE_COMPUTE_BIT, + nullptr + }; + + VkDescriptorSetLayoutBinding numBladesLayoutBinding = { + 2, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1, + VK_SHADER_STAGE_COMPUTE_BIT, + nullptr + }; + + std::vector bindings = { bladesLayoutBinding, culledBladesLayoutBinding, numBladesLayoutBinding }; + + 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 compute descriptor set layout"); + } + } void Renderer::CreateDescriptorPool() { @@ -216,6 +275,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(scene->GetBlades().size() * 3) } }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -318,8 +378,45 @@ void Renderer::CreateModelDescriptorSets() { } void Renderer::CreateGrassDescriptorSets() { - // TODO: Create Descriptor sets for the grass. + // 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(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->GetBlades().size(); ++i) { + VkDescriptorBufferInfo grassBufferInfo = {}; + grassBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + grassBufferInfo.offset = 0; + grassBufferInfo.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 = &grassBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +457,87 @@ 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 and allocate + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + nullptr, + descriptorPool, + 1, + layouts + }; + + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + + uint32_t bladesBufferSize = NUM_BLADES * sizeof(Blade); + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo bladesBufferInfo = { + scene->GetBlades()[i]->GetBladesBuffer(), // buffer + 0, // offset + bladesBufferSize // range + }; + + VkDescriptorBufferInfo culledBladesBufferInfo = { + scene->GetBlades()[i]->GetCulledBladesBuffer(), // buffer + 0, // offset + bladesBufferSize // range + }; + + VkDescriptorBufferInfo numBladesBufferInfo = { + scene->GetBlades()[i]->GetNumBladesBuffer(), // buffer + 0, // offset + bladesBufferSize // range + }; + + descriptorWrites[3 * i + 0] = { + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, // sType + nullptr, // pNext + computeDescriptorSets[i], // dstSet + 0, // dstBinding + 0, // dstArrayElement + 1, // descriptorCount + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // descriptorType + nullptr, // pImageInfo + & bladesBufferInfo, // pBufferInfo + nullptr // pTexelBufferView + }; + + descriptorWrites[3 * i + 1] = { + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + nullptr, + computeDescriptorSets[i], + 1, + 0, + 1, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + nullptr, + &culledBladesBufferInfo, + nullptr + }; + + descriptorWrites[3 * i + 2] = { + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + nullptr, + computeDescriptorSets[i], + 2, + 0, + 1, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + nullptr, + &numBladesBufferInfo, + nullptr + }; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -654,7 +832,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 = {}; @@ -717,7 +895,7 @@ void Renderer::CreateComputePipeline() { 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 = {}; @@ -795,11 +973,11 @@ void Renderer::CreateFrameResources() { ); depthImageView = Image::CreateView(device, depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); - + // Transition the image for use as depth-stencil Image::TransitionLayout(device, graphicsCommandPool, depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - + // CREATE FRAMEBUFFERS framebuffers.resize(swapChain->GetCount()); for (size_t i = 0; i < swapChain->GetCount(); i++) { @@ -884,6 +1062,10 @@ void Renderer::RecordComputeCommandBuffer() { vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 1, 1, &timeDescriptorSet, 0, nullptr); // TODO: For each group of blades bind its descriptor set and dispatch + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr); + vkCmdDispatch(computeCommandBuffer, (NUM_BLADES / WORKGROUP_SIZE) + 1, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -975,14 +1157,15 @@ 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); + // Uncomment this when the buffers are populated + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); - // TODO: Bind the descriptor set for each grass blades model + // 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)); + // Uncomment this when the buffers are populated + vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); } // End render pass @@ -1045,7 +1228,7 @@ Renderer::~Renderer() { vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); vkFreeCommandBuffers(logicalDevice, computeCommandPool, 1, &computeCommandBuffer); - + vkDestroyPipeline(logicalDevice, graphicsPipeline, nullptr); vkDestroyPipeline(logicalDevice, grassPipeline, nullptr); vkDestroyPipeline(logicalDevice, computePipeline, nullptr); @@ -1057,6 +1240,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); @@ -1064,4 +1249,4 @@ Renderer::~Renderer() { DestroyFrameResources(); vkDestroyCommandPool(logicalDevice, computeCommandPool, nullptr); vkDestroyCommandPool(logicalDevice, graphicsCommandPool, nullptr); -} +} \ No newline at end of file diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..85a21ac 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,11 +57,15 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; - + VkDescriptorSetLayout grassDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; + VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; + std::vector grassDescriptorSets; + std::vector computeDescriptorSets; VkDescriptorSet timeDescriptorSet; VkPipelineLayout graphicsPipelineLayout; diff --git a/src/SwapChain.cpp b/src/SwapChain.cpp index 711fec0..ca63826 100644 --- a/src/SwapChain.cpp +++ b/src/SwapChain.cpp @@ -3,6 +3,7 @@ #include "Instance.h" #include "Device.h" #include "Window.h" +#include "Camera.h" namespace { // Specify the color channel format and color space type @@ -63,7 +64,7 @@ namespace { SwapChain::SwapChain(Device* device, VkSurfaceKHR vkSurface, unsigned int numBuffers) : device(device), vkSurface(vkSurface), numBuffers(numBuffers) { - Create(); + Create(0, 0); VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; @@ -74,14 +75,24 @@ SwapChain::SwapChain(Device* device, VkSurfaceKHR vkSurface, unsigned int numBuf } } -void SwapChain::Create() { +void SwapChain::Create(int w, int h) { + auto* instance = device->GetInstance(); const auto& surfaceCapabilities = instance->GetSurfaceCapabilities(); VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(instance->GetSurfaceFormats()); VkPresentModeKHR presentMode = chooseSwapPresentMode(instance->GetPresentModes()); - VkExtent2D extent = chooseSwapExtent(surfaceCapabilities, GetGLFWWindow()); + //VkExtent2D extent = chooseSwapExtent(surfaceCapabilities, GetGLFWWindow()); + VkExtent2D extent{ w, h }; + if (extent.width == 0 || extent.height == 0) { + extent = chooseSwapExtent(surfaceCapabilities, GetGLFWWindow()); + } + else + { + WIDTH = w; + HEIGHT = h; + } uint32_t imageCount = surfaceCapabilities.minImageCount + 1; imageCount = numBuffers > imageCount ? numBuffers : imageCount; @@ -188,9 +199,9 @@ VkSemaphore SwapChain::GetRenderFinishedVkSemaphore() const { return renderFinishedSemaphore; } -void SwapChain::Recreate() { +void SwapChain::Recreate(int w, int h) { Destroy(); - Create(); + Create(w, h); } bool SwapChain::Acquire() { @@ -204,7 +215,7 @@ bool SwapChain::Acquire() { } if (result == VK_ERROR_OUT_OF_DATE_KHR) { - Recreate(); + Recreate(0, 0); return false; } @@ -233,7 +244,7 @@ bool SwapChain::Present() { } if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { - Recreate(); + Recreate(0, 0); return false; } diff --git a/src/SwapChain.h b/src/SwapChain.h index dbafcf0..1103c90 100644 --- a/src/SwapChain.h +++ b/src/SwapChain.h @@ -17,14 +17,14 @@ class SwapChain { VkSemaphore GetImageAvailableVkSemaphore() const; VkSemaphore GetRenderFinishedVkSemaphore() const; - void Recreate(); + void Recreate(int w, int h); bool Acquire(); bool Present(); ~SwapChain(); private: SwapChain(Device* device, VkSurfaceKHR vkSurface, unsigned int numBuffers); - void Create(); + void Create(int w, int h); void Destroy(); Device* device; diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..63885b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,7 +16,7 @@ namespace { if (width == 0 || height == 0) return; vkDeviceWaitIdle(device->GetVkDevice()); - swapChain->Recreate(); + swapChain->Recreate(width, height); renderer->RecreateFrameResources(); } @@ -67,7 +67,7 @@ namespace { int main() { static constexpr char* applicationName = "Vulkan Grass Rendering"; - InitializeWindow(640, 480, applicationName); + InitializeWindow(WIDTH, HEIGHT, applicationName); unsigned int glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -90,7 +90,7 @@ int main() { swapChain = device->CreateSwapChain(surface, 5); - camera = new Camera(device, 640.f / 480.f); + camera = new Camera(device, WIDTH / HEIGHT); VkCommandPoolCreateInfo transferPoolInfo = {}; transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..28c5292 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,13 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 + +#define THRES_ORIENT 0.4 +#define THRES_VIEWFRUST 1.0 +#define DISTANCE_N 30 +#define DISTANCE_MIN 4.0 +#define DISTANCE_FADE 50.0 + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -23,34 +30,128 @@ struct Blade { // TODO: Add bindings to: // 1. Store the input blades +layout(set = 2, binding = 0) buffer Blades { + Blade blades[]; +}; + // 2. Write out the culled blades +layout(set = 2, binding = 1) buffer CulledBlades { + Blade culledBlades[]; +}; + // 3. Write the total number of blades remaining // 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; - -bool inBounds(float value, float bounds) { - return (value >= -bounds) && (value <= bounds); +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 overlap(float value, float bound) { + return (value >= -bound) && (value <= bound); +} + +bool inViewFrustum(mat4 VP, vec3 p) { + vec4 p_prime = VP * vec4(p, 1.0f); + float h = p_prime.w + THRES_VIEWFRUST; + return overlap(p_prime.x, h) && overlap(p_prime.y, h) && overlap(p_prime.z, h); + } 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 + Blade blade = blades[gl_GlobalInvocationID.x]; + vec3 v0 = vec3(blade.v0); + vec3 v1 = vec3(blade.v1); + vec3 v2 = vec3(blade.v2); + vec3 up = vec3(blade.up); + + float ori = blade.v0.w; + float h = blade.v1.w; + float w = blade.v2.w; + float stiff = blade.up.w; + + vec3 t1 = vec3(sin(ori), 0, cos(ori)); + + // apply forces - // TODO: Cull blades that are too far away or not in the camera frustum and write them + // gravity + vec4 D = vec4(0.0f, -1.0f, 0.0f, 9.8f); + vec3 gE = normalize(D.xyz) * D.w; + vec3 f = normalize(cross(t1, up)); + vec3 gF = 0.25f * length(gE) * f; + vec3 g = gE + gF; + + // recovery + vec3 iv2 = v0 + up * h; + vec3 r = (iv2 - v2) * stiff; + + // wind + vec3 wi = vec3(0.5 * sin(totalTime), 0.0f, 0.5 * sin(totalTime)); + float fd = 1.0f - abs(dot(normalize(wi), normalize(v2 - v0))); + float fr = dot(v2 - v0, up) / h; + float theta = fd * fr; + vec3 wind = wi * theta * 2.0; + + // total force + vec3 tv2 = (g + r + wind) * deltaTime; + v2 += tv2; + + // position correction + v2 -= up * min(dot(up, v2 - v0), 0.0f); + float projHorizontal = length(v2 - v0 - up * dot(up, v2 - v0)); + v1 = v0 + up * h * max(1.0f - projHorizontal / h, 0.05f * max(projHorizontal / h, 1.0f)); + + float L0 = length(v2 - v0); + float L1 = length(v1 - v0) + length(v2 - v1); + float L = (2.0f * L0 + L1) / 3.0f; + + float ratio = h / L; + vec3 v1n = v0 + ratio * (v1 - v0); + vec3 v2n = v1n + ratio * (v2 - v1); + + v1 = v1n; + v2 = v2n; + blade.v1 = vec4(v1, h); + blade.v2 = vec4(v2, w); + + // 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 + + // orient culling + vec3 dir_blade = t1; // vector along the width of the blade + vec3 dir_cull = (camera.view * vec4(0, 0, 1, 0)).xyz;// view direction + dir_cull.y = 0.0f; + dir_cull = normalize(dir_cull); + if (abs(dot(dir_cull, dir_blade)) < THRES_ORIENT) { + return; + } + + // view frustum culling + mat4 VP = camera.proj * camera.view; + vec3 midpoint = 0.25f * v0 + 0.5f * v1 + 0.25f * v2; + if (!inViewFrustum(VP, v0) || !inViewFrustum(VP, midpoint) || !inViewFrustum(VP, v2)) return; + + // distance culling + vec3 c = vec3(inverse(camera.view) * vec4(0, 0, 0, 1)); // position of camera + float dist = length(v0 - c); + if ((gl_GlobalInvocationID.x % DISTANCE_N) < (DISTANCE_N * ((dist - DISTANCE_MIN) / DISTANCE_FADE))) { + return; + } + + blades[gl_GlobalInvocationID.x] = blade; + + culledBlades[atomicAdd(numBlades.vertexCount, 1)] = blades[gl_GlobalInvocationID.x]; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..1e0c03d 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,22 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 fs_Pos; +layout(location = 1) in vec3 fs_Nor; +layout(location = 2) in float fs_v; + +layout(location = 0) out vec4 outCol; + +const vec3 green = vec3(0.404f, 0.620f, 0.231f); +const vec3 lightPos = vec3(0.0f, 50.0f, 15.0f); -layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - outColor = vec4(1.0); + vec4 diffuse = vec4(green, 1.0f); + + vec3 fs_LightVec = normalize(lightPos - fs_Pos); +// float lambert = abs(dot(normalize(fs_Nor), fs_LightVec)); + outCol = vec4(green * fs_v * fs_v + 0.2f * green, 1.0f); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..99171ec 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -1,6 +1,11 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +#define TessLevelMin 3.0 +#define TessLevelMax 15.0 +#define TessMinDist 5.0 +#define TessFadeDist 20.0 + layout(vertices = 1) out; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -9,18 +14,35 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4 tesc_v0[]; +layout(location = 1) in vec4 tesc_v1[]; +layout(location = 2) in vec4 tesc_v2[]; +layout(location = 3) in vec4 tesc_up[]; + +layout(location = 0) out vec4 tese_v0[]; +layout(location = 1) out vec4 tese_v1[]; +layout(location = 2) out vec4 tese_v2[]; +layout(location = 3) out vec4 tese_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 + tese_v0[gl_InvocationID] = tesc_v0[gl_InvocationID]; + tese_v1[gl_InvocationID] = tesc_v1[gl_InvocationID]; + tese_v2[gl_InvocationID] = tesc_v2[gl_InvocationID]; + tese_up[gl_InvocationID] = tesc_up[gl_InvocationID]; + + vec3 camPos = vec3(inverse(camera.view) * vec4(0, 0, 0, 1)); + float dist = distance(camPos, gl_in[0].gl_Position.xyz); + float tessLevel = mix(TessLevelMin, TessLevelMax, clamp((dist - TessMinDist) / TessFadeDist, 0.0, 1.0)); // TODO: Set level of tesselation - // gl_TessLevelInner[0] = ??? - // gl_TessLevelInner[1] = ??? - // gl_TessLevelOuter[0] = ??? - // gl_TessLevelOuter[1] = ??? - // gl_TessLevelOuter[2] = ??? - // gl_TessLevelOuter[3] = ??? + gl_TessLevelInner[0] = 1.0; + gl_TessLevelInner[1] = tessLevel; + gl_TessLevelOuter[0] = tessLevel; + gl_TessLevelOuter[1] = 1.0; + gl_TessLevelOuter[2] = tessLevel; + gl_TessLevelOuter[3] = 1.0; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..c1d17de 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,45 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 tese_v0[]; +layout(location = 1) in vec4 tese_v1[]; +layout(location = 2) in vec4 tese_v2[]; +layout(location = 3) in vec4 tese_up[]; + +layout(location = 0) out vec3 fs_Pos; +layout(location = 1) out vec3 fs_Nor; +layout(location = 2) out float fs_v; 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 + vec3 v0 = tese_v0[0].xyz; + vec3 v1 = tese_v1[0].xyz; + vec3 v2 = tese_v2[0].xyz; + vec3 up = tese_up[0].xyz; + + float orient = tese_v0[0].w; + float width = tese_v2[0].w; + + // bezier + vec3 p0 = mix(v0, v1, v); + vec3 p1 = mix(v1, v2, v); + vec3 p = mix(p0, p1, v); + + // bitangent + vec3 t1 = vec3(sin(orient), 0, cos(orient)); + vec3 c0 = p - width * t1; + vec3 c1 = p + width * t1; + vec3 t0 = normalize(p1 - p0); + + // Shape + float tau = 0.1f; + float t = 0.5 + (u - 0.5) * (1.0f - max(v - tau, 0.0f) / (1.0f - tau)); + + fs_Nor = normalize(cross(t0, t1)); + fs_Pos = mix(c0, c1, t); + fs_v = v; + gl_Position = camera.proj * camera.view * vec4(fs_Pos, 1.0f); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..b6c8a6e 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,6 +7,15 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { }; // TODO: Declare vertex shader inputs and outputs +layout(location = 0) in vec4 v0; +layout(location = 1) in vec4 v1; +layout(location = 2) in vec4 v2; +layout(location = 3) in vec4 up; + +layout(location = 0) out vec4 tesc_v0; +layout(location = 1) out vec4 tesc_v1; +layout(location = 2) out vec4 tesc_v2; +layout(location = 3) out vec4 tesc_up; out gl_PerVertex { vec4 gl_Position; @@ -14,4 +23,9 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + gl_Position = model * v0; + tesc_v0 = model * v0; + tesc_v1 = model * v1; + tesc_v2 = model * v2; + tesc_up = up; }