diff --git a/README.md b/README.md index 20ee451..61636d2 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,51 @@ 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) +* Edward Zhang + * https://www.linkedin.com/in/edwardjczhang/ + * https://zedward23.github.io/personal_Website/ + +* Tested on: Windows 10 Home, i7-11800H @ 2.3GHz, 16.0GB, NVIDIA GeForce RTX 3060 Laptop GPU -### (TODO: Your README) +## Vulkan Grass Rendering + +This project is an implementation of 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). + +## Tessellating Grass +![](img/NoWind.png) + +We populate our scene by getting the necessary pipelines and tessellation shaders to depict each blade of grass as a bezier curve. As there are no forces active, the curves remain static. + +## Force Application +![](img/grassWind.gif) + +To give the grass a more dynamic appearance, we apply three forces to the control points of our bezier grass, as per the paper being implemented. +1. Gravity - Gives the grass a natural curve +2. Recovery - Takes the stiffness of a blade of grass into account; otherwise the gravity would cause all the blades of grass to fall to the ground. +3. Wind - Simulated wind using Fractional Brownian Motion perterbed by sinusoidal functions. + +The applied forces together per frame update yields the result above. + +## Culling Optimizations + +### Frustrum and Distance Cull +![](img/FrustrumAndDistanceCulling.gif) + +As an optimization, we do not render any grass that is either too far away from from the camera or falls outside of the camera's view frustrum. This way, we only render grass that is actually seen by the camera. + +### Orientation Cull +![](img/orientCull.gif) + +Due to the fact that grass blades are being represented as 2D bezier curves, another optimization is simply not drawing the grass that has its flat portion faces the camera (within a certain margin). + +## Performance Tests +![](img/Graph1.png) + +Grass blade counts are increased by powers of 2; here the X axis is transformed by the log operation for the sake of readability; this transformed X axis better demonstrates diminshing returns of the culling optimization based on the increases in grass blades in the scene. + +![](img/Graph2.png) + +Here's the performance chart without the log transformation. + +Here are two graphs presenting the framerate of our grass scene vs the total number of grass blades in the scene with and without the culling optimizations implemented above. What we can see is that the optimizations allow us to maintain a higher framerate for larger amounts of grass before also succumbing to the computational demands the sheer density of grass scenes entails. -*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. diff --git a/build/.vs/cis565_project4_vulkan_grass_rendering/v16/ipch/AutoPCH/2a6ae2afcc232bc4/SWAPCHAIN.ipch b/build/.vs/cis565_project4_vulkan_grass_rendering/v16/ipch/AutoPCH/2a6ae2afcc232bc4/SWAPCHAIN.ipch new file mode 100644 index 0000000..436d3cf Binary files /dev/null and b/build/.vs/cis565_project4_vulkan_grass_rendering/v16/ipch/AutoPCH/2a6ae2afcc232bc4/SWAPCHAIN.ipch differ diff --git a/img/FrustrumAndDistanceCulling.gif b/img/FrustrumAndDistanceCulling.gif new file mode 100644 index 0000000..117e245 Binary files /dev/null and b/img/FrustrumAndDistanceCulling.gif differ diff --git a/img/Graph1.png b/img/Graph1.png new file mode 100644 index 0000000..7b01216 Binary files /dev/null and b/img/Graph1.png differ diff --git a/img/Graph2.png b/img/Graph2.png new file mode 100644 index 0000000..09a75d2 Binary files /dev/null and b/img/Graph2.png differ diff --git a/img/NoWind.png b/img/NoWind.png new file mode 100644 index 0000000..608e5ea Binary files /dev/null and b/img/NoWind.png differ diff --git a/img/grassWind.gif b/img/grassWind.gif new file mode 100644 index 0000000..b0f529e Binary files /dev/null and b/img/grassWind.gif differ diff --git a/img/orientCull.gif b/img/orientCull.gif new file mode 100644 index 0000000..b33b699 Binary files /dev/null and b/img/orientCull.gif differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..0142372 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -45,7 +45,7 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode indirectDraw.firstInstance = 0; BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory); - BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory); + BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory); BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory); } diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..24a76ba 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 << 22; 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/Instance.cpp b/src/Instance.cpp index 7f6b01c..4c8f9dd 100644 --- a/src/Instance.cpp +++ b/src/Instance.cpp @@ -40,7 +40,6 @@ namespace { return VK_FALSE; } } - Instance::Instance(const char* applicationName, unsigned int additionalExtensionCount, const char** additionalExtensions) { // --- Specify details about our application --- VkApplicationInfo appInfo = {}; @@ -88,6 +87,11 @@ VkPhysicalDevice Instance::GetPhysicalDevice() { return physicalDevice; } +const VkPhysicalDeviceProperties& Instance::GetPhysicalDeviceProperties() const { + return physicalDeviceProperties; +} + + const VkSurfaceCapabilitiesKHR& Instance::GetSurfaceCapabilities() const { return surfaceCapabilities; } diff --git a/src/Instance.h b/src/Instance.h index afc54c5..76f68a4 100644 --- a/src/Instance.h +++ b/src/Instance.h @@ -16,6 +16,7 @@ class Instance { VkInstance GetVkInstance(); VkPhysicalDevice GetPhysicalDevice(); + const VkPhysicalDeviceProperties& GetPhysicalDeviceProperties() const; const QueueFamilyIndices& GetQueueFamilyIndices() const; const VkSurfaceCapabilitiesKHR& GetSurfaceCapabilities() const; const std::vector& GetSurfaceFormats() const; @@ -34,6 +35,7 @@ class Instance { void initDebugReport(); + VkPhysicalDeviceProperties physicalDeviceProperties; VkInstance instance; VkDebugReportCallbackEXT debugReportCallback; std::vector deviceExtensions; diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..aba8c0c 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), @@ -198,6 +198,37 @@ 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 grassInLayoutBinding = {}; + grassInLayoutBinding.binding = 0; + grassInLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + grassInLayoutBinding.descriptorCount = 1; + grassInLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + grassInLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding grassOutLayoutBinding = {}; + grassOutLayoutBinding.binding = 1; + grassOutLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + grassOutLayoutBinding.descriptorCount = 1; + grassOutLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + grassOutLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding numRemainLayoutBinding = {}; + numRemainLayoutBinding.binding = 2; + numRemainLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numRemainLayoutBinding.descriptorCount = 1; + numRemainLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numRemainLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { grassInLayoutBinding, grassOutLayoutBinding, numRemainLayoutBinding }; + + 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 +247,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(3 * scene->GetBlades().size()) } }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,6 +352,35 @@ 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->GetBlades().size()); + VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout }; // cf. CreateModelDescriptorSetLayout() binding 0 + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(grassDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate grass descriptor set"); + } + + std::vector descriptorWrites(grassDescriptorSets.size()); + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo modelBufferInfo = {}; + modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + modelBufferInfo.offset = 0; + modelBufferInfo.range = sizeof(ModelBufferObject); + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = grassDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = &modelBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +421,67 @@ 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->GetModels().size()); + // storage buffers to hold the grass blades, the culled grass blades, and the output number of grass blades + 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; + 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 bladesBufferInfo = {}; + bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + bladesBufferInfo.offset = 0; + bladesBufferInfo.range = NUM_BLADES * sizeof(Blade); // buffer size + + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect); + + 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 = &bladesBufferInfo; + 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 = &culledBladesBufferInfo; + 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 = &numBladesBufferInfo; + descriptorWrites[3 * i + 2].pImageInfo = nullptr; + descriptorWrites[3 * i + 2].pTexelBufferView = nullptr; + } + + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +839,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 +917,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 +1006,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) { @@ -976,13 +1102,14 @@ void Renderer::RecordCommandBuffers() { VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() }; VkDeviceSize offsets[] = { 0 }; // TODO: Uncomment this when the buffers are populated - // vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); // TODO: Bind the descriptor set for each grass blades model + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &grassDescriptorSets[j], 0, nullptr); // Draw // TODO: Uncomment this when the buffers are populated - // vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); + vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); } // End render pass @@ -1045,7 +1172,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 +1184,8 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + //Added Destructors + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); @@ -1064,4 +1193,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..7c9ab09 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,11 +56,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/main.cpp b/src/main.cpp index 8bf822b..dd0a2b6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,8 @@ #include "Camera.h" #include "Scene.h" #include "Image.h" +#include +#include Device* device; SwapChain* swapChain; @@ -143,10 +145,31 @@ int main() { glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback); glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback); + double fps = 0; + double timebase = 0; + int frame = 0; + while (!ShouldQuit()) { glfwPollEvents(); + + frame++; + double time = glfwGetTime(); + + double delta = time - timebase; + if (delta > 1.0) { + fps = frame / (delta); + timebase = time; + frame = 0; + } + scene->UpdateTime(); renderer->Frame(); + + std::ostringstream ss; + ss << "("; + ss.precision(1); + ss << std::fixed << fps << " fps)" << applicationName; + glfwSetWindowTitle(GetGLFWWindow(), ss.str().c_str()); } vkDeviceWaitIdle(device->GetVkDevice()); @@ -154,6 +177,8 @@ int main() { vkDestroyImage(device->GetVkDevice(), grassImage, nullptr); vkFreeMemory(device->GetVkDevice(), grassImageMemory, nullptr); + vkDestroySurfaceKHR(instance->GetVkInstance(), surface, nullptr); + delete scene; delete plane; delete blades; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..308b437 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,10 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define CULL 1 +#define WIND 1 +#define RESIST 1 + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -29,28 +33,194 @@ 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 Blades { + Blade blades[]; +}; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade 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); } +// Cosine Noise Function from 460 +float rand3D(vec3 p, float time) { + return cos(float(time) * 0.008) * sin(length(vec3( + dot(p, vec3(164.1, 348.1, 392.1)), + dot(p, vec3(836.5, 287.3, 723.1)), + dot(p, vec3(265.4, 148.2, 222.2)) + ) * 0.01 )); +} + +float interpNoise3D(vec3 p, float time) +{ + int intX = int(floor(p.x)); + float fractX = fract(p.x); + + int intY = int(floor(p.y)); + float fractY = fract(p.y); + + int intZ = int(floor(p.z)); + float fractZ = fract(p.z); + + float v1 = rand3D(vec3(intX, intY, intZ), time); + float v2 = rand3D(vec3(intX + 1, intY, intZ), time); + float v3 = rand3D(vec3(intX, intY + 1, intZ), time); + float v4 = rand3D(vec3(intX + 1, intY + 1, intZ), time); + float v5 = rand3D(vec3(intX, intY, intZ + 1), time); + float v6 = rand3D(vec3(intX + 1, intY, intZ + 1), time); + float v7 = rand3D(vec3(intX, intY + 1, intZ + 1), time); + float v8 = rand3D(vec3(intX + 1, intY + 1, intZ + 1), time); + + float i1 = mix(v1, v2, fractX); + float i2 = mix(v3, v4, fractY); + float i3 = mix(v5, v6, fractY); + float i4 = mix(v7, v8, fractZ); + float i5 = mix(v1, v3, fractZ); + float i6 = mix(v2, v4, fractX); + float i7 = mix(v5, v7, fractZ); + float i8 = mix(v6, v8, fractX); + + float mix1 = mix(mix(i1, i2, fractZ), mix(i3, i4, fractX), fractY); + float mix2 = mix(mix(i5, i6, fractX), mix(i7, i8, fractY), fractZ); + float finalMix = mix(mix1, mix2, fractX); + return finalMix; +} + +vec3 fbm(vec3 p, float time) +{ + float total = 0.f; + + float persist = 0.5f; + float freq = 1.f; + float amp = 2.f; + + for (int i = 1; i <= 6; i++) { + total += amp * interpNoise3D(freq * p, time); + freq *= 2.0; + amp *= persist; + } + + return 9 * vec3((1 - total) * sin(time), 0, total * cos(time)); +} + + 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 idx = gl_GlobalInvocationID.x; - // 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 -} + vec3 v0 = vec3(blades[idx].v0); + vec3 v1 = vec3(blades[idx].v1); + vec3 v2 = vec3(blades[idx].v2); + vec3 up = vec3(blades[idx].up); + + float theta = blades[idx].v0.w; + float bladeHeight = blades[idx].v1.w; + float width = blades[idx].v2.w; + float stiffness = blades[idx].up.w; + + vec3 bitangent = vec3(width * cos(theta), 0, width * sin(theta)); + + // gravity + float environmental = length(vec3(0.0, -1.0, 0.0) * 9.8); + vec3 norm = cross(up, bitangent); + vec3 front = 0.25 * norm * environmental; + vec3 gravity = front + environmental; + + // recovery + + vec3 v2_0 = v0 + bladeHeight * up; +#if RESIST + vec3 recovery = (v2_0 - v2) * stiffness; +#else + vec3 recovery = vec3(0,0,0); +#endif + // wind + vec3 windDir = fbm(v0, totalTime); + float fd = 1 - length(dot(normalize(windDir), normalize(v2 - v0))); + float fr = dot(normalize(v2 - v0), up) / bladeHeight; + float windAlignment = 3 * fd * fr; + +#if WIND + vec3 wind = windDir * windAlignment; +#else + vec3 wind = vec3(0,0,0); +#endif + + vec3 deltaV2 = (gravity + recovery + wind) * deltaTime; + + v2 += deltaV2; + + v2 = v2 - up * min(dot(up, (v2 - v0)), 0.0); + + float lProj = length(v2 - v0 - up * dot((v2 - v0), up)); + v1 = v0 + bladeHeight * up * + max(1 - (lProj / bladeHeight), + 0.05 * max(lProj / bladeHeight, + 1)); + + float l0 = distance(v2, v0); + float l1 = distance(v2, v1) + distance(v1, v0); + float len = (2 * l0 + 2 * l1) / 4; + float r = bladeHeight / len; + + v1 = v0 + r * (v1 - v0); + v2 = v1 + r * (v2 - v1); + + blades[idx].v1.xyz = v1.xyz; + blades[idx].v2.xyz = v2.xyz; + +#if CULL + vec3 cameraPos = vec3(inverse(camera.view)[3]); + vec3 camToBladeVec = v0 - cameraPos - up * dot(v0 - cameraPos, up); + + float projDist = length(camToBladeVec); + int maxDist = 40; + int n = 4; + + float cullFactor = floor(n * (1 - projDist / maxDist)); + + if (idx % n >= cullFactor) { + // GET CULLED BASED ON DISTANCE BOI + return; + } + + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; + vec4 p1 = camera.proj * camera.view * vec4(m, 1.0); + float h = p1.w * 0.95; + bool inView = inBounds(p1.x, h) && inBounds(p1.y, h); + + if (!inView) { + // GET CULLED OUT OF THE FRUSTRUM BOI + return; + } + + + float orientation = abs(dot(camToBladeVec, bitangent)); + + if (orientation > 0.9) { + // get CULLED BASED ON ORIENTATION BOI + return; + } + +#endif + + uint originalVertCount = atomicAdd(numBlades.vertexCount, 1); + culledBlades[originalVertCount] = blades[idx]; +} \ No newline at end of file diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..5975cea 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,14 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec2 in_uv; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color + vec3 root = vec3(0.0, 0.3, 0.0); + vec3 tip = vec3(0.3, 1, 0.0); - outColor = vec4(1.0); + outColor = vec4(mix(root, tip, in_uv.y), 1.); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..44a325a 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -9,18 +9,51 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: 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; + +#define TESS_LVL_INNER 5 +#define TESS_LVL_OUTER 5 + 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 + 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] = ??? + float tessLvl = 0.f; + vec3 camPos = inverse(camera.view)[3].xyz; + float dist = length(in_v0[0].xyz - camPos); + if (dist < 2.0) { + tessLvl = 20.0; + } else if (dist < 5.0) { + tessLvl = 10.0; + } else if (dist < 10.0) { + tessLvl = 5.0; + } else if (dist < 15.0) { + tessLvl = 2.5; + } else { + tessLvl = 1.0; + } + + gl_TessLevelInner[0] = tessLvl; + gl_TessLevelInner[1] = tessLvl; + + gl_TessLevelOuter[0] = tessLvl; + gl_TessLevelOuter[1] = tessLvl; + gl_TessLevelOuter[2] = tessLvl; + gl_TessLevelOuter[3] = tessLvl; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..9bdcdc0 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,39 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: 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 vec2 out_uv; void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; + vec3 v0 = in_v0[0].xyz; + vec3 v1 = in_v1[0].xyz; + vec3 v2 = in_v2[0].xyz; + + float dir_angle = in_v0[0].w; + float width = in_v2[0].w; + + // interp along height of blade w/ v + vec3 a = mix(v0, v1, v); + vec3 b = mix(v1, v2, v); + vec3 c = mix(a, b, v); + + // from Blades.cpp convention, y(height) 0 + vec3 t0 = normalize(b - a); + vec3 t1 = normalize(vec3(-cos(dir_angle), 0, sin(dir_angle))); + + vec3 c0 = c - width * t1; + vec3 c1 = c + width * t1; + + float t = (u + 0.5f * v - u * v); + // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + out_uv = vec2(u, v); + gl_Position = camera.proj * camera.view * vec4(mix(c0, c1, t), 1.); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..1852396 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,11 +7,20 @@ 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; -out gl_PerVertex { - vec4 gl_Position; -}; +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() { // TODO: Write gl_Position and any other shader outputs + out_v0 = vec4((model * vec4(in_v0.xyz, 1.f)).xyz, in_v0.w); + out_v1 = vec4((model * vec4(in_v1.xyz, 1.f)).xyz, in_v1.w); + out_v2 = vec4((model * vec4(in_v2.xyz, 1.f)).xyz, in_v2.w); + out_up = vec4((model * vec4(in_up.xyz, 1.f)).xyz, in_up.w); }