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..eba84f0 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,44 @@ 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) +* Zhuohao Lin + * [LinkedIn](https://www.linkedin.com/in/zhuohao-lin-960b54194/) +* Tested on: Windows 10, i7-10875H @ 2.30GHz 16GB, NVIDIA Grforce RTX 2060 6GB (personal machine) -### (TODO: Your README) +# Overview +This project implements real-time grass rendering based on a [paper](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf) about `responsive real-time rendering for general 3D scenes`. +![](img/overview.gif) -*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. +# Features +### `Physics Simulation` +I apply recovery, gravity and wind forces on the grass in this project. The comparison between using forces and not using forces is shown below. +Without Forces | With Forces +:-------------------------:|:-------------------------: +![](img/noForce.PNG) | ![](img/force.gif) + +### `Optimizations by Culling` +By culling grass based on orientation, view frustrum and distance, the program gets quite obvious FPS improvement. + +Orientation Culling: +![](img/orientation.gif) + +View Frustrum Culling: +![](img/frustrum.gif) + +Distance Culling: +![](img/distance.gif) + +# Performance Analysis + +### `How performance of the renderer change with different number of grass blades` + +![](img/bladeNumber.PNG) + +The FPS drops as the number of grass blades increases. This is expected since there are more data needs to be computed with more grass blades. + +### `Improvement from Culling` +All of the data below are derived under 640 x 480 resolution with 213 grass blades. + +![](img/culling.PNG) + +As we can see, all of culling methods contribute decent amount of performance improvements, especially for the orientation culling. This is simply because I set the threshold for culling low. diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe index f68db3a..409dfe3 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/img/bladeNumber.PNG b/img/bladeNumber.PNG new file mode 100644 index 0000000..264ab7f Binary files /dev/null and b/img/bladeNumber.PNG differ diff --git a/img/culling.PNG b/img/culling.PNG new file mode 100644 index 0000000..ff33379 Binary files /dev/null and b/img/culling.PNG differ diff --git a/img/distance.gif b/img/distance.gif new file mode 100644 index 0000000..3853afd Binary files /dev/null and b/img/distance.gif differ diff --git a/img/force.gif b/img/force.gif new file mode 100644 index 0000000..09ae80a Binary files /dev/null and b/img/force.gif differ diff --git a/img/frustrum.gif b/img/frustrum.gif new file mode 100644 index 0000000..ee86087 Binary files /dev/null and b/img/frustrum.gif differ diff --git a/img/noForce.PNG b/img/noForce.PNG new file mode 100644 index 0000000..51a0ff5 Binary files /dev/null and b/img/noForce.PNG differ diff --git a/img/orientation.gif b/img/orientation.gif new file mode 100644 index 0000000..33b64e9 Binary files /dev/null and b/img/orientation.gif differ diff --git a/img/overview.gif b/img/overview.gif new file mode 100644 index 0000000..65b1d8d Binary files /dev/null and b/img/overview.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..5698ac4 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -140,7 +140,7 @@ void Renderer::CreateCameraDescriptorSetLayout() { layoutInfo.pBindings = bindings.data(); if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &cameraDescriptorSetLayout) != VK_SUCCESS) { - throw std::runtime_error("Failed to create descriptor set layout"); + throw std::runtime_error("Failed to create camera descriptor set layout"); } } @@ -168,7 +168,7 @@ void Renderer::CreateModelDescriptorSetLayout() { layoutInfo.pBindings = bindings.data(); if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &modelDescriptorSetLayout) != VK_SUCCESS) { - throw std::runtime_error("Failed to create descriptor set layout"); + throw std::runtime_error("Failed to create model descriptor set layout"); } } @@ -190,7 +190,7 @@ void Renderer::CreateTimeDescriptorSetLayout() { layoutInfo.pBindings = bindings.data(); if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &timeDescriptorSetLayout) != VK_SUCCESS) { - throw std::runtime_error("Failed to create descriptor set layout"); + throw std::runtime_error("Failed to create time descriptor set layout"); } } @@ -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 bladesBinding{}; + bladesBinding.binding = 0; + bladesBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladesBinding.descriptorCount = 1; + bladesBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladesBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding culledBladesBinding{}; + culledBladesBinding.binding = 1; + culledBladesBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledBladesBinding.descriptorCount = 1; + culledBladesBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + culledBladesBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding numBladesBinding{}; + numBladesBinding.binding = 2; + numBladesBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBladesBinding.descriptorCount = 1; + numBladesBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numBladesBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { bladesBinding, culledBladesBinding, numBladesBinding }; + + // 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 +250,9 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + + // blades, culledBlades, numBlades (compute) + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3 * static_cast(scene->GetBlades().size()) } }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,6 +357,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()); + + // Describe the descriptor 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 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 +434,73 @@ 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 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 = NUM_BLADES * sizeof(Blade); + + 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 = 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 = &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() { @@ -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 = {}; @@ -802,7 +943,7 @@ void Renderer::CreateFrameResources() { // CREATE FRAMEBUFFERS framebuffers.resize(swapChain->GetCount()); - for (size_t i = 0; i < swapChain->GetCount(); i++) { + for (uint32_t i = 0; i < swapChain->GetCount(); i++) { std::vector attachments = { imageViews[i], depthImageView @@ -825,7 +966,7 @@ void Renderer::CreateFrameResources() { } void Renderer::DestroyFrameResources() { - for (size_t i = 0; i < imageViews.size(); i++) { + for (uint32_t i = 0; i < imageViews.size(); i++) { vkDestroyImageView(logicalDevice, imageViews[i], nullptr); } @@ -833,7 +974,7 @@ void Renderer::DestroyFrameResources() { vkFreeMemory(logicalDevice, depthImageMemory, nullptr); vkDestroyImage(logicalDevice, depthImage, nullptr); - for (size_t i = 0; i < framebuffers.size(); i++) { + for (uint32_t i = 0; i < framebuffers.size(); i++) { vkDestroyFramebuffer(logicalDevice, framebuffers[i], nullptr); } } @@ -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 (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr); + vkCmdDispatch(computeCommandBuffer, static_cast(NUM_BLADES / WORKGROUP_SIZE), 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -906,7 +1051,7 @@ void Renderer::RecordCommandBuffers() { } // Start command buffer recording - for (size_t i = 0; i < commandBuffers.size(); i++) { + for (uint32_t i = 0; i < commandBuffers.size(); i++) { VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; @@ -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..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/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..f66525f 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -1,6 +1,11 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +#define APPLY_PHYSICS 1 +#define ORIENTATION_TEST 1 +#define FRUSTRUM_TEST 1 +#define DISTANCE_TEST 1 + #define WORKGROUP_SIZE 32 layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; @@ -26,31 +31,182 @@ struct Blade { // 2. Write out the culled blades // 3. Write the total number of blades remaining +layout(set = 2, binding = 0) buffer Blades { + Blade blades[]; +} blades; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade culledBlades[]; +} 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); } + + +vec2 random2(vec2 p) { + return fract(sin(vec2(dot(p, vec2(127.1f, 311.7f)), dot(p, vec2(269.5f,183.3f)))) * 43758.5453f); +} + +float surflet(vec2 p, vec2 gridPoint) { + // Compute the distance between p and the grid point along each axis, and warp it with a + // quintic function so we can smooth our cells + vec2 t2 = abs(p - gridPoint); + // vec2 t = vec2(1.f) - 6.f * pow(t2, vec2(5.f)) + 15.f * pow(t2, vec2(4.f)) - 10.f * pow(t2, vec2(3.f)); + vec2 t; + t.x = 1.0f - 6.0f*t2.x*t2.x*t2.x*t2.x*t2.x + 15.0f*t2.x*t2.x*t2.x*t2.x - 10.0f*t2.x*t2.x*t2.x; + t.y = 1.0f - 6.0f*t2.y*t2.y*t2.y*t2.y*t2.y + 15.0f*t2.y*t2.y*t2.y*t2.y - 10.0f*t2.y*t2.y*t2.y; + // Get the random vector for the grid point (assume we wrote a function random2 + // that returns a vec2 in the range [0, 1]) + vec2 gradient = random2(gridPoint) * 2.f - vec2(1.f,1.f); + // Get the vector from the grid point to P + vec2 diff = p - gridPoint; + // Get the value of our height field by dotting grid->P with our gradient + float height = dot(diff, gradient); + // Scale our height field (i.e. reduce it) by our polynomial falloff function + return height * t.x * t.y; +} + +float perlin2D(vec2 uv) { + float surfletSum = 0.f; + // Iterate over the four integer corners surrounding uv + for(int dx = 0; dx <= 1; ++dx) { + for(int dy = 0; dy <= 1; ++dy) { + surfletSum += surflet(uv, floor(uv) + vec2(dx, dy)); + } + } + return surfletSum; +} + + +vec3 windFunction(vec3 pos, float time) { + vec2 uv = pos.xz; + + vec3 windDir = vec3(perlin2D(uv * 5.f + time * 0.5f), + 0.f, + perlin2D(uv * 5.f + 100.f + time * 0.5f)); + + return windDir; +} + + +bool inFrustrum(vec3 point) { + float tolerance = 0.1f; + vec4 projectedPoint = camera.proj * camera.view * vec4(point, 1.f); + float h = projectedPoint.w + tolerance; + bool isInFrustrum = inBounds(projectedPoint.x, h) && inBounds(projectedPoint.y, h) && inBounds(projectedPoint.z, h); + return isInFrustrum; +} + + 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.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; + // TODO: Apply forces on every blade and update the vertices in the buffer +#if APPLY_PHYSICS + // recovery + vec3 initialV2 = v0 + up * height; + vec3 recoveryForce = stiffness * (initialV2 - v2); + + // gravity + vec3 right = vec3(cos(orientation), 0.0, sin(orientation)); + vec3 front = cross(up, right); + + vec3 ge = vec3(0.f, -9.81f, 0.f); + vec3 gf = 0.25 * length(ge) * front; + vec3 gravity = ge + gf; + + // wind + vec3 wind = windFunction(v0, totalTime); + float fd = 1 - abs(dot(normalize(wind), normalize(v2 - v0))); + float fr = dot(v2 - v0, up) / height; + float strength = 5.f; + vec3 windForce = wind * fd * fr * strength;; + + + vec3 totalForce = recoveryForce + gravity + windForce; + + v2 = v2 + deltaTime * totalForce; + + // State Validation + v2 = 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.f - lProj / height, 0.05f * max(lProj / height, 1.f)); + + float L0 = distance(v0, v2); + float L1 = distance(v1, v0) + distance(v1, v2); + float n = 2.f; + float L = (2.f * L0 + (n - 1.f) * L1) / (n + 1.f); + float r = height / L; + vec3 v1Corr = v0 + r * (v1 - v0); + vec3 v2Corr = v1Corr + r * (v2 - v1); +#endif + +#if !APPLY_PHYSICS + vec3 v1Corr = v1; + vec3 v2Corr = v2; +#endif + + blade.v1.xyz = v1Corr; + blade.v2.xyz = v2Corr; + blades.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 + + vec3 camPos = vec3(inverse(camera.view) * vec4(0.f, 0.f, 0.f, 1.f)); +#if ORIENTATION_TEST + // orientation test + vec3 viewDir = normalize(camPos - v0); + vec3 bladeDir = vec3(cos(orientation), 0.f, sin(orientation)); + if (abs(dot(viewDir, bladeDir)) > 0.3f) return; +#endif + +#if FRUSTRUM_TEST + // view frustum test + // center point + vec3 m = 0.25f * v0 + 0.5f * v1 + 0.25f * v2; + if ( !(inFrustrum(v0) || inFrustrum(m) || inFrustrum(v2)) ) return; +#endif + +#if DISTANCE_TEST + // distance test + float dProj = length(v0 - camPos - up * dot(v0 - camPos, up)); + float distMax = 15.f; + int distLevel = 10; + if (gl_GlobalInvocationID.x % distLevel < int(floor(distLevel * (1.f - dProj / distMax)))) return; +#endif + + culledBlades.culledBlades[atomicAdd(numBlades.vertexCount, 1)] = blade; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..5162719 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,17 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec4 pos; +layout(location = 1) in vec4 nor; +layout(location = 2) in vec2 uv; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - - outColor = vec4(1.0); + vec3 darkGreen = vec3(0.11f, 0.25f, 0.01f); + vec3 lightGreen = vec3(0.6f, 0.97f, 0.39f); + vec3 grassColor = mix(darkGreen, lightGreen, uv.y); + + outColor = vec4(grassColor, 1.f); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..5998874 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 v0Out[]; +layout(location = 1) out vec4 v1Out[]; +layout(location = 2) out vec4 v2Out[]; +layout(location = 3) out vec4 upOut[]; 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 + v0Out[gl_InvocationID] = v0[gl_InvocationID]; + v1Out[gl_InvocationID] = v1[gl_InvocationID]; + v2Out[gl_InvocationID] = v2[gl_InvocationID]; + upOut[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] = 8.0; + gl_TessLevelInner[1] = 8.0; + gl_TessLevelOuter[0] = 8.0; + gl_TessLevelOuter[1] = 8.0; + gl_TessLevelOuter[2] = 8.0; + gl_TessLevelOuter[3] = 8.0; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..893cc55 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,46 @@ 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 vec4 pos; +layout(location = 1) out vec4 nor; +layout(location = 2) out vec2 uv; 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 + + // De Casteljau¡¯s algorithm + + // bitangent + vec3 t1 = vec3(cos(v0[0].w), 0.f, sin(v0[0].w)); + + vec3 a = vec3(v0[0] + v * (v1[0] - v0[0])); + vec3 b = vec3(v1[0] + v * (v2[0] - v1[0])); + vec3 c = a + v * (b - a); + vec3 c0 = c - v2[0].w * t1; + vec3 c1 = c + v2[0].w * t1; + + // tangent + vec3 t0 = normalize(b - a); + // normal + vec3 n = normalize(cross(t0, t1)); + + // vertex position (triangle-tip) + float threshold = 0.2f; // This value is between 0 and 1 + float t = 0.5f + (u - 0.5f) * (1.f - max(v - threshold, 0.f) / (1.f - threshold)); + vec3 p = mix(c0, c1, t); + + pos = vec4(p, 1.f); + nor = vec4(n, 0.f); + uv = vec2(u, v); + + gl_Position = camera.proj * camera.view * pos; + } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..b41035c 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,6 +7,16 @@ 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; + +// Outputs are passed to tessellation shaders +layout(location = 0) out vec4 v0Out; +layout(location = 1) out vec4 v1Out; +layout(location = 2) out vec4 v2Out; +layout(location = 3) out vec4 upOut; out gl_PerVertex { vec4 gl_Position; @@ -14,4 +24,10 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + + gl_Position = model * vec4(v0.xyz, 1.f); + v0Out = vec4((model * vec4(v0.xyz, 1.f)).xyz, v0.w); + v1Out = vec4((model * vec4(v1.xyz, 1.f)).xyz, v1.w); + v2Out = vec4((model * vec4(v2.xyz, 1.f)).xyz, v2.w); + upOut = vec4((model * vec4(up.xyz, 0.f)).xyz, up.w); }