diff --git a/README.md b/README.md index 20ee451..1f5aba5 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,48 @@ 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) +* Yiyang Chen + * [LinkedIn](https://www.linkedin.com/in/yiyang-chen-6a7641210/), [personal website](https://cyy0915.github.io/) +* Tested on: Windows 10, i5-8700k @ 3.7GHz, GTX 1080, personal computer -### (TODO: Your README) +## Overview -*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. +In the computing shader, I simulate all the force, and implement 3 culling tests. + +In the tessellation shader, I randomly choose 3 different grass shapes (triangle, quadratic and triangle-tip) to tessellate. And I implement the extra credit: Tessellate to varying levels of detail as a function of how far the grass blade is from the camera. + +## Results + +First I just draw rectangles with pure color + +![](img/pure_color.png) + +And then I complete tessellation and fragment shader + +![](img/no_force.png) + +Finally I add force and culling, the following GIF is my final result. And because GIF is a bit blurry, I also record a short video: `img/result.mp4` + +![](img/result.gif) + +Here are some effect of culling. The parameters are specialized to show the effect clearly. +| distance culling | view frustum culling | orientation culling | +|---|---|---| +|![](img/distance.gif)|![](img/view.gif)|![](img/orientation.gif)| + +## Performance Analysis + +![](img/graph.png) + +See the above graph. The blades number is `2^n`, and the frame rate per second is also in logarithmic coordinate. Notice that the `distance` line overlaps the `orientation` line so we can't see the `orientation` line clearly. + +* The 3 culling methods all contribute to the performance, and their improvements are almost the same. +* When the number of blades increases, the frame rate decreases linearly +* From the original data, when applying all the 3 culling methods, the frame rate increases about 50%. + +### Dynamic tessellation level +![](img/tesslevel.png) + +It's with 2^14 grass blades and all culling methods. + +We can see that dynamic tess level improve the performance. \ No newline at end of file diff --git a/img/distance.gif b/img/distance.gif new file mode 100644 index 0000000..d65aded Binary files /dev/null and b/img/distance.gif differ diff --git a/img/graph.png b/img/graph.png new file mode 100644 index 0000000..7dd405c Binary files /dev/null and b/img/graph.png differ diff --git a/img/no_force.png b/img/no_force.png new file mode 100644 index 0000000..659af48 Binary files /dev/null and b/img/no_force.png differ diff --git a/img/orientation.gif b/img/orientation.gif new file mode 100644 index 0000000..3ac5ef1 Binary files /dev/null and b/img/orientation.gif differ diff --git a/img/pure_color.png b/img/pure_color.png new file mode 100644 index 0000000..99faa13 Binary files /dev/null and b/img/pure_color.png differ diff --git a/img/result.gif b/img/result.gif new file mode 100644 index 0000000..5c6fb80 Binary files /dev/null and b/img/result.gif differ diff --git a/img/result.mp4 b/img/result.mp4 new file mode 100644 index 0000000..4ef7879 Binary files /dev/null and b/img/result.mp4 differ diff --git a/img/tesslevel.png b/img/tesslevel.png new file mode 100644 index 0000000..b08a596 Binary files /dev/null and b/img/tesslevel.png differ diff --git a/img/view.gif b/img/view.gif new file mode 100644 index 0000000..a082462 Binary files /dev/null and b/img/view.gif differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..e275dfa 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_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory); BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory); } diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..ad375ce 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -21,12 +21,13 @@ Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* c CreateModelDescriptorSetLayout(); CreateTimeDescriptorSetLayout(); CreateComputeDescriptorSetLayout(); + CreateGrassDescriptorSetLayout(); CreateDescriptorPool(); CreateCameraDescriptorSet(); CreateModelDescriptorSets(); - CreateGrassDescriptorSets(); CreateTimeDescriptorSet(); CreateComputeDescriptorSets(); + CreateGrassDescriptorSets(); CreateFrameResources(); CreateGraphicsPipeline(); CreateGrassPipeline(); @@ -198,6 +199,59 @@ 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 compute descriptor set layout"); + } +} + +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::CreateDescriptorPool() { @@ -216,6 +270,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(3 * scene->GetBlades().size()) } + }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,6 +376,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 + 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 +452,64 @@ 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 compute 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++) + { + int di = i * 3 + j; + descriptorWrites[di].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[di].dstSet = computeDescriptorSets[i]; + descriptorWrites[di].dstBinding = j; + descriptorWrites[di].dstArrayElement = 0; + descriptorWrites[di].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[di].descriptorCount = 1; + if (j == 0) { + descriptorWrites[di].pBufferInfo = &inputBladesBufferInfo; + } + else if (j == 1) { + descriptorWrites[di].pBufferInfo = &culledBladesBufferInfo; + } + else { + descriptorWrites[di].pBufferInfo = &numBladesBufferInfo; + } + descriptorWrites[di].pImageInfo = nullptr; + descriptorWrites[di].pTexelBufferView = nullptr; + } + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -654,7 +804,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 = {}; @@ -717,7 +867,7 @@ void Renderer::CreateComputePipeline() { computeShaderStageInfo.pName = "main"; // TODO: Add the compute dsecriptor set layout you create to this list - std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout }; + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout }; // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -884,6 +1034,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, ceil(NUM_BLADES / WORKGROUP_SIZE), 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1131,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 +1213,8 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, grassDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..e9bfbc1 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -19,6 +19,7 @@ class Renderer { void CreateModelDescriptorSetLayout(); void CreateTimeDescriptorSetLayout(); void CreateComputeDescriptorSetLayout(); + void CreateGrassDescriptorSetLayout(); void CreateDescriptorPool(); @@ -56,12 +57,17 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; + VkDescriptorSetLayout grassDescriptorSetLayout; + VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + std::vector computeDescriptorSets; + std::vector grassDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/Scene.h b/src/Scene.h index 7699d78..3264e3c 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -42,4 +42,7 @@ high_resolution_clock::time_point startTime = high_resolution_clock::now(); VkBuffer GetTimeBuffer() const; void UpdateTime(); + float getTotalTime() { + return time.totalTime; + } }; diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..c13bf11 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "Camera.h" #include "Scene.h" #include "Image.h" +#include Device* device; SwapChain* swapChain; @@ -143,10 +144,24 @@ int main() { glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback); glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback); + int frameCount = 0; + int startTime = 1; while (!ShouldQuit()) { glfwPollEvents(); scene->UpdateTime(); renderer->Frame(); + + float cTime = scene->getTotalTime(); + if (cTime > 1) { + if (cTime - startTime < 1) { + frameCount++; + } + else { + startTime++; + std::cout << frameCount << std::endl; + frameCount = 0; + } + } } vkDeviceWaitIdle(device->GetVkDevice()); diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..56f3c46 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,12 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define SQRT_OF_ONE_THIRD 0.5773502691896257645091487805019574556476f + +#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 { @@ -35,22 +41,151 @@ struct Blade { // uint firstVertex; // = 0 // uint firstInstance; // = 0 // } numBlades; +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; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +vec3 localToGlobal(vec3 p, vec3 n){ + vec3 directionNotNormal; + if (abs(n.x) < SQRT_OF_ONE_THIRD) { + directionNotNormal = vec3(1, 0, 0); + } + else if (abs(n.y) < SQRT_OF_ONE_THIRD) { + directionNotNormal = vec3(0, 1, 0); + } + else { + directionNotNormal = vec3(0, 0, 1); + } + + // Use not-normal direction to generate two perpendicular directions + vec3 perpendicularDirection1 = normalize(cross(n, directionNotNormal)); + vec3 perpendicularDirection2 = normalize(cross(n, perpendicularDirection1)); + return p.x * perpendicularDirection1 + p.y * perpendicularDirection2 + p.z * n; +} + 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 + 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 angle = 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 right = localToGlobal(vec3(cos(angle), sin(angle), 0), up); + vec3 front = cross(up, right); + + // calculate forces + vec3 gravity = vec3(0.f); + vec3 recovery = vec3(0.f); + vec3 wind = vec3(0.f); + + // gravity + vec3 gE = vec3(0, -1, 0) * 9.8f; + vec3 gF = 0.25f * length(gE) * front; + gravity = gE + gF; + + // recovery + vec3 i_v2 = v0 + up * height; + recovery = (i_v2 - v2) * stiffness; + + // wind + float maxWindStrength = 2.f; + float windSpeed = 2.f; + vec4 windDirection = vec4(-0.5f, 0.5f, 0, maxWindStrength * cos(windSpeed * totalTime + 0.25 * v0)); + float windAlignment = abs(dot(windDirection.xyz, front)); + wind = normalize(windDirection.xyz) * windAlignment * windDirection.w; + + vec3 tv2 = (gravity + recovery + wind) * deltaTime; + + // correct v1 and v2 + v2 += tv2; + 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)); + + // correct length of curve + float L0 = length(v2 - v0); + float L1 = length(v2 - v1) + length(v1 - v0); + float L = (2 * L0 + L1) / 3; + float r = height / L; + vec3 v1c = v0 + r * (v1 - v0); + vec3 v2c = v1c + r * (v2 - v1); + v1 = v1c; v2 = v2c; + + // update vectors + inputBlades[gl_GlobalInvocationID.x].v1 = vec4(v1, height); + inputBlades[gl_GlobalInvocationID.x].v2 = vec4(v2, width); // 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; + vec3 eye = vec3(inverse(camera.view) * vec4(0.f, 0.f, 0.f, 1.f)); + + // orientation test +#ifdef ORIENTATION_CULLING + vec3 viewDir = eye - v0; + if (abs(dot(normalize(viewDir), normalize(right))) > 0.9){ + culled = true; + } +#endif + + // view-frustum test +#ifdef VIEW_FRUSTUM_CULLING + mat4 projView = camera.proj * camera.view; + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; + vec4 ndcV0 = projView * vec4(v0, 1.f); ndcV0 /= ndcV0.w; + vec4 ndcM = projView * vec4(m, 1.f); ndcM /= ndcM.w; + vec4 ndcV2 = projView * vec4(v2, 1.f); ndcV2 /= ndcV2.w; + + float tolerance = 1.05f; + bool testV0 = inBounds(ndcV0.x, tolerance) && inBounds(ndcV0.y, tolerance); + bool testM = inBounds(ndcM.x, tolerance) && inBounds(ndcM.y, tolerance); + bool testV2 = inBounds(ndcV2.x, tolerance) && inBounds(ndcV2.y, tolerance); + + if (!testV0 && !testM && !testV2) { + culled = true; + } +#endif + + // distance Test +#ifdef DISTANCE_CULLING + float dProj = length(v0 - eye - up * dot((v0 - eye), up)); + float dMax = 80; + uint id = gl_GlobalInvocationID.x; + uint n = 10; + if (mod(id, n) >= (n * (1 - dProj / dMax))) { + culled = true; + } +#endif + + if (!culled) + { + culledBlades[atomicAdd(numBlades.vertexCount, 1)] = inputBlades[gl_GlobalInvocationID.x]; + } } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..3e8fb68 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,16 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 normal; +layout(location = 1) in float height; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - - outColor = vec4(1.0); + vec3 lightDir = normalize(vec3(1, -1, 1)); + vec3 grassColor = vec3(.24, .59, .09); + float diffuse = abs(dot(lightDir, normal)); + + outColor = vec4(grassColor * (0.4 + 0.4 * height + 0.2 * abs(dot(lightDir, normal))), 1.0); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..57aa9c9 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -9,18 +9,38 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } 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] = ??? + // because the blade is long and narrow + // dynamic tesselation level + vec3 eye = vec3(inverse(camera.view) * vec4(0.f, 0.f, 0.f, 1.f)); + vec3 v0 = vec3(in_v0[gl_InvocationID]); + float d = distance(v0, eye); + float level = max(0, 1 - d / 80.f); + int u = 1 << int(ceil(1 * level)), v = 1 << int(ceil(4 * level)); + gl_TessLevelInner[0] = u; + gl_TessLevelInner[1] = v; + gl_TessLevelOuter[0] = v; + gl_TessLevelOuter[1] = u; + gl_TessLevelOuter[2] = v; + gl_TessLevelOuter[3] = u; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..debf2b9 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -1,5 +1,6 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +#define SQRT_OF_ONE_THIRD 0.5773502691896257645091487805019574556476f layout(quads, equal_spacing, ccw) in; @@ -8,11 +9,83 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } 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 vec3 normal; +layout(location = 1) out float height; + + +vec3 localToGlobal(vec3 p, vec3 n){ + vec3 directionNotNormal; + if (abs(n.x) < SQRT_OF_ONE_THIRD) { + directionNotNormal = vec3(1, 0, 0); + } + else if (abs(n.y) < SQRT_OF_ONE_THIRD) { + directionNotNormal = vec3(0, 1, 0); + } + else { + directionNotNormal = vec3(0, 0, 1); + } + + // Use not-normal direction to generate two perpendicular directions + vec3 perpendicularDirection1 = normalize(cross(n, directionNotNormal)); + vec3 perpendicularDirection2 = normalize(cross(n, perpendicularDirection1)); + return p.x * perpendicularDirection1 + p.y * perpendicularDirection2 + p.z * n; +} + +float noise1D(vec3 p) +{ + return fract(sin(dot(p, vec3(135.1, 211.6, 173.3))) * 43758.5453); +} void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; + height = v; // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + vec3 v0 = in_v0[0].xyz; + vec3 v1 = in_v1[0].xyz; + vec3 v2 = in_v2[0].xyz; + vec3 up = in_up[0].xyz; + float angle = in_v0[0].w; + float height = in_v1[0].w; + float width = in_v2[0].w; + vec3 t1 = localToGlobal(vec3(cos(angle), sin(angle), 0), up); + + 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 = normalize(cross(t0, t1)); + normal = norm; + + float rand = noise1D(v0); + + float t; + if (rand > 0.66) // triangles + { + t = u + 0.5 * v - u * v; + } + else if (rand > 0.33) // quadratic + { + t = u - u * v * v; + } + else // triangle-tip + { + float tau = noise1D(v0); + t = 0.5 + (u - 0.5) * (1 - max(v - tau, 0)/(1 - tau)); + } + + // 3D displacement + vec3 d = width * norm * (0.5 - abs(u - 0.5) * (1 - v)); + + gl_Position = camera.proj * camera.view * vec4((1 - t) * c0 + t * c1 + d, 1.f); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..6e82403 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,13 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + 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; + gl_Position = model * vec4(in_v0.xyz, 1.f); }