diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d5ec50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/out +.vs/ +/build diff --git a/CMakeLists.txt b/CMakeLists.txt index 860a279..51af884 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,7 @@ ELSE(WIN32) link_libraries(${XCB_LIBRARIES} ${VULKAN_LIB}) ENDIF(WIN32) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/") +# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/") add_subdirectory(external) add_subdirectory(src) diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..b58599c --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,27 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 20ee451..6db9f06 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,62 @@ Vulkan Grass Rendering -================================== +======================= +In this project, I implemented a real-time Vulkan Grass Simulation based on this [paper](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf). **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) +* Mengxuan Huang + * [LinkedIn](https://www.linkedin.com/in/mengxuan-huang-52881624a/) +* Tested on: Windows 11, i9-13980HX @ 2.22GHz 64.0 GB, RTX4090-Laptop 16384MB +----------------------------- +## Overview +

+ +

-### (TODO: Your README) +In this project, I used three control points($v_0, v_1, v_2$) and its $width$, $height$, and $direction$ to represent a grass.The control point $v_0$ act as the root of the grass and will not move during the simulation. All forces are applied on $v_2$. Control point $v_1$ is used to preserve the length of the grass. The result is shown in the gif below. -*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. +

+ +

+ +## Features +- Force Simulation +- Culling + +### Force Simulation +------------------------------------------------- +There are three kinds of forces applied to grass in the simulation: +- Gravity +- Recovery +- Wind + +#### Gravity +The Gravity term includes the *environmental gravity* $g_E$ and the *front gravity* $g_F$, where +`gF = gF = (1/4) * ||gE|| * f`. And `f` if the vector perpendicular to the direction of grass. + +#### Recovery +Recovery is used to represent the force the grass recover to the origin position, which is defined as `r = (v0 + up * height - v2) * stiffness`. + +#### Wind +Wind can be any analytic functions that represent wind waves moving through 3D space. In my simulation, I used a combination of `sin` and `cos` functions: `windMagnitude = u * sin(v) - v * cos(u)`. Besides, total wind force `w = windMagnitude * windDirection * windAlignment`. + +#### Total Force +Having the above foces, the total force `f = gE + gF + r + w`. Affter appling the total force to the control point $v_2$, it is also necessary to compute the position of $v_1$ and correct the position of $v_1$ and $v_2$ to preserve the length of grass. + +### Culling +------------------------------------------------- +There are three kinds of culling used to improve the performance: +- Orienation Culling (cull grass that are align with view direction. ) +- View Frustrum Culling (cull grass that are outside the view Frustrum. ) +- Distance Culling (cull grass based on distance.) + +|Orientation Culling|Frustrum Culling|Distance Culling| +|:-----------:|:-------------:|:-----------:| +|![](img/orientation.gif)|![](img/frustrum.gif)|![](img/distance.gif)| + +## Performance Analysis +

+ +

+ +As shown in the graph, with the increase of the number of grass, the compute + graphics pipline time increases. And the time drop significantly after culling blades before rendering. According to the graph, distance culling improve the performance most. \ No newline at end of file diff --git a/img/cube_demo.png b/img/cube_demo.png deleted file mode 100644 index 21e928e..0000000 Binary files a/img/cube_demo.png and /dev/null differ diff --git a/img/distance.gif b/img/distance.gif new file mode 100644 index 0000000..44ca6fc Binary files /dev/null and b/img/distance.gif differ diff --git a/img/frustrum.gif b/img/frustrum.gif new file mode 100644 index 0000000..86d16f1 Binary files /dev/null and b/img/frustrum.gif differ diff --git a/img/graph.png b/img/graph.png new file mode 100644 index 0000000..29246fb Binary files /dev/null and b/img/graph.png differ diff --git a/img/my_grass.gif b/img/my_grass.gif new file mode 100644 index 0000000..fd876aa Binary files /dev/null and b/img/my_grass.gif differ diff --git a/img/orientation.gif b/img/orientation.gif new file mode 100644 index 0000000..9bd4672 Binary files /dev/null and b/img/orientation.gif differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..dde104b 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -6,7 +6,9 @@ float generateRandomFloat() { return rand() / (float)RAND_MAX; } -Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Model(device, commandPool, {}, {}) { +Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) + : Model(device, commandPool, {}, {}) +{ std::vector blades; blades.reserve(NUM_BLADES); @@ -45,7 +47,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..0f4f100 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,43 @@ 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 in_blade_layout_binding = {}; + in_blade_layout_binding.binding = 0; + in_blade_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + in_blade_layout_binding.descriptorCount = 1; + in_blade_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + in_blade_layout_binding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding out_blade_layout_binding = {}; + out_blade_layout_binding.binding = 1; + out_blade_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + out_blade_layout_binding.descriptorCount = 1; + out_blade_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + out_blade_layout_binding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding num_blade_layout_binding = {}; + num_blade_layout_binding.binding = 2; + num_blade_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + num_blade_layout_binding.descriptorCount = 1; + num_blade_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + num_blade_layout_binding.pImmutableSamplers = nullptr; + + std::vector bindings = { + in_blade_layout_binding, + out_blade_layout_binding, + num_blade_layout_binding + }; + + // 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 compute descriptor set layout"); + } } void Renderer::CreateDescriptorPool() { @@ -216,6 +253,7 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(scene->GetModels().size() + scene->GetBlades().size())}, }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,6 +358,42 @@ void Renderer::CreateModelDescriptorSets() { void Renderer::CreateGrassDescriptorSets() { // TODO: Create Descriptor sets for the grass. // This should involve creating descriptor sets which point to the model matrix of each group of grass blades + grassDescriptorSets.resize(scene->GetBlades().size()); + + VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout }; + 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 (int i = 0; i < grassDescriptorSets.size(); ++i) + { + VkDescriptorBufferInfo modelBufferInfo = {}; + modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + modelBufferInfo.offset = 0; + modelBufferInfo.range = sizeof(ModelBufferObject); + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = grassDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = &modelBufferInfo; + descriptorWrites[i].pTexelBufferView = nullptr; + descriptorWrites[i].pImageInfo = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +434,71 @@ 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()); + + 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 < computeDescriptorSets.size(); ++i) { + VkDescriptorBufferInfo in_blade_buffer_info = {}; + in_blade_buffer_info.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + in_blade_buffer_info.offset = 0; + in_blade_buffer_info.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo out_blade_buffer_info = {}; + out_blade_buffer_info.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + out_blade_buffer_info.offset = 0; + out_blade_buffer_info.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo num_blade_buffer_info{}; + num_blade_buffer_info.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + num_blade_buffer_info.offset = 0; + num_blade_buffer_info.range = sizeof(BladeDrawIndirect); + + descriptorWrites[3 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 0].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 0].dstBinding = 0; + descriptorWrites[3 * i + 0].dstArrayElement = 0; + descriptorWrites[3 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 0].descriptorCount = 1; + descriptorWrites[3 * i + 0].pBufferInfo = &in_blade_buffer_info; + descriptorWrites[3 * i + 0].pImageInfo = nullptr; + descriptorWrites[3 * i + 0].pTexelBufferView = nullptr; + + descriptorWrites[3 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 1].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 1].dstBinding = 1; + descriptorWrites[3 * i + 1].dstArrayElement = 0; + descriptorWrites[3 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 1].descriptorCount = 1; + descriptorWrites[3 * i + 1].pBufferInfo = &out_blade_buffer_info; + 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 = &num_blade_buffer_info; + 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() { @@ -665,7 +804,7 @@ void Renderer::CreateGrassPipeline() { pipelineLayoutInfo.pPushConstantRanges = 0; if (vkCreatePipelineLayout(logicalDevice, &pipelineLayoutInfo, nullptr, &grassPipelineLayout) != VK_SUCCESS) { - throw std::runtime_error("Failed to create pipeline layout"); + throw std::runtime_error("Failed to create grass pipeline layout"); } // Tessellation state @@ -717,7 +856,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 +1023,12 @@ 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 (auto& descriptor_set : computeDescriptorSets) + { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &descriptor_set, 0, nullptr); + vkCmdDispatch(computeCommandBuffer, std::max(NUM_BLADES / WORKGROUP_SIZE, unsigned int(1)), 1, 1); + } + // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { throw std::runtime_error("Failed to record compute command buffer"); @@ -958,12 +1102,12 @@ void Renderer::RecordCommandBuffers() { VkBuffer vertexBuffers[] = { scene->GetModels()[j]->getVertexBuffer() }; VkDeviceSize offsets[] = { 0 }; vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); - + vkCmdBindIndexBuffer(commandBuffers[i], scene->GetModels()[j]->getIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); - + // Bind the descriptor set for each model vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 1, 1, &modelDescriptorSets[j], 0, nullptr); - + // Draw std::vector indices = scene->GetModels()[j]->getIndices(); vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); @@ -976,13 +1120,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 @@ -1004,7 +1149,7 @@ void Renderer::Frame() { computeSubmitInfo.pCommandBuffers = &computeCommandBuffer; if (vkQueueSubmit(device->GetQueue(QueueFlags::Compute), 1, &computeSubmitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { - throw std::runtime_error("Failed to submit draw command buffer"); + throw std::runtime_error("Failed to submit compute command buffer"); } if (!swapChain->Acquire()) { @@ -1057,6 +1202,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..139cc33 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,12 +56,16 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + std::vector computeDescriptorSets; + + std::vector grassDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..19e2909 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -26,31 +26,175 @@ struct Blade { // 2. Write out the culled blades // 3. Write the total number of blades remaining +layout(set = 2, binding = 0) buffer InBlades{ + Blade in_blades[]; +}; + +layout(set = 2, binding = 1) buffer OutBlades{ + Blade out_blades[]; +}; + // The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call // This is sort of an advanced feature so we've showed you what this buffer should look like // -// layout(set = ???, binding = ???) buffer NumBlades { -// uint vertexCount; // Write the number of blades remaining here -// uint instanceCount; // = 1 -// uint firstVertex; // = 0 -// uint firstInstance; // = 0 -// } numBlades; - -bool inBounds(float value, float bounds) { - return (value >= -bounds) && (value <= bounds); +layout(set = 2, binding = 2) buffer NumBlades { + uint vertexCount; // Write the number of blades remaining here + uint instanceCount; // = 1 + uint firstVertex; // = 0 + uint firstInstance; // = 0 +} numBlades; + +// global controls +// *********************************** +const float G = 4.8f; +const float TimeScale = 1.5f; +const float WindScale = 2.f; +const float OrientationThreshold = 0.9f; +const float ViewFrustrumThreshold = 0.3f; +const int DistanceCullLevels = 10; +const float DistanceCullMax = 20.f; + +#define ENABLE_ORIENTATION_CULL 1 +#define ENABLE_FRUSTRUM_CULL 1 +#define ENABLE_DISTANCE_CULL 1 +// *********************************** + +bool inBounds(vec3 value, float bounds) { + return (value.x >= -bounds) && (value.x <= bounds) && + (value.y >= -bounds) && (value.y <= bounds) && + (value.z >= -1.f) && (value.z <= 1.f); +} + +vec3 GetForce(in const Blade blade) +{ + const float t = TimeScale * totalTime; + + // attributes + const float direction = blade.v0.w; + const float height = blade.v1.w; + const float stiffness = blade.up.w; + const vec3 gE = vec3(0, -G, 0); + const vec3 width = vec3(cos(direction), 0.f, sin(direction)); + // gravity + const vec3 gF = 0.25f * G * normalize(cross(width, blade.up.xyz)); + const vec3 g = gF + gE; + + // recovery + const vec3 iv2 = blade.v0.xyz + blade.up.xyz * height; + const vec3 r = (iv2 - blade.v2.xyz) * stiffness; + + // wind function + const vec3 w_d = normalize(vec3(1, 0, 1)); + const float w_m = blade.v0.y * sin(blade.v0.x + t) - blade.v0.x * cos(blade.v0.y + t); + + const float fd = 1.f - abs(dot(w_d, normalize(blade.v2.xyz - blade.v0.xyz))); + const float fr = dot(blade.v2.xyz - blade.v0.xyz, blade.up.xyz) / height; + + const vec3 w = WindScale * w_d * w_m * fd * fr; + + // total force + return g + r + w; +} + +void ValidateBlade(inout Blade blade) +{ + // validate v2 and v1 + blade.v2.xyz -= blade.up.xyz * min(dot(blade.up.xyz, blade.v2.xyz - blade.v0.xyz), 0.f); + + const float l_proj = length(blade.v2.xyz - blade.v0.xyz - dot(blade.v2.xyz - blade.v0.xyz, blade.up.xyz) * blade.up.xyz); + + const float height = blade.v1.w; + blade.v1.xyz = blade.v0.xyz + height * blade.up.xyz * max(1.f - l_proj / height, 0.05f * max(l_proj / height, 1.f)); + + // correct the length + // L = 2L0 + (n - 1)L1 / (n + 1) + const float L = (2.f * distance(blade.v2.xyz, blade.v0.xyz) + (distance(blade.v1.xyz, blade.v0.xyz) + distance(blade.v2.xyz, blade.v1.xyz))) / 3.f; + const float r = height / L; + + const vec3 temp_v1 = blade.v0.xyz + r * (blade.v1.xyz - blade.v0.xyz); + blade.v2.xyz = temp_v1 + r * (blade.v2.xyz - blade.v1.xyz); + + blade.v1.xyz = temp_v1; +} + +void UpdateBlade(inout Blade blade) +{ + // Apply forces on every blade and update the vertices in the buffer + vec3 f = GetForce(blade); + f = clamp(f, vec3(-100.f), vec3(100.f)); + // total force + blade.v2.xyz += f * deltaTime; + + ValidateBlade(blade); +} + + +bool CullTest(const in Blade blade) +{ + const vec3 vec_dir = normalize(vec3(cos(blade.v0.w), 0.f, sin(blade.v0.w))); + + // Orientation Test + vec3 forward = transpose(camera.view)[2].xyz; + forward.y = 0.f; + forward = normalize(forward); +#if ENABLE_ORIENTATION_CULL + if(abs(dot(vec_dir, forward)) > OrientationThreshold ) return true; +#endif + // View-Frustum Test + mat4 vp = camera.proj * camera.view; + vec4 v0_cam = camera.view * vec4(blade.v0.xyz, 1.f); + vec4 v0_ndc = camera.proj * v0_cam; + v0_ndc /= v0_ndc.w; + + vec4 v2_ndc = vp * vec4(blade.v2.xyz, 1.f); + v2_ndc /= v2_ndc.w; + + const vec3 m = 0.25f * (blade.v0.xyz + blade.v2.xyz) + 0.5f * blade.v1.xyz; + vec4 m_ndc = vp * vec4(m, 1.f); + m_ndc /= m_ndc.w; + + const float h = .7f + ViewFrustrumThreshold; +#if ENABLE_FRUSTRUM_CULL + if(!inBounds(v0_ndc.xyz, h) && !inBounds(v2_ndc.xyz, h) && !inBounds(m_ndc.xyz, h)) return true; +#endif + // Distance Test + const vec3 up = (camera.view * vec4(blade.up.xyz, 0.f)).xyz; + const float d_proj = length(v0_cam.xyz - dot(v0_cam.xyz, up) * up); +#if ENABLE_DISTANCE_CULL + if(gl_GlobalInvocationID.x % DistanceCullLevels > floor(float(DistanceCullLevels) * (1.f - d_proj / DistanceCullMax))) return true; +#endif + return false; } void main() { + //if(gl_GlobalInvocationID.x >= numBlades.vertexCount) return; + // 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 + + // global index + const uint index = gl_GlobalInvocationID.x; + + Blade blade = in_blades[index]; + + UpdateBlade(blade); + + - // TODO: Apply forces on every blade and update the vertices in the buffer + // For now, simply passthrough the compute shader + in_blades[index] = blade; + - // TODO: Cull blades that are too far away or not in the camera frustum and write them + // 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(CullTest(blade)) return; + + out_blades[atomicAdd(numBlades.vertexCount, 1)] = blade; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..9ff2eaa 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 position; +layout(location = 1) in vec3 normal; +layout(location = 2) in vec2 uv; + layout(location = 0) out vec4 outColor; -void main() { - // TODO: Compute fragment color +void main() +{ + // lambert + + // use view dir as light dir + const vec3 light_dir = normalize(transpose(camera.view)[2].xyz); + + const vec3 top = vec3(0.1f, 0.9f, 0.1f); + const vec3 buttom = vec3(0.1f, 0.4f, 0.1f); + + const vec3 base_color = mix(buttom, top, uv.y); - outColor = vec4(1.0); + outColor = vec4(base_color * abs(dot(light_dir, normal)), 1.0); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..dffd7c5 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -8,19 +8,35 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation control shader inputs and outputs +//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[]; -void main() { +layout(location = 0) out vec4 out_v0[]; +layout(location = 1) out vec4 out_v1[]; +layout(location = 2) out vec4 out_v2[]; +layout(location = 3) out vec4 out_up[]; + +void main() +{ // 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] = ??? + if(gl_InvocationID == 0) + { + gl_TessLevelInner[0] = 5; + gl_TessLevelInner[1] = 5; + gl_TessLevelOuter[0] = 5; + gl_TessLevelOuter[1] = 5; + gl_TessLevelOuter[2] = 5; + gl_TessLevelOuter[3] = 5; + } } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..b45020d 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -10,9 +10,46 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { // 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 vec3 position; +layout(location = 1) out vec3 normal; +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 + + vec3 p0 = in_v0[0].xyz; + vec3 p1 = in_v1[0].xyz; + vec3 p2 = in_v2[0].xyz; + + float direction = in_v0[0].w; + //float height = in_v1[0].w; + float width = in_v2[0].w; + //float stiffness = in_up[0].w; + + vec3 p00 = mix(p0, p1, v); + vec3 p01 = mix(p1, p2, v); + vec3 p10 = mix(p00, p01, v); + + vec3 dir_vec = vec3(cos(direction), 0, sin(direction)); + width = mix(width, 0.f, v); + + float t = u + 0.5f * v - u * v; + + vec3 p = mix(p10 - width * dir_vec, p10 + width * dir_vec, t); + + normal = normalize(cross(p01 - p00, dir_vec)); + vec4 pos = camera.proj * camera.view * vec4(p, 1.f); + position = pos.xyz; + + uv = gl_TessCoord.xy; + + gl_Position = pos; } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..5bedec3 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,11 +7,19 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { }; // TODO: Declare vertex shader inputs and outputs +layout(location = 0) in vec4 in_v0; +layout(location = 1) in vec4 in_v1; +layout(location = 2) in vec4 in_v2; +layout(location = 3) in vec4 in_up; -out gl_PerVertex { - vec4 gl_Position; -}; +layout(location = 0) out vec4 out_v0; +layout(location = 1) out vec4 out_v1; +layout(location = 2) out vec4 out_v2; +layout(location = 3) out vec4 out_up; void main() { - // TODO: Write gl_Position and any other shader outputs + out_v0 = vec4(vec3(model * vec4(in_v0.xyz, 1.f)), in_v0.w); + out_v1 = vec4(vec3(model * vec4(in_v1.xyz, 1.f)), in_v1.w); + out_v2 = vec4(vec3(model * vec4(in_v2.xyz, 1.f)), in_v2.w); + out_up = vec4(vec3(model * vec4(in_up.xyz, 1.f)), in_up.w); }