diff --git a/README.md b/README.md index 20ee451..70c5533 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,75 @@ 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) +* Yifan Lu + * [LinkedIn](https://www.linkedin.com/in/yifan-lu-495559231/), [personal website](http://portfolio.samielouse.icu/index.php/category/featured/) +* Tested on: Windows 11, AMD Ryzen 7 5800H 3.20 GHz, Nvidia GeForce RTX 3060 Laptop GPU (Personal Laptop) -### (TODO: Your README) +![](img/cover.gif) + +### Feature +- Real-time rendering of grass blades with lambert shading +- Three distinct culling tests: orientation, view-frustum, and distance culling +- Tessellation shader to transform Bezier curves into grass blade geometry + +### Introduction +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). + +This project presents a Vulkan-based grass simulator and renderer using compute shaders for efficient, realistic grass animation. Each grass blade is represented as a Bezier curve, with physics calculations applied to simulate natural movements influenced by gravity, wind, and recovery forces. Grass blades are dynamically culled to improve performance, removing those outside the frame or that won’t contribute meaningfully to the scene. The rendering pipeline includes a vertex shader for transforming Bezier control points, tessellation shaders to generate grass geometry, and a fragment shader for shading the blades. + +### Grass Animation +The grass blades' animation is griven by three forces: +- wind force +- recovery +- gravity + +The forces are applied to the "guid point" v2: + +

+ +

+ +A comparison of applying force before and after: +

+ + +

+ + +### Culling +In this grass simulation project, three culling methods are employed to optimize performance by omitting grass blades that won’t significantly impact the final render: + +- Orientation Culling: + + Grass blades whose front faces are perpendicular to the camera view are removed, as they would appear thinner than a pixel and cause aliasing artifacts. This is determined by comparing the dot product of the view vector and the blade’s front face direction. + +- View-Frustum Culling: + +Blades outside the camera’s view are discarded. This is determined by checking three key points along each Bezier curve (v0, v2, and an approximated midpoint) to ensure the blade is within the view-frustum. If all points fall outside, the blade is culled. + +- Distance Culling: + +Blades that are too far from the camera to be visually impactful are culled. The scene is divided into distance-based "buckets" with blades progressively culled in each bucket as they are farther from the camera, allowing for a controlled fade-out of distant grass blades. + +The following three gifs show the Orientation Culling, View-Frustum Culling and Distance Culling respectively. + +

+ + + +

+ + +### Performance Analysis +I added a fps counter to the main loop. The following graph shows the fps change with different culling methods: + +

+ +

+ +The following chart illustrates the average frames per second (FPS) at different tessellation levels with a default camera angle in a grass simulation project. As the tessellation level increases, FPS decreases, reflecting the higher computational load. At lower tessellation levels (2 to 16), FPS remains high, with a peak of 3210 FPS at level 2, gradually decreasing to 2339 FPS at level 16. Beyond level 32, FPS drops sharply, stabilizing around 260 FPS from level 64 onward, indicating that increasing tessellation beyond this point has minimal impact on visual fidelity but significantly affects performance. + +

+ +

-*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/img/cover.gif b/img/cover.gif new file mode 100644 index 0000000..ca9fb9b Binary files /dev/null and b/img/cover.gif differ diff --git a/img/distance.gif b/img/distance.gif new file mode 100644 index 0000000..ccff33f Binary files /dev/null and b/img/distance.gif differ diff --git a/img/orientation.gif b/img/orientation.gif new file mode 100644 index 0000000..526ef78 Binary files /dev/null and b/img/orientation.gif differ diff --git a/img/view.gif b/img/view.gif new file mode 100644 index 0000000..eb54227 Binary files /dev/null and b/img/view.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/Renderer.cpp b/src/Renderer.cpp index b445d04..5ee3c11 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -21,6 +21,7 @@ Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* c CreateModelDescriptorSetLayout(); CreateTimeDescriptorSetLayout(); CreateComputeDescriptorSetLayout(); + CreateDescriptorPool(); CreateCameraDescriptorSet(); CreateModelDescriptorSets(); @@ -198,6 +199,40 @@ 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 BladeBufferLayoutBinding = {}; + BladeBufferLayoutBinding.binding = 0; + BladeBufferLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + BladeBufferLayoutBinding.descriptorCount = 1; + BladeBufferLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT | VK_SHADER_STAGE_VERTEX_BIT; + BladeBufferLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding CullingBufferLayoutBinding = {}; + CullingBufferLayoutBinding.binding = 1; + CullingBufferLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + CullingBufferLayoutBinding.descriptorCount = 1; + CullingBufferLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT | VK_SHADER_STAGE_VERTEX_BIT; + CullingBufferLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding NumBladesBufferLayoutBinding = {}; + NumBladesBufferLayoutBinding.binding = 2; + NumBladesBufferLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + NumBladesBufferLayoutBinding.descriptorCount = 1; + NumBladesBufferLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT | VK_SHADER_STAGE_VERTEX_BIT; + NumBladesBufferLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { BladeBufferLayoutBinding, CullingBufferLayoutBinding, NumBladesBufferLayoutBinding }; + + // Create the descriptor set layout + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } + } void Renderer::CreateDescriptorPool() { @@ -216,13 +251,15 @@ 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, + 3 * static_cast(scene->GetBlades().size())}, }; VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = static_cast(poolSizes.size()); poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = 5; + poolInfo.maxSets = 8; if (vkCreateDescriptorPool(logicalDevice, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("Failed to create descriptor pool"); @@ -320,8 +357,47 @@ 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 descriptor set"); + } + + std::vector descriptorWrites( + grassDescriptorSets.size()); + + for (size_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() { // Describe the desciptor set VkDescriptorSetLayout layouts[] = { timeDescriptorSetLayout }; @@ -360,6 +436,79 @@ 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 + // Describe the desciptor set + computeDescriptorSets.resize(scene->GetBlades().size()); + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(computeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites( + 3 * computeDescriptorSets.size()); + + for (size_t i = 0; i < scene->GetBlades().size(); ++i) { + // create input blades buffer descriptor sets + VkDescriptorBufferInfo inputBladesBufferInfo{}; + inputBladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + inputBladesBufferInfo.offset = 0; + inputBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkWriteDescriptorSet& inputBladesDescriptorWrite = descriptorWrites[3 * i + 0]; + inputBladesDescriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + inputBladesDescriptorWrite.dstSet = computeDescriptorSets[i]; + inputBladesDescriptorWrite.dstBinding = 0; + inputBladesDescriptorWrite.dstArrayElement = 0; + inputBladesDescriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + inputBladesDescriptorWrite.descriptorCount = 1; + inputBladesDescriptorWrite.pBufferInfo = &inputBladesBufferInfo; + inputBladesDescriptorWrite.pImageInfo = nullptr; + inputBladesDescriptorWrite.pTexelBufferView = nullptr; + + // create culled blades buffer descriptor sets + VkDescriptorBufferInfo culledBladesBufferInfo{}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkWriteDescriptorSet& culledBladesDescriptorWrite = descriptorWrites[3 * i + 1]; + culledBladesDescriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + culledBladesDescriptorWrite.dstSet = computeDescriptorSets[i]; + culledBladesDescriptorWrite.dstBinding = 1; + culledBladesDescriptorWrite.dstArrayElement = 0; + culledBladesDescriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledBladesDescriptorWrite.descriptorCount = 1; + culledBladesDescriptorWrite.pBufferInfo = &culledBladesBufferInfo; + culledBladesDescriptorWrite.pImageInfo = nullptr; + culledBladesDescriptorWrite.pTexelBufferView = nullptr; + + // create num blades buffer descriptor sets + VkDescriptorBufferInfo numBladesBufferInfo{}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect); + + VkWriteDescriptorSet& numBladesDescriptorWrite = descriptorWrites[3 * i + 2]; + numBladesDescriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + numBladesDescriptorWrite.dstSet = computeDescriptorSets[i]; + numBladesDescriptorWrite.dstBinding = 2; + numBladesDescriptorWrite.dstArrayElement = 0; + numBladesDescriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBladesDescriptorWrite.descriptorCount = 1; + numBladesDescriptorWrite.pBufferInfo = &numBladesBufferInfo; + numBladesDescriptorWrite.pImageInfo = nullptr; + numBladesDescriptorWrite.pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +866,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,6 +1033,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) / WORKGROUP_SIZE, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1129,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 +1211,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..e2679d6 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -19,6 +19,7 @@ class Renderer { void CreateModelDescriptorSetLayout(); void CreateTimeDescriptorSetLayout(); void CreateComputeDescriptorSetLayout(); + void CreateGrassDescriptorSetLayout(); // new void CreateDescriptorPool(); @@ -56,12 +57,15 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; // new VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + std::vector computeDescriptorSets; // new + std::vector grassDescriptorSets; // new VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..2c47af8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "Camera.h" #include "Scene.h" #include "Image.h" +#include Device* device; SwapChain* swapChain; @@ -143,12 +144,22 @@ int main() { glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback); glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback); + // add fps counter + high_resolution_clock::time_point lastTime = high_resolution_clock::now(); + int frameCount = 0; + while (!ShouldQuit()) { glfwPollEvents(); scene->UpdateTime(); renderer->Frame(); + frameCount++; } + high_resolution_clock::time_point currentTime = high_resolution_clock::now(); + duration timeSpan = duration_cast>(currentTime - lastTime); + float fps = frameCount / timeSpan.count(); + std::cout << "FPS: " << fps << std::endl; + vkDeviceWaitIdle(device->GetVkDevice()); vkDestroyImage(device->GetVkDevice(), grassImage, nullptr); diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..6b635e0 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -22,7 +22,7 @@ struct Blade { }; // TODO: Add bindings to: -// 1. Store the input blades +// 1. Store the input blades // 2. Write out the culled blades // 3. Write the total number of blades remaining @@ -36,21 +36,146 @@ struct Blade { // uint firstInstance; // = 0 // } numBlades; +layout(set = 2, binding = 0) buffer BladeBuffer { + Blade blades[]; +} bladeBuffer; + +layout(set = 2, binding = 1) buffer CulledBladeBuffer { + Blade blades[]; +} culledBladeBuffer; + +layout(set = 2, binding = 2) buffer NumBladesBuffer { + uint vertexCount; + uint instanceCount; + uint firstVertex; // = 0 + uint firstInstance; // = 0 +} numBlades; + bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +bool isInFrustum(vec4 pos) { + vec4 clipSpace = camera.proj * camera.view * vec4(pos.xyz, 1.0); + return inBounds(clipSpace.x, clipSpace.w) && + inBounds(clipSpace.y, clipSpace.w) && + inBounds(clipSpace.z, clipSpace.w); +} + +float getRandom(vec3 v0) { + return fract(sin(dot(v0.xyz, vec3(12.9898, 78.233, 151.7182))) * 43758.5453); +} + +vec3 random3(vec3 p) { + return fract(sin(vec3(dot(p, vec3(127.1, 311.7, 513.76)), + dot(p, vec3(269.5, 183.3, 389.22)), + dot(p, vec3(378.1, 210.4, 193.9)))) * + 43758.5453); +} + 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 = bladeBuffer.blades[gl_GlobalInvocationID.x]; + vec3 v0 = blade.v0.xyz; + vec3 v1 = blade.v1.xyz; + vec3 v2 = blade.v2.xyz; + vec3 up = blade.up.xyz; + float orientation = blade.v0.w; + float height = blade.v1.w; + float width = blade.v2.w; + vec3 tangent = vec3(cos(orientation), 0.0, sin(orientation)); + vec3 f = cross(tangent, up); + float stiffness = blade.up.w; + + // FORCE 1 : GRAVITY + vec4 D = vec4(0.0, -1.0, 0.0, 9.81); // D.xyz is the gravity direction, and D.w is the magnitude of acceleration + vec3 gE = vec3(0.0, -9.81, 0.0); + vec3 gF = 0.25 * length(gE) * f; + vec3 g = gE + gF; + + // FORCE 2 : RECOVERY + vec3 iv2 = v0 + up * height; // position before simulation + vec3 r = (iv2 - v2) * stiffness; + + // FORCE 3: WIND + vec3 windDir = random3(v0) * sin(totalTime) * cos(totalTime) * 3.0; + float dirAligment = 1 - abs(dot(normalize(windDir), normalize(v2 - v0))); + float heightRatio = dot((v2 - v0), up) / height; + float aligment = dirAligment * heightRatio; + vec3 windForce = windDir * aligment; + + // Update blade position + vec3 tv2 = (g + r + windForce) * deltaTime; + v2 += tv2; + + // Position Validation + // 1. v2 above ground + v2 = v2 - up * min(dot(up, v2 - v0), 0.0); + + // 2. v1 above v0 + float vl_proj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + height * up * max(1 - vl_proj / height, 0.05 * max(vl_proj / height, 1.0)); + + // 3. length of curve no longer than height + float n = 2.0; + float L0 = length(v2 - v0); + float L1 = length(v2 - v1)+ length(v1 - v0); + float L = (2.0 * L0 + (n - 1) * L1) / (n + 1); + + float ratio = height / L; + vec3 v1_corr = v0 + ratio * (v1 - v0); + vec3 v2_corr = v1_corr + ratio * (v2 - v1); + v1 = v1_corr; + v2 = v2_corr; + + + // update blade info + blade.v1.xyz = v1; + blade.v2.xyz = v2; + bladeBuffer.blades[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 + bool oriCullFlag = false; + bool viewCullFlag = false; + bool distanceCullFlag = false; + + // Orientation Culling + vec3 viewDir = normalize(vec3(inverse(camera.view) * vec4(0.0, 0.0, 0.0, 1.0))); + if(dot(viewDir, tangent) < 0.9) { + oriCullFlag = true; + } + + // View Frustum Culling + vec4 m = (1.0/4.0) * blade.v0 * (1.0/2.0) * blade.v1 * (1.0/4.0) * blade.v2; + if(isInFrustum(blade.v0) && isInFrustum(blade.v1) && isInFrustum(m)) { + viewCullFlag = true; + } + + // Distance Culling + float culledDistance = 20.0; + int levelMax = 20; + vec3 cameraPos = vec3(inverse(camera.view) * vec4(0.0, 0.0, 0.0, 1.0)); + float projDistance = length(v0 - cameraPos - up * dot(up , (v0 - cameraPos))); + if((gl_GlobalInvocationID.x % levelMax) <= floor(levelMax * (1 - projDistance / culledDistance))) { + distanceCullFlag = true; + } + + + if(oriCullFlag && viewCullFlag && distanceCullFlag) { + uint index = atomicAdd(numBlades.vertexCount, 1); + culledBladeBuffer.blades[index] = blade; + } + + //culledBladeBuffer.blades[atomicAdd(numBlades.vertexCount, 1)] = blade; + } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..08f3586 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -8,10 +8,20 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { // TODO: Declare fragment shader inputs +layout(location = 0) in vec4 pos; +layout(location = 1) in vec4 nor; +layout(location = 2) in vec2 uv; + layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color + vec4 tipColor = vec4(0.31, 0.74, 0.36, 1.0); + vec4 baseColor = vec4(0.06, 0.42, 0.25, 1.0); + outColor = mix(baseColor, tipColor, uv.y); - outColor = vec4(1.0); + float ambient = 0.3; + vec3 lightDir = normalize(vec3(1.0, 1.0, 0.0)); + float diff = max(dot(nor.xyz, lightDir), 0.0); + outColor = outColor * (ambient + diff); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..382c314 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -9,18 +9,35 @@ 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[]; + +in gl_PerVertex { + vec4 gl_Position; +} gl_in[]; 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] = ??? + gl_TessLevelInner[0] = 8.0; + gl_TessLevelInner[1] = 8.0; + gl_TessLevelOuter[0] = 8.0; + gl_TessLevelOuter[1] = 8.0; + gl_TessLevelOuter[2] = 8.0; + gl_TessLevelOuter[3] = 8.0; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..4116f22 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,40 @@ 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 vec4 pos; +layout(location = 1) out vec4 nor; +layout(location = 2) out vec2 uv; 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 + vec4 a = in_v0[0] + v * (in_v1[0] - in_v0[0]); + vec4 b = in_v1[0] + v * (in_v2[0] - in_v1[0]); + vec4 c = a + v * (b - a); + vec4 t1 = vec4(sin(in_v0[0].w), 0.0, cos(in_v0[0].w), 0.0); + vec4 c0 = c - in_v2[0].w * t1; + vec4 c1 = c + in_v2[0].w * t1; + vec4 t0 = normalize(b - a); + vec4 n = normalize(vec4(cross(t0.xyz, t1.xyz), 0.0)); + + //float t = u; // quad + //float t = u + 0.5 * v - u * v; // triangle + //float t = u - u * v * v; // side parabola + float threshold = 0.5; + float t = 0.5 + (u - 0.5) * (1 - max(v - threshold, 0.0) / (1.0 - threshold)); + + + pos = (1 - t) * c0 + t * c1; + nor = n; + uv = vec2(u, v); + gl_Position = camera.proj * camera.view * vec4(pos.xyz, 1.0); + } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..4ee8454 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -8,10 +8,27 @@ 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; + +// output to tesellation control shader +layout(location = 0) out vec4 out_v0; +layout(location = 1) out vec4 out_v1; +layout(location = 2) out vec4 out_v2; +layout(location = 3) out vec4 out_up; + out gl_PerVertex { vec4 gl_Position; }; void main() { // TODO: Write gl_Position and any other shader outputs + out_v0 = model * v0; + out_v1 = model * v1; + out_v2 = model * v2; + out_up = model * up; + gl_Position = out_v0; }