diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..602682d --- /dev/null +++ b/.gitignore @@ -0,0 +1,560 @@ +*.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 + + +# 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 +*.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..4233e67 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,62 @@ 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) +* Shineng Tang + * [LinkedIn](https://www.linkedin.com/in/shineng-t-224192195/) +* Tested on: Windows 11, i9-10900k @3.7GHz 32GB, RTX 3090 24GB -### (TODO: Your README) -*DO NOT* leave the README to the last minute! It is a crucial part of the -project, and we will not be able to grade you without a good README. +## Proejct Description +This is a Vulkan grass simulator that simluates the animation of the grass under the impact of physical forces. +Throughout the development, I further understand Vulkan rendering pipeline. +![](img/1.gif) + +## Features +### Representing Grass as Bezier Curves +![](img/blade_model.jpg) + +Each blade of grass is represented by Bezier curve. +Each curve has three control points. +* `v0`: the position of the grass blade on the geomtry +* `v1`: a Bezier curve guide that is always "above" `v0` with respect to the grass blade's up vector +* `v2`: a physical guide for which we simulate forces on + +There are also different characteristics of the grass storing in the w element of each vector +* `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at `v0` +* Orientation: the orientation of the grass blade's face +* Height: the height of the grass blade +* Width: the width of the grass blade's face +* Stiffness coefficient: the stiffness of our grass blade, which will affect the force computations on our blade + +### Physics Simulation +The animation of the grass blades are affected by gravity, recovery force and the wind. +![](img/wind.gif) + +### Different Culling Method + +| Distance Culling | Orientation Culling | +|---|---| +|![](img/dist.gif)|![](img/orientation.gif)| + + +| No Culling | View-Frustrum Culling | +|---|---| +|![](img/nocull.gif)|![](img/cull.gif)| + +The visual effect of View-Frustrum Culling is not obvious. But we can tell from the gifs above that when the grass blades are outside of the window, they are culled so the fps goes extremely higher. However, if I disable the culling, the fps remains approximately on the same level even if the blades are not rendered inside the window. + +## Performance Analysis +![](img/nob.png) + +The framerates when rendering relatively larger number of blades are proportionate to the number of blades. When I double the number of blades, the framerates will be approximately halved. The reason of why the framerates do not change relatively a lot for the small numbers of blades is that it reaches certain threshold of the power of my GPU. + + +![](img/culling.png) + + +For the default camera angle and the grass blades number of 2^15, the most effcient culling method is the orientation culling and the least effcient one is the view-frustrum method. The result is expected. For the view-frustrum method with the default camera angle, not a lot of blades are outside of the camera, so it produces a slight boost on the fps. For the orientation method, a lot of blades are culled because the camera contains a huge portion of the blades in the scene and they are culled based on their orientations towards the camera. + + +### Default Camera Angle + +![](img/default.png) \ No newline at end of file diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe index f68db3a..8128fae 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/img/1.gif b/img/1.gif new file mode 100644 index 0000000..0bd749f Binary files /dev/null and b/img/1.gif differ diff --git a/img/Wind.gif b/img/Wind.gif new file mode 100644 index 0000000..764f143 Binary files /dev/null and b/img/Wind.gif differ diff --git a/img/cull.gif b/img/cull.gif new file mode 100644 index 0000000..d9ce8b2 Binary files /dev/null and b/img/cull.gif differ diff --git a/img/culling.png b/img/culling.png new file mode 100644 index 0000000..dbd3a75 Binary files /dev/null and b/img/culling.png differ diff --git a/img/default.png b/img/default.png new file mode 100644 index 0000000..d7a7f84 Binary files /dev/null and b/img/default.png differ diff --git a/img/dist.gif b/img/dist.gif new file mode 100644 index 0000000..3833921 Binary files /dev/null and b/img/dist.gif differ diff --git a/img/nob.png b/img/nob.png new file mode 100644 index 0000000..e34e8e4 Binary files /dev/null and b/img/nob.png differ diff --git a/img/nocull.gif b/img/nocull.gif new file mode 100644 index 0000000..93b3da0 Binary files /dev/null and b/img/nocull.gif differ diff --git a/img/orientation.gif b/img/orientation.gif new file mode 100644 index 0000000..298504f Binary files /dev/null and b/img/orientation.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/Blades.h b/src/Blades.h index 9bd1eed..b438e24 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 << 15; constexpr static float MIN_HEIGHT = 1.3f; constexpr static float MAX_HEIGHT = 2.5f; constexpr static float MIN_WIDTH = 0.1f; diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..a0a6e23 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -5,7 +5,7 @@ #include "Blades.h" #include "Camera.h" #include "Image.h" - +#include static constexpr unsigned int WORKGROUP_SIZE = 32; Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* camera) @@ -198,6 +198,40 @@ 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 grassBladesLayoutBinding = {}; + grassBladesLayoutBinding.binding = 0; + grassBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + grassBladesLayoutBinding.descriptorCount = 1; + grassBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + grassBladesLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding culledBladesLayoutBinding = {}; + culledBladesLayoutBinding.binding = 1; + culledBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledBladesLayoutBinding.descriptorCount = 1; + culledBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + grassBladesLayoutBinding.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 = { grassBladesLayoutBinding, 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() { @@ -216,6 +250,7 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3 * static_cast(scene->GetBlades().size()) } }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,6 +355,43 @@ 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; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate grass descriptor set"); + } + + + std::vector descriptorWrites(grassDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); i++) { + VkDescriptorBufferInfo grassBufferInfo = {}; + + grassBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + grassBufferInfo.offset = 0; + grassBufferInfo.range = sizeof(ModelBufferObject); + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = grassDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = &grassBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +432,78 @@ 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 + + //One blade one set + computeDescriptorSets.resize(scene->GetBlades().size()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(computeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate compute descriptor set"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + + VkDescriptorBufferInfo bladesBufferInfo = {}; + bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + bladesBufferInfo.offset = 0; + bladesBufferInfo.range = sizeof(Blade) * NUM_BLADES; + + descriptorWrites[3 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 0].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 0].dstBinding = 0; + descriptorWrites[3 * i + 0].dstArrayElement = 0; + descriptorWrites[3 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 0].descriptorCount = 1; + descriptorWrites[3 * i + 0].pBufferInfo = &bladesBufferInfo; + descriptorWrites[3 * i + 0].pImageInfo = nullptr; + descriptorWrites[3 * i + 0].pTexelBufferView = nullptr; + + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = sizeof(Blade) * NUM_BLADES; + + 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; + + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.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 = &numBladesBufferInfo; + descriptorWrites[3 * i + 2].pImageInfo = nullptr; + descriptorWrites[3 * i + 2].pTexelBufferView = nullptr; + + + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } void Renderer::CreateGraphicsPipeline() { @@ -704,6 +848,7 @@ void Renderer::CreateGrassPipeline() { vkDestroyShaderModule(logicalDevice, tescShaderModule, nullptr); vkDestroyShaderModule(logicalDevice, teseShaderModule, nullptr); vkDestroyShaderModule(logicalDevice, fragShaderModule, nullptr); + } void Renderer::CreateComputePipeline() { @@ -717,7 +862,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,7 +1029,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 (size_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, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { throw std::runtime_error("Failed to record compute command buffer"); @@ -976,13 +1124,13 @@ 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 +1205,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/main.cpp b/src/main.cpp index 8bf822b..354c8e5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,7 +67,7 @@ namespace { int main() { static constexpr char* applicationName = "Vulkan Grass Rendering"; - InitializeWindow(640, 480, applicationName); + InitializeWindow(1280, 960, applicationName); unsigned int glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -161,6 +161,7 @@ int main() { delete renderer; delete swapChain; delete device; + vkDestroySurfaceKHR(instance->GetVkInstance(), surface, nullptr); delete instance; DestroyWindow(); return 0; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..2e37e1d 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,55 +2,183 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define ORIENTATION_CULLING 1 +#define VIEW_FRUSTRUM_CULLING 1 +#define DISTANCE_CULLING 1 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; -} camera; + mat4 view; + mat4 proj; +} +camera; layout(set = 1, binding = 0) uniform Time { - float deltaTime; - float totalTime; + float deltaTime; + float totalTime; }; struct Blade { - vec4 v0; - vec4 v1; - vec4 v2; - vec4 up; + vec4 v0; // v0.w -- orientation + vec4 v1; // v1.w -- height + vec4 v2; // v2.w -- width + vec4 up; // up.w -- stiffness }; -// 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 Blades { + Blade blades[]; +}; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade culledBladesBuffer[]; +}; + +// 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 = 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); } -void main() { - // Reset the number of blades to 0 - if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; - } - barrier(); // Wait till all threads reach this point - - // TODO: Apply forces on every blade and update the vertices in the buffer - - // 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 +// +// by Nikita Miropolskiy + +/* discontinuous pseudorandom uniformly distributed in [-0.5, +0.5]^3 */ +vec3 random3(vec3 c) { + float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0))); + vec3 r; + r.z = fract(512.0*j); + j *= .125; + r.x = fract(512.0*j); + j *= .125; + r.y = fract(512.0*j); + return r-0.5; } + +void main() { + // Reset the number of blades to 0 + if (gl_GlobalInvocationID.x == 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; + + float theta = blade.v0.w; + float h = blade.v1.w; + float width = blade.v2.w; + // stiffness + float s = blade.up.w; + + vec3 bitangent = vec3(-cos(theta), 0.0, sin(theta)); + vec3 f = cross(bitangent, up); + //gravity + vec3 D = vec3(0.0, -1.0, 0.0); + vec3 gE = normalize(D.xyz) * 9.8; + vec3 gF = 0.25 * length(gE) * f; + vec3 g = gE + gF; + + // recovery force + vec3 iV2 = v0 + h * up; + vec3 re = (iV2 - v2) * s; + + // wind + vec3 windDirection = random3(v0) * cos(sin(totalTime)) * 5; + + float fd = 1 - abs(dot(normalize(windDirection), normalize(v2 - v0))); + float fr = dot(v2 - v0, up) / h; + vec3 w = windDirection * fd * fr; + + // total force + vec3 tv2 = (g + re + w) * deltaTime; + + v2 += tv2; + + // state validation + // must be above the ground + v2 = v2 - up * min(dot(up, v2 - v0), 0.0); + // curvature + float lProj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + h * up * max(1 - lProj / h, 0.05 * max(lProj / h, 1)); + + //Bezier curve length correction + float L0 = length(v2 - v0); + float L1 = length(v2 - v1) + length(v1 - v0); + // degree of Bezier curve + float n = 2.0; + float L = (2 * L0 + (n - 1) * L1) / (n + 1); + float r = h / L; + vec3 v1Corr = v0 + r * (v1 - v0); + vec3 v2Corr = v1Corr + r * (v2 - v1); + + v1 = v1Corr; + v2 = v2Corr; + + // Update blade buffer + blades[gl_GlobalInvocationID.x].v1.xyz = v1; + blades[gl_GlobalInvocationID.x].v2.xyz = v2; + + // 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 + + // 1. Orientation test + // P_eye = view^-1 fourth row + vec3 eye = vec3(inverse(camera.view) * vec4(0,0,0,1)); +#if ORIENTATION_CULLING + //camera pos + + vec3 viewDir = v0 - eye; + vec3 bladeDir = bitangent; + if(abs(dot(normalize(viewDir), normalize(bitangent))) > 0.3) return; + +#endif + +#if VIEW_FRUSTRUM_CULLING + //2. View-frustrum test + vec3 m = 0.25f * v0 + 0.5f * v1 + 0.25f * v2; + mat4 viewProjection = camera.proj * camera.view; + vec4 v0_ndc = viewProjection * vec4(v0, 1.f); + vec4 m_ndc = viewProjection * vec4(m, 1.f); + vec4 v2_ndc = viewProjection * vec4(v2, 1.f); + + float t = 0.001f; + float h0 = v0_ndc.w + t; + float hm = m_ndc.w + t; + float h2 = v2_ndc.w + t; + + bool v_v0 = inBounds(v0_ndc.x, h0) && inBounds(v0_ndc.y, h0) && inBounds(v0_ndc.z, h0); + bool v_m = inBounds(m_ndc.x, hm) && inBounds(m_ndc.y, hm) && inBounds(m_ndc.z, hm); + bool v_v2 = inBounds(v2_ndc.x, h2) && inBounds(v2_ndc.y, h2) && inBounds(v2_ndc.z, h2); + + if(!v_v0 && !v_m && !v_v2) return; +#endif + +#if DISTANCE_CULLING + float dProj = length(v0 - eye - up * dot(v0 - eye, up)); + if(mod(gl_GlobalInvocationID.x, 10) >= (10 * (1 - dProj / 15))) { + return; + } +#endif + uint idx = atomicAdd(numBlades.vertexCount, 1); + culledBladesBuffer[idx] = blades[gl_GlobalInvocationID.x]; +} \ No newline at end of file diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..c0947d3 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,14 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec4 position; +layout(location = 1) in vec4 normal; +layout(location = 2) in float v; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - - outColor = vec4(1.0); + vec4 grassColor = vec4(52/255.0, 140/255.0, 49/255.0, 1.0); + outColor = grassColor * v * 1.8; } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..a1a6306 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -9,18 +9,31 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation control shader inputs and outputs +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 v0_out[]; +layout(location = 1) out vec4 v1_out[]; +layout(location = 2) out vec4 v2_out[]; +layout(location = 3) out vec4 up_out[]; 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_out[gl_InvocationID] = v0[gl_InvocationID]; + v1_out[gl_InvocationID] = v1[gl_InvocationID]; + v2_out[gl_InvocationID] = v2[gl_InvocationID]; + up_out[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.0; + gl_TessLevelInner[1] = 10.0; + gl_TessLevelOuter[0] = 10.0; + gl_TessLevelOuter[1] = 10.0; + gl_TessLevelOuter[2] = 10.0; + gl_TessLevelOuter[3] = 10.0; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..4d0214d 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -10,9 +10,49 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { // TODO: Declare tessellation evaluation shader inputs and outputs +//lists +layout(location = 0) in vec4 v_0[]; +layout(location = 1) in vec4 v_1[]; +layout(location = 2) in vec4 v_2[]; +layout(location = 3) in vec4 up[]; + + +layout(location = 0) out vec4 position; +layout(location = 1) out vec4 normal; +//v from uv +layout(location = 2) out float uv_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 + float theta = v_0[0].w; + float width = v_2[0].w; + vec3 t1 = vec3(-cos(theta), 0, sin(theta)); + + vec3 v0 = v_0[0].xyz; + vec3 v1 = v_1[0].xyz; + vec3 v2 = v_2[0].xyz; + + vec3 a = vec3(v0 + v * (v1 - v0)); + vec3 b = vec3(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 n = normalize(cross(t0, t1)); + //quad + //float t = u; + //triangle + float t = u + 0.5 * v - u * v; + //quadratic + //float t = u - u * v * v; + vec3 p = mix(c0, c1, t); + //triangle tip + position = vec4(p, 1.0f); + normal = vec4(n, 0.0f); + uv_h = v; + gl_Position = camera.proj * camera.view * position; + } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..bb5772b 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; +layout(location = 1) in vec4 v1; +layout(location = 2) in vec4 v2; +layout(location = 3) in vec4 up; + +layout(location = 0) out vec4 v0_out; +layout(location = 1) out vec4 v1_out; +layout(location = 2) out vec4 v2_out; +layout(location = 3) out vec4 up_out; 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_out = vec4((model * vec4(v0.xyz, 1.0f)).xyz, v0.w); + v1_out = vec4((model * vec4(v1.xyz, 1.0f)).xyz, v1.w); + v2_out = vec4((model * vec4(v2.xyz, 1.0f)).xyz, v2.w); + up_out = vec4((model * vec4(up.xyz, 1.0f)).xyz, up.w); + + gl_Position = model * vec4(v0.xyz, 1.0f); + }