diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1191f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,561 @@ +*.orig +*.filters +*.sln +*.vcxproj +*.xcodeproj +build + +# Created by https://www.gitignore.io/api/linux,osx,sublimetext,windows,jetbrains,vim,emacs,cmake,c++,cuda,visualstudio,webstorm,eclipse,xcode + +### Linux ### +*~ + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + + +### OSX ### +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### SublimeText ### +# cache files for sublime text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# workspace files are user-specific +*.sublime-workspace + +# project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using SublimeText +# *.sublime-project + +# sftp configuration file +sftp-config.json + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### JetBrains ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +#.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + +### Vim ### +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ + + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ + + +### CMake ### +CMakeCache.txt +CMakeFiles +CMakeScripts +Makefile +cmake_install.cmake +install_manifest.txt + + +### C++ ### +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + + +### CUDA ### +*.i +*.ii +*.gpu +*.ptx +*.cubin +*.fatbin + + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + + +### WebStorm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + +### Eclipse ### +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# Eclipse Core +#.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +#*.launch + +# CDT-specific +#.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + +### Xcode ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata + +## Other +*.xccheckout +*.moved-aside +*.xcuserstate diff --git a/README.md b/README.md index 20ee451..16c6b81 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,93 @@ -Vulkan Grass Rendering -================================== +# 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) +* Tong Hu +* Tested on: Windows 11, Ryzen 7 1700X @ 3.4GHz, 16GB RAM, NVIDIA RTX 2060 6GB (Personal Desktop) -### (TODO: Your README) +## Summary + +![Demo GIF](/img/demo.gif) + +This project is a Vulkan-based application designed to render realistic grass with an integrated physics simulation, utilizing compute shaders. The implementation is inspired by the techniques described in 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). + +## Simulation Details + +The physical simulation of grass in this project models each blade as an individual entity, influenced by three primary forces: + +1. **Gravity**: Bends the grass blades downward. +2. **Recovery**: Acts against gravity, helping the blades maintain their upright position. +3. **Wind**: Varies based on location and time, adding dynamic movement to the grass. + +Each grass blade is geometrically represented by a Bezier curve, composed of multiple control points. + +![Blade Definition](./img/blade_model.jpg) + +## Features + +### Forces Simulation + +This application simulates the effects of gravity, recovery, and wind on grass blades. Below are visual demonstrations of these simulations: + +#### Gravity Effect + +| Original (No Force) | Gravity Only | +|:--------------------:|:-------------:| +| ![No Force Pic](/img/no_force_no_cull.png) | ![Gravity Pic](/img/gravity_no_cull.png) | + +#### Comprehensive Forces + +| Gravity & Recovery | Gravity, Recovery & Wind | +|:------------------:|:-------------------------:| +| ![Recovery Pic](/img/recovery_no_cull.png) | ![Wind GIF](/img/wind_no_cull.gif) | + +### Culling Optimization + +To enhance performance and minimize the number of grass blades rendered, the application employs three types of culling: + +1. **Orientation Test**: Determines visibility based on the blade’s orientation relative to the camera. +2. **View-Frustum Test**: Ensures only blades within the camera’s field of view are rendered. +3. **Distance Test**: Reduces detail for distant blades, saving computational resources. + +#### Culling Demonstrations + +##### Orientation Test + +| Original (No Culling) | Orientation Test | +|:------------------:|:------------:| +| ![Original GIF](/img/wind_no_cull.gif) | ![Orien Test](/img/orientation_cull.gif) | + +##### View-Frustum and Distance Tests + +| View-Frustum Test | Distance Test | +|:------------------:|:------------:| +| ![VF Test](/img/frustum_cull.gif) | ![Distance GIF](/img/distance_cull.gif) | + +## Performance Analysis + +### Impact of Grass Blade Quantity + +The quantity of grass blades significantly affects rendering performance. Below is an analysis of Frames Per Second (FPS) against various blade counts. + +![FPS vs #Blades](/img/fps_blades.png) + +This plot shows the fps under different number of blades with all culling mode disabled. + +From above plot, we can see that when the number of blades is small (< $2^{13}$), fps is relative stable (~1000 fps). In this case, the bottleneck for rendering is not the number of blades. After this point, as the number of blades increased, the fps drops quickly, since when there are too many blades to be rendered, the computation becomes intense, and the number of blades becomes the bottleneck of scene rendering. + +### Efficiency of Culling Techniques + +Culling helps in reducing the rendering workload by limiting the number of grass blades processed. + +![FPS vs Culling Methods](/img/fps_culling.png) + +This plot shows the fps under different culling modes with number of blades equals to $2^{15}$, and all fps value is recorded without changing camera position. + +From above plot, we can see that without any culling, the fps is low since all blades need to be rendered. When enable orientation culling, rendering performance is better since the blades are all 2D and we will not render the blades whose orientation is aligned with the direction of camera view, and this reducing the rendering workload the most among three culling modes. + +With distance culling enabled, we can also see the improvement of performance in rendering the grass. This fps value is related to the distance between the grass and the camera. When the camera is distant from the grass, the number of blades need to be rendered is smaller, and thus the fps is higher (~1000 when the camera is too far and no blades need to be rendered). + +When enabling frustum culling, and dive into the grass, we can see the fps is higher compared with looking the overall grassland, since when we dive into the grass, the blades out of the scope will not be rendered. + +With all culling modes enabled, the fps is much higher than any of other scenario, since we exclude all blades that do not need to be rendered, thus improving the performance a lot. -*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. diff --git a/img/demo.gif b/img/demo.gif new file mode 100644 index 0000000..1021a59 Binary files /dev/null and b/img/demo.gif differ diff --git a/img/distance_cull.gif b/img/distance_cull.gif new file mode 100644 index 0000000..51ef76f Binary files /dev/null and b/img/distance_cull.gif differ diff --git a/img/fps_blades.png b/img/fps_blades.png new file mode 100644 index 0000000..f0e0222 Binary files /dev/null and b/img/fps_blades.png differ diff --git a/img/fps_culling.png b/img/fps_culling.png new file mode 100644 index 0000000..5e314bf Binary files /dev/null and b/img/fps_culling.png differ diff --git a/img/frustum_cull.gif b/img/frustum_cull.gif new file mode 100644 index 0000000..961153b Binary files /dev/null and b/img/frustum_cull.gif differ diff --git a/img/gravity_no_cull.png b/img/gravity_no_cull.png new file mode 100644 index 0000000..70a4b9a Binary files /dev/null and b/img/gravity_no_cull.png differ diff --git a/img/no_force_no_cull.png b/img/no_force_no_cull.png new file mode 100644 index 0000000..4c49539 Binary files /dev/null and b/img/no_force_no_cull.png differ diff --git a/img/orientation_cull.gif b/img/orientation_cull.gif new file mode 100644 index 0000000..ac7dc7e Binary files /dev/null and b/img/orientation_cull.gif differ diff --git a/img/recovery_no_cull.png b/img/recovery_no_cull.png new file mode 100644 index 0000000..07e712c Binary files /dev/null and b/img/recovery_no_cull.png differ diff --git a/img/wind_no_cull.gif b/img/wind_no_cull.gif new file mode 100644 index 0000000..3f6431a Binary files /dev/null and b/img/wind_no_cull.gif 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/Renderer.cpp b/src/Renderer.cpp index b445d04..a9aa544 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,41 @@ 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 + // Describe the binding of the descriptor set layout + 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 bladesAfterCullLayoutBinding = {}; + bladesAfterCullLayoutBinding.binding = 1; + bladesAfterCullLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladesAfterCullLayoutBinding.descriptorCount = 1; + bladesAfterCullLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladesAfterCullLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding bladesCountLayoutBinding = {}; + bladesCountLayoutBinding.binding = 2; + bladesCountLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladesCountLayoutBinding.descriptorCount = 1; + bladesCountLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladesCountLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { bladesLayoutBinding, + bladesAfterCullLayoutBinding , + bladesCountLayoutBinding }; + + // 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::CreateDescriptorPool() { @@ -216,6 +251,8 @@ void Renderer::CreateDescriptorPool() { { 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 = {}; @@ -320,6 +357,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[] = { modelDescriptorSetLayout }; + 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 bladesBufferInfo = {}; + bladesBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + bladesBufferInfo.offset = 0; + bladesBufferInfo.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 = &bladesBufferInfo; + 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 +433,74 @@ void Renderer::CreateTimeDescriptorSet() { void Renderer::CreateComputeDescriptorSets() { // TODO: Create Descriptor sets for the compute pipeline // The descriptors should point to Storage buffers which will hold the grass blades, the culled grass blades, and the output number of grass blades + computeDescriptorSets.resize(scene->GetBlades().size()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(computeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(computeDescriptorSets.size() * 3); + + for (uint32_t i = 0; i < scene->GetBlades().size(); i++) { + VkDescriptorBufferInfo bladesBufferInfo = {}; + bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + bladesBufferInfo.offset = 0; + bladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + 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 = &bladesBufferInfo; + descriptorWrites[3 * i].pImageInfo = nullptr; + descriptorWrites[3 * i].pTexelBufferView = nullptr; + + + VkDescriptorBufferInfo bladesAfterCullBufferInfo = {}; + bladesAfterCullBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + bladesAfterCullBufferInfo.offset = 0; + bladesAfterCullBufferInfo.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 = &bladesAfterCullBufferInfo; + descriptorWrites[3 * i + 1].pImageInfo = nullptr; + descriptorWrites[3 * i + 1].pTexelBufferView = nullptr; + + + VkDescriptorBufferInfo bladesCountBufferInfo = {}; + bladesCountBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + bladesCountBufferInfo.offset = 0; + bladesCountBufferInfo.range = NUM_BLADES * 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 = &bladesCountBufferInfo; + descriptorWrites[3 * i + 2].pImageInfo = nullptr; + descriptorWrites[3 * i + 2].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +858,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 +1025,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 (int 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, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1121,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 +1203,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..728ab34 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,12 +56,15 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + std::vector computeDescriptorSets; + std::vector grassDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..0997e9c 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,13 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define APPLY_GRAVITY 1 +#define APPLY_RECOVERY 1 +#define APPLY_WIND 1 +#define CULL_ORIENTATION 1 +#define CULL_VIEWFRUSTRUM 1 +#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 { @@ -25,32 +32,135 @@ struct Blade { // 1. Store the input blades // 2. Write out the culled blades // 3. Write the total number of blades remaining +layout(set = 2, binding = 0) buffer Blades{ + Blade blades[]; +}; + +layout(set = 2, binding = 1) buffer BladesAfterCull{ + Blade culledBlades[]; +}; // 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 = 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 generateRandomWind(vec3 v0, float totalTime) { + return vec3(3 * (1 + sin(0.5 * (0.6 * v0.x + totalTime))), 0.0, + (cos(0.7 * (v0.z + totalTime)))); +} + +bool isInBounds(vec4 p, float tolerance) { + float h = p.w + tolerance; + return (inBounds(p.x, h) && inBounds(p.y, h) && inBounds(p.z, h)); +} + 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 + Blade blade = blades[gl_GlobalInvocationID.x]; + vec3 v0 = blade.v0.xyz; + vec3 v1 = blade.v1.xyz; + vec3 v2 = blade.v2.xyz; + vec3 up = blade.up.xyz; + + // direction angle + float theta = blade.v0.w; + float height = blade.v1.w; + float width = blade.v2.w; + float stiffness = blade.up.w; + vec3 t1 = normalize(vec3(-cos(theta), 0.f, sin(theta))); + + vec3 gravity = vec3(0.f); + vec3 recovery = vec3(0.f); + vec3 wind = vec3(0.f); + // TODO: Apply forces on every blade and update the vertices in the buffer +#ifdef APPLY_GRAVITY + float a = 9.8f; + vec3 gE = vec3(0.f, -a, 0.f); + vec3 gF = 0.25 * length(gE) * cross(t1, up); + gravity = gE + gF; +#endif /*APPLY_GRAVITY*/ +#ifdef APPLY_RECOVERY + vec3 Iv2 = v0 + height * up; + recovery = (Iv2 - v2) * stiffness; +#endif /*APPLY_RECOVERY*/ +#ifdef APPLY_WIND + vec3 windDir = generateRandomWind(v0, totalTime); + float fd = 1 - abs(dot(normalize(windDir), normalize(v2 - v0))); + float fr = dot((v2 - v0), up) / height; + wind = fd * fr * windDir; +#endif /*APPLY_WIND*/ + // total force + vec3 tv2 = (gravity + recovery + wind) * deltaTime; + v2 += tv2; + + // state validation: all grass should be above the ground + v2 -= up * min(dot(up, v2 - v0), 0.f); + + float lProj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + height * up * max(1 - lProj / height, 0.05 * max(lProj / height, 1)); + float l0 = length(v0 - v2); + float l1 = length(v2 - v1) + length(v1 - v0); + float n = 2.f; + float l = (2 * l0 + (n - 1) * l1) / (n + 1); + float r = height / l; + vec3 v1corr = v0 + r * (v1 - v0); + vec3 v2corr = v1corr + r * (v2 - v1); + v1 = v1corr; + v2 = v2corr; + + blades[gl_GlobalInvocationID.x].v1.xyz = v1; + blades[gl_GlobalInvocationID.x].v2.xyz = v2; // 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 + + //camera position + vec3 camPos = inverse(camera.view)[3].xyz; + +#ifdef CULL_ORIENTATION + vec3 dirB = v0 - camPos; + vec3 dirC = t1; + if (abs(dot(normalize(dirB), normalize(dirC))) > 0.6) { + return; + } +#endif /*CULL_ORIENTATION*/ +#ifdef CULL_VIEWFRUSTRUM + float tolerance = 0.0001; + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; + mat4 viewProj = camera.proj * camera.view; + vec4 m_vp = viewProj * vec4(m, 1.f); + vec4 v0_vp = viewProj * vec4(v0, 1.f); + vec4 v2_vp = viewProj * vec4(v2, 1.f); + if (!isInBounds(m_vp, tolerance) && !isInBounds(v0_vp, tolerance) && !isInBounds(v2_vp, tolerance)) { + return; + } +#endif /*CULL_VIEWFRUSTRUM*/ +#ifdef CULL_DISTANCE + uint n1 = 10; + uint dmax = 30; + float dPorj = length(v0 - camPos - up * dot(v0 - camPos, up)); + if ((gl_GlobalInvocationID.x % n1) > (n1 * (1 - (dPorj / dmax)))) { + return; + } +#endif /*CULL_DISTANCE*/ + uint vertCount = atomicAdd(numBlades.vertexCount, 1); + culledBlades[vertCount] = blades[gl_GlobalInvocationID.x]; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..ef16ac9 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,12 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in float h; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - - outColor = vec4(1.0); + vec4 green = vec4(107/255.f, 173/255.f, 69/255.f, 1.f); + outColor = green * h; } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..17c21ba 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -9,18 +9,34 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4 v0_i[]; +layout(location = 1) in vec4 v1_i[]; +layout(location = 2) in vec4 v2_i[]; +layout(location = 3) in vec4 up_i[]; + +layout(location = 0) out vec4 v0_o[]; +layout(location = 1) out vec4 v1_o[]; +layout(location = 2) out vec4 v2_o[]; +layout(location = 3) out vec4 up_o[]; + 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 + v0_o[gl_InvocationID] = v0_i[gl_InvocationID]; + v1_o[gl_InvocationID] = v1_i[gl_InvocationID]; + v2_o[gl_InvocationID] = v2_i[gl_InvocationID]; + up_o[gl_InvocationID] = up_i[gl_InvocationID]; + + float level = 8.f; // 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] = level; + gl_TessLevelInner[1] = level; + gl_TessLevelOuter[0] = level; + gl_TessLevelOuter[1] = level; + gl_TessLevelOuter[2] = level; + gl_TessLevelOuter[3] = level; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..74ba68c 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,36 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 v0_i[]; +layout(location = 1) in vec4 v1_i[]; +layout(location = 2) in vec4 v2_i[]; + +layout(location = 0) out float h; 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 + vec3 v0 = v0_i[0].xyz; + vec3 v1 = v1_i[0].xyz; + vec3 v2 = v2_i[0].xyz; + float theta = v0_i[0].w; + float w = v2_i[0].w; + + // bitangent (given directly by the direction vector along the width fo the blade) + vec3 t1 = normalize(vec3(-cos(theta), 0.f, sin(theta))); + + vec3 a = v0 + v * (v1 - v0); + vec3 b = v1 + v * (v2 - v1); + vec3 c = a + v * (b - a); + vec3 c0 = c - w * t1; + vec3 c1 = c + w * t1; + vec3 t0 = normalize(b - a); + + // basic shape + float t = u + 0.5 * v - u * v; + vec3 p = (1.f - t) * c0 + t * c1; + h = v; + gl_Position = camera.proj * camera.view * vec4(p, 1.f); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..3b9cdeb 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 v0_i; +layout(location = 1) in vec4 v1_i; +layout(location = 2) in vec4 v2_i; +layout(location = 3) in vec4 up_i; + +layout(location = 0) out vec4 v0_o; +layout(location = 1) out vec4 v1_o; +layout(location = 2) out vec4 v2_o; +layout(location = 3) out vec4 up_o; out gl_PerVertex { vec4 gl_Position; @@ -14,4 +23,11 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + v0_o = model * vec4(v0_i.xyz, 1.f); + gl_Position = v0_o; + v0_o.w = v0_i.w; + + v1_o = vec4((model * vec4(v1_i.xyz, 1.f)).xyz, v1_i.w); + v2_o = vec4((model * vec4(v2_i.xyz, 1.f)).xyz, v2_i.w); + up_o = vec4((model * vec4(up_i.xyz, 1.f)).xyz, up_i.w); }