diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b2211d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build* diff --git a/README.md b/README.md index 20ee451..f90cf8c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,87 @@ 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) +* Anthony Mansur + - https://www.linkedin.com/in/anthony-mansur-ab3719125/ + +- Tested on: Windows 10, AMD Ryzen 5 3600, Geforce RTX 2060 Super (personal) + +### Introduction + +For this project, we use [Vulkan](https://www.vulkan.org/) to implement a grass simulator and renderer based on the research 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) . We use compute shaders to perform physics calculations on Bezier curves that represent individual grass blades in our application. Since rendering every grass blade on every frame is fairly inefficient, we also use compute shaders to cull grass blades that do not contribute to a given frame. The remaining blades are then passed to a graphics pipeline. In this pipeline, we have a vertex shader that transforms our Bezier control points, tessellation shaders to dynamically create the grass geometry from the Bezier curves, and a fragment shader to shade the grass blades. + +### Grass Model + + + +To render the grass, we take in as input several control points for every grass blade. These control points are: + +- `v0`: the position of the grass blade on the geometry +- `v1`: a Bezier curve guide that is always "above" v0 with respect to the grass blade's up vector +- `v2`: a physical guide for which we simulate forces on +- `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at v0 + +These control points are four-dimensional vectors, with the fourth coordinate representing per-blade characteristics that helps us simulate and tessellate our grass blades correctly: + +- **Orientation**: the orientation of the grass blade's face +- **Height**: the height of the grass blade +- **Width**: the width of the grass blade's face +- **Stiffness coefficient**: the stiffness of our grass blade, which will affect the force computations on our blade (used in physics simulation) + +### Rendering + + + +Once we have passed the control points for every grass blade, we can render four different types of grass blades, as described in the research paper. As described in the introduction, our pipeline includes the tessellation shaders to render these blades as quadratic bezier curves. The result of our render is shown below: + + + + + +### Simulation + +We first simulate the grass blades and then render it every frame, using the time difference between every frame as our time step for the physics simulation. The forces are applied to the control point `v2`, with the added constraint that the length of the grass blade remains constant. + +The three types of forces the simulation computes are: + +- **Gravity:** both the environmental and the "front" gravity +- **Recovery:** the counter-force that brings our grass blade back into equilibrium (using Hooke's law) +- **Wind:** primitive wind function that has a direction and strength and varies with respect to time and the position of the grass blade. + +| Gravity + Recovery | Gravity + Recovery + Wind | +| :-----------------------------------------: | :---------------------------------------------: | +| | | + + + +### Performance Analysis + +We use several culling techniques to reduce the amount of grass blades we simulate and render for performance reasons. The three different types of tests we perform are: + +- **Orientation**: To minimize anti-aliasing effects, we eliminate grass blades when their front-facing direction is nearly perpendicular to the view vector +- **View-frustum**: Remove any grass blades that isn't inside our viewing frustum +- **Distance**: Reduce the number of grass blades as a function of distance from the camera + + + +To see these effects in extreme, see the following gifs: + +| Orientation test | View-Frustum test | Distance test | +| :-----------------------------------------------------: | :-------------------------------------------------: | :--------------------------------------------------: | +| | | | + + + +We then see how much our frames per second changes as we add these culling techniques and add more blades. To perform this test, we make the plane width 100 by 100, and we change the camera position to be 20 units from the origin, with a phi angle of -35 degrees. + + + + + -### (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. +As we can see from the performance analysis, each culling technique improves our frames per second, with the view-frustum test being the most impactful and the orientation test being the least, as expected. This trend follows as we increase the number of blades. diff --git a/bin/Debug/vulkan_grass_rendering.exe b/bin/Debug/vulkan_grass_rendering.exe new file mode 100644 index 0000000..43b953e Binary files /dev/null and b/bin/Debug/vulkan_grass_rendering.exe differ diff --git a/bin/Debug/vulkan_grass_rendering.pdb b/bin/Debug/vulkan_grass_rendering.pdb new file mode 100644 index 0000000..2669fe1 Binary files /dev/null and b/bin/Debug/vulkan_grass_rendering.pdb differ diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe index f68db3a..4ee6002 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/img/distance-culling.gif b/img/distance-culling.gif new file mode 100644 index 0000000..0118d82 Binary files /dev/null and b/img/distance-culling.gif differ diff --git a/img/final.gif b/img/final.gif new file mode 100644 index 0000000..953b9cd Binary files /dev/null and b/img/final.gif differ diff --git a/img/frustum-culling.gif b/img/frustum-culling.gif new file mode 100644 index 0000000..9fac676 Binary files /dev/null and b/img/frustum-culling.gif differ diff --git a/img/grass-types.png b/img/grass-types.png new file mode 100644 index 0000000..7f59d14 Binary files /dev/null and b/img/grass-types.png differ diff --git a/img/no-force.gif b/img/no-force.gif new file mode 100644 index 0000000..b63b9a1 Binary files /dev/null and b/img/no-force.gif differ diff --git a/img/no-force.png b/img/no-force.png new file mode 100644 index 0000000..6921ec1 Binary files /dev/null and b/img/no-force.png differ diff --git a/img/no-wind.gif b/img/no-wind.gif new file mode 100644 index 0000000..7b5da09 Binary files /dev/null and b/img/no-wind.gif differ diff --git a/img/orientation-culling.gif b/img/orientation-culling.gif new file mode 100644 index 0000000..a9ca812 Binary files /dev/null and b/img/orientation-culling.gif differ diff --git a/img/performance.png b/img/performance.png new file mode 100644 index 0000000..c73667f Binary files /dev/null and b/img/performance.png differ diff --git a/img/strong-wind.gif b/img/strong-wind.gif new file mode 100644 index 0000000..5a0bc37 Binary files /dev/null and b/img/strong-wind.gif differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..ba039dc 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -44,8 +44,8 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode indirectDraw.firstVertex = 0; 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::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory); + 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..f740050 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 << 13; // default is 1 << 13 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/Renderer.cpp b/src/Renderer.cpp index b445d04..4967d91 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -20,6 +20,7 @@ Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* c CreateCameraDescriptorSetLayout(); CreateModelDescriptorSetLayout(); CreateTimeDescriptorSetLayout(); + CreateGrassDescriptorSetLayout(); CreateComputeDescriptorSetLayout(); CreateDescriptorPool(); CreateCameraDescriptorSet(); @@ -194,10 +195,63 @@ void Renderer::CreateTimeDescriptorSetLayout() { } } +void Renderer::CreateGrassDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + uboLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { uboLayoutBinding }; + + // 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, &grassDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } +} + 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 inputBladesLayoutBinding = {}; + inputBladesLayoutBinding.binding = 0; + inputBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + inputBladesLayoutBinding.descriptorCount = 1; + inputBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + inputBladesLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding culledBladesLayoutBinding = {}; + culledBladesLayoutBinding.binding = 1; + culledBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledBladesLayoutBinding.descriptorCount = 1; + culledBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + culledBladesLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding numOfBladesLayoutBinding = {}; + numOfBladesLayoutBinding.binding = 2; + numOfBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numOfBladesLayoutBinding.descriptorCount = 1; + numOfBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numOfBladesLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { inputBladesLayoutBinding, culledBladesLayoutBinding, numOfBladesLayoutBinding }; + + // 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 +270,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 +375,44 @@ 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 + + // Update descriptor sets + grassDescriptorSets.resize(scene->GetBlades().size()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { grassDescriptorSetLayout }; + 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 (uint32_t i = 0; i < scene->GetBlades().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].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +453,57 @@ 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 < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo inputBladesBufferInfo = {}; + inputBladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + inputBladesBufferInfo.offset = 0; + inputBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect);; + + for (uint32_t j = 0; j < 3; j++) + { + descriptorWrites[i * 3 + j].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i * 3 + j].dstSet = computeDescriptorSets[i]; + descriptorWrites[i * 3 + j].dstBinding = j; + descriptorWrites[i * 3 + j].dstArrayElement = 0; + descriptorWrites[i * 3 + j].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[i * 3 + j].descriptorCount = 1; + descriptorWrites[i * 3 + j].pBufferInfo = + ((j == 0) ? &inputBladesBufferInfo : (j == 1) ? &culledBladesBufferInfo : &numBladesBufferInfo); + descriptorWrites[i * 3 + j].pImageInfo = nullptr; + descriptorWrites[i * 3 + j].pTexelBufferView = nullptr; + } + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -654,7 +798,7 @@ void Renderer::CreateGrassPipeline() { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout }; + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, grassDescriptorSetLayout }; // Pipeline layout: used to specify uniform values VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -716,8 +860,7 @@ void Renderer::CreateComputePipeline() { computeShaderStageInfo.module = computeShaderModule; 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 = {}; @@ -883,7 +1026,12 @@ void Renderer::RecordComputeCommandBuffer() { // Bind descriptor set for time uniforms 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 + // Bind each compute descriptor set and dispatch + for (int i = 0; i < computeDescriptorSets.size(); i++) + { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr); + vkCmdDispatch(computeCommandBuffer, ceil(NUM_BLADES / WORKGROUP_SIZE), 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1124,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 +1206,8 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, grassDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..c48e716 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -18,6 +18,7 @@ class Renderer { void CreateCameraDescriptorSetLayout(); void CreateModelDescriptorSetLayout(); void CreateTimeDescriptorSetLayout(); + void CreateGrassDescriptorSetLayout(); void CreateComputeDescriptorSetLayout(); void CreateDescriptorPool(); @@ -56,12 +57,16 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout grassDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + std::vector grassDescriptorSets; + std::vector computeDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/Scene.cpp b/src/Scene.cpp index 86894f2..98efdfa 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -1,10 +1,14 @@ #include "Scene.h" #include "BufferUtils.h" +#include Scene::Scene(Device* device) : device(device) { BufferUtils::CreateBuffer(device, sizeof(Time), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, timeBuffer, timeBufferMemory); vkMapMemory(device->GetVkDevice(), timeBufferMemory, 0, sizeof(Time), 0, &mappedData); memcpy(mappedData, &time, sizeof(Time)); + + fps = 0.0f; + count = 0; } const std::vector& Scene::GetModels() const { @@ -31,6 +35,19 @@ void Scene::UpdateTime() { time.deltaTime = nextDeltaTime.count(); time.totalTime += time.deltaTime; + fps += time.deltaTime; + count++; + + if (count == 100) + { + fps /= count; + fps = 1 / fps; + std::cout << "fps: " << fps << std::endl; + + fps = 0; + count = 0; + } + memcpy(mappedData, &time, sizeof(Time)); } diff --git a/src/Scene.h b/src/Scene.h index 7699d78..582605a 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -26,6 +26,9 @@ class Scene { std::vector models; std::vector blades; + float fps; + int count; + high_resolution_clock::time_point startTime = high_resolution_clock::now(); public: diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..6f62352 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,8 @@ #include "Scene.h" #include "Image.h" +#include "chrono" + Device* device; SwapChain* swapChain; Renderer* renderer; @@ -91,6 +93,7 @@ int main() { swapChain = device->CreateSwapChain(surface, 5); camera = new Camera(device, 640.f / 480.f); + camera->UpdateOrbit(0, -35, -10); // for performance analysis VkCommandPoolCreateInfo transferPoolInfo = {}; transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; @@ -116,7 +119,7 @@ int main() { grassImageMemory ); - float planeDim = 15.f; + float planeDim = 15.f; // 15.f is default float halfWidth = planeDim * 0.5f; Model* plane = new Model(device, transferCommandPool, { diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..d41eb71 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,14 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 + +// TODO: update definitions depending on desired result +//#define FORCE // Uncomment to simulate forces +#define WIND // Uncomment to simulate wind +#define ORIENTATION_CULLING +#define VIEW_FRUSTUM_CULLING +#define DISTANCE_CULLING + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -21,6 +29,21 @@ struct Blade { vec4 up; }; +layout(set = 2, binding = 0) buffer InputBlades { + Blade inputBlades[]; +}; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade culledBlades[]; +}; + + layout(set = 2, binding = 2) buffer NumBlades { + uint vertexCount; // Write the number of blades remaining here + uint instanceCount; // = 1 + uint firstVertex; // = 0 + uint firstInstance; // = 0 + } numBlades; + // TODO: Add bindings to: // 1. Store the input blades // 2. Write out the culled blades @@ -43,14 +66,138 @@ bool inBounds(float value, float bounds) { void main() { // Reset the number of blades to 0 if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + numBlades.vertexCount = 0; } barrier(); // Wait till all threads reach this point - // TODO: Apply forces on every blade and update the vertices in the buffer + // get required variables + vec3 v0 = vec3(inputBlades[gl_GlobalInvocationID.x].v0); + vec3 v1 = vec3(inputBlades[gl_GlobalInvocationID.x].v1); + vec3 v2 = vec3(inputBlades[gl_GlobalInvocationID.x].v2); + vec3 up = vec3(inputBlades[gl_GlobalInvocationID.x].up); + float theta = inputBlades[gl_GlobalInvocationID.x].v0.w; + float height = inputBlades[gl_GlobalInvocationID.x].v1.w; + float width = inputBlades[gl_GlobalInvocationID.x].v2.w; + float stiffness = inputBlades[gl_GlobalInvocationID.x].up.w; + + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; + vec3 eye = vec3(inverse(camera.view) * vec4(0.f, 0.f, 0.f, 1.f)); + vec3 bitangent = vec3(cos(theta), 0, -sin(theta)); + + // TODO: calculate forces + vec3 recovery = vec3(0.0); + vec3 gravity = vec3(0.0); + vec3 wind = vec3(0.0); + + /** recovery */ + vec3 i_v2 = v0 + up * height; + recovery = (i_v2 - v2) * stiffness; + + /** gravity */ + vec3 gE = vec3(0, -1, 0) * 9.80665f; + vec3 norm = cross(up, bitangent); + vec3 gF = 0.25f * length(gE) * norm; + + gravity = gE + gF; + + /** wind */ + float maxWindStrength = 2.f; // TODO: update to your own liking + float windSpeed = 2.f; // TODO: update to your own liking + vec4 windDirection = vec4(-0.5f, 0.5f, 0, maxWindStrength * cos(windSpeed * totalTime + 0.25 * v0)); + float windAlignment = abs(dot(windDirection.xyz, norm)); + + #ifdef WIND + wind = normalize(windDirection.xyz) * windAlignment * windDirection.w; + #endif + + #ifdef FORCE + vec3 delta = (recovery + gravity + wind) * deltaTime; + #else + vec3 delta = vec3(0); + #endif + + // update the position of v2 + v2 += delta; // translate based on force + v2 -= up * min(dot(up, v2 - v0), 0); // ensure valid posiiton of v2 + + // update the position of v1 + float lProj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + height * up * max(1 - lProj / height, 0.05 * max(lProj/height, 1)); + + // ensure length of curve is valid + float L0 = length(v2 - v0); + float L1 = length(v2 - v1) + length(v1 - v0); + float L = (2 * L0 + L1) / 3; + + float r = height / L; + + // TODO: implement correctly first + v1 = v0 + r * (v1 - v0); + v2 = v1 + r * (v2 - v1); + + // update vectors + inputBlades[gl_GlobalInvocationID.x].v1 = vec4(v1,inputBlades[gl_GlobalInvocationID.x].v1.w); + inputBlades[gl_GlobalInvocationID.x].v2 = vec4(v2, inputBlades[gl_GlobalInvocationID.x].v2.w); + + barrier(); // 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 culled = false; + + // Orientation test + #ifdef ORIENTATION_CULLING + vec3 dirC = eye - v0; + float dotProd = abs(dot(normalize(dirC), normalize(bitangent))); + + // TODO: implement correctly first + if (dotProd > 0.9) + culled = true; + #endif + + // view-frustum test + #ifdef VIEW_FRUSTUM_CULLING + // TODO: verify this is working + mat4 viewProj = camera.proj * camera.view; + + vec4 normalizedV0 = viewProj * vec4(v0, 1.f); normalizedV0 /= normalizedV0.w; + vec4 normalizedM = viewProj * vec4(m, 1.f); normalizedM /= normalizedM.w; + vec4 normalizedV2 = viewProj * vec4(v2, 1.f); normalizedV2 /= normalizedV2.w; + + float tolerance = 0.1f; // TODO: need changing? + float toleranceV0 = 1 + tolerance; + float toleranceM = 1 + tolerance; + float toleranceV2 = 1 + tolerance; + + bool v0Test = inBounds(normalizedV0.x, toleranceV0) && + inBounds(normalizedV0.y, toleranceV0); + bool MTest = inBounds(normalizedM.x, toleranceM) && + inBounds(normalizedM.y, toleranceM); + bool v2Test = inBounds(normalizedV2.x, toleranceV2) && + inBounds(normalizedV2.y, toleranceV2); + + if (!v0Test && !MTest && !v2Test) + culled = true; + #endif + + // Distance Test + #ifdef DISTANCE_CULLING + float dProj = length(v0 - eye - up * dot((v0 - eye), up)); + float dMax = 80.f; // TODO: find the ideal number + uint id = gl_GlobalInvocationID.x; + uint n = 10; // TODO: find the ideal number + + if (mod(id, n) >= (n * (1 - dProj / dMax))) + culled = true; + #endif + + if (!culled) + { + // If we did not remove the blade, add it to the culled blades buffer and increment + // our vertex count with an atomic addition. + culledBlades[atomicAdd(numBlades.vertexCount, 1)] = inputBlades[gl_GlobalInvocationID.x]; + } } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..5481853 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,14 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs - +layout(location = 0) in float vCoord; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color + vec4 grassColor = vec4(.24, .59, .09, 1.0); - outColor = vec4(1.0); -} + float verticalMultiplier = (1 - vCoord) * 0.1 + vCoord * 1.5; + + outColor = verticalMultiplier * grassColor; +} diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..318c923 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -3,24 +3,33 @@ layout(vertices = 1) out; -layout(set = 0, binding = 0) uniform CameraBufferObject { - mat4 view; - mat4 proj; -} 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[]; 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] = ??? + float o = 64, i = 64; // TODO: change based on distance from camera + gl_TessLevelInner[0] = i; + gl_TessLevelInner[1] = i; + gl_TessLevelOuter[0] = o; + gl_TessLevelOuter[1] = o; + gl_TessLevelOuter[2] = o; + gl_TessLevelOuter[3] = o; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..5839d37 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,75 @@ 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 float vCoord; + +// functions +float noise1D(vec2 p); void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; + vCoord = v; // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + vec3 v0 = gl_in[0].gl_Position.xyz; + vec3 v1 = in_v1[0].xyz; + vec3 v2 = in_v2[0].xyz; + vec3 up = in_up[0].xyz; + float theta = in_v0[0].w; + float height = in_v1[0].w; + float width = in_v2[0].w; + + // TODO: Assumes up is (0, 1, 0), should be changed to a more generic solution + vec3 t1 = vec3(cos(theta), 0, -sin(theta)); + + vec3 a = v0 + v * (v1 - v0); + vec3 b = v1 + v * (v2 - v1); + vec3 c = a + v * (b - a); + vec3 c0 = c - width * t1; + vec3 c1 = c + width * t1; + vec3 t0 = normalize(b-a); + vec3 norm = cross(normalize(t0.xyz), normalize(t1.xyz)); + + float rand = noise1D(vec2(v0.x, v0.z)); + + float t; + if (rand > 0.99) + { + // quad + t = u; + } + else if (rand > 0.4) + { + // triangles + t = u + 0.5 * v - u * v; + } + else if (rand > 0.2) + { + // quadratic + t = u - u * v * v; + } + else + { + // triangle-tip + float thresh = noise1D(vec2(v0.x, v0.z)); + t = 0.5 + (u - 0.5) * (1 - max(v - thresh, 0)/(1 - thresh)); + } + + // 3D displacement + vec3 d = width * norm * (0.5 - abs(u - 0.5) * (1 - v)); + + // TODO: width correction? + gl_Position = camera.proj * camera.view * vec4((1 - t) * c0 + t * c1 + d, 1.f); +} + +// Taken from CIS 460 +float noise1D(vec2 p) +{ + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..5e2de60 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,6 +7,15 @@ 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; + +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; @@ -14,4 +23,11 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + + // get the transformed vertices + gl_Position = model * vec4(in_v0.xyz, 1.f); + out_v0 = model * vec4(in_v0.xyz, 1.f); out_v0.w = in_v0.w; + out_v1 = model * vec4(in_v1.xyz, 1.f); out_v1.w = in_v1.w; + out_v2 = model * vec4(in_v2.xyz, 1.f); out_v2.w = in_v2.w; + out_up = model * vec4(in_up.xyz, 1.f); out_up.w = in_up.w; }