diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70e7bcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +bin/Debug/ \ No newline at end of file diff --git a/README.md b/README.md index 20ee451..fd022bf 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,145 @@ 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) -### (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. +* Yilin Liu + * [LinkedIn](https://www.linkedin.com/in/yilin-liu-9538ba1a5/) + * [Personal website](https://www.yilin.games) +* Tested on personal laptop: + - Windows 10, Intel(R) Core(TM), i7-10750H CPU @ 2.60GHz 2.59 GHz, RTX 2070 Max-Q 8GB + +Overview +============= + +A Vulkan-based grass renderer was implemented in this project. I used methods discussed in K. Jahrmann and M. Wimmer's 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). + +The simulation and rendering of the grass mainly includes the following parts: a vertex shader to transform Bezier control points, tessellation shaders to dynamically create the grass geometry from the Bezier curves, and a fragment shader to shade the grass blades. + +![](img/cover.gif) + + +Features +============= +### Blade Model + +We use Bezier curve with three control points, V0 (position), V1 (up vector), and V2 (direction) to represent the blades. + +![](img/blade_model.jpg) + + +### Basic Grass + +Specificly, we need to use tessellation shader to generate vertices. The process should be as followings: + +First, we specify the detail of tessellation (inner level and outer level) in tessellation control shader + +Then, the tessellation primitive generator will create a bunch of vertices according to details of tessellation. Each vertex carries a tessellation coordinate uv, which ranges from (0, 0) to (1, 1) if we use quad mode + +Last, in tessellation evaluation shader, we decide the position of these new vertices based on their tessellation coordinates. For example, a vertex's position can be sampled on a height map using these coordinates position = texture(heightMap, uv) + +After tessellation, these vertices are guaranteed to be assembled to the primitives we specify when creating the pipeline + +### Wind Simulation +The alignment of the blade towards the wind wave is developed following two ideas: First, a blade of grass that is standing in its straight position should be influenced more by the wind than a blade that is pushed to the ground. In addition, if the direction of the force caused by the wind is directed +along the width of the blade, the influence should be less than if the direction of the wind is orthogonal to the blade. + +Performance Analysis +============ +### Distance Culling + +We can cull all the blades that are out of a distance to the camera. + +![](img/distance_culling.gif) + +```cpp +#if DIST_CULL + float dProj = length(v0 - eye_pos - up * dot((v0 - eye_pos), up)); + uint n = 10; + if (mod(id, n) >= (n * (1 - dProj / dMax))) + return; +#endif +``` + +### Frustum Culling + +We can cull objects which are out of camera's frustum. Note how the blades around edges of the screen are loaded. + +![](img/frustum_culling.gif) + +```cpp +#if VIEW_CULL + vec3 m = (1/4) * v0 * (1/2) * v1 * (1/4) * v2; // midpoint + if(!(inFrustum(v0) && inFrustum(v2) && inFrustum(m))) + return; +#endif +``` + + +### Orientation Culling + +We can further cull blades that are parallel to the camera. + +![](img/orient_culling.gif) + +```cpp +#if ORIE_CULL + vec3 dirC = eye_pos - v0; + vec3 dirB = orient; + if (abs(dot(normalize(dirC), normalize(dirB))) >= 0.9) + return; +#endif +``` + +### Dynamic Tessellation Level + +We also added a dynamic tessellation level to the tessellation control shader to control the inner and outer levels. + +We can see from the gif below that the tessellation level changes as the camera goes away. + + ![](img/lod.gif) + +Here is a diagram of how tessellation works in Vulkan. + +![](img/Tessellation.png) + + +```cpp +vec3 camPos = inverse(camera.view)[3].xyz; + +float z = length(tcV0in[gl_InvocationID].xyz - camPos); + +int LOD; +if (z < 4.0) + LOD = 16; +else if (z < 8.0) + LOD = 12; +else if (z < 16.0) + LOD = 8; +else if (z < 32.0) + LOD = 4; +else + LOD = 1; + +gl_TessLevelInner[0] = LOD; +gl_TessLevelInner[1] = LOD; + +gl_TessLevelOuter[0] = LOD; +gl_TessLevelOuter[1] = LOD; +gl_TessLevelOuter[2] = LOD; +gl_TessLevelOuter[3] = LOD; +``` + +### Analysis + +The final table of comparision can be seen from the figue below. + + ![](img/table1.png) + +We can see that as the number of blades increases, the rendering time increases almost linearly. However, if we enable the optimization methods, the rendering time could be significantly reduced. + + + +Reference +=============== +* [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). \ No newline at end of file diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe index f68db3a..d1b10ac 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/img/Tessellation.png b/img/Tessellation.png new file mode 100644 index 0000000..126f2bc Binary files /dev/null and b/img/Tessellation.png differ diff --git a/img/cover.gif b/img/cover.gif new file mode 100644 index 0000000..749d42d Binary files /dev/null and b/img/cover.gif differ diff --git a/img/distance_culling.gif b/img/distance_culling.gif new file mode 100644 index 0000000..6e3832a Binary files /dev/null and b/img/distance_culling.gif differ diff --git a/img/frustum_culling.gif b/img/frustum_culling.gif new file mode 100644 index 0000000..49712c6 Binary files /dev/null and b/img/frustum_culling.gif differ diff --git a/img/lod.gif b/img/lod.gif new file mode 100644 index 0000000..739e30b Binary files /dev/null and b/img/lod.gif differ diff --git a/img/orient_culling.gif b/img/orient_culling.gif new file mode 100644 index 0000000..a3ef501 Binary files /dev/null and b/img/orient_culling.gif differ diff --git a/img/table1.png b/img/table1.png new file mode 100644 index 0000000..c605c42 Binary files /dev/null and b/img/table1.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..914dc1d 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 << 14; 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..a470ac5 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,52 @@ 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 + + // binding: the binding number of this entry + // and corresponds to a resource of the same binding number in the shader stages. + + // descriptor: which type of resource descriptors are used for this binding + + // descriptorCount: the number of descriptors contained in the binding + + // stageFlags: bitmask of VkShaderStageFlagBits specifying which pipeline shader stages can access a resource for this binding + + // pImmutableSamplers: affects initialization of samplers + + VkDescriptorSetLayoutBinding bladeNumBinding{}; + bladeNumBinding.binding = 0; + bladeNumBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladeNumBinding.descriptorCount = 1; + bladeNumBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladeNumBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding bladeInBinding{}; + bladeInBinding.binding = 1; + bladeInBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladeInBinding.descriptorCount = 1; + bladeInBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladeInBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding bladeOutBinding{}; + bladeOutBinding.binding = 2; + bladeOutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladeOutBinding.descriptorCount = 1; + bladeOutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladeOutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding bindings[] = { bladeNumBinding, bladeInBinding, bladeOutBinding }; + + // VkDescriptorSetLayoutCreateInfo: + // specifying parameters of a newly created descriptor set layout + VkDescriptorSetLayoutCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + createInfo.bindingCount = 3; + createInfo.pBindings = bindings; + + if (vkCreateDescriptorSetLayout(logicalDevice, &createInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout (compute)"); + } + } void Renderer::CreateDescriptorPool() { @@ -216,6 +262,8 @@ 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 +368,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()); + + // Describe the desciptor set + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(scene->GetBlades().size()); + allocInfo.pSetLayouts = &modelDescriptorSetLayout; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set in CreateGrassDescriptorSets"); + } + + std::vector descriptorWrites(grassDescriptorSets.size()); + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + auto& blade = scene->GetBlades()[i]; + + VkDescriptorBufferInfo grassBufferInfo = {}; + grassBufferInfo.buffer = blade->GetModelBuffer(); + grassBufferInfo.offset = 0; + grassBufferInfo.range = sizeof(ModelBufferObject); + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = grassDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = &grassBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), + descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +444,73 @@ 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 descriptor 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 in CreateComputeDescriptorSets"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + 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); + + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.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 = &bladesBufferInfo; + 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 = &culledBladesBufferInfo; + 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 = &numBladesBufferInfo; + descriptorWrites[3 * i + 2].pImageInfo = nullptr; + descriptorWrites[3 * i + 2].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +868,8 @@ 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 }; + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout }; // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -729,6 +881,8 @@ void Renderer::CreateComputePipeline() { if (vkCreatePipelineLayout(logicalDevice, &pipelineLayoutInfo, nullptr, &computePipelineLayout) != VK_SUCCESS) { throw std::runtime_error("Failed to create pipeline layout"); + throw std::runtime_error("Failed to create pipeline layout in CreateComputePipeline"); + } // Create compute pipeline @@ -884,7 +1038,11 @@ 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 (int i = 0; i < computeDescriptorSets.size(); ++i) + { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr); + vkCmdDispatch(computeCommandBuffer, NUM_BLADES / WORKGROUP_SIZE, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { throw std::runtime_error("Failed to record compute command buffer"); @@ -926,7 +1084,7 @@ void Renderer::RecordCommandBuffers() { renderPassInfo.renderArea.extent = swapChain->GetVkExtent(); std::array clearValues = {}; - clearValues[0].color = { 0.0f, 0.0f, 0.0f, 1.0f }; + clearValues[0].color = { 0.4f, 0.3f, 0.2f, 1.0f }; clearValues[1].depthStencil = { 1.0f, 0 }; renderPassInfo.clearValueCount = static_cast(clearValues.size()); renderPassInfo.pClearValues = clearValues.data(); @@ -976,13 +1134,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 +1216,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..ec9b8a4 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,12 +56,15 @@ 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/Scene.cpp b/src/Scene.cpp index 86894f2..c4302d5 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -32,6 +32,10 @@ void Scene::UpdateTime() { time.totalTime += time.deltaTime; memcpy(mappedData, &time, sizeof(Time)); + + usedTime = usedTime * .9f + time.deltaTime * .1f; + printf("\rTime: %f", usedTime * 1000.f); + frames += 1.f; } VkBuffer Scene::GetTimeBuffer() const { diff --git a/src/Scene.h b/src/Scene.h index 7699d78..b4db7d2 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -27,7 +27,8 @@ class Scene { std::vector blades; high_resolution_clock::time_point startTime = high_resolution_clock::now(); - +float usedTime = 0.f; +float frames = 0.f; public: Scene() = delete; Scene(Device* device); diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..813ec18 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,7 +67,7 @@ namespace { int main() { static constexpr char* applicationName = "Vulkan Grass Rendering"; - InitializeWindow(640, 480, applicationName); + InitializeWindow(1200, 1200, applicationName); unsigned int glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -90,7 +90,7 @@ int main() { swapChain = device->CreateSwapChain(surface, 5); - camera = new Camera(device, 640.f / 480.f); + camera = new Camera(device, 1200 / 1200); VkCommandPoolCreateInfo transferPoolInfo = {}; transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..de1c058 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,11 +2,22 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 + +#define GRAVITY_ON 1 +#define RECOVER_ON 1 +#define WIND_ON 1 + +#define ORIE_CULL 0 +#define DIST_CULL 0 +#define VIEW_CULL 0 + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; + vec3 pos; + } camera; layout(set = 1, binding = 0) uniform Time { @@ -23,34 +34,143 @@ struct Blade { // TODO: Add bindings to: // 1. Store the input blades -// 2. Write out the culled blades -// 3. Write the total number of blades remaining +layout(set = 2, binding = 0) buffer InputBlades { + Blade in_blades[]; +} ; +// 2. Write out the culled blades +layout(set = 2, binding = 1) buffer CulledBlades { + Blade culled_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; + +// 3. Write the total number of blades remaining +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; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +// helper for frustum culling +bool inFrustum(vec3 point) +{ + mat4 vp = camera.proj * camera.view; + vec4 pp = vp * vec4(point, 1.0f); + float tolerance = 0.2f; + float h = pp.w + tolerance; + return inBounds(pp.x, h) && inBounds(pp.y, h) && inBounds(pp.z, h); +} + +#define dMax 25 // distance culling +#define GRAVITY vec4(0, -1, 0, 5) +#define windFunc (vec3(3.0, 3.0, 3.0) * sin(totalTime)) + + + 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 + uint id = gl_GlobalInvocationID.x; + Blade b = in_blades[id]; + vec3 v0 = b.v0.xyz; + vec3 v1 = b.v1.xyz; + vec3 v2 = b.v2.xyz; + vec3 up = b.up.xyz; + + float direction = b.v0.w; + float h = b.v1.w; + float width = b.v2.w; + float stiffness = b.up.w; + vec3 orient = vec3(cos(direction), 0, sin(direction)); + + // gravity + #if GRAVITY_ON + vec3 gE = normalize(GRAVITY.xyz) * GRAVITY.w; + vec3 frontDir = length(gE) * normalize(cross(orient, up)); + vec3 gF = 0.25f * frontDir; + vec3 gravity = gE + gF; // front gravity and environment gravity + #else + vec3 gravity = vec3(0,0,0); + #endif + + // recovery + #if RECOVER_ON + vec3 iv2 = v0 + up * h; + vec3 recovery = (iv2 - v2) * stiffness; + #else + vec3 recovery = vec3(0,0,0); + #endif + + // wind + #if WIND_ON + float windDirection = 1 - abs(dot(normalize(windFunc), normalize(v2 - v0))); + float windAlign = dot((v2 - v0), up) / h; + vec3 wind = windFunc * windDirection * windAlign; + #else + vec3 wind = vec3(0,0,0); + #endif + + // add all forces together + vec3 totalForce = gravity + recovery + wind; + v2 = v2 + totalForce * deltaTime; + + // state update + v2 = v2 - up * min(dot(up, v2 - v0), 0); + float Lproj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + h * up * max(1 - Lproj / h, 0.05 * max(Lproj/h, 1)); + + float L0 = length(v2 - v0); + float L1 = length(v2 - v1) + length(v1 - v0); + float L = (2 * L0 + (2 - 1) * L1) / (2 + 1); + float r = h / L; + + v1 = v0 + r * (v1 - v0); + v2 = v1 + r * (v2 - v1); + + in_blades[id].v1 = vec4(v1, b.v1.w); + in_blades[id].v2 = vec4(v2, b.v2.w); // 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 -} + // Reset the number of blades to 0 + + vec3 eye_pos = inverse(camera.view)[3].xyz; + // Orientation culling + #if ORIE_CULL + vec3 dirC = eye_pos - v0; + vec3 dirB = orient; + if (abs(dot(normalize(dirC), normalize(dirB))) >= 0.9) + return; + #endif + + // Frustum culling + #if VIEW_CULL + vec3 m = (1/4) * v0 * (1/2) * v1 * (1/4) * v2; // midpoint + if(!(inFrustum(v0) && inFrustum(v2) && inFrustum(m))) + return; + #endif + + // Distance culling + #if DIST_CULL + float dProj = length(v0 - eye_pos - up * dot((v0 - eye_pos), up)); + uint n = 10; + if (mod(id, n) >= (n * (1 - dProj / dMax))) + return; + #endif + + culled_blades[atomicAdd(numBlades.vertexCount, 1)] = in_blades[id]; +} \ No newline at end of file diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..3201381 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,22 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 ps_position; +layout(location = 1) in vec3 ps_normal; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color + vec3 base = vec3(0.45, 0.74, 0.28); + vec3 ambient = vec3(0.2, 0.3, 0.1); + + vec4 light_pos = vec4(0.0, 100.0, 0.0, 1.0); + vec4 normal = vec4(normalize(ps_normal), 0.0); + vec4 L = normalize(light_pos - vec4(ps_position, 1.0f)); + float diffuse = max(dot(L, normal), 0.0); - outColor = vec4(1.0); -} + vec3 color = ambient + diffuse * base; + + outColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..73a11aa 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -1,6 +1,8 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +#define LOD_ON 1; + layout(vertices = 1) out; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -10,17 +12,58 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { // TODO: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4 tcV0in[]; +layout(location = 1) in vec4 tcV1in[]; +layout(location = 2) in vec4 tcV2in[]; +layout(location = 3) in vec3 tcUpin[]; + +layout(location = 0) out vec4 teV0out[]; +layout(location = 1) out vec4 teV1out[]; +layout(location = 2) out vec4 teV2out[]; + + 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 + teV0out[gl_InvocationID] = tcV0in[gl_InvocationID]; + teV1out[gl_InvocationID] = tcV1in[gl_InvocationID]; + teV2out[gl_InvocationID] = tcV2in[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] = ??? + vec3 camPos = inverse(camera.view)[3].xyz; + + float z = length(tcV0in[gl_InvocationID].xyz - camPos); + + int LOD; + if (z < 4.0) + LOD = 16; + else if (z < 8.0) + LOD = 12; + else if (z < 16.0) + LOD = 8; + else if (z < 32.0) + LOD = 4; + else + LOD = 1; + + gl_TessLevelInner[0] = LOD; + gl_TessLevelInner[1] = LOD; + + gl_TessLevelOuter[0] = LOD; + gl_TessLevelOuter[1] = LOD; + gl_TessLevelOuter[2] = LOD; + gl_TessLevelOuter[3] = LOD; + + // fixed LOD for test +// gl_TessLevelInner[0] = 4; +// gl_TessLevelInner[1] = 4; +// +// gl_TessLevelOuter[0] = 4; +// gl_TessLevelOuter[1] = 4; +// gl_TessLevelOuter[2] = 4; +// gl_TessLevelOuter[3] = 4; + // gl_InvocationID is 0 for all te + } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..94b49b5 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -8,11 +8,44 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; +layout(location = 0) in vec4 teV0In[]; +layout(location = 1) in vec4 teV1In[]; +layout(location = 2) in vec4 teV2In[]; + +layout(location = 0) out vec3 norm; +layout(location = 1) out vec3 pos; +layout(location = 2) out float mixFactor; + // TODO: Declare tessellation evaluation shader inputs and outputs 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 + // TODO: Use u and v to parameterize along the grass blade + // and output positions for each vertex of the grass blade + vec3 v0 = teV0In[0].xyz; + vec3 v1 = teV1In[0].xyz; + vec3 v2 = teV2In[0].xyz; + + float phi = teV0In[0].w; + float width = teV2In[0].w; + + vec3 a = mix(v0, v1, v); + vec3 b = mix(v1, v2, v); + vec3 c = mix(a, b, v); + + vec3 t0 = normalize(b - a); + vec3 t1 = vec3(cos(phi), 0.0, sin(phi)); + + vec3 c0 = c - width * t1; + vec3 c1 = c + width * t1; + + float t = u + 0.5 * v - u * v; + + norm = normalize(cross(t0, t1)); + pos = mix(c0, c1, t); + mixFactor = 2 * v - v * v; + + gl_Position = camera.proj * camera.view * vec4(pos, 1.0); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..283ab45 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,11 +7,26 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { }; // TODO: Declare vertex shader inputs and outputs +layout(location = 0) in vec4 v0In; +layout(location = 1) in vec4 v1In; +layout(location = 2) in vec4 v2In; +layout(location = 3) in vec4 upIn; -out gl_PerVertex { - vec4 gl_Position; -}; +layout(location = 0) out vec4 v0Out; +layout(location = 1) out vec4 v1Out; +layout(location = 2) out vec4 v2Out; +layout(location = 3) out vec3 upOut; + +//out gl_PerVertex { +// vec4 gl_Position; +//}; void main() { // TODO: Write gl_Position and any other shader outputs + gl_Position = model * vec4(v0In.xyz, 1.0); + + v0Out = model * v0In; + v1Out = model * v1In; + v2Out = model * v2In; + upOut = upIn.xyz; }