diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87fe527 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +build \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..72e834c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Process", + "type": "cppdbg", + "request": "attach", + "program": "${workspaceFolder}/bin/vulkan_grass_rendering", + "processId": "${command:pickProcess}", + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e7ace40 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "ros.distro": "humble", + "files.associations": { + "*.sdf": "xml", + "*.world": "xml", + "*.gltf": "json", + "array": "cpp", + "string": "cpp", + "string_view": "cpp", + "ranges": "cpp", + "*.ipp": "cpp", + "bitset": "cpp", + "initializer_list": "cpp", + "utility": "cpp", + "valarray": "cpp" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 20ee451..3cb8a6e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,126 @@ -Vulkan Grass Rendering +Touching Grass in Vulkan ================================== -**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5** +![grass](img/submission/grass.gif) +Jason Xie, October 2023 -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +## About -### (TODO: Your README) +A grass simulation in Vulkan. -*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. +This project implements the paper [Responsive Real-Time Grass Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf), including Bezier curve grass blades, physical models for grass motion, and culling. + +## Features + +### 🌿 Bezier Curve Grass Blades + +Grass is represented as Bezier curves, where $v_0, v_1, v_2, v_3$ are the control points. In addition to these control points, the direction, height, width, and the "up" vector are also stored, resulting in four vec4s per grass blade. + +![bezier](img/blade_model.jpg) + +To draw the grass, vertices from the initial quad is refined through the tessellation shader. First, two separate Bezier curves are generated for each side of the grass blade. Then, the vertex position is determined by interpolating between the two curves. + +![shape](img/shape.png) + +### 🌿 Physical Models + +![physics](img/physics.png) + +Grass motion is modeled as displacements of the grass tip $v_2$ due to a comination of forces. + +**🌎️ Gravity** + +Gravity is simulated as a combination of *environmental gravity* and *frontal gravity*: + +$g = g_e + g_f$ + +$g_f = \frac{1}{4} ||g_e|| f$ + +**🌬️ Wind** + +The deflection of grass due to wind is modeled as a function of wind magnitude and grass alignment with the wind direction: + +$w = w_i(v_0) \theta(w_i(v_o), h)$ + +Wind vector is modeled as a function of position and time, and it is layered using progressively smaller sinusoids. + +![wind](img/submission/grass1.gif) + +**🪃 Recovery** + +Elastic forces that help the grass bounce back is modeled using Hooke's law: + +$r = (I_{v_2} - v_2) s$ + +### 🌿 Culling + +![culling](img/submission/grass2.gif) + +To allow for fast simulations, grass blades are culled through a combination of orientation culling, frustum culling and distance culling. + +**Orientation Culling** + +Grass blades that are not facing the camera are culled. This is done by checking the angle between the camera direction and the grass blade direction. + +**Frustum Culling** + +Grass blades that are outside of the view frustum are culled. Culling is performed by checking the intersection between the grass blade control points and the view frustum. + +**Distance Culling** + +Grass blades that are too far away from the camera are culled. + +## Performance Analysis + +Let's analyze the impace of culling and number of grass blades on the performance of the simulation. + +**All Culling** + +| Number of Grass Blades | FPS | +| ---------------------- | --- | +| 2^22 | 34 | +| 2^20 | 90 | +| 2^18 | 250 | + +**No Culling** + +| Number of Grass Blades | FPS | +| ---------------------- | --- | +| 2^22 | 9 | +| 2^20 | 32 | +| 2^18 | 100 | + +**Orientation Culling** + +| Number of Grass Blades | FPS | +| ---------------------- | --- | +| 2^22 | 9 | +| 2^20 | 30 | +| 2^18 | 80 | + +**Frustum Culling** + +| Number of Grass Blades | FPS | +| ---------------------- | --- | +| 2^22 | 35 | +| 2^20 | 92 | +| 2^18 | 250 | + +**Distance Culling** + +| Number of Grass Blades | FPS | +| ---------------------- | --- | +| 2^22 | 8 | +| 2^20 | 32 | +| 2^18 | 78 | + +![culling](img/submission/perf_cull.png) + +From the analysis, it appears that frustrum culling has the most impact on FPS. This is likely because in a large scene, the vast majority of blades will be hidden from view. + +## Build & Run + +```bash +bash build.sh debug +cd bin && ./vulkan_grass_rendering +``` diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe deleted file mode 100644 index f68db3a..0000000 Binary files a/bin/Release/vulkan_grass_rendering.exe and /dev/null differ diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..35d9e63 --- /dev/null +++ b/build.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Create build directory if it doesn't exist +mkdir -p build + +# Change into the build directory +cd build + +# Default build type to Release +build_type="Release" + +# Check for argument "debug" to change build type +if [ "$1" == "debug" ]; then + echo "Building in debug mode" + build_type="Debug" +fi + +# Run cmake with the specified build type +cmake -DCMAKE_BUILD_TYPE=$build_type .. + +# Build the project with dbg if debug was specified +if [ "$1" == "debug" ]; then + make dbg=1 +else + make +fi + +# recursively move bin dir/ into build/ diff --git a/images/grass.jpg b/images/grass.jpg new file mode 100644 index 0000000..d8713bd Binary files /dev/null and b/images/grass.jpg differ diff --git a/img/grass1.gif b/img/grass1.gif new file mode 100644 index 0000000..f7a8d73 Binary files /dev/null and b/img/grass1.gif differ diff --git a/img/grass3.gif b/img/grass3.gif new file mode 100644 index 0000000..429105d Binary files /dev/null and b/img/grass3.gif differ diff --git a/img/physics.png b/img/physics.png new file mode 100644 index 0000000..ae90fb9 Binary files /dev/null and b/img/physics.png differ diff --git a/img/shape.png b/img/shape.png new file mode 100644 index 0000000..7178d13 Binary files /dev/null and b/img/shape.png differ diff --git a/img/submission/grass.gif b/img/submission/grass.gif new file mode 100644 index 0000000..3e14bc6 Binary files /dev/null and b/img/submission/grass.gif differ diff --git a/img/submission/grass1.gif b/img/submission/grass1.gif new file mode 100644 index 0000000..f7a8d73 Binary files /dev/null and b/img/submission/grass1.gif differ diff --git a/img/submission/grass2.gif b/img/submission/grass2.gif new file mode 100644 index 0000000..152c189 Binary files /dev/null and b/img/submission/grass2.gif differ diff --git a/img/submission/grass3.gif b/img/submission/grass3.gif new file mode 100644 index 0000000..429105d Binary files /dev/null and b/img/submission/grass3.gif differ diff --git a/img/submission/perf_cull.png b/img/submission/perf_cull.png new file mode 100644 index 0000000..456370f Binary files /dev/null and b/img/submission/perf_cull.png differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..6c23a5d 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -14,6 +14,9 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode Blade currentBlade = Blade(); glm::vec3 bladeUp(0.0f, 1.0f, 0.0f); + // perturb up vector + bladeUp.x += (generateRandomFloat() - 0.5f) * 0.2f; + bladeUp.z += (generateRandomFloat() - 0.5f) * 0.2f; // Generate positions and direction (v0) float x = (generateRandomFloat() - 0.5f) * planeDim; @@ -45,7 +48,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..6b97bfd 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 << 18; 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/BufferUtils.cpp b/src/BufferUtils.cpp index acf617e..513b787 100644 --- a/src/BufferUtils.cpp +++ b/src/BufferUtils.cpp @@ -1,5 +1,6 @@ #include "BufferUtils.h" #include "Instance.h" +#include void BufferUtils::CreateBuffer(Device* device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { // Create buffer diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aea02fe..ffd9fb8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,7 +8,7 @@ file(GLOB IMAGES foreach(IMAGE ${IMAGES}) get_filename_component(fname ${IMAGE} NAME) - configure_file(${IMAGE} ${CMAKE_CURRENT_BINARY_DIR}/images/${fname} COPYONLY) + configure_file(${IMAGE} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/images/${fname} COPYONLY) endforeach() file(GLOB_RECURSE SHADER_SOURCES @@ -30,8 +30,10 @@ else(WIN32) target_link_libraries(vulkan_grass_rendering ${CMAKE_THREAD_LIBS_INIT}) endif(WIN32) +set(COMPILED_SHADERS "") + foreach(SHADER_SOURCE ${SHADER_SOURCES}) - set(SHADER_DIR ${CMAKE_CURRENT_BINARY_DIR}/shaders) + set(SHADER_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/shaders) if(WIN32) get_filename_component(fname ${SHADER_SOURCE} NAME) @@ -42,11 +44,26 @@ foreach(SHADER_SOURCE ${SHADER_SOURCES}) ) ExternalTarget("Shaders" ${fname}.spv) add_dependencies(vulkan_grass_rendering ${fname}.spv) + else(WIN32) + get_filename_component(fname ${SHADER_SOURCE} NAME) + set(SHADER_OUTPUT ${SHADER_DIR}/${fname}.spv) + + add_custom_command( + OUTPUT ${SHADER_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR} + COMMAND glslc ${SHADER_SOURCE} -o ${SHADER_OUTPUT} + DEPENDS ${SHADER_SOURCE} + COMMENT "Compiling ${fname} to SPIR-V" + ) + + list(APPEND COMPILED_SHADERS ${SHADER_OUTPUT}) endif(WIN32) - # TODO: Build shaders on not windows endforeach() +add_custom_target(compile_shaders DEPENDS ${COMPILED_SHADERS}) +add_dependencies(vulkan_grass_rendering compile_shaders) + target_link_libraries(vulkan_grass_rendering ${ASSIMP_LIBRARIES} Vulkan::Vulkan glfw) target_include_directories(vulkan_grass_rendering PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/src/Device.h b/src/Device.h index 163204b..71d1b76 100644 --- a/src/Device.h +++ b/src/Device.h @@ -6,6 +6,7 @@ #include "QueueFlags.h" #include "SwapChain.h" +class Instance; class SwapChain; class Device { friend class Instance; diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..fb04e06 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -195,9 +195,42 @@ 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 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 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 = { + inputBladesLayoutBinding, + culledBladesLayoutBinding, + numBladesLayoutBinding + }; + + 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::CreateDescriptorPool() { @@ -215,7 +248,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 input blades, culled blades, and num blades + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(3 * scene->GetBlades().size()) } }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -230,7 +264,7 @@ void Renderer::CreateDescriptorPool() { } void Renderer::CreateCameraDescriptorSet() { - // Describe the desciptor set + // Describe the descriptor set VkDescriptorSetLayout layouts[] = { cameraDescriptorSetLayout }; VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; @@ -318,8 +352,105 @@ 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()); + + VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(grassDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate grassDescriptorSets"); + } + + 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; + } + + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); +} + +void Renderer::CreateComputeDescriptorSets() { + computeDescriptorSets.resize(scene->GetBlades().size()); + + 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; + + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate computeDescriptorSets"); + } + + 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 = sizeof(Blade) * NUM_BLADES; + + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = sizeof(Blade) * NUM_BLADES; + + 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 = &inputBladesBufferInfo; + 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; + } + + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -357,10 +488,6 @@ void Renderer::CreateTimeDescriptorSet() { vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } -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 -} void Renderer::CreateGraphicsPipeline() { VkShaderModule vertShaderModule = ShaderModule::Create("shaders/graphics.vert.spv", logicalDevice); @@ -716,8 +843,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 +1009,13 @@ 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 + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr); + + int numGroups = int(ceil(NUM_BLADES - 1) / WORKGROUP_SIZE); + + vkCmdDispatch(computeCommandBuffer, numGroups, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -926,7 +1058,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.5f, 0.8f, 1.0f }; clearValues[1].depthStencil = { 1.0f, 0 }; renderPassInfo.clearValueCount = static_cast(clearValues.size()); renderPassInfo.pClearValues = clearValues.data(); @@ -954,17 +1086,14 @@ void Renderer::RecordCommandBuffers() { vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); for (uint32_t j = 0; j < scene->GetModels().size(); ++j) { - // Bind the vertex and index buffers VkBuffer vertexBuffers[] = { scene->GetModels()[j]->getVertexBuffer() }; - VkDeviceSize offsets[] = { 0 }; + VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); vkCmdBindIndexBuffer(commandBuffers[i], scene->GetModels()[j]->getIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); - // Bind the descriptor set for each model vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 1, 1, &modelDescriptorSets[j], 0, nullptr); - // Draw std::vector indices = scene->GetModels()[j]->getIndices(); vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); } @@ -975,14 +1104,13 @@ void Renderer::RecordCommandBuffers() { for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) { VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() }; VkDeviceSize offsets[] = { 0 }; - // TODO: Uncomment this when the buffers are populated - // vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); - // TODO: Bind the descriptor set for each grass blades model + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + + 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 @@ -1041,8 +1169,6 @@ void Renderer::Frame() { Renderer::~Renderer() { vkDeviceWaitIdle(logicalDevice); - // TODO: destroy any resources you created - vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); vkFreeCommandBuffers(logicalDevice, computeCommandPool, 1, &computeCommandBuffer); @@ -1057,6 +1183,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..36caa9b 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 grassDescriptorSets; + std::vector computeDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/SwapChain.cpp b/src/SwapChain.cpp index 711fec0..047b6c2 100644 --- a/src/SwapChain.cpp +++ b/src/SwapChain.cpp @@ -1,4 +1,5 @@ #include +#include #include "SwapChain.h" #include "Instance.h" #include "Device.h" diff --git a/src/Window.cpp b/src/Window.cpp index a365dc9..2672a40 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -1,4 +1,5 @@ #include +#include #include "Window.h" namespace { diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..9aa1967 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,7 @@ #include +#include +#include +#include #include "Instance.h" #include "Window.h" #include "Renderer.h" @@ -66,8 +69,14 @@ namespace { } int main() { + + // sleep + std::cout << "Sleeping ..." << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(3)); + std::cout << "Done sleeping" << std::endl; + static constexpr char* applicationName = "Vulkan Grass Rendering"; - InitializeWindow(640, 480, applicationName); + InitializeWindow(1920, 1080, applicationName); unsigned int glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -116,7 +125,9 @@ int main() { grassImageMemory ); - float planeDim = 15.f; + std::cout << "Grass image created" << std::endl; + + float planeDim = 150.f; float halfWidth = planeDim * 0.5f; Model* plane = new Model(device, transferCommandPool, { @@ -128,6 +139,8 @@ int main() { { 0, 1, 2, 2, 3, 0 } ); plane->SetTexture(grassImage); + + std::cout << "Plane created" << std::endl; Blades* blades = new Blades(device, transferCommandPool, planeDim); @@ -137,16 +150,26 @@ int main() { scene->AddModel(plane); scene->AddBlades(blades); + std::cout << "Scene created" << std::endl; + renderer = new Renderer(device, swapChain, scene, camera); + std::cout << "Renderer created" << std::endl; + glfwSetWindowSizeCallback(GetGLFWWindow(), resizeCallback); glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback); glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback); + float t0 = glfwGetTime(); + while (!ShouldQuit()) { glfwPollEvents(); scene->UpdateTime(); renderer->Frame(); + float t1 = glfwGetTime(); + float dt = t1 - t0; + std::cout << "FPS: " << 1.0f / dt << std::endl; + t0 = t1; } vkDeviceWaitIdle(device->GetVkDevice()); diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..c262b0f 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,10 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define CULL_ORIENTATION 0 +#define CULL_FRUSTRUM 0 +#define CULL_DISTANCE 1 + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -21,36 +25,158 @@ struct Blade { vec4 up; }; -// TODO: Add bindings to: +// 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 InputBlades { + Blade blades[]; +} inputBlades; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade blades[]; +} 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); } +bool verify_frustrum(vec4 v0_proj, vec4 m_proj, vec4 v2_proj, float h_v0, float h_m, float h_v2) { + // return true if true for any of the three vertices + bool v0_ver = inBounds(v0_proj.x, h_v0) && inBounds(v0_proj.y, h_v0) && inBounds(v0_proj.z, h_v0); + bool m_ver = inBounds(m_proj.x, h_m) && inBounds(m_proj.y, h_m) && inBounds(m_proj.z, h_m); + bool v2_ver = inBounds(v2_proj.x, h_v2) && inBounds(v2_proj.y, h_v2) && inBounds(v2_proj.z, h_v2); + return v0_ver || m_ver || v2_ver; +} + +vec3 generateWind(float time, vec3 pos) { + + vec3 principalDirection = normalize(vec3(1.0, 0.0, 0.0)); + + float magnitude = sin(time + pos.z) + 1 + + 0.50 * sin(2.0 * (time + 2.0 * pos.z)) + + 0.33 * sin(3.0 * (time + 3.0 * pos.z)) + + 0.25 * sin(4.0 * (time + 4.0 * pos.z)); + float scale = 30.0; + + vec3 windVector = scale * magnitude * principalDirection; + + return windVector; +} + + void main() { + + // Reset the number of blades to 0 + if (gl_GlobalInvocationID.x == 0) { + //numBlades.vertexCount = 0; + } + barrier(); + // Reset the number of blades to 0 if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + numBlades.vertexCount = 0; + numBlades.instanceCount = 1; + numBlades.firstVertex = 0; + numBlades.firstInstance = 0; } barrier(); // Wait till all threads reach this point - // TODO: Apply forces on every blade and update the vertices in the buffer + Blade blade = inputBlades.blades[gl_GlobalInvocationID.x]; + + vec3 v0 = blade.v0.xyz; + vec3 v2 = blade.v2.xyz; + vec3 up = blade.up.xyz; + + float orientation = blade.v0.w; + float height = blade.v1.w; + float width = blade.v2.w; + float stiffness = blade.up.w; + + // recovery force + vec3 Iv2 = v0 + up * height; + vec3 r = (Iv2 - v2) * stiffness; + + // gravity + vec3 t = vec3(cos(orientation), 0, sin(orientation)); + vec3 front = normalize(cross(t, up)); + vec3 gE = -9.8 * up; + vec3 gF = 0.25 * length(gE) * front; + vec3 g = gE + gF; + + // wind + vec3 w = generateWind(totalTime, v0); + float fd = 1 - abs(dot(normalize(w), normalize(v2 - v0))); + float fr = dot((v2 - v0), up) / height; + float theta = fd * fr; + w = w * theta; + + vec3 total_displacement = (r + g + w) * 0.01; + + v2 += total_displacement; + + // verification + v2 = v2 - up * min(dot(v2 - v0, up), 0.0); + float lproj = length(v2 - v0 - dot(v2 - v0, up) * up); + vec3 v1 = v0 + height * up * max(1.0 - lproj / height, 0.05 * max(lproj / height, 1.0)); + float L = (2 * length(v2 - v0) + length(v1 - v0) + length(v2 - v1)) / 3; + + float ratio = height / L; + + vec3 v1_ver = v0 + ratio * (v1-v0); + vec3 v2_ver = v1_ver + ratio * (v2-v1); + + blade.v1.xyz = v1_ver; + blade.v2.xyz = v2_ver; + + // we cull if test is false + + bool cull_orientation = (CULL_ORIENTATION == 1); + bool cull_frustrum = (CULL_FRUSTRUM == 1); + bool cull_distance = (CULL_DISTANCE == 1); + + // orientation + vec3 dirc = normalize((inverse(camera.view) * vec4(0, 0, 1, 0)).xyz); + bool orientationTest = abs(dot(dirc, t)) < 0.90; + + // frustrum + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; + vec4 v0_proj = camera.proj * camera.view * vec4(v0, 1); + vec4 m_proj = camera.proj * camera.view * vec4(m, 1); + vec4 v2_proj = camera.proj * camera.view * vec4(v2, 1); + float tol = 0.001; + float h_v0 = v0_proj.w + tol; + float h_m = m_proj.w + tol; + float h_v2 = v2_proj.w + tol; + bool frustrum_test = verify_frustrum(v0_proj, m_proj, v2_proj, h_v0, h_m, h_v2); + + // distance + vec3 c = (inverse(camera.view) * vec4(0, 0, 0, 1)).xyz; + float dproj = length(v0 - c - up * dot(v0 - c, up)); + float dmax = 10.0; + int levels = 30; + uint id = gl_GlobalInvocationID.x; + bool distance_test = id % levels >= floor(levels * (1 - dproj / dmax)); + + // cull + bool cull = (!orientationTest && cull_orientation) || (!frustrum_test && cull_frustrum) || (!distance_test && cull_distance); + if (!cull) { + uint index = atomicAdd(numBlades.vertexCount, 1); + culledBlades.blades[index] = blade; + } + + //culledBlades.blades[gl_GlobalInvocationID.x] = blade; - // 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 } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..ea6dc57 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -6,12 +6,20 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare fragment shader inputs + + +layout(location = 0) in vec3 grassNormal; +layout(location = 1) in vec3 grassPosition; layout(location = 0) out vec4 outColor; void main() { - // TODO: Compute fragment color - outColor = vec4(1.0); + vec3 lightDir = normalize(vec3(1.0f, 1.0f, 1.0f)); + + float lambertian = min(max(dot(grassNormal, lightDir), 0.0f) + 0.4f, 1.0f); + + vec3 grassGreen = vec3(0.1f, 0.7f, 0.2f); + + outColor = vec4(grassGreen * lambertian, 1.0f); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..164389b 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -8,19 +8,39 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation control shader inputs and outputs + +layout(location = 0) in vec4 blade_v0[]; +layout(location = 1) in vec4 blade_v1[]; +layout(location = 2) in vec4 blade_v2[]; +layout(location = 3) in vec4 blade_up[]; + +layout(location = 0) out vec4 blade_v0_out[]; +layout(location = 1) out vec4 blade_v1_out[]; +layout(location = 2) out vec4 blade_v2_out[]; +layout(location = 3) out vec4 blade_up_out[]; + +in gl_PerVertex { + vec4 gl_Position; + float gl_PointSize; + float gl_ClipDistance[]; +} gl_in[]; + 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 + // Pass the grass blade data through + blade_v0_out[gl_InvocationID] = blade_v0[gl_InvocationID]; + blade_v1_out[gl_InvocationID] = blade_v1[gl_InvocationID]; + blade_v2_out[gl_InvocationID] = blade_v2[gl_InvocationID]; + blade_up_out[gl_InvocationID] = blade_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] = 5; + gl_TessLevelInner[1] = 5; + gl_TessLevelOuter[0] = 5; + gl_TessLevelOuter[1] = 5; + gl_TessLevelOuter[2] = 5; + gl_TessLevelOuter[3] = 5; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..a01da37 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -8,11 +8,46 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 blade_v0[]; +layout(location = 1) in vec4 blade_v1[]; +layout(location = 2) in vec4 blade_v2[]; +layout(location = 3) in vec4 blade_up[]; + +layout(location = 0) out vec3 grassNormal; +layout(location = 1) out vec3 grassPosition; 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 + float orientation = blade_v0[0].w; + float height = blade_v1[0].w; + float width = blade_v2[0].w; + + vec3 v0 = blade_v0[0].xyz; + vec3 v1 = blade_v1[0].xyz; + vec3 v2 = blade_v2[0].xyz; + vec3 up = blade_up[0].xyz; + + vec3 t1 = normalize(vec3(sin(orientation), 0.0, cos(orientation))); + + 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 normal = normalize(cross(t0, t1)); + + float t = u + 0.5 * v - u * v; + + vec3 position = mix(c0, c1, t); + + grassPosition = position; + grassNormal = normal; + + gl_Position = camera.proj * camera.view * vec4(position, 1.0); + } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..b0c9e5d 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -2,16 +2,50 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +layout(set = 0, binding = 0) uniform CameraBufferObject { + mat4 view; + mat4 proj; +} camera; + layout(set = 1, binding = 0) uniform ModelBufferObject { mat4 model; }; -// TODO: Declare vertex shader inputs and outputs +// Grass blade data structure +struct GrassBlade { + vec4 v0; // control point 0 + orientation + vec4 v1; // control point 1 + height + vec4 v2; // control point 2 + width + vec4 up; // up vector + stiffness coefficient +}; + +layout(location = 0) in vec4 blade_v0; +layout(location = 1) in vec4 blade_v1; +layout(location = 2) in vec4 blade_v2; +layout(location = 3) in vec4 blade_up; + +layout(location = 0) out vec4 blade_v0_out; +layout(location = 1) out vec4 blade_v1_out; +layout(location = 2) out vec4 blade_v2_out; +layout(location = 3) out vec4 blade_up_out; out gl_PerVertex { vec4 gl_Position; + float gl_PointSize; + float gl_ClipDistance[]; }; void main() { - // TODO: Write gl_Position and any other shader outputs + + gl_Position = model * vec4(blade_v0.xyz, 1.0); + + blade_v0_out = model * vec4(blade_v0.xyz, 1.0); + blade_v1_out = model * vec4(blade_v1.xyz, 1.0); + blade_v2_out = model * vec4(blade_v2.xyz, 1.0); + blade_up_out = model * vec4(blade_up.xyz, 1.0); + + blade_v0_out.w = blade_v0.w; + blade_v1_out.w = blade_v1.w; + blade_v2_out.w = blade_v2.w; + blade_up_out.w = blade_up.w; }