diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/.vs/CMake Overview b/.vs/CMake Overview new file mode 100644 index 0000000..e69de29 diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000..8f0d733 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": "x64-Debug" +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..f9ad33c --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,7 @@ +{ + "ExpandedNodes": [ + "" + ], + "SelectedNode": "\\cis565_project5_vulkan_grass_rendering.sln", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/cmake.db b/.vs/cmake.db new file mode 100644 index 0000000..bc40708 Binary files /dev/null and b/.vs/cmake.db differ diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000..e58eeec Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/CMakeLists.txt b/CMakeLists.txt index 860a279..36d5dcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 2.8.12) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") -project(cis565_project4_vulkan_grass_rendering) +project(cis565_project5_vulkan_grass_rendering) OPTION(USE_D2D_WSI "Build the project using Direct to Display swapchain" OFF) diff --git a/New Bitmap image.bmp b/New Bitmap image.bmp new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 20ee451..5a6225b 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,33 @@ 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) +* Helena Zhang +* Tested on: Windows 11, i7-10750 @ 2.6GHz 16GB, Geforce RTX 2060 6GB -### (TODO: Your README) +### Scenes -*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. +![](img/all_forces.gif) + +This field of grass is a collection of Second-degree Bezier Curves. Physical Forces - gravity, recovery, and wind forces - are applied onto each blade of grass, and any grass out of range - parallel to the camera, out of the camera's view frustum, or too far in the distance - are culled using a compute shader. Each Bezier Curve is then tessellated from the three control points to a full blade of grass. + +![](img/just_grass.png) + +Grass without any forces + +![](img/all_culling.gif) + +Various culling effects + +### Analysis + +As the number of blades increased, the actions done in parallel - dispatching the compute shader and drawing the grass blades - both slowed down. + +![](img/chart.png) + +The draw function displayed expected behavior - as the number of blades increases, the number of vertices the tessellation shaders generate increases, and the runtime increases in a linear fashion. The runtime of the compute shader also increased. However, due to various factors, such as waiting for threads to sync, and the lack of sufficient significant digits used in the Nsight Graphics GPU trace analysis, the runtime appears slightly unpredictable, and it's difficult to conclude how the runtime increases as the number of blades increased. + +The culling functionality also affected the runtime of the draw function. Since view frustum and distance culling are highly dependent on the camera's position, only orientation culling provides significant runtime impact at any instance. + +![](img/culling_chart.png) + +Orientation culling removes any blades whose orientation is perpendicular to the camera's view angle. The dot product represents a threshold where a blade would be removed even if it isn't fully perpendicular, but it would appear very thin on camera. As the threshold decreases, more blades would fall into the threshold zone, and they would be removed. The dot product and runtime decreased in a linear fashion, meaning the blades are uniformly distributed in terms of orientation. However, there is a large runtime difference between culling 5% of the blades versus keeping all of blades. It's possible this is caused by z-fighting when all the vertices on the grass blade are colinear with each other from the view of the camera. diff --git a/bin/Debug/vulkan_grass_rendering.exe b/bin/Debug/vulkan_grass_rendering.exe new file mode 100644 index 0000000..e6b604b 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..86d4a52 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..2c3c151 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/img/all_culling.gif b/img/all_culling.gif new file mode 100644 index 0000000..69707ed Binary files /dev/null and b/img/all_culling.gif differ diff --git a/img/all_forces.gif b/img/all_forces.gif new file mode 100644 index 0000000..eaa1c40 Binary files /dev/null and b/img/all_forces.gif differ diff --git a/img/basic_forces_grass.png b/img/basic_forces_grass.png new file mode 100644 index 0000000..8deb858 Binary files /dev/null and b/img/basic_forces_grass.png differ diff --git a/img/chart.png b/img/chart.png new file mode 100644 index 0000000..60dcdaa Binary files /dev/null and b/img/chart.png differ diff --git a/img/culling_chart.png b/img/culling_chart.png new file mode 100644 index 0000000..3c78bfc Binary files /dev/null and b/img/culling_chart.png differ diff --git a/img/just_grass.png b/img/just_grass.png new file mode 100644 index 0000000..ef5df21 Binary files /dev/null and b/img/just_grass.png differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..230d65c 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_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_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/Camera.cpp b/src/Camera.cpp index 3afb5b8..fba61d2 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -15,6 +15,7 @@ Camera::Camera(Device* device, float aspectRatio) : device(device) { cameraBufferObject.viewMatrix = glm::lookAt(glm::vec3(0.0f, 1.0f, 10.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); cameraBufferObject.projectionMatrix = glm::perspective(glm::radians(45.0f), aspectRatio, 0.1f, 100.0f); cameraBufferObject.projectionMatrix[1][1] *= -1; // y-coordinate is flipped + cameraBufferObject.invViewMatrix = glm::inverse(cameraBufferObject.viewMatrix); BufferUtils::CreateBuffer(device, sizeof(CameraBufferObject), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, buffer, bufferMemory); vkMapMemory(device->GetVkDevice(), bufferMemory, 0, sizeof(CameraBufferObject), 0, &mappedData); @@ -36,6 +37,7 @@ void Camera::UpdateOrbit(float deltaX, float deltaY, float deltaZ) { glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), radTheta, glm::vec3(0.0f, 1.0f, 0.0f)) * glm::rotate(glm::mat4(1.0f), radPhi, glm::vec3(1.0f, 0.0f, 0.0f)); glm::mat4 finalTransform = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f)) * rotation * glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 1.0f, r)); + cameraBufferObject.invViewMatrix = finalTransform; cameraBufferObject.viewMatrix = glm::inverse(finalTransform); memcpy(mappedData, &cameraBufferObject, sizeof(CameraBufferObject)); diff --git a/src/Camera.h b/src/Camera.h index 6b10747..0a4071c 100644 --- a/src/Camera.h +++ b/src/Camera.h @@ -7,6 +7,7 @@ struct CameraBufferObject { glm::mat4 viewMatrix; glm::mat4 projectionMatrix; + glm::mat4 invViewMatrix; }; class Camera { diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..08f1116 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -195,9 +195,38 @@ void Renderer::CreateTimeDescriptorSetLayout() { } 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 bladesLayoutBinding = {}; + bladesLayoutBinding.binding = 0; + bladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladesLayoutBinding.descriptorCount = 1; + bladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladesLayoutBinding.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 numBladesLayoutBinding = {}; + numBladesLayoutBinding.binding = 2; + numBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBladesLayoutBinding.descriptorCount = 1; + numBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numBladesLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { bladesLayoutBinding, culledBladesLayoutBinding, numBladesLayoutBinding }; + + // 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() { @@ -215,7 +244,8 @@ void Renderer::CreateDescriptorPool() { // Time (compute) { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, - // TODO: Add any additional types and counts of descriptors you will need to allocate + // Compute + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(scene->GetBlades().size() * 3)}, }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -318,8 +348,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 + bladeDescriptorSets.resize(scene->GetBlades().size()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(bladeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, bladeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(bladeDescriptorSets.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 = bladeDescriptorSets[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() { @@ -358,6 +422,73 @@ void Renderer::CreateTimeDescriptorSet() { } void Renderer::CreateComputeDescriptorSets() { + 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 bladeBufferInfo = {}; + bladeBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + bladeBufferInfo.offset = 0; + bladeBufferInfo.range = NUM_BLADES * sizeof(Blade); + + descriptorWrites[3 * i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i].dstBinding = 0; + descriptorWrites[3 * i].dstArrayElement = 0; + descriptorWrites[3 * i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i].descriptorCount = 1; + descriptorWrites[3 * i].pBufferInfo = &bladeBufferInfo; + descriptorWrites[3 * i].pImageInfo = nullptr; + descriptorWrites[3 * i].pTexelBufferView = nullptr; + + VkDescriptorBufferInfo culledBladeBufferInfo = {}; + culledBladeBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladeBufferInfo.offset = 0; + culledBladeBufferInfo.range = NUM_BLADES * sizeof(Blade); + + 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 = &culledBladeBufferInfo; + descriptorWrites[3 * i + 1].pImageInfo = nullptr; + descriptorWrites[3 * i + 1].pTexelBufferView = nullptr; + + VkDescriptorBufferInfo numBladeBufferInfo = {}; + numBladeBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladeBufferInfo.offset = 0; + numBladeBufferInfo.range = sizeof(BladeDrawIndirect); + + 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 = &numBladeBufferInfo; + 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); + // 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 } @@ -717,7 +848,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 +1015,10 @@ 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 (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr); + vkCmdDispatch(computeCommandBuffer, (NUM_BLADES + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1111,12 @@ 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, &bladeDescriptorSets[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 diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..2da6df0 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 bladeDescriptorSets; + std::vector computeDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..db4ccf5 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -7,6 +7,7 @@ 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; + mat4 invView; } camera; layout(set = 1, binding = 0) uniform Time { @@ -21,36 +22,173 @@ struct Blade { vec4 up; }; -// TODO: Add bindings to: -// 1. Store the input blades -// 2. Write out the culled blades -// 3. Write the total number of blades remaining - -// The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call -// This is sort of an advanced feature so we've showed you what this buffer should look like -// -// layout(set = ???, binding = ???) buffer NumBlades { -// uint vertexCount; // Write the number of blades remaining here -// uint instanceCount; // = 1 -// uint firstVertex; // = 0 -// uint firstInstance; // = 0 -// } numBlades; +layout(set = 2, binding = 0) buffer Blades { + Blade in_blades[]; +}; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade out_blades[]; +}; + +layout(set = 2, binding = 2) buffer NumBlades { + uint vertexCount; + uint instanceCount; + uint firstVertex; + uint firstInstance; +} numBlades; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +vec3 random3(vec3 p) { + return fract(sin(vec3(dot(p, vec3(127.1, 311.7, 593.2)), + dot(p, vec3(269.5, 183.3, 293.4)), + dot(p, vec3(420.6, 631.2, 586.8)) + )) * 43758.5453f); +} + +float surflet3D(vec3 p, vec3 gridPoint) { + vec3 t2 = abs(p - gridPoint); + vec3 t = vec3(1.0) - 6.0 * vec3(pow(t2.x, 5.0), pow(t2.y, 5.0), pow(t2.z, 5.0)) + + 15.0 * vec3(pow(t2.x, 4.0), pow(t2.y, 4.0), pow(t2.z, 4.0)) + - 10.0 * vec3(pow(t2.x, 3.0), pow(t2.y, 3.0), pow(t2.z, 3.0)); + vec3 gradient = random3(gridPoint) * 2.0 - vec3(1.0, 1.0, 1.0); + vec3 diff = p - gridPoint; + float height = dot(diff, gradient); + return height * t.x * t.y * t.z; +} + +float perlinNoise3D(vec3 p) { + float surfletSum = 0.0; + for(int dx = 0; dx <= 1; ++dx) { + for(int dy = 0; dy <= 1; ++dy) { + for(int dz = 0; dz <= 1; ++dz) { + surfletSum += surflet3D(p, floor(p) + vec3(dx, dy, dz)); + } + } + } + return surfletSum; +} + +vec3 toNDC(vec4 p) { + p = camera.proj * camera.view * p; + p = p / p.w; + return p.xyz; +} + 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 + barrier(); + + uint index = gl_GlobalInvocationID.x; + + Blade b = in_blades[index]; + + vec3 v0 = b.v0.xyz; + vec3 v1 = b.v1.xyz; + vec3 v2 = b.v2.xyz; + vec3 up = b.up.xyz; + + float o = b.v0.w; + float h = b.v1.w; + float w = b.v2.w; + float s = b.up.w; + + // Recovery: + + vec3 r = ((v0 + up * h) - v2) * s; - // TODO: Apply forces on every blade and update the vertices in the buffer + // Gravity: + + vec3 gE = -3.0 * normalize(up); + vec3 gF = 0.25 * vec3(cos(o), 0.0, sin(o)); + + // Wind: + float windO = 3.1415926 * (1.0 + perlinNoise3D(vec3(v0.x, v0.z, totalTime))); + vec3 wind = vec3(cos(windO), 0.0, sin(windO)); + + v2 = v2 + deltaTime * (gE + gF + r + wind); + + // End: + v2 = v2 - up * min(dot(up, v2 - v0), 0.0); + float proj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + h * up * max(1 - proj / h, 0.05 * max(1, proj / h)); + + float L0 = distance(v2, v0); + float L1 = distance(v2, v1) + distance(v1, v0); + + float L = (2.0 * L0 + L1) / 3.0; + float ratio = h / L; + + v1 = v0 + ratio * (v1 - v0); + v2 = v1 + ratio * (v2 - v1); + + b.v0 = vec4(v0, o); + b.v1 = vec4(v1, h); + b.v2 = vec4(v2, w); + b.up = vec4(up, s); + + in_blades[index] = b; // 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 + + // Orientation + + vec3 cam_position = (camera.invView * vec4(0.0, 0.0, 0.0, 1.0)).xyz; + + vec3 viewDir = normalize(v0 - cam_position); + if (0.75 < abs(dot(vec3(viewDir.x, 0.0, viewDir.z), vec3(cos(o), 0.0, sin(o))))) { + return; + } + + // View Frustum + + vec4 m = vec4(0.25 * v0 + 0.5 * v1 + 0.25 * v2, 1.0); + vec3 h_v0 = toNDC(vec4(v0, 1.0)); + vec3 h_m = toNDC(m); + vec3 h_v2 = toNDC(vec4(v2, 1.0)); + bool keep = false; + if (!keep && inBounds(h_v0.x, 1) && inBounds(h_v0.y, 1) && h_v0.z > 0) { + keep = true; + } + if (!keep && inBounds(m.x, 1) && inBounds(m.y, 1) && m.z > 0) { + keep = true; + } + if (!keep && inBounds(h_v2.x, 1) && inBounds(h_v2.y, 1) && h_v2.z > 0) { + keep = true; + } + if (!keep) { + return; + } + + // Distance + + float proj_d = length(v0 - cam_position - up * (dot(v0 - cam_position, up))); + float max_d = 40.0; + + if (proj_d > max_d) { + return; + } else if (proj_d > 0.75 * max_d) { + if (mod(gl_GlobalInvocationID.x, 2.0) < floor(2.0 * (1 - proj_d / max_d))) { + return; + } + } else if (proj_d > 0.625 * max_d) { + if (mod(gl_GlobalInvocationID.x, 3.0) < floor(3.0 * (1 - proj_d / max_d))) { + return; + } + } else if (proj_d > 0.5 * max_d) { + if (mod(gl_GlobalInvocationID.x, 6.0) < floor(6.0 * (1 - proj_d / max_d))) { + return; + } + } + + const uint culledIndex = atomicAdd(numBlades.vertexCount, 1); + out_blades[culledIndex] = b; } diff --git a/src/shaders/graphics.vert b/src/shaders/graphics.vert index fb9bf8e..ff87c80 100644 --- a/src/shaders/graphics.vert +++ b/src/shaders/graphics.vert @@ -4,6 +4,7 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; + mat4 invView; } camera; layout(set = 1, binding = 0) uniform ModelBufferObject { diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..c271fc5 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -4,14 +4,21 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; + mat4 invView; } camera; -// TODO: Declare fragment shader inputs - layout(location = 0) out vec4 outColor; +layout(location = 0) in vec4 N; +layout(location = 1) in vec4 L; +layout(location = 2) in float height; + void main() { - // TODO: Compute fragment color + float diffuse = abs(dot(normalize(N), normalize(L))); + diffuse = clamp(diffuse, 0.0, 0.8); + + float amb = 0.2; + float intensity = diffuse + amb; - outColor = vec4(1.0); + outColor = vec4(0.0, 1.0, 0.0, 1.0) * intensity * (height / 1.5); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..86a8dea 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -6,21 +6,34 @@ layout(vertices = 1) out; layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; + mat4 invView; } camera; -// TODO: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4 v1[]; +layout(location = 1) in vec4 v2[]; +layout(location = 2) in vec4 up[]; + +layout(location = 0) out vec4 tescV1[]; +layout(location = 1) out vec4 tescV2[]; +layout(location = 2) out vec4 tescUp[]; + +in gl_PerVertex +{ + vec4 gl_Position; +} gl_in[gl_MaxPatchVertices]; + 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 + tescV1[gl_InvocationID] = v1[gl_InvocationID]; + tescV2[gl_InvocationID] = v2[gl_InvocationID]; + tescUp[gl_InvocationID] = 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] = ??? + gl_TessLevelInner[0] = 10; + gl_TessLevelInner[1] = 10; + gl_TessLevelOuter[0] = 10; + gl_TessLevelOuter[1] = 10; + gl_TessLevelOuter[2] = 10; + gl_TessLevelOuter[3] = 10; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..dbf8774 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -6,13 +6,43 @@ layout(quads, equal_spacing, ccw) in; layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; + mat4 invView; } camera; -// TODO: Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 v1[]; +layout(location = 1) in vec4 v2[]; +layout(location = 2) in vec4 up[]; + +layout(location = 0) out vec4 fs_Nor; +layout(location = 1) out vec4 fs_L; +layout(location = 2) out float height; void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; + vec3 curr_v0 = gl_in[0].gl_Position.xyz; + vec3 curr_v1 = v1[0].xyz; + vec3 curr_v2 = v2[0].xyz; + vec3 curr_up = up[0].xyz; + + vec3 a = curr_v0 + v * (curr_v1 - curr_v0); + vec3 b = curr_v1 + v * (curr_v2 - curr_v1); + vec3 c = a + v * (b - a); + + vec3 t1 = vec3(cos(gl_in[0].gl_Position.w), 0.0, sin(gl_in[0].gl_Position.w)); + vec3 c0 = c - v2[0].w * t1; + vec3 c1 = c + v2[0].w * t1; + + vec3 t0 = normalize(b - a); + vec3 n = normalize(cross(t0, t1)); + + float t = 0.5 + (u - 0.5) * (u - u * v * v); + gl_Position = camera.proj * camera.view * vec4((1.0 - t) * c0 + t * c1, 1.0); + + fs_Nor = vec4(n, 1.0); + fs_L = camera.invView * vec4(0.0, 0.0, 0.0, 1.0) - vec4(curr_v0, 1.0); + height = v + 0.5; + // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..acac842 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -6,6 +6,15 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { mat4 model; }; +layout(location = 0) in vec4 v0; +layout(location = 1) in vec4 v1; +layout(location = 2) in vec4 v2; +layout(location = 3) in vec4 up; + +layout(location = 0) out vec4 tescV1; +layout(location = 1) out vec4 tescV2; +layout(location = 2) out vec4 tescUp; + // TODO: Declare vertex shader inputs and outputs out gl_PerVertex { @@ -14,4 +23,9 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + gl_Position = vec4((model * vec4(v0.xyz, 1.0)).xyz, v0.w); + + tescV1 = vec4((model * vec4(v1.xyz, 1.0)).xyz, v1.w); + tescV2 = vec4((model * vec4(v2.xyz, 1.0)).xyz, v2.w); + tescUp = vec4((model * vec4(up.xyz, 0.0)).xyz, up.w); }