diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3643d4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/* +build/* \ No newline at end of file diff --git a/README.md b/README.md index 20ee451..6ba4a60 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,76 @@ 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) +* Zhangkaiwen Chu + * [LinkedIn](https://www.linkedin.com/in/zhangkaiwen-chu-b53060225/) +* Tested on: Windows 10, R7-5800H @ 3.20GHz 16GB, RTX 3070 Laptop GPU 16310MB (Personal Laptop) -### (TODO: Your README) +This project implement a a grass simulator and renderer, which is based on 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). -*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. +![](img/final.gif) + +## Core Features +* Shading Pipeline +* Tessellation +* Fragment Shader +* Force Simulation +* Culling + +**Tessellation and Fragment Shader** + +The shape used by tessellation can be controlled by the interpolation parameter between two curve points. We use the triangle shape, which is `t = u + 0.5v - uv`, where uv are generated by tessellation. The curve points are generated by De Casteljan's algorithm. After generating the blade geometry, we implement the fragment shader by using a gradient green texture on the grass, combined with lambertian shading. Here is the result after implementing these two steps: + +| Gradient Green | Lambertian Shading | Gradient Green + Lambertian Shading | +|---|---|---| +|![](img/gradient.png)|![](img/lambertian.png)|![](img/both.png) + +**Gravity and Recovery** + +Given the gravity vector `D`, the environmental gravity is given by `gE = normalize(D.xyz) * D.w`, and the front gravity is computed as `gf = (1/4) * ||gE|| * f`, where `f` is the front facing direcction of the blade. The Recovery is simulated by Hooke's law. Let `iv2` denote the original position of `v2`, the recovery force is given by `r = (iv2 - v2) * stiffness`. +| Grass with Gravity and Recorvey | +|---| +|![](img/gr.gif)| + +**Wind** + +We simulate wind by simple wave functions. Since there are no requirements for initial states, `abs(cos(a * t + b * f(v0)))` is enough to simulate planner wave and spherical waves. "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) also provide a method to process the alignment of the blade towards the wind. +| Plannar Wave | Plannar Wave with Alignment | +|---|---| +|![](img/plannar.gif)|![](img/plannar_alignment.gif)| + +| Spherical Wave | Spherical Wave with Alignment | +|---|---| +|![](img/spherical.gif)|![](img/spherical_alignmenr.gif)| + +**Orientation Culling** + +Due to the pseudo three-dimensionality of the grass blade, we can cull the blades by the alignment of the viewing direction and the vector along the width of the blade. + +| Naive | Orientation Culling| +|---|---| +|![](img/orientation_naive.png)|![](img/orientation.png)| + +**View-frustum Culling** + +The projection of a point on the image can be calculated by multypling the position of the point with the view-projection matrix. We can check whether the bottom, the middle and the top point is on the image to know whether this blade will appear in the view. A small tollerance is added in case the blade is still visable after failing the tests due to its width. A minous tollerance can help us check the performance of the view-frustum culling: + +![](img/view.gif) + +**Distance Culling** + +A field of grass seems denser when it is far from the carmera, thus, we can make the grass field sparser with the increase of the distance to the camera. This can also prevent shading blades that are smaller than one pixel. + +![](img/distance.gif) + +## Performance Analysis +We compare the pulling methods with a close camera depth and a large camera depth. + +![](img/1.png) + +For a close camera depth, the view-frustum culling is the best, since it has the lowest computation requirement. + +![](img/2.png) + +For a far camera depth, the distance culling is the best, since it will greatly reduce the number of blades, and view-frustum is the worst, since it only increase the workload. + +The result shows that the time complexity scales nearly linear with the number of blades. \ No newline at end of file diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe index f68db3a..e612e13 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/img/1.png b/img/1.png new file mode 100644 index 0000000..28e023b Binary files /dev/null and b/img/1.png differ diff --git a/img/2.png b/img/2.png new file mode 100644 index 0000000..d2ac916 Binary files /dev/null and b/img/2.png differ diff --git a/img/both.png b/img/both.png new file mode 100644 index 0000000..921e1cf Binary files /dev/null and b/img/both.png differ diff --git a/img/distance.gif b/img/distance.gif new file mode 100644 index 0000000..e62a794 Binary files /dev/null and b/img/distance.gif differ diff --git a/img/final.gif b/img/final.gif new file mode 100644 index 0000000..c27f7f2 Binary files /dev/null and b/img/final.gif differ diff --git a/img/gr.gif b/img/gr.gif new file mode 100644 index 0000000..8315721 Binary files /dev/null and b/img/gr.gif differ diff --git a/img/gradient.png b/img/gradient.png new file mode 100644 index 0000000..330acd9 Binary files /dev/null and b/img/gradient.png differ diff --git a/img/lambertian.png b/img/lambertian.png new file mode 100644 index 0000000..ce6b7fc Binary files /dev/null and b/img/lambertian.png differ diff --git a/img/orientation.png b/img/orientation.png new file mode 100644 index 0000000..1ec4dee Binary files /dev/null and b/img/orientation.png differ diff --git a/img/orientation_naive.png b/img/orientation_naive.png new file mode 100644 index 0000000..815f0cd Binary files /dev/null and b/img/orientation_naive.png differ diff --git a/img/plannar.gif b/img/plannar.gif new file mode 100644 index 0000000..84dd391 Binary files /dev/null and b/img/plannar.gif differ diff --git a/img/plannar_alignment.gif b/img/plannar_alignment.gif new file mode 100644 index 0000000..00cb809 Binary files /dev/null and b/img/plannar_alignment.gif differ diff --git a/img/spherical.gif b/img/spherical.gif new file mode 100644 index 0000000..ef7d4d9 Binary files /dev/null and b/img/spherical.gif differ diff --git a/img/spherical_alignmenr.gif b/img/spherical_alignmenr.gif new file mode 100644 index 0000000..4559c4f Binary files /dev/null and b/img/spherical_alignmenr.gif differ diff --git a/img/view.gif b/img/view.gif new file mode 100644 index 0000000..532df5b Binary files /dev/null and b/img/view.gif differ diff --git a/profile.txt b/profile.txt new file mode 100644 index 0000000..e69de29 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..f329d50 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 << 12; constexpr static float MIN_HEIGHT = 1.3f; constexpr static float MAX_HEIGHT = 2.5f; constexpr static float MIN_WIDTH = 0.1f; diff --git a/src/Camera.cpp b/src/Camera.cpp index 3afb5b8..b24280d 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -12,7 +12,8 @@ Camera::Camera(Device* device, float aspectRatio) : device(device) { r = 10.0f; theta = 0.0f; phi = 0.0f; - cameraBufferObject.viewMatrix = glm::lookAt(glm::vec3(0.0f, 1.0f, 10.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); + cameraBufferObject.pos = glm::vec3(0.0f, 1.0f, 10.0f); + cameraBufferObject.viewMatrix = glm::lookAt(cameraBufferObject.pos, glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); cameraBufferObject.projectionMatrix = glm::perspective(glm::radians(45.0f), aspectRatio, 0.1f, 100.0f); cameraBufferObject.projectionMatrix[1][1] *= -1; // y-coordinate is flipped @@ -37,6 +38,8 @@ void Camera::UpdateOrbit(float deltaX, float deltaY, float deltaZ) { glm::mat4 finalTransform = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f)) * rotation * glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 1.0f, r)); cameraBufferObject.viewMatrix = glm::inverse(finalTransform); + glm::vec4 p = finalTransform * glm::vec4(0.f, 0.f, 0.f, 1.f); + cameraBufferObject.pos = glm::vec3(p) / p.w; memcpy(mappedData, &cameraBufferObject, sizeof(CameraBufferObject)); } diff --git a/src/Camera.h b/src/Camera.h index 6b10747..752171e 100644 --- a/src/Camera.h +++ b/src/Camera.h @@ -7,6 +7,7 @@ struct CameraBufferObject { glm::mat4 viewMatrix; glm::mat4 projectionMatrix; + glm::vec3 pos; }; class Camera { diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..5457988 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,38 @@ 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 numBladesBinding = {}; + numBladesBinding.binding = 0; + numBladesBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBladesBinding.descriptorCount = 1; + numBladesBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numBladesBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding bladesBinding = {}; + bladesBinding.binding = 1; + bladesBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladesBinding.descriptorCount = 1; + bladesBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladesBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding culledBladesBinding = {}; + culledBladesBinding.binding = 2; + culledBladesBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledBladesBinding.descriptorCount = 1; + culledBladesBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + culledBladesBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { numBladesBinding, bladesBinding, culledBladesBinding }; + + // 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,6 +248,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 +353,45 @@ 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 + bladeDescriptorSets.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(bladeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, bladeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(bladeDescriptorSets.size()); + + for (uint32_t i = 0; i < bladeDescriptorSets.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 = bladeDescriptorSets[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 +432,76 @@ void Renderer::CreateTimeDescriptorSet() { void Renderer::CreateComputeDescriptorSets() { // TODO: Create Descriptor sets for the compute pipeline // The descriptors should point to Storage buffers which will hold the grass blades, the culled grass blades, and the output number of grass blades + computeDescriptorSets.resize(scene->GetBlades().size()); + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(computeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + + for (uint32_t i = 0; i < computeDescriptorSets.size(); ++i) { + + //numblades info + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect); + + //blades info + VkDescriptorBufferInfo bladesBufferInfo = {}; + bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + bladesBufferInfo.offset = 0; + bladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + //culled blades info + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + + descriptorWrites[3 * i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i].dstBinding = 0; + descriptorWrites[3 * i].dstArrayElement = 0; + descriptorWrites[3 * i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i].descriptorCount = 1; + descriptorWrites[3 * i].pBufferInfo = &numBladesBufferInfo; + descriptorWrites[3 * i].pImageInfo = nullptr; + descriptorWrites[3 * i].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 = &bladesBufferInfo; + 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 = &culledBladesBufferInfo; + 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 +859,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 +1026,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 (size_t i = 0; i < computeDescriptorSets.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 +1122,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, graphicsPipelineLayout, 1, 1, &bladeDescriptorSets[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 +1204,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..9ff9b81 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,11 +56,14 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; + std::vector bladeDescriptorSets; + std::vector computeDescriptorSets; VkDescriptorSet timeDescriptorSet; VkPipelineLayout graphicsPipelineLayout; diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..b244b2a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -143,12 +143,15 @@ int main() { glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback); glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback); + + while (!ShouldQuit()) { glfwPollEvents(); scene->UpdateTime(); renderer->Frame(); } + vkDeviceWaitIdle(device->GetVkDevice()); vkDestroyImage(device->GetVkDevice(), grassImage, nullptr); @@ -161,6 +164,7 @@ int main() { delete renderer; delete swapChain; delete device; + vkDestroySurfaceKHR(instance->GetVkInstance(), surface, nullptr); delete instance; DestroyWindow(); return 0; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..20a8ec5 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,17 +2,26 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 + + +#define SPHERIAL 0 +#define FORCE 1 +#define ORIENTATION 1 +#define VIEW_FRUSTUM 1 +#define DISTANCE 1 + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; + vec3 pos; } camera; layout(set = 1, binding = 0) uniform Time { float deltaTime; float totalTime; -}; +} time; struct Blade { vec4 v0; @@ -29,28 +38,118 @@ 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 NumBlades { + uint vertexCount; + uint instanceCount; + uint firstVertex; + uint firstInstance; +} numBlades; + +layout(set = 2, binding = 1) buffer Blades { + Blade data[]; +} blades; + +layout(set = 2, binding = 2) buffer CulledBlades { + Blade data[]; +} culledBlades; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +bool inBounds(vec3 value, float bounds) { + return (value.x >= -bounds) && (value.x <= bounds) && + (value.y >= -bounds) && (value.y <= bounds) && + (value.y >= -bounds) && (value.y <= bounds); +} + +bool viewFrustum(mat4 VP, vec3 p){ + vec4 p2 = VP * vec4(p, 1.f); + float h = p2.w + .5f; + return inBounds(p2.xyz, h); +} + void main() { // Reset the number of blades to 0 if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + numBlades.vertexCount = 0; } barrier(); // Wait till all threads reach this point // TODO: Apply forces on every blade and update the vertices in the buffer + Blade blade = blades.data[gl_GlobalInvocationID.x]; + vec3 v0 = blade.v0.xyz; + float direction = blade.v0.w; + vec3 v1 = blade.v1.xyz; + float height = blade.v1.w; + vec3 v2 = blade.v2.xyz; + float width = blade.v2.w; + vec3 up = blade.up.xyz; + float stiffness = blade.up.w; + + //recovery + vec3 r = (v0 + up * height - v2) * stiffness; + + //gravity + vec4 D = vec4(0, -1, 0, 9.81); + vec3 gE = D.xyz * D.w; + vec3 f = normalize(cross(vec3(-cos(direction), 0, sin(direction)), up)); + vec3 gF = 0.25 * length(gE) * f; + vec3 g = gE + gF; + + //wind +#if SPHERIAL + vec3 wi = 20 * normalize(v0) * abs(cos(1.F * time.totalTime - 0.5 * length(v0))); +#else + vec3 wi = 10 * vec3(1,0,1) * abs(cos(1.F * time.totalTime - 0.1 * dot(v0, vec3(1,0,1)))); +#endif + + + float fd = 1 - abs(dot(normalize(wi), normalize(v2 - v0))); + float fr = dot(v2 - v0, up) / height; + + vec3 w = wi * fd * fr; + + //total force + vec3 F = r + g + w; + + //update +#if FORCE + v2 = v2 + F * time.deltaTime; +#endif + + //validation + v2 = v2 - up * min(dot(up, v2 - v0), 0.f); + float lproj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + height * up * max(1.f - lproj / height, 0.05 * max(lproj / height, 1.f)); + + float L = (2.f * length(v0 - v2) + length(v0 - v1) + length(v1 - v2)) / 3.f; + float R = height / L; + blade.v1.xyz = v0 + R * (v1 - v0); + blade.v2.xyz = blade.v1.xyz + R * (v2 - v1); + blades.data[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 + +#if VIEW_FRUSTUM + mat4 VP = camera.proj * camera.view; + if (!viewFrustum(VP, v0) && !viewFrustum(VP, v2) && !viewFrustum(VP, 0.25f * v0 + 0.5f * v1 + 0.25f * v2)) return; +#endif + +#if DISTANCE + float dproj = length(v0 - camera.pos - up * dot(v0 - camera.pos, up)); + float dmax = 30; + uint n = 20; + if (gl_GlobalInvocationID.x % n > floor(n * (1 - dproj / dmax))) return; +#endif + +#if ORIENTATION + if (abs(dot(normalize(v0 - camera.pos), vec3(-cos(direction), 0.f, sin(direction)))) > 0.9f) return; +#endif + + + culledBlades.data[atomicAdd(numBlades.vertexCount, 1)] = blade; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..d9d7551 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -4,14 +4,30 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; + vec3 pos; } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 n; +layout(location = 1) in vec3 p; +layout(location = 2) in float v; + layout(location = 0) out vec4 outColor; + + void main() { // TODO: Compute fragment color + vec3 source = vec3(0.f, 15.f, 0.f); + vec3 rayDirection = normalize(source - p); + + vec3 topColor = vec3(153.f, 204.f, 51.f) / 255.f; + vec3 botColor = vec3(0.f, 50.f, 0.f) / 255.f; + + vec3 color = botColor + v * (topColor - botColor); + color = color * min(max(abs(dot(rayDirection, n)) * 1.5, 0.5), 1); + - outColor = vec4(1.0); + outColor = vec4(color, 1.f); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..274657b 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -6,21 +6,34 @@ layout(vertices = 1) out; layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; + vec3 pos; } camera; // TODO: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4 inV0[]; +layout(location = 1) in vec4 inV1[]; +layout(location = 2) in vec4 inV2[]; +layout(location = 3) in vec4 inUp[]; + +layout(location = 0) out vec4 outV0[]; +layout(location = 1) out vec4 outV1[]; +layout(location = 2) out vec4 outV2[]; 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 = gl_in[gl_InvocationID].gl_Position; // TODO: Write any shader outputs + outV0[gl_InvocationID] = inV0[gl_InvocationID]; + outV1[gl_InvocationID] = inV1[gl_InvocationID]; + outV2[gl_InvocationID] = inV2[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] = 15; + gl_TessLevelInner[1] = 15; + gl_TessLevelOuter[0] = 15; + gl_TessLevelOuter[1] = 15; + gl_TessLevelOuter[2] = 15; + gl_TessLevelOuter[3] = 15; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..4441e71 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -6,13 +6,40 @@ layout(quads, equal_spacing, ccw) in; layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; + vec3 pos; } camera; // TODO: Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 inV0[]; +layout(location = 1) in vec4 inV1[]; +layout(location = 2) in vec4 inV2[]; +layout(location = 0) out vec3 n; +layout(location = 1) out vec3 p; +layout(location = 2) out float v; void main() { float u = gl_TessCoord.x; - float v = gl_TessCoord.y; + v = gl_TessCoord.y; // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + vec3 v0 = inV0[0].xyz; + vec3 v1 = inV1[0].xyz; + vec3 v2 = inV2[0].xyz; + float w = inV2[0].w; + float direction = inV0[0].w; + vec3 t1 = vec3(-cos(direction), 0.f,sin(direction)); + vec3 a = v0 + v * (v1 - v0); + vec3 b = v1 + v * (v2 - v1); + vec3 c = a + v * (b - a); + vec3 c0 = c - w * t1; + vec3 c1 = c + w * t1; + vec3 t0 = normalize(b - a); + + n = normalize(cross(t0, t1)); + // triangle + float t = (u + 0.5 * v - u * v); + p = (1 - t) * c0 + t * c1; + + gl_Position = camera.proj * camera.view * vec4(p, 1.f); + p = gl_Position.xyz; } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..66f0229 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 inV0; +layout(location = 1) in vec4 inV1; +layout(location = 2) in vec4 inV2; +layout(location = 3) in vec4 inUp; -out gl_PerVertex { - vec4 gl_Position; -}; +layout(location = 0) out vec4 outV0; +layout(location = 1) out vec4 outV1; +layout(location = 2) out vec4 outV2; +layout(location = 3) out vec4 outUp; void main() { // TODO: Write gl_Position and any other shader outputs + outV0 = vec4((model * vec4(inV0.xyz, 1.f)).xyz, inV0.w); + outV1 = vec4((model * vec4(inV1.xyz, 1.f)).xyz, inV1.w); + outV2 = vec4((model * vec4(inV2.xyz, 1.f)).xyz, inV2.w); + outUp = vec4((model * vec4(inUp.xyz, 1.f)).xyz, inUp.w); }