From cc861ae4080708be9e26074ce2895c278124dd4e Mon Sep 17 00:00:00 2001 From: Coco Kneer Date: Sun, 27 Oct 2024 14:51:57 -0400 Subject: [PATCH 1/7] resize bug --- src/SwapChain.cpp | 12 ++++++++---- src/SwapChain.h | 4 ++-- src/main.cpp | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/SwapChain.cpp b/src/SwapChain.cpp index 711fec0..d9d8e96 100644 --- a/src/SwapChain.cpp +++ b/src/SwapChain.cpp @@ -74,14 +74,18 @@ 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{ w, h }; + if (w == 0 || h == 0) + { + extent = chooseSwapExtent(surfaceCapabilities, GetGLFWWindow()); + } uint32_t imageCount = surfaceCapabilities.minImageCount + 1; imageCount = numBuffers > imageCount ? numBuffers : imageCount; @@ -188,9 +192,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() { diff --git a/src/SwapChain.h b/src/SwapChain.h index dbafcf0..318b41b 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 = 0, int h = 0); bool Acquire(); bool Present(); ~SwapChain(); private: SwapChain(Device* device, VkSurfaceKHR vkSurface, unsigned int numBuffers); - void Create(); + void Create(int w = 0, int h = 0); void Destroy(); Device* device; diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..2783046 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(); } From 0e86d0a35e59224e13d617d1dbf6f3acce86cb50 Mon Sep 17 00:00:00 2001 From: Coco Kneer Date: Sun, 27 Oct 2024 20:06:29 -0400 Subject: [PATCH 2/7] Compute shader, teseelation, drawing, & pipeline set up done. Need to do culling. --- src/Blades.cpp | 3 +- src/Renderer.cpp | 146 +++++++++++++++++++++++++++++++++++++-- src/Renderer.h | 3 + src/Scene.cpp | 8 +++ src/Scene.h | 3 + src/shaders/compute.comp | 69 +++++++++++++++++- src/shaders/grass.frag | 15 +++- src/shaders/grass.tesc | 29 ++++++-- src/shaders/grass.tese | 31 +++++++++ src/shaders/grass.vert | 15 ++++ 10 files changed, 308 insertions(+), 14 deletions(-) diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..0a31bab 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -45,7 +45,8 @@ 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); + //Used as Vertex buffer + 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/Renderer.cpp b/src/Renderer.cpp index b445d04..a51e06b 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,26 @@ 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 + std::vector bindings = {}; + for (int i = 0; i < 3; ++i) + { + VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + uboLayoutBinding.binding = i; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + uboLayoutBinding.pImmutableSamplers = nullptr; + bindings.push_back(uboLayoutBinding); + } + + 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 +236,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 +341,48 @@ 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()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(grassDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate 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); + + // Bind image and sampler resources to the descriptor + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = scene->GetModels()[i]->GetTextureView(); + imageInfo.sampler = scene->GetModels()[i]->GetTextureSampler(); + + 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() { @@ -360,6 +423,75 @@ 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 compute descriptor set"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + // input blade buffer + VkDescriptorBufferInfo inputBladeBufferInfo = {}; + inputBladeBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + inputBladeBufferInfo.offset = 0; + inputBladeBufferInfo.range = NUM_BLADES * sizeof(Blade); + + 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 = &inputBladeBufferInfo; + descriptorWrites[3 * i + 0].pImageInfo = nullptr; + descriptorWrites[3 * i + 0].pTexelBufferView = nullptr; + + // culled blade buffer + VkDescriptorBufferInfo culledBladeBufferInfo = {}; + culledBladeBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladeBufferInfo.offset = 0; + culledBladeBufferInfo.range = NUM_BLADES * sizeof(Blade); + + descriptorWrites[3 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 1].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 1].dstBinding = 1; + descriptorWrites[3 * i + 1].dstArrayElement = 0; + descriptorWrites[3 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 1].descriptorCount = 1; + descriptorWrites[3 * i + 1].pBufferInfo = &culledBladeBufferInfo; + descriptorWrites[3 * i + 1].pImageInfo = nullptr; + descriptorWrites[3 * i + 1].pTexelBufferView = nullptr; + + // num blades buffer + VkDescriptorBufferInfo numBladeBufferInfo = {}; + numBladeBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladeBufferInfo.offset = 0; + numBladeBufferInfo.range = sizeof(BladeDrawIndirect); + + descriptorWrites[3 * i + 2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 2].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 2].dstBinding = 2; + descriptorWrites[3 * i + 2].dstArrayElement = 0; + descriptorWrites[3 * i + 2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 2].descriptorCount = 1; + descriptorWrites[3 * i + 2].pBufferInfo = &numBladeBufferInfo; + descriptorWrites[3 * i + 2].pImageInfo = nullptr; + descriptorWrites[3 * i + 2].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +849,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 = {}; @@ -884,7 +1016,11 @@ 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 (int 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) / WORKGROUP_SIZE, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { throw std::runtime_error("Failed to record compute command buffer"); @@ -976,13 +1112,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 @@ -1057,6 +1194,7 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..36caa9b 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,12 +56,15 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + 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..3ead9f0 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -1,5 +1,6 @@ #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); @@ -29,6 +30,13 @@ void Scene::UpdateTime() { startTime = currentTime; time.deltaTime = nextDeltaTime.count(); + if (count % 1000 == 0 && count != 0) + { + std::cout << avgFPS/count << std::endl; + } + count++; + avgFPS += 1.f / time.deltaTime; + time.totalTime += time.deltaTime; memcpy(mappedData, &time, sizeof(Time)); diff --git a/src/Scene.h b/src/Scene.h index 7699d78..04a6a61 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -26,6 +26,9 @@ class Scene { std::vector models; std::vector blades; + int count = 0; + float avgFPS = 0; + high_resolution_clock::time_point startTime = high_resolution_clock::now(); public: diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..7d38321 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,11 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define GRAVITY -4.8 +#define ORIENTATION_CULLING 1 +#define VIEW_FRUSTRUM_CULLING 1 +#define DISTANCE_CULLING 1 + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -36,6 +41,21 @@ struct Blade { // uint firstInstance; // = 0 // } numBlades; +layout(set = 2, binding = 0) buffer InputBlades { + Blade inputBlades[]; +}; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade culledBlades[]; +}; + +layout(set = 2, binding = 2) buffer NumBlades { + uint vertexCount; + uint instanceCount; + uint firstVertex; + uint firstInstance; +} numBlades; + bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } @@ -43,14 +63,61 @@ bool inBounds(float value, float bounds) { void main() { // Reset the number of blades to 0 if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + numBlades.vertexCount = 0; } barrier(); // Wait till all threads reach this point // TODO: Apply forces on every blade and update the vertices in the buffer + Blade blade = inputBlades[gl_GlobalInvocationID.x]; + const vec3 v0 = blade.v0.xyz; + vec3 v1 = blade.v1.xyz; + vec3 v2 = blade.v2.xyz; + const vec3 up = blade.up.xyz; + const float orientation = blade.v0.w; + const float height = blade.v1.w; + const float width = blade.v2.w; + const float stiffness = blade.up.w; + + // Gravity + const vec4 D = vec4(0.f, 1.f, 0.f, GRAVITY); + const vec3 gE = normalize(D.xyz) * D.w; + const vec3 orientationDir = vec3(cos(orientation), 0, sin(orientation)); + const vec3 f = normalize(cross(orientationDir, up)); + const vec3 gF = 0.25f * length(gE) * f; + const vec3 g = gE + gF; + + // Recovery + const vec3 iv2 = v0 + up * height; + const vec3 r = (iv2 - v2) * stiffness; + + // Wind + const vec3 wind = vec3(5.f, -2.f, 5.f) * 0.5f * (sin(0.5f * totalTime) + cos(0.25f * totalTime + 123.f) + 1.f); + const float dirAlignment = 1.f - abs(dot(normalize(wind), normalize(v2 - v0))); + const float hAlignment = dot(v2 - v0, up) / height; + const vec3 w = wind * dirAlignment * hAlignment; + + + // Apply TotalForce + v2 += (g + r + w) * deltaTime; + const float lproj = length(v2 - v0 - up * dot((v2 - v0), up)); + v1 = v0 + height * up * max(1.f - lproj / height, 0.05f * max(lproj / height, 1.f)); + // State Validation + v2 = v2 - up * min(up * (v2 - v0), 0); + const float L0 = distance(v2, v0); + const float L1 = distance(v1, v0) + distance(v2, v1); + const float L = (L0 + L1) / 2.f; + const float ratio = height / L; + v1 = v0 + ratio * (v1 - v0); + v2 = v1 + ratio * (v2 - v1); + + blade.v1.xyz = v1.xyz; + blade.v2.xyz = v2.xyz; + inputBlades[gl_GlobalInvocationID.x] = blade; // 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 + const uint idx = atomicAdd(numBlades.vertexCount, 1); + culledBlades[idx] = inputBlades[gl_GlobalInvocationID.x]; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..cc9f7bd 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -8,10 +8,23 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 nor; +layout(location = 1) in float heightFrac; + layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color + const vec3 lightDir = normalize(vec3(camera.view[0][2], camera.view[1][2], camera.view[2][2])); + const float diffuse = max(dot(nor, lightDir), 0.f); + + // albedo, let grass be lighter at the tip + const vec3 topColor = vec3(0.1f, 0.8f, 0.1f); + const vec3 bottomColor = vec3(0.0, 0.5f, 0.0); + const vec3 baseColor = mix(bottomColor, topColor, heightFrac); + + const float ambient = 0.1f; - outColor = vec4(1.0); + const vec3 col = baseColor * (diffuse + ambient); + outColor = vec4(col, 1.0); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..99791f1 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -1,6 +1,8 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +#define TESSELATION_LEVEL 6 + layout(vertices = 1) out; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -9,18 +11,31 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4 v0_cs[]; +layout(location = 1) in vec4 v1_cs[]; +layout(location = 2) in vec4 v2_cs[]; +layout(location = 3) in vec4 up_cs[]; + +layout(location = 0) out vec4 v0_es[]; +layout(location = 1) out vec4 v1_es[]; +layout(location = 2) out vec4 v2_es[]; +layout(location = 3) out vec4 up_es[]; void main() { // Don't move the origin location of the patch - gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; + gl_out[gl_InvocationID].gl_Position = v0_cs[gl_InvocationID]; // TODO: Write any shader outputs + v0_es[gl_InvocationID] = v0_cs[gl_InvocationID]; + v1_es[gl_InvocationID] = v1_cs[gl_InvocationID]; + v2_es[gl_InvocationID] = v2_cs[gl_InvocationID]; + up_es[gl_InvocationID] = up_cs[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] = ??? + gl_TessLevelInner[0] = TESSELATION_LEVEL; + gl_TessLevelInner[1] = TESSELATION_LEVEL; + gl_TessLevelOuter[0] = TESSELATION_LEVEL; + gl_TessLevelOuter[1] = TESSELATION_LEVEL; + gl_TessLevelOuter[2] = TESSELATION_LEVEL; + gl_TessLevelOuter[3] = TESSELATION_LEVEL; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..5f93f1c 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,41 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 v0_es[]; +layout(location = 1) in vec4 v1_es[]; +layout(location = 2) in vec4 v2_es[]; +layout(location = 3) in vec4 up_es[]; + +layout(location = 0) out vec3 nor; +layout(location = 1) out float heightFrac; 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 + const vec3 v0 = v0_es[0].xyz; + const vec3 v1 = v1_es[0].xyz; + const vec3 v2 = v2_es[0].xyz; + const float orientation = v0_es[0].w; + const float height = v1_es[0].w; + const float width = v2_es[0].w; + + // normal + const vec3 t1 = vec3(cos(orientation), 0, sin(orientation)); + const vec3 a = v0 + v * (v1 - v0); + const vec3 b = v1 + v * (v2 - v1); + const vec3 c = a + v * (b - a); + const vec3 c0 = c - width * t1; + const vec3 c1 = c + width * t1; + const vec3 t0 = normalize(b - a); + nor = normalize(cross(t0, t1)); + + // position + const float t = u + 0.5f * v - u * v; //triangle + const vec3 pos = mix(c0, c1, t); + gl_Position = camera.proj * camera.view * vec4(pos, 1.f); + + // height fraction + heightFrac = pos.y / height; } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..f6dfd72 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,6 +7,16 @@ 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 v0_cs; +layout(location = 1) out vec4 v1_cs; +layout(location = 2) out vec4 v2_cs; +layout(location = 3) out vec4 up_cs; + out gl_PerVertex { vec4 gl_Position; @@ -14,4 +24,9 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + v0_cs = model * v0; + v1_cs = model * v1; + v2_cs = model * v2; + up_cs = up; + gl_Position = v0_cs; } From 75cda741d12f365cca1b87053ade9597c81fd9c9 Mon Sep 17 00:00:00 2001 From: Coco Kneer Date: Tue, 29 Oct 2024 19:45:14 -0400 Subject: [PATCH 3/7] base code done --- src/Renderer.cpp | 2 +- src/shaders/compute.comp | 40 +++++++++++++++++++++++++++++++++++++++- src/shaders/grass.frag | 4 ++-- src/shaders/grass.tesc | 14 ++++++++++++-- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/Renderer.cpp b/src/Renderer.cpp index a51e06b..dc9ad2d 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -732,7 +732,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 /*Wireframe: VK_POLYGON_MODE_LINE*/; rasterizer.lineWidth = 1.0f; rasterizer.cullMode = VK_CULL_MODE_NONE; rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 7d38321..695622a 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -4,8 +4,14 @@ #define WORKGROUP_SIZE 32 #define GRAVITY -4.8 #define ORIENTATION_CULLING 1 +#define ORIENTATION_CULLING_THRESHOLD 0.9 #define VIEW_FRUSTRUM_CULLING 1 +#define VIEW_FRUSTRUM_CULLING_TOLERANCE -0.01 #define DISTANCE_CULLING 1 +#define DISTANCE_CULLING_FREQUENCY 5 +#define DISTANCE_CULLING_MAX 20.0 + +#define ENABLE_CULLING_Z 0 layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; @@ -60,6 +66,15 @@ bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +bool inViewFrustrum(const vec3 p){ + const vec4 pPrime = camera.proj * camera.view * vec4(p, 1.f); + const float h = pPrime.w + VIEW_FRUSTRUM_CULLING_TOLERANCE; +#if ENABLE_CULLING_Z + return (inBounds(pPrime.x, h) && inBounds(pPrime.y, h) && inBounds(pPrime.z, h)); +#endif + return (inBounds(pPrime.x, h) && inBounds(pPrime.y, h)); +} + void main() { // Reset the number of blades to 0 if (gl_GlobalInvocationID.x == 0) { @@ -91,7 +106,7 @@ void main() { const vec3 r = (iv2 - v2) * stiffness; // Wind - const vec3 wind = vec3(5.f, -2.f, 5.f) * 0.5f * (sin(0.5f * totalTime) + cos(0.25f * totalTime + 123.f) + 1.f); + const vec3 wind = vec3(5.f, -2.f, 5.f) * 0.5f * (sin(0.75f * totalTime) + cos(0.5f * totalTime + 123.f) + 1.f); const float dirAlignment = 1.f - abs(dot(normalize(wind), normalize(v2 - v0))); const float hAlignment = dot(v2 - v0, up) / height; const vec3 w = wind * dirAlignment * hAlignment; @@ -118,6 +133,29 @@ void main() { // 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 + const vec3 cameraPos = inverse(camera.view)[3].xyz; +#if ORIENTATION_CULLING + const vec3 dirc = normalize(cameraPos - v0); + const vec3 dirb = normalize(orientationDir); + if (abs(dot(dirc, dirb)) > ORIENTATION_CULLING_THRESHOLD){ + return; + } +#endif + +#if VIEW_FRUSTRUM_CULLING + const vec3 m = 0.25f * v0 + 0.5f * v1 + 0.25f * v2; + if (!inViewFrustrum(v0) && !inViewFrustrum(v2) && !inViewFrustrum(m)){ + return; + } +#endif + +#if DISTANCE_CULLING + const float dproj = length(v0 - cameraPos - up * dot((v0 - cameraPos), up)); + if (mod(gl_GlobalInvocationID.x, DISTANCE_CULLING_FREQUENCY) > floor(DISTANCE_CULLING_FREQUENCY * (1.f - dproj/DISTANCE_CULLING_MAX))){ + return; + } +#endif + const uint idx = atomicAdd(numBlades.vertexCount, 1); culledBlades[idx] = inputBlades[gl_GlobalInvocationID.x]; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index cc9f7bd..ddccadc 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -15,8 +15,8 @@ layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - const vec3 lightDir = normalize(vec3(camera.view[0][2], camera.view[1][2], camera.view[2][2])); - const float diffuse = max(dot(nor, lightDir), 0.f); + const vec3 lightDir = normalize(vec3(transpose(camera.view)[2].xyz)); + const float diffuse = abs(dot(nor, lightDir)); // albedo, let grass be lighter at the tip const vec3 topColor = vec3(0.1f, 0.8f, 0.1f); diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index 99791f1..0b46086 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -1,10 +1,12 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable -#define TESSELATION_LEVEL 6 - layout(vertices = 1) out; +#define TESSELATION_LEVEL_MIN 1.0 +#define TESSELATION_LEVEL_MAX 10.0 +#define TESSELATION_DIST 20.0 + layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; @@ -21,6 +23,12 @@ layout(location = 1) out vec4 v1_es[]; layout(location = 2) out vec4 v2_es[]; layout(location = 3) out vec4 up_es[]; + +int getTessLevel(float distance) +{ + return int(mix(TESSELATION_LEVEL_MIN, TESSELATION_LEVEL_MAX, max(0.f, (TESSELATION_DIST - distance)) / TESSELATION_DIST)); +} + void main() { // Don't move the origin location of the patch gl_out[gl_InvocationID].gl_Position = v0_cs[gl_InvocationID]; @@ -32,6 +40,8 @@ void main() { up_es[gl_InvocationID] = up_cs[gl_InvocationID]; // TODO: Set level of tesselation + const vec3 cameraPos = inverse(camera.view)[3].xyz; + const int TESSELATION_LEVEL = getTessLevel(distance(v0_cs[gl_InvocationID].xyz, cameraPos)); gl_TessLevelInner[0] = TESSELATION_LEVEL; gl_TessLevelInner[1] = TESSELATION_LEVEL; gl_TessLevelOuter[0] = TESSELATION_LEVEL; From 31e1c6abc82f618c7ee223a4502e2c2c7388ef79 Mon Sep 17 00:00:00 2001 From: Coco Kneer Date: Tue, 29 Oct 2024 21:55:28 -0400 Subject: [PATCH 4/7] wind wave adjustment --- src/shaders/compute.comp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 695622a..1d11e0f 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -106,7 +106,7 @@ void main() { const vec3 r = (iv2 - v2) * stiffness; // Wind - const vec3 wind = vec3(5.f, -2.f, 5.f) * 0.5f * (sin(0.75f * totalTime) + cos(0.5f * totalTime + 123.f) + 1.f); + const vec3 wind = vec3(5.f, -2.f, 5.f) * 0.5f * (sin(0.7f * totalTime) + cos(0.25f * totalTime + 123.f) + 1.f); const float dirAlignment = 1.f - abs(dot(normalize(wind), normalize(v2 - v0))); const float hAlignment = dot(v2 - v0, up) / height; const vec3 w = wind * dirAlignment * hAlignment; From 5934d3be3e67aecbb6f54340173c2d1b6bde72fc Mon Sep 17 00:00:00 2001 From: Coco Kneer Date: Tue, 29 Oct 2024 22:12:30 -0400 Subject: [PATCH 5/7] parameter tuning --- src/shaders/compute.comp | 2 +- src/shaders/grass.tesc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 1d11e0f..e87b25f 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -9,7 +9,7 @@ #define VIEW_FRUSTRUM_CULLING_TOLERANCE -0.01 #define DISTANCE_CULLING 1 #define DISTANCE_CULLING_FREQUENCY 5 -#define DISTANCE_CULLING_MAX 20.0 +#define DISTANCE_CULLING_MAX 30.0 #define ENABLE_CULLING_Z 0 diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index 0b46086..172008e 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -4,8 +4,8 @@ layout(vertices = 1) out; #define TESSELATION_LEVEL_MIN 1.0 -#define TESSELATION_LEVEL_MAX 10.0 -#define TESSELATION_DIST 20.0 +#define TESSELATION_LEVEL_MAX 15.0 +#define TESSELATION_DIST 30.0 layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; From 746a6ac2bdd7922d42a64689083aef0e96e27023 Mon Sep 17 00:00:00 2001 From: Coco Kneer Date: Tue, 29 Oct 2024 23:08:57 -0400 Subject: [PATCH 6/7] fps logic --- src/Scene.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Scene.cpp b/src/Scene.cpp index 3ead9f0..0700972 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -30,12 +30,14 @@ void Scene::UpdateTime() { startTime = currentTime; time.deltaTime = nextDeltaTime.count(); + + count++; + + avgFPS = float(count) / time.totalTime; if (count % 1000 == 0 && count != 0) { - std::cout << avgFPS/count << std::endl; + std::cout << avgFPS << std::endl; } - count++; - avgFPS += 1.f / time.deltaTime; time.totalTime += time.deltaTime; From 905a69641a05c7dc2d6dacd1b42c96be80d967bc Mon Sep 17 00:00:00 2001 From: Coco Kneer <32113955+JiaoMaMa@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:43:09 -0400 Subject: [PATCH 7/7] Update README.md --- README.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 20ee451..0822a8f 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,107 @@ 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) +* Christine Kneer + * https://www.linkedin.com/in/christine-kneer/ + * https://www.christinekneer.com/ +* Tested on: Windows 11, i7-13700HX @ 2.1GHz 32GB, RTX 4060 8GB (Personal Laptop) -### (TODO: Your README) +## Part 1: Introduction -*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. +In this project, I used Vulkan to implement a grass simulator and renderer based on the paper [Responsive Real-Time Grass Grass Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf). The paper leverages compute shaders and tesselation to render and simulate physically accurate grass in real time. + +

+ +

+ +### Part 1.1: The Grass Blade Model + +Based on the paper, grass is represented as Bezier Curve with 3 control points. + +

+image +

+ +Each Bezier curve has three control points. +* `v0`: the position of the grass blade on the geomtry +* `v1`: a Bezier curve guide that is always "above" `v0` with respect to the grass blade's up vector (explained soon) +* `v2`: a physical guide for which we simulate forces on + +We also need to store per-blade characteristics that will help us simulate and tessellate our grass blades correctly. +* `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at `v0` +* Orientation: the orientation of the grass blade's face +* Height: the height of the grass blade +* Width: the width of the grass blade's face +* Stiffness coefficient: the stiffness of our grass blade, which will affect the force computations on our blade + + +### Part 1.2: Simulating Forces + +Forces (gravity, recovery, and wind) are applied to Bezier Curve represented grass blades. + +|![without](https://github.com/user-attachments/assets/11b952b6-9810-440f-8e23-23e25d61e814)|![with](https://github.com/user-attachments/assets/8cd90295-49af-443b-9b64-cb8183d82b0c)| +|:--:|:--:| +|*Without Physics*|*With Physics*| + +### Part 1.3: Culling + +In order to further optimize our simulator for real time, we need to cull glass blades that do not need to be rendered due to a variety of reasons. +* **Orientation Culling**: Cull grass blades whose front face direction is perpendicular to the camera's view vector, in which case the blade does not have width. +* **View-Frustrum Culling**: Cull grass blades that are outside of the view-frustum, effectively cannot be seen by the camera. +* **Distance Culling**: Cull grass blades that are far enough that end up smaller than a pixel. + +|![ori_cull](https://github.com/user-attachments/assets/3a91c1f4-1ee8-41e2-a8ba-362de6151707)|![view_cull](https://github.com/user-attachments/assets/379cff28-624f-42b5-973f-4bf8c623eb3e)|![dist_cull](https://github.com/user-attachments/assets/a6800bb9-f259-447c-b91f-505b2f5ec2bb)| +|:--:|:--:|:--:| +|*Orientation Culling*|*View Frustrum Culling*|*Distance Culling*| + +**Note**: The above demos were produced with enhanced parameters to better showcase the features. + +### Part 1.4: Tesselation + +Finally, Bezier Curves need to be tesselated into polygons to be processed by the grass graphics pipeline. In this simulator, I chose to tesselate into trangles. The tesselation level is a function of how far the grass blade is from the camera, because further objects require fewer details to be represented accurately. + +|![LOD](https://github.com/user-attachments/assets/1feb473a-29e5-44d5-bdd9-89951feea74a)| +|:--:| +|*Dynamic LOD*| + +**Note**: The above demo was produced with enhanced parameters to better showcase the feature. + +## Part 2: Performance Analysis + +In this part, we discuss the performance of our simulator under different performance improvement techiniques. + +### Part 2.1: Culling vs # of Grass Blades + +|![chart (4)](https://github.com/user-attachments/assets/3b0d9f37-a470-40d6-8a67-9e8e8d784420)| +|:--:| +|*Hardcoded Tesselation Level = 8*| + +As the number of grass blades increases, the FPS of both with & without culling significantly drops. This is expected since more blades equates to more computational workload in the compute shader. However, it can be seen from the digram above that there is a consistent performance boost associated with using culling. Culling effectively reduces the amount of work. + +It is also interesting to note that the performance benifit introduced by culling is more significant as the number of grass blades increases. This may not be straightforward from the graph itself. + +At **2^10** number of blades, culling increases the FPS from 2300 to 2850. At **2^18** number of blades, culling inreases the FPS from 26 to 48. At first glance, a 550 FPS increase looks more prominent than a 22 FPS increase. However, the relative impact of the FPS gain is more meaningful in lower FPS scenarios. + +Here's the math to clarify: +* At 2300 FPS, the frame time is approximately 1/2300 = 0.435 ms. +* At 2850 FPS, the frame time is approximately 1/2850 = 0.351 ms. +* **The difference in frame time is 0.435 − 0.351 = 0.084 ms, which is very small.** + +Now, consider the case of **lower FPS**: +* At 26 FPS, the frame time is approaximately 1/26 = 38.46 ms. +* At 48 FPS, the frame time is approximately 1/48 = 20.83 ms. +* **The difference in frame time is a whopping 38.46 - 20.83 = 17.63 ms, which is MUCH larger.** + +This means that culling is more substantial as the number of grass blades increases, which is also expected since more blades means that we will probably cull more blades as well. + +### Part 2.2: Culling Methods + +As discussed in part 2.2, culling is more substantial at hight number of grass blades, so let us now compare the three different culling methods at 2^18 grass blades. + +|![chart (5)](https://github.com/user-attachments/assets/b6006729-b663-4c6e-823a-df1b60456c39)| +|:--:| +|*2^18 Grass Blades, Hardcoded Tesselation Level = 8*| + +As seen from the graph above, all three culling methods introduces some performance boost, to different extent. View-frustrum culling seems to have less of an impact compared to orientation and distance culling, but the three combined results in the best performance. This is also expected since each culling method culls blades according to different criteria, and the three combined would cull the most blades. + +However, it should be noted that the above test is not sound since each culling method has tunable parameters. Admittedly, these parameters and the camera position & orientation would definitely have an impact on how much performance is increased.