diff --git a/CMakeLists.txt b/CMakeLists.txt index 36d5dcb..6668cc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.10) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") project(cis565_project5_vulkan_grass_rendering) diff --git a/README.md b/README.md index 20ee451..d255302 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,67 @@ Vulkan Grass Rendering ================================== **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5** +![](img/GrassMain.gif) +* Pavel Peev +* Tested on: Windows 11, Intel Core Ultra 5 225f @ 3.3GHz, NVIDIA GeForce RTX 5060 + +### Description +This project implements 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) using Vulkan. + +In the paper, the blades of grass are represented as 3 point cubic Bezier curves, upon which forces are applied on. The first control point of the bezier curve represents the base of the blade of grass, and is unmoving. The third control point in the bezier curve represents the tip of the grass blade, and has three forces applied to it: gravity, recovery, and wind. Finally, the second control point helps keep the blade of grass at a constant height. + +We run a compute pipeline to calculate the forces and new positions of the second and third control points. During this stage, we also calculate which blades of grass can be culled with 3 culling operation: orientation, view-frustrum, and distance based culling. + +Afterwards, the control points are sent to the graphics pipeline with a tesselation shader, which tesselates a quad and uses the uv positions of the points on the quad to align them to the bezier curve in a shape of our choosing (in our case, a triangular tipped rectangle). Upon completion of the tesselation, the new geometry is sent to the fragment shader to be rendered. + + +## Images + +### Grass No Force +![](img/GrassNoForce.gif) + +### Grass No Wind +![](img/GrassNoWind.gif) + +### Grass with Wind +![](img/GrassWithWind.gif) + +### Grass Culling +![](img/GrassCulling.gif) + + + +## Performance Analysis + + + +![](img/grassAnalysis.png) + +The above data is done without culling. As the number of blades increases exponentially, we see the fps half. This shows that the grass simulation is very parralel, only increasing in cost logarithmically alongside the exponential increase in the amount of grass we need to render. For a grass simulation, the performance is alright, but for large scale simulations of entire environments with potentially millions or billions of grass blades, it requires other optimizations to run smoothly, such as the culling. + +### Culling Performance + +Number of Blades: 131072 (2^17) + +| Base | Orientation | Orientation Optimal| +|---|---|---| +| 110 fps | 140 fps| 200 fps| + +| Base | View-Frustrum | View-Frustrum Optimal | +|---|---|---| +| 110 fps | 150 fps| 300 fps | + +| Base | Distance | Distance Optimal | +|---|---|---| +| 110 fps | 250 fps| 750 fps | + +| Base | All Culling | All Culling Optimal | +|---|---|---| +| 110 fps | 300 fps| 1000 fps | + +For each of the optimal measurements, we position the camera such that they can more effectively cull. For the orientation culling, we position in a spot where more grass is perpendicular to the camera. For view frustrum culling, we look at it from above and zoomed in at a patch of grass. For distance, we zoom out a lot so that about half the patches of grass are culled fully. + +We see notable improvements with each of the different culling methods, with the first two giving a marginal 1.5 times performance boost generally and with the potential to double or triple performance in more specific situations. Distance seems to be the most effective, although this one is of course the most visible, as it can reduces . Of course, all 3 together provides the best performance boost, though it's important to note that it's not additive, as there is a bit of overlap between the different culling methods. -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) -### (TODO: Your README) -*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/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe index f68db3a..c25ff5b 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/external/GLFW/CMakeLists.txt b/external/GLFW/CMakeLists.txt index 56c1f38..eef43b5 100644 --- a/external/GLFW/CMakeLists.txt +++ b/external/GLFW/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.10) project(GLFW C) @@ -7,7 +7,7 @@ set(CMAKE_LEGACY_CYGWIN_WIN32 OFF) if (NOT CMAKE_VERSION VERSION_LESS "3.0") # Until all major package systems have moved to CMake 3, # we stick with the older INSTALL_NAME_DIR mechanism - cmake_policy(SET CMP0042 OLD) + #cmake_policy(SET CMP0042 OLD) endif() if (NOT CMAKE_VERSION VERSION_LESS "3.1") diff --git a/img/GrassCulling.gif b/img/GrassCulling.gif new file mode 100644 index 0000000..efac81c Binary files /dev/null and b/img/GrassCulling.gif differ diff --git a/img/GrassMain.gif b/img/GrassMain.gif new file mode 100644 index 0000000..c37392f Binary files /dev/null and b/img/GrassMain.gif differ diff --git a/img/GrassNoForces.gif b/img/GrassNoForces.gif new file mode 100644 index 0000000..5351ba9 Binary files /dev/null and b/img/GrassNoForces.gif differ diff --git a/img/GrassNoWind.gif b/img/GrassNoWind.gif new file mode 100644 index 0000000..3722453 Binary files /dev/null and b/img/GrassNoWind.gif differ diff --git a/img/GrassWithWind.gif b/img/GrassWithWind.gif new file mode 100644 index 0000000..ef42966 Binary files /dev/null and b/img/GrassWithWind.gif differ diff --git a/img/grassAnalysis.png b/img/grassAnalysis.png new file mode 100644 index 0000000..94a8d10 Binary files /dev/null and b/img/grassAnalysis.png differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..0142372 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -45,7 +45,7 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode indirectDraw.firstInstance = 0; BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory); - BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory); + BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory); BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory); } diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..a637462 100644 --- a/src/Blades.h +++ b/src/Blades.h @@ -5,12 +5,12 @@ #include "Model.h" constexpr static unsigned int NUM_BLADES = 1 << 13; -constexpr static float MIN_HEIGHT = 1.3f; -constexpr static float MAX_HEIGHT = 2.5f; +constexpr static float MIN_HEIGHT = 1.3f * 1.5; +constexpr static float MAX_HEIGHT = 2.5f * 1.5; constexpr static float MIN_WIDTH = 0.1f; constexpr static float MAX_WIDTH = 0.14f; -constexpr static float MIN_BEND = 7.0f; -constexpr static float MAX_BEND = 13.0f; +constexpr static float MIN_BEND = 13.0f; +constexpr static float MAX_BEND = 20.0f; struct Blade { // Position and direction diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..5444091 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,39 @@ 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 bladesBufferBinding = {}; + bladesBufferBinding.binding = 1; + bladesBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladesBufferBinding.descriptorCount = 1; + bladesBufferBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladesBufferBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding culledBladesBufferBinding = {}; + culledBladesBufferBinding.binding = 2; + culledBladesBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledBladesBufferBinding.descriptorCount = 1; + culledBladesBufferBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + culledBladesBufferBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { numBladesBinding, bladesBufferBinding, culledBladesBufferBinding }; + + // 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 +249,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 +354,53 @@ 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()); + //Since it uses the same layout as the models (model matrix + texture), we just reuse the modelDescriptorSetLayout here + 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; + + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(2 * grassDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo grassBufferInfo = {}; + grassBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + grassBufferInfo.offset = 0; + grassBufferInfo.range = sizeof(ModelBufferObject); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = scene->GetBlades()[i]->GetTextureView(); + imageInfo.sampler = scene->GetBlades()[i]->GetTextureSampler(); + + descriptorWrites[2 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[2 * i + 0].dstSet = grassDescriptorSets[i]; + descriptorWrites[2 * i + 0].dstBinding = 0; + descriptorWrites[2 * i + 0].dstArrayElement = 0; + descriptorWrites[2 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[2 * i + 0].descriptorCount = 1; + descriptorWrites[2 * i + 0].pBufferInfo = &grassBufferInfo; + descriptorWrites[2 * i + 0].pImageInfo = nullptr; + descriptorWrites[2 * i + 0].pTexelBufferView = nullptr; + + descriptorWrites[2 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[2 * i + 1].dstSet = grassDescriptorSets[i]; + descriptorWrites[2 * i + 1].dstBinding = 1; + descriptorWrites[2 * i + 1].dstArrayElement = 0; + descriptorWrites[2 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[2 * i + 1].descriptorCount = 1; + descriptorWrites[2 * i + 1].pImageInfo = &imageInfo; + } + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } void Renderer::CreateTimeDescriptorSet() { @@ -360,8 +441,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()); + + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = layouts; + + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect); + + VkDescriptorBufferInfo bladesBufferInfo = {}; + bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + bladesBufferInfo.offset = 0; + bladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.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 = &numBladesBufferInfo; + 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 = &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; + + } + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } + + + + void Renderer::CreateGraphicsPipeline() { VkShaderModule vertShaderModule = ShaderModule::Create("shaders/graphics.vert.spv", logicalDevice); VkShaderModule fragShaderModule = ShaderModule::Create("shaders/graphics.frag.spv", logicalDevice); @@ -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 = {}; @@ -885,6 +1034,17 @@ void Renderer::RecordComputeCommandBuffer() { // 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, 1); + + + + } + + // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { throw std::runtime_error("Failed to record compute command buffer"); @@ -976,13 +1136,13 @@ 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 +1217,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..fb26e85 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,12 +56,18 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; + VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + std::vector computeDescriptorSets; + std::vector< VkDescriptorSet> grassDescriptorSets; + + VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/images/grassBlue.jpg b/src/images/grassBlue.jpg new file mode 100644 index 0000000..7f6ec2c Binary files /dev/null and b/src/images/grassBlue.jpg differ diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..c551c28 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -106,7 +106,7 @@ int main() { VkDeviceMemory grassImageMemory; Image::FromFile(device, transferCommandPool, - "images/grass.jpg", + "images/grassBlue.jpg", VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_SAMPLED_BIT, @@ -131,6 +131,8 @@ int main() { Blades* blades = new Blades(device, transferCommandPool, planeDim); + blades->SetTexture(grassImage); + vkDestroyCommandPool(device->GetVkDevice(), transferCommandPool, nullptr); Scene* scene = new Scene(device); diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..e9139eb 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -4,6 +4,13 @@ #define WORKGROUP_SIZE 32 layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; + +#define WIND 1 +#define FORCE 1 +#define ORIENTATION_CULLING 1 +#define VIEW_FRUSTRUM_CULLING 1 +#define DISTANCE_CULLING 1 + layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; @@ -29,12 +36,21 @@ 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; // Write the number of blades remaining here + uint instanceCount; // = 1 + uint firstVertex; // = 0 + uint firstInstance; // = 0 +} numBlades; + +layout(set = 2, binding = 1) buffer bladesBuffer +{ + Blade blades[]; +}; +layout(set = 2, binding = 2) buffer culledBladesBuffer +{ + Blade culledBlades[]; +}; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); @@ -43,10 +59,111 @@ 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 + + vec3 v0 = blades[gl_GlobalInvocationID.x].v0.xyz; + vec3 v1 = blades[gl_GlobalInvocationID.x].v1.xyz; + vec3 v2 = blades[gl_GlobalInvocationID.x].v2.xyz; + vec3 up = blades[gl_GlobalInvocationID.x].up.xyz; + + float orientation = blades[gl_GlobalInvocationID.x].v0.w; + float height = blades[gl_GlobalInvocationID.x].v1.w; + float width = blades[gl_GlobalInvocationID.x].v2.w;; + float stiffness = blades[gl_GlobalInvocationID.x].up.w; + + //For now, assume up is always (0, 1, 0) + vec3 bitangent = normalize(vec3(sin(orientation), 0.0, cos(orientation))); + vec3 forward = normalize(cross(bitangent, up)); + + vec3 gravDir = vec3(0, -1, 0); + float gravConst = 9.806 * 1.5; + + vec3 enviroGrav = gravDir * gravConst; + vec3 frontGrav = 0.25 * forward * gravConst; + + + vec3 totalGrav = enviroGrav + frontGrav; + + //Recover original position of V2 + vec3 iV2 = v0 + height * up; + + vec3 recovery = (iV2 - v2) * stiffness; + + vec3 wind = vec3((cos((totalTime + v0.x * 5) * 1) + 2) * 2, 0, (sin((v0.z * 5 + totalTime) * 1.5) + 2) * 2); + + float directionalAlignment = 1 - abs(dot(normalize(wind), normalize(v2 - v0))); + float heightRatio = dot(v2 - v0, up) / height; + + vec3 windForce = vec3(0, 0, 0); +#if WIND + windForce = wind * directionalAlignment * heightRatio * (cos(totalTime * 1) + 4) * 1.5; +#endif + +#if FORCE + v2 += (totalGrav + recovery + windForce) * deltaTime; +#endif + + v2 = v2 - up * min(dot(up, v2 - v0), 0); + + float lProj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + height * up * max(1 - lProj/height, 0.05 * max(lProj/height, 1)); + + //float L = length(v1 - v0) + length(v2 - v1); + float L0 = length(v2 - v0); + float L1 = length(v1 - v0) + length(v2 - v1); + float L = ((2 * L0) + L1) / 3.0; + float r = height / L; + + + + vec3 v1Corrected = v0 + r * (v1 - v0); + vec3 v2Corrected = v1Corrected + r * (v2 - v1); + + blades[gl_GlobalInvocationID.x].v1 = vec4(v1Corrected, height); + blades[gl_GlobalInvocationID.x].v2 = vec4(v2Corrected, width); + + mat4 inverseView = inverse(camera.view); + vec3 cameraPos = inverseView[3].xyz; + + +#if ORIENTATION_CULLING + + vec3 viewDir = normalize(v0 - cameraPos); + vec3 bladeDir = normalize(cross(up, forward)); + + if(abs(dot(viewDir, bladeDir)) > 0.9) + { + return; + } +#endif +#if VIEW_FRUSTRUM_CULLING + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; + vec4 ndcM = camera.proj * camera.view * vec4(m, 1.0); + float h = ndcM.w + 0.5; + if(!(ndcM.x > -h && ndcM.x < h && ndcM.y > -h && ndcM.y < h && ndcM.z > -h && ndcM.z < h)) + { + return; + } + +#endif +#if DISTANCE_CULLING + + float dProj = length(v0 - cameraPos - up * dot(v0 - cameraPos, up)); + int numBucket = 20; + float dMax = 30; + if ((gl_GlobalInvocationID.x % numBucket) < int(floor(numBucket * (1 - dProj / dMax)))) + { + return; + } +#endif + + + culledBlades[atomicAdd(numBlades.vertexCount, 1)] = blades[gl_GlobalInvocationID.x]; + + // TODO: Apply forces on every blade and update the vertices in the buffer // TODO: Cull blades that are too far away or not in the camera frustum and write them diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..27064ee 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -6,12 +6,20 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; +layout(set = 1, binding = 1) uniform sampler2D texSampler; + // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 normal; +layout(location = 1) in vec2 uv; + layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - outColor = vec4(1.0); + vec4 color1 = vec4(12.0 / 255.0, 124.0 / 255.0, 89.0 / 255.0, 1); + vec4 color2 = vec4(88.0 / 255.0, 164.0 / 255.0, 176.0 / 255.0, 1); + + outColor = mix(color1, color2, uv.y); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..cd5aa9e 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -9,18 +9,41 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation control shader inputs and outputs +out gl_PerVertex { + vec4 gl_Position; +} gl_out[]; + +in gl_PerVertex { + vec4 gl_Position; +} gl_in[]; + + +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[]; +layout(location = 3) out vec4 outUp[]; + + 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 - + outV0[gl_InvocationID] = inV0[gl_InvocationID]; + outV1[gl_InvocationID] = inV1[gl_InvocationID]; + outV2[gl_InvocationID] = inV2[gl_InvocationID]; + outUp[gl_InvocationID] = inUp[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] = 6.0; + gl_TessLevelInner[1] = 6.0; + gl_TessLevelOuter[0] = 6.0; + gl_TessLevelOuter[1] = 6.0; + gl_TessLevelOuter[2] = 6.0; + gl_TessLevelOuter[3] = 6.0; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..91b027e 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,56 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } 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 = 3) in vec4 inUp[]; + +layout(location = 0) out vec3 outNormal; +layout(location = 1) out vec2 outUV; void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; + vec3 v0 = inV0[0].xyz; + vec3 v1 = inV1[0].xyz; + vec3 v2 = inV2[0].xyz; + vec3 up = inUp[0].xyz; + + float orientation = inV0[0].w; + float height = inV1[0].w; + float width = inV2[0].w; + float stiffness = inUp[0].w; + + + vec3 a = v0 + v * (v1 - v0); + vec3 b = v1 + v * (v2 - v1); + //Point along bezier curve + vec3 c = a + v * (b - a); + + //Tangent + vec3 t0 = normalize(b - a); + //Bitangent + //For now, just assume up is always (0, 1, 0) + vec3 t1 = vec3(sin(orientation), 0.0, cos(orientation)); + vec3 c0 = c - width * t1; + vec3 c1 = c + width * t1; + + outNormal = normalize(cross(t0, t1)); + outUV = vec2(u, v); + //Square shape + + //vec3 interpoPos = (1 - u) * c0 + u * c1; + + //TriangleTip + + float t = 0.5 + (u - 0.5) * (1 - max(v - 0.25, 0)/ (1 - 0.25)); + vec3 interpoPos = (1 - t) * c0 + t * c1; + + + gl_Position = camera.proj * camera.view * vec4(interpoPos, 1); + + // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..e87ec16 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,11 +7,43 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { }; // TODO: Declare vertex shader inputs and outputs +// Position and direction +layout(location = 0) in vec4 v0; +// Bezier point and height +layout(location = 1) in vec4 v1; +// Physical model guide and width +layout(location = 2) in vec4 v2; +// Up vector and stiffness coefficient +layout(location = 3) in vec4 up; + 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 = model * vec4(v0.xyz, 1); + outV1 = model * vec4(v1.xyz, 1); + outV2 = model * vec4(v2.xyz, 1); + outUp = model * vec4(up.xyz, 0); + + //Save the w values + outV0.w = v0.w; + outV1.w = v1.w; + outV2.w = v2.w; + outUp.w = up.w; + + + gl_Position = model * vec4(v0.xyz, 1); + + }