diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89942d9 --- /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 +*.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..290053a 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,71 @@ 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) +* Yu-Chia Shen + * [LinkedIn](https://www.linkedin.com/in/ycshen0831/) +* Tested on: Windows 10, i5-11400F @ 4.3GHz 16GB, GTX 3060 12GB (personal) -### (TODO: Your README) +# Overview +This project is to build a grass simulation using Vulkan. In this simulation, gravity and wind forces are applied to make it more realistic. Alos, a culling test is added to remove reduntant grass while rendering. Below video is the result of the grass simulation. -*DO NOT* leave the README to the last minute! It is a crucial part of the -project, and we will not be able to grade you without a good README. +![](./img/result.gif) + +# Forces Simulation +There are three types of forces added in the simulation: Gravity, Recovery, and Wind forces. Below are the demonstration without these forces. + +![](./img/noF.gif) + +## Gravity Froce +Gravity is a force that always point to the ground ```(y=-1)``` with the value of ```G = 9.8```. Below is the result that only contain gravity force. Also, a front gravity is applied to make the grass fall in the front facing direction. + +![](./img/onlyG.gif) + +As you can see, all grass are pressed to the ground. This is because there is only gravity force that push the grass to the ground. In order to solve this problem, a recovery force is added to make the grass balence. + +## Recovery Force +This force represents a counter-force that brings the grass blade back into equilibrium. Recovery force will make the grass to perpendicular to the ground. Below is the video shows how the recovery force works. The force increases over time. + +![](./img/Recover.gif) +Recovery force is controlled by one variable ```stiffness``` which is different for all the grass blades. + +## Wind Force +Wind force is generated arbitrarily using noise functions. The image below is based on the following function: + +```wind = noise(x, z) * cos(totalTime + x) * noise(totalTime) + noise(x, z)``` + +where ```(x, z)``` are the grass blade's coordinate. + +## Total Force +The total froce is the combination of these forces. Then, the total force is added to every grass blade. The result: + +![](./img/finalResult.gif) + +# Culling Test +A culling test is applied to the simulation to remove reduntant grass blades. Reduntant grass blades include the blades whose directions are perpendicular to the view vector, the grass that are not appear in the frame, and the blades that are too far to the camera. By removing them, there will be a great performance increase while not affect the visual effect. + +## Orientation Culling +Orientation culling removes blades that has face direction perpendicular to the view vector. Since the grass blades does not have width, they will become lines when we see them. Hence, we can remove them. + +![](./img/oriCull.gif) + +## View-frustum Culling +View-frustum culling removes the blades that are outside the camera's view. A small tolerance value is added to control how far the blades are outside the view needed to be removed. + +![](./img/vfCull.gif) + +## Distance Culling +The final test culls the blades according to their distance toward the camera. + +![](./img/disCull.gif) + +# Performance Analysis + +## Number of Blades Analysis +As expected, FPS decreases when the number of blades increases. Also, by applying culling, the simulation get better performance for FPS, and culling will not affect most of the visual result. + +![](./img/Number%20of%20Grass%20Blades%20vs%20FPS.png) + +## Culling Comparison +To understand the effect of the three culling implementations, each culling is applied to 65K blades to test the FPS performance. We can see that orientation and view-frustum culling will get the similar performance, and distance culling will get a sightly better result. + +![](./img/Each%20Type%20of%20Culling%20vs%20FPS.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..d94cbb1 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/img/Each Type of Culling vs FPS.png b/img/Each Type of Culling vs FPS.png new file mode 100644 index 0000000..628d2c7 Binary files /dev/null and b/img/Each Type of Culling vs FPS.png differ diff --git a/img/Number of Grass Blades vs FPS.png b/img/Number of Grass Blades vs FPS.png new file mode 100644 index 0000000..64feb95 Binary files /dev/null and b/img/Number of Grass Blades vs FPS.png differ diff --git a/img/Recover.gif b/img/Recover.gif new file mode 100644 index 0000000..22e0d17 Binary files /dev/null and b/img/Recover.gif differ diff --git a/img/disCull.gif b/img/disCull.gif new file mode 100644 index 0000000..4d2a9b6 Binary files /dev/null and b/img/disCull.gif differ diff --git a/img/finalResult.gif b/img/finalResult.gif new file mode 100644 index 0000000..c76920d Binary files /dev/null and b/img/finalResult.gif differ diff --git a/img/noF.gif b/img/noF.gif new file mode 100644 index 0000000..93e9e91 Binary files /dev/null and b/img/noF.gif differ diff --git a/img/onlyG.gif b/img/onlyG.gif new file mode 100644 index 0000000..182330e Binary files /dev/null and b/img/onlyG.gif differ diff --git a/img/oriCull.gif b/img/oriCull.gif new file mode 100644 index 0000000..66fc2e9 Binary files /dev/null and b/img/oriCull.gif differ diff --git a/img/result.gif b/img/result.gif new file mode 100644 index 0000000..b5b3fc3 Binary files /dev/null and b/img/result.gif differ diff --git a/img/vfCull.gif b/img/vfCull.gif new file mode 100644 index 0000000..95302d5 Binary files /dev/null and b/img/vfCull.gif differ diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..9c77ac2 100644 --- a/src/Blades.h +++ b/src/Blades.h @@ -4,13 +4,13 @@ #include #include "Model.h" -constexpr static unsigned int NUM_BLADES = 1 << 13; +constexpr static unsigned int NUM_BLADES = 1 << 16; constexpr static float MIN_HEIGHT = 1.3f; constexpr static float MAX_HEIGHT = 2.5f; constexpr static float MIN_WIDTH = 0.1f; constexpr static float MAX_WIDTH = 0.14f; -constexpr static float MIN_BEND = 7.0f; -constexpr static float MAX_BEND = 13.0f; +constexpr static float MIN_BEND = 12.0f; +constexpr static float MAX_BEND = 18.0f; struct Blade { // Position and direction diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..3581679 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -194,10 +194,44 @@ void Renderer::CreateTimeDescriptorSetLayout() { } } +// For the unifrom variables in compute.comp void Renderer::CreateComputeDescriptorSetLayout() { // TODO: Create the descriptor set layout for the compute pipeline // Remember this is like a class definition stating why types of information // will be stored at each binding + VkDescriptorSetLayoutBinding bladesLayoutBinding = {}; + bladesLayoutBinding.binding = 0; + bladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladesLayoutBinding.descriptorCount = 1; + bladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladesLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding culledBladesLayoutBinding = {}; + culledBladesLayoutBinding.binding = 1; + culledBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledBladesLayoutBinding.descriptorCount = 1; + culledBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + culledBladesLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding numBladesLayoutBinding = {}; + numBladesLayoutBinding.binding = 2; + numBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBladesLayoutBinding.descriptorCount = 1; + numBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numBladesLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { bladesLayoutBinding, + culledBladesLayoutBinding, numBladesLayoutBinding }; + + // Create the descriptor set layout + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } } void Renderer::CreateDescriptorPool() { @@ -216,6 +250,8 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + // Add Storage Buffer for compute shader + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(3 * scene->GetBlades().size()) } }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,6 +356,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 modelBufferInfo = {}; + modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + modelBufferInfo.offset = 0; + modelBufferInfo.range = sizeof(ModelBufferObject); + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = grassDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = &modelBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +432,72 @@ 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(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; + + 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 = &bladesBufferInfo; + 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; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +855,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 = {}; @@ -882,8 +1020,12 @@ 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 (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, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1118,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 +1200,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/main.cpp b/src/main.cpp index 8bf822b..527bcea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "Camera.h" #include "Scene.h" #include "Image.h" +#include Device* device; SwapChain* swapChain; @@ -67,7 +68,7 @@ namespace { int main() { static constexpr char* applicationName = "Vulkan Grass Rendering"; - InitializeWindow(640, 480, applicationName); + InitializeWindow(1600, 900, applicationName); unsigned int glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -90,7 +91,7 @@ int main() { swapChain = device->CreateSwapChain(surface, 5); - camera = new Camera(device, 640.f / 480.f); + camera = new Camera(device, 1600.f / 900.f); VkCommandPoolCreateInfo transferPoolInfo = {}; transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; @@ -116,7 +117,7 @@ int main() { grassImageMemory ); - float planeDim = 15.f; + float planeDim = 30.f; float halfWidth = planeDim * 0.5f; Model* plane = new Model(device, transferCommandPool, { @@ -143,10 +144,39 @@ int main() { glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback); glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback); + double fps = 0; + double timebase = 0; + int frame = 0; + std::vector fpsList; + while (!ShouldQuit()) { glfwPollEvents(); + + frame++; + double time = glfwGetTime(); + + if (time - timebase > 1.0) { + fps = frame / (time - timebase); + timebase = time; + frame = 0; + + fpsList.push_back(fps); + } + scene->UpdateTime(); renderer->Frame(); + + int numIgnore = 10; + if (fpsList.size() > numIgnore) { + std::vector newFpsList(fpsList.begin() + numIgnore, fpsList.end()); + + double averageFps = 0; + for (auto fps : newFpsList) + averageFps += fps; + averageFps /= newFpsList.size(); + + std::cout << "Average FPS: " << averageFps << std::endl; + } } vkDeviceWaitIdle(device->GetVkDevice()); diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..72bd45a 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -23,34 +23,168 @@ struct Blade { // TODO: Add bindings to: // 1. Store the input blades +layout(set = 2, binding = 0) buffer Blades { + Blade blades[]; +}; + // 2. Write out the culled blades +layout(set = 2, binding = 1) buffer CulledBlades { + Blade culledBlades[]; +}; + // 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 = 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 insideViewFrustum(vec4 point, mat4 vp){ + float tolerance = 1.0f; + + vec4 point_ = vp * point; + + float h = point_.w + tolerance; + + return inBounds(point_.x, h) && inBounds(point_.y, h) && inBounds(point_.z, h); +} + +float hash(float n) { return fract(sin(n) * 125.54); } + +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; +} + +vec3 hash3( vec2 p ){ + vec3 q = vec3( dot(p,vec2(127.1,311.7)), + dot(p,vec2(269.5,183.3)), + dot(p,vec2(419.2,371.9)) ); + return fract(sin(q)*43758.5453); +} + +float hash2(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); } + void main() { // Reset the number of blades to 0 if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + numBlades.vertexCount = 0; } barrier(); // Wait till all threads reach this point - // TODO: Apply forces on every blade and update the vertices in the buffer + 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 orientation = blade.v0.w; + float height = blade.v1.w; + float width = blade.v2.w; + float stiffness = blade.up.w; + + vec3 t1 = normalize(vec3(cos(orientation), 0.f, sin(orientation))); + + // TODO: Apply forces on every blade and update the vertices in the buffer + + // Gravity + vec4 D = vec4(0.f, -1.f, 0.f, 5.f); + vec3 gE = normalize(D.xyz) * D.w; + vec3 f = normalize(cross(t1, up)); + vec3 gF = 0.75f * length(gE) * f; + + vec3 gravity = gE + gF; + + // Recovery + vec3 iv2 = v0 + height * up; + vec3 recovery = (iv2 - v2) * stiffness; + + // Wind + vec3 wind = hash3(v0.xz) * cos(totalTime + int((v0.x + v0.z) / 7.f)) * 6 + hash2(v0.xz) * 2; + wind.y = 0; + + float f_d = 1 - abs(dot((normalize(wind)), normalize(v2 - v0))); + float f_r = dot(v2 - v0, up) / height; + + wind = wind * f_d * f_r; + + // Total Force + vec3 totalForce = gravity + recovery + wind; + + // Apply froce to control points + v2 = v2 + deltaTime * totalForce; + + // Ensure v2 above the local plane + v2 = v2 - up * min(dot(up, (v2 - v0)), 0); + + // Length (v2, v0) projected onto the ground + float l_proj = length(v2 - v0 - up * dot((v2 - v0), up)); + + // Ensure v0 != v1 + v1 = v0 + height * up * max(1 - (l_proj / height), 0.05 * max((l_proj / height), 1)); + + // Ensure lenth of curve < height of blade + float L0 = length(v2 - v0); + float L1 = length(v1 - v0) + length(v2 - v1); + float n = 2.f; + float L = (2.f * L0 + (n - 1) * L1) / (n + 1); + + // Correction + float r = height / L; + v1 = v0 + r * (v1 - v0); + v2 = v1 + r * (v2 - v1); + + blade.v1.xyz = v1.xyz; + blade.v2.xyz = v2.xyz; + 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 + + // Orientation culling + vec3 c = inverse(camera.view)[3].xyz; + vec3 viewDir = (v0 - c - up * dot((v0 - c), up)); + vec3 widthDir = t1; + + if(abs(dot(normalize(viewDir), widthDir)) > 0.9){ + return; + } + + // View-frustum culling + mat4 vp = camera.proj * camera.view; + vec3 m = 0.25f * v0 + 0.5f * v1 + 0.25f * v2; + + if(!insideViewFrustum(vec4(v0, 1.f), vp) || !insideViewFrustum(vec4(v2, 1.f), vp) || !insideViewFrustum(vec4(m, 1.f), vp)){ + return; + } + + // Distance test + float d_proj = length(viewDir); + + int levels = 10; + float d_max = 40.f; + bool isCulled = (gl_GlobalInvocationID.x % levels) > (floor(levels * (1.f - d_proj / d_max))); + if(isCulled){ + return; + } + + culledBlades[atomicAdd(numBlades.vertexCount, 1)] = blade; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..36a17c7 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,28 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 normal; +layout(location = 2) in float height; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color + // Lighting Parameters + vec3 green = vec3(0.25, 0.75, 0.0); + vec3 darkGreen = vec3(0.1, 0.4, 0.0); - outColor = vec4(1.0); + float t = (height - 1.3f) / 2.5f; + vec3 color = mix(green, darkGreen, t); + + vec3 lightPos = vec3(10.0, 50.0, 0.0); + + // Diffuse Lighting + vec3 lightDir = normalize(lightPos - position); + float lambert = max(dot(lightDir, normalize(normal)), 0.0); + + vec3 result = color + lambert * vec3(0.3); + + outColor = vec4(result, 1.0); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..7df9168 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -9,18 +9,35 @@ 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[]; + +in gl_PerVertex { + vec4 gl_Position; +} 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 + 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]; // 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..b159784 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,35 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation evaluation 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 vec3 position; +layout(location = 1) out vec3 normal; +layout(location = 2) out float height; void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; + height = v1[0].w; + // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + vec3 a = v0[0].xyz + v * (v1[0] - v0[0]).xyz; + vec3 b = v1[0].xyz + v * (v2[0] - v1[0]).xyz; + vec3 c = a + v * (b - a); + + vec3 t1 = vec3(cos(v0[0].w), 0.f, sin(v0[0].w)); + vec3 c0 = c - v2[0].w * t1; + vec3 c1 = c + v2[0].w * t1; + + vec3 t0 = normalize(b - a); + normal = normalize(cross(t0, t1)); + + float t = u - u * v * v; + position = ((1 - t) * c0 + t * c1); + + gl_Position = camera.proj * camera.view * vec4(position, 1.f); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..a2767f5 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,10 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + gl_Position = model * v0_i; + + v0_o = model * v0_i; + v1_o = model * v1_i; + v2_o = model * v2_i; + up_o = model * up_i; }