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..873771d 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,39 @@ 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)
+* Zirui Zang
+ * [LinkedIn](https://www.linkedin.com/in/zirui-zang/)
+* Tested on: Windows 10, AMD Ryzen 7 3700X @ 3.60GHz 32GB, RTX2070 SUPER 8GB (Personal)
-### (TODO: Your README)
+## Physics-based Grass Simulation in Vulkan
+
+
+
-*DO NOT* leave the README to the last minute! It is a crucial part of the
-project, and we will not be able to grade you without a good README.
+Vulkan is a low-overhead, cross-platform API, open standard for 3D graphics and computing.
+This project implements grass simulation using Vulkan graphics pipeline.
+The simulation is based on this [paper](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf).
+
+## Physics Features
+
+Three force simulations are implemented.Grass is rendered as a up-standing triangle with second degree Bezier curves.
+| No Force | w/ Gravity |
+| ----------- | ----------- |
+| ![](img/no_force.png) | ![](img/g_.png) |
+
+| w/ Gravity & Recovery | w/ Gravity & Recovery & Wind |
+| ----------- | ----------- |
+| ![](img/gr_.png) | ![](img/grw_.png) |
+
+## Optimization Features
+
+Three culling are implemented as optimization features.
+Orientation Cull is amitting object that are to thin to the camera position.
+View-frustum Cull is amitting object that are visible in the camera view frustum.
+Distance Cull is amitting object that are farther than a certain distance.
+
+| Orientation Cull | View-frustum Cull | Distance Cull |
+| ----------- | ----------- | ----------- |
+| ![](img/cull1.gif) | ![](img/cull2.gif) | ![](img/cull3.gif) |
diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe
index f68db3a..c7e48d5 100644
Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ
diff --git a/img/cull1.gif b/img/cull1.gif
new file mode 100644
index 0000000..efe112d
Binary files /dev/null and b/img/cull1.gif differ
diff --git a/img/cull2.gif b/img/cull2.gif
new file mode 100644
index 0000000..0f3ea78
Binary files /dev/null and b/img/cull2.gif differ
diff --git a/img/cull3.gif b/img/cull3.gif
new file mode 100644
index 0000000..eefbadf
Binary files /dev/null and b/img/cull3.gif differ
diff --git a/img/g_.png b/img/g_.png
new file mode 100644
index 0000000..dee1d81
Binary files /dev/null and b/img/g_.png differ
diff --git a/img/gr_.png b/img/gr_.png
new file mode 100644
index 0000000..45b3bb9
Binary files /dev/null and b/img/gr_.png differ
diff --git a/img/green_rect.png b/img/green_rect.png
new file mode 100644
index 0000000..8ff063f
Binary files /dev/null and b/img/green_rect.png differ
diff --git a/img/grw_.png b/img/grw_.png
new file mode 100644
index 0000000..ffb6e73
Binary files /dev/null and b/img/grw_.png differ
diff --git a/img/no_cull.gif b/img/no_cull.gif
new file mode 100644
index 0000000..85ca0ae
Binary files /dev/null and b/img/no_cull.gif differ
diff --git a/img/no_force.png b/img/no_force.png
new file mode 100644
index 0000000..e837cfa
Binary files /dev/null and b/img/no_force.png differ
diff --git a/src/Blades.cpp b/src/Blades.cpp
index 80e3d76..0142372 100644
--- a/src/Blades.cpp
+++ b/src/Blades.cpp
@@ -45,7 +45,7 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode
indirectDraw.firstInstance = 0;
BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory);
- BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory);
+ BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory);
BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory);
}
diff --git a/src/Renderer.cpp b/src/Renderer.cpp
index b445d04..5efc5a0 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -195,9 +195,38 @@ void Renderer::CreateTimeDescriptorSetLayout() {
}
void Renderer::CreateComputeDescriptorSetLayout() {
- // TODO: Create the descriptor set layout for the compute pipeline
- // Remember this is like a class definition stating why types of information
- // will be stored at each binding
+ VkDescriptorSetLayoutBinding 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() {
@@ -215,7 +244,8 @@ void Renderer::CreateDescriptorPool() {
// Time (compute)
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 },
- // TODO: Add any additional types and counts of descriptors you will need to allocate
+ // Blades (compute)
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(3 * scene->GetBlades().size()) },
};
VkDescriptorPoolCreateInfo poolInfo = {};
@@ -318,8 +348,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 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() {
@@ -358,8 +422,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 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() {
@@ -716,8 +845,7 @@ void Renderer::CreateComputePipeline() {
computeShaderStageInfo.module = computeShaderModule;
computeShaderStageInfo.pName = "main";
- // TODO: Add the compute dsecriptor set layout you create to this list
- std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout };
+ std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout };
// Create pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
@@ -883,7 +1011,11 @@ 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 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, (uint32_t)ceil((NUM_BLADES + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE), 1, 1);
+ }
// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
@@ -926,7 +1058,7 @@ void Renderer::RecordCommandBuffers() {
renderPassInfo.renderArea.extent = swapChain->GetVkExtent();
std::array clearValues = {};
- clearValues[0].color = { 0.0f, 0.0f, 0.0f, 1.0f };
+ clearValues[0].color = { 0.604f, 0.871f, 1.0f, 1.0f };
clearValues[1].depthStencil = { 1.0f, 0 };
renderPassInfo.clearValueCount = static_cast(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
@@ -975,14 +1107,11 @@ void Renderer::RecordCommandBuffers() {
for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) {
VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() };
VkDeviceSize offsets[] = { 0 };
- // TODO: Uncomment this when the buffers are populated
- // vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);
-
- // TODO: Bind the descriptor set for each grass blades model
+ vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);
+ vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &grassDescriptorSets[j], 0, nullptr);
// Draw
- // TODO: Uncomment this when the buffers are populated
- // vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect));
+ vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect));
}
// End render pass
@@ -1041,7 +1170,6 @@ void Renderer::Frame() {
Renderer::~Renderer() {
vkDeviceWaitIdle(logicalDevice);
- // TODO: destroy any resources you created
vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data());
vkFreeCommandBuffers(logicalDevice, computeCommandPool, 1, &computeCommandBuffer);
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/images/grass.jpg b/src/images/grass.jpg
index d8713bd..0a34282 100644
Binary files a/src/images/grass.jpg and b/src/images/grass.jpg differ
diff --git a/src/images/grass2.jpg b/src/images/grass2.jpg
new file mode 100644
index 0000000..d8713bd
Binary files /dev/null and b/src/images/grass2.jpg differ
diff --git a/src/main.cpp b/src/main.cpp
index 8bf822b..65931e4 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, 720, applicationName);
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
@@ -90,7 +90,7 @@ int main() {
swapChain = device->CreateSwapChain(surface, 5);
- camera = new Camera(device, 640.f / 480.f);
+ camera = new Camera(device, 1280.f / 720.f);
VkCommandPoolCreateInfo transferPoolInfo = {};
transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
diff --git a/src/shaders.rar b/src/shaders.rar
new file mode 100644
index 0000000..74f7857
Binary files /dev/null and b/src/shaders.rar differ
diff --git a/src/shaders/.vscode/settings.json b/src/shaders/.vscode/settings.json
new file mode 100644
index 0000000..83245a8
--- /dev/null
+++ b/src/shaders/.vscode/settings.json
@@ -0,0 +1,65 @@
+{
+ "files.associations": {
+ "algorithm": "cpp",
+ "array": "cpp",
+ "atomic": "cpp",
+ "bit": "cpp",
+ "bitset": "cpp",
+ "cctype": "cpp",
+ "charconv": "cpp",
+ "chrono": "cpp",
+ "clocale": "cpp",
+ "cmath": "cpp",
+ "compare": "cpp",
+ "concepts": "cpp",
+ "cstddef": "cpp",
+ "cstdint": "cpp",
+ "cstdio": "cpp",
+ "cstdlib": "cpp",
+ "cstring": "cpp",
+ "ctime": "cpp",
+ "cwchar": "cpp",
+ "exception": "cpp",
+ "format": "cpp",
+ "forward_list": "cpp",
+ "initializer_list": "cpp",
+ "iomanip": "cpp",
+ "ios": "cpp",
+ "iosfwd": "cpp",
+ "istream": "cpp",
+ "iterator": "cpp",
+ "limits": "cpp",
+ "locale": "cpp",
+ "map": "cpp",
+ "memory": "cpp",
+ "new": "cpp",
+ "optional": "cpp",
+ "ostream": "cpp",
+ "ratio": "cpp",
+ "sstream": "cpp",
+ "stdexcept": "cpp",
+ "streambuf": "cpp",
+ "string": "cpp",
+ "system_error": "cpp",
+ "tuple": "cpp",
+ "type_traits": "cpp",
+ "typeinfo": "cpp",
+ "utility": "cpp",
+ "vector": "cpp",
+ "xfacet": "cpp",
+ "xiosbase": "cpp",
+ "xlocale": "cpp",
+ "xlocbuf": "cpp",
+ "xlocinfo": "cpp",
+ "xlocmes": "cpp",
+ "xlocmon": "cpp",
+ "xlocnum": "cpp",
+ "xloctime": "cpp",
+ "xmemory": "cpp",
+ "xstddef": "cpp",
+ "xstring": "cpp",
+ "xtr1common": "cpp",
+ "xtree": "cpp",
+ "xutility": "cpp"
+ }
+}
\ No newline at end of file
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..1a1ab22 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -29,28 +29,140 @@ struct Blade {
// 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 culledBlades[];
+};
+
+layout(set = 2, binding = 2) buffer NumBlades {
+ uint vertexCount; // Write the number of blades remaining here
+ uint instanceCount; // = 1
+ uint firstVertex; // = 0
+ uint firstInstance; // = 0
+} numBlades;
+
bool inBounds(float value, float bounds) {
return (value >= -bounds) && (value <= bounds);
}
void main() {
+ // This is based on https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf
// 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
+ // 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;
+ vec3 side = normalize(vec3(cos(blade.v0.w), 0.0, sin(blade.v0.w)));
+ vec3 front = normalize(cross(up, side));
+ float height = blade.v1.w;
+ float width = blade.v2.w;
+ float stiffness = blade.up.w;
+
+ // Gravity
+ vec3 gE = vec3(0, -9.8, 0);
+ vec3 gF = 0.2 * length(gE) * front;
+ vec3 gravity = gE + gF;
+
+ // Recovery
+ vec3 I_v2 = v0 + height * up;
+ vec3 recovery = (I_v2 - v2) * stiffness;
+
+ // Wind
+ vec3 wind_function = vec3(1.0, 0.0, 0.0) * sin(totalTime * 0.7) * 2.0;
+ float f_d = 1 - abs(length(wind_function) * length(v2 - v0));
+ float f_r = dot(v2 - v0, up) / height;
+ vec3 wind = wind_function * f_d * f_r;
+
+ // Update control points
+ // v2 = v2 + gravity * deltaTime;
+ // v2 = v2 + (gravity + recovery) * deltaTime;
+ v2 = v2 + (gravity + recovery + wind) * deltaTime;
+
+ // State Validation
+ v2 = v2 - up * min(up * (v2 - v0), 0);
+ float l_proj = length(v2 - v0 - up * ((v2 - v0) * up));
+ v1 = v0 + height * up * max(1 - (l_proj / height), 0.05 * max(l_proj / height, 1));
+ float L0 = length(v2 - v0);
+ float L1 = length(v2 - v1) + length(v1 - v0);
+ const float n = 2.0;
+ float L = (2 * L0 + (n - 1.0) * L1) / (n + 1.0);
+ float r = height / L;
+ vec3 v1_corr = v0 + r * (v1 - v0);
+ vec3 v2_corr = v1_corr + r * (v2 - v1);
+ v1 = v1_corr;
+ v2 = v2_corr;
+
+ // Update the buffer
+ blade.v1.xyz = v1;
+ blade.v2.xyz = v2;
+ blades[gl_GlobalInvocationID.x] = blade;
- // TODO: Cull blades that are too far away or not in the camera frustum and write them
+ // 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 Cull
+ vec3 dir_c = normalize((inverse(camera.view) * vec4(0, 0, 0, 1)).xyz);
+ vec3 dir_b = normalize(side);
+ if (abs(dot(dir_c, dir_b)) > 0.5) {
+ return;
+ }
+
+ // View-frustum Cull
+ mat4 VP = camera.proj * camera.view;
+ const float tolerance = 0;
+ bool view_frustum_cull = false;
+
+ vec4 p_prime = VP * blade.v0;
+ float h = p_prime.w + tolerance;
+ if (!(inBounds(p_prime.x, h) && inBounds(p_prime.y, h) && inBounds(p_prime.z, h))) {
+ view_frustum_cull = true;
+ } else {
+ view_frustum_cull = false;
+ }
+
+ p_prime = VP * blade.v2;
+ h = p_prime.w + tolerance;
+ if (!(inBounds(p_prime.x, h) && inBounds(p_prime.y, h) && inBounds(p_prime.z, h))) {
+ view_frustum_cull = true;
+ } else {
+ view_frustum_cull = false;
+ }
+
+ vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2;
+ p_prime = VP * vec4(m, 1);
+ h = p_prime.w + tolerance;
+ if (!(inBounds(p_prime.x, h) && inBounds(p_prime.y, h) && inBounds(p_prime.z, h))) {
+ view_frustum_cull = true;
+ } else {
+ view_frustum_cull = false;
+ }
+ if (view_frustum_cull) {
+ return;
+ }
+
+ // Distance Cull
+ const float d_max = 25.0;
+ const int bucket_n = 10;
+ dir_c = (inverse(camera.view) * vec4(0, 0, 0, 1)).xyz;
+ float d_proj = length(v0 - dir_c.xyz - up * dot(v0 - dir_c.xyz, up));
+ if (gl_GlobalInvocationID.x % bucket_n > bucket_n * (1 - (d_proj / d_max))) {
+ return;
+ }
+
+ culledBlades[atomicAdd(numBlades.vertexCount, 1)] = blade;
+
+
}
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..53351f4 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -6,12 +6,13 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
mat4 proj;
} camera;
-// TODO: Declare fragment shader inputs
+layout (location = 0) in vec3 n;
+layout (location = 1) in vec2 uv;
-layout(location = 0) out vec4 outColor;
+layout(location = 0) out vec4 frag_color;
void main() {
- // TODO: Compute fragment color
-
- outColor = vec4(1.0);
+ vec4 color_top = vec4(0, 0.788, 0.263, 1.0);
+ vec4 color_bottom = vec4(0, 0.439, 0.294, 1.0);
+ frag_color = mix(color_bottom, color_top, uv[1]);
}
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..df30c1f 100644
--- a/src/shaders/grass.tesc
+++ b/src/shaders/grass.tesc
@@ -8,19 +8,32 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
mat4 proj;
} camera;
-// TODO: Declare tessellation control shader inputs and outputs
+// Declare tessellation control shader inputs and outputs
+layout(location = 0) in vec4 v0_in[];
+layout(location = 1) in vec4 v1_in[];
+layout(location = 2) in vec4 v2_in[];
+layout(location = 3) in vec4 up_in[];
+
+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
+ // Write any shader outputs
+ v0_out[gl_InvocationID] = v0_in[gl_InvocationID];
+ v1_out[gl_InvocationID] = v1_in[gl_InvocationID];
+ v2_out[gl_InvocationID] = v2_in[gl_InvocationID];
+ up_out[gl_InvocationID] = up_in[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] = ???
+ // Set level of tesselation
+ gl_TessLevelInner[0] = 5;
+ gl_TessLevelInner[1] = 5;
+ gl_TessLevelOuter[0] = 5;
+ gl_TessLevelOuter[1] = 5;
+ gl_TessLevelOuter[2] = 5;
+ gl_TessLevelOuter[3] = 5;
}
diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese
index 751fff6..52f82af 100644
--- a/src/shaders/grass.tese
+++ b/src/shaders/grass.tese
@@ -8,11 +8,36 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
mat4 proj;
} camera;
-// TODO: Declare tessellation evaluation shader inputs and outputs
+layout(location = 0) in vec4 v0_in[];
+layout(location = 1) in vec4 v1_in[];
+layout(location = 2) in vec4 v2_in[];
+layout(location = 3) in vec4 up_in[];
+
+layout (location = 0) out vec3 n;
+layout (location = 1) out vec2 uv;
void main() {
float u = gl_TessCoord.x;
float v = gl_TessCoord.y;
+ float width = v2_in[0].w;
+ vec3 v0 = v0_in[0].xyz;
+ vec3 v1 = v1_in[0].xyz;
+ vec3 v2 = v2_in[0].xyz;
+ vec3 up = up_in[0].xyz;
+ vec3 t1 = normalize(vec3(sin(v0_in[0].w), 0.0, cos(v0_in[0].w)));
+
+ // Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade
+ vec3 a = v0 + v * (v1 - v0);
+ vec3 b = v1 + v * (v2 - v1);
+ vec3 c = a + v * (b - a);
+ vec3 c0 = c - width * t1;
+ vec3 c1 = c + width * t1;
+ vec3 t0 = normalize(b - a);
+ vec3 n = normalize(cross(t0, t1));
- // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade
+ float t = u + 0.5 * v - u * v;
+ uv = vec2(u, v);
+ vec4 pos = vec4(mix(c0, c1, t), 1.0);
+
+ gl_Position = camera.proj * camera.view * pos;
}
diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert
index db9dfe9..6ca7004 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -6,12 +6,24 @@ layout(set = 1, binding = 0) uniform ModelBufferObject {
mat4 model;
};
-// TODO: Declare vertex shader inputs and outputs
+layout(location = 0) in vec4 v0_in;
+layout(location = 1) in vec4 v1_in;
+layout(location = 2) in vec4 v2_in;
+layout(location = 3) in vec4 up_in;
+
+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;
};
void main() {
- // TODO: Write gl_Position and any other shader outputs
+ gl_Position = model * vec4(v0_in.xyz, 1.0);
+ v0_out = model * v0_in;
+ v1_out = model * v1_in;
+ v2_out = model * v2_in;
+ up_out = up_in;
}