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..61e9fd4 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,84 @@ 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)
+* RHUTA JOSHI
+ * [LinkedIn](https://www.linkedin.com/in/rcj9719/)
+ * [Website](https://sites.google.com/view/rhuta-joshi)
-### (TODO: Your README)
+* Tested on: Windows 11 Home - 21H2, AMD Ryzen 5 5600H CPU @ 3.30 GHz, NVIDIA RTX 3060 6144 MB
+* GPU Compatibility: 7.5
-*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.
+## Introduction
+
+In this project, I use Vulkan to implement a grass simulator and renderer. I used compute shaders to perform physics calculations on Bezier curves that represent individual grass blades in this application. Since rendering every grass blade on every frame will is fairly inefficient, I have also used compute shaders to cull grass blades that don't contribute to a given frame. The remaining blades are passed to a graphics pipeline. The vertex shader transforms Bezier control points, tessellation shaders dynamically create the grass geometry from the Bezier curves, and the fragment shader shades the grass blades.
+This project is an implementation of the paper, "Responsive Real-Time Grass Rendering for General 3D Scenes" by Klemens Jahrmann and Michael Wimmer using Vulkan API.
+
+|220 blades under wind|212 in close-up|
+|---|---|
+|||
+
+
+## Vulkan pipelines
+
+The basic Vulkan pipeline as per Khronos documentation is as follows:
+
+
+
+However, each of these shaders can be used to our advantage to harness the processing power of GPU. For this simulation, instead of storing many vertices forming each blade of grass, we use only 3 vertices and one direction vector to represent each blade. We then use tessellation shaders to generate blade geometry and compute shaders to influence blades by natural forces.
+The following diagram shows how the Vulkan pipeline has been used in this project to simulate grass:
+
+
+
+### Grass Tessellation
+
+|Remarks|Result|
+|---|---|
+|Initially we start off with values of v0, v1, v2 as shown here | |
+|Tessellation control shader sets outer and inner tessellation level to 10 for each side||
+|Dynamic tessellation - As the distance from the camera increases, we reduce the number of tessellated points.||
+|Resultant tessellated grass, number of blades = 100||
+|Resultant tessellated grass, number of blades = 1<<13||
+
+
+### Compute pipeline
+
+In this project, we simulate forces on grass blades while they are still Bezier curves. This is done in a compute shader using the compute pipeline.
+
+1. **Binding Resources** - In order to update the state of grass blades on every frame, we create a storage buffer to maintain the grass data. We also pass information about how much time has passed in the simulation and the time since the last frame.
+
+2. **Applying natural forces** - We calculate the resultant force on each blade of grass when affected by gravity and wind. Considering the stiffness of each grass, a recovery force is also added which ensures that the grass does not fall on ground. A wave-based noise function determines the direction of the wind.
+
+3. **Culling Tests** - To optimize performance, we perform culling operations on our blades based on orientation, distance from camera and camera's field of view. The following table explains each effect. Refer the base paper for thresholds and calculations.
+
+|Culling type|Result|
+|---|---|
+|**Orientation Culling** - If the front face direction of the blade is perpendicular to view vector, Our grass blade won't have visible width. So we remove these blades. *Number of blades - 10, orientation threshold 0.95*||
+|**Distance Culling** - If the blades are too far away from the camera, we may get artifacts if the visible width is negligible (smaller than size of pixel). So we remove these blades. *Number of blades 1 << 13, max distance 20, number of buckets 5* ||
+|**View-frustum culling** - If our blades are not within the camera view, we remove them. For the result shown here, we have added some offset(threshold) to our frustum to see the effect more clearly. *Number of blades 1<<13, offset 3*||
+
+
+## Performance Analysis
+
+For performance improvement we have implemented 3 culling techniques described above. The following chart shows comparison between different culling techniques and their effect on the framerate for a certain number of blades.
+
+
+
+We see that the most effective technique is distance culling technique as it removes a large number of blades. Frustum culling may appear to be least effective but it's effect can be observed better as the camera position changes. The above observations were made with most of the grass land within the camera view as shown in the figure below:
+
+
+
+We can also compare the performance with increasing number of blades, with and without culling as shown below. We observe that the framerate initially starts off consistent for smaller number of grass blades, but starts decreasing suddenly after a certain threshold. We can also see that overall framerate is higher when culling is on.
+
+
+
+The above graph also shows the effect of varying tesselation level on framerate. The grass blades are tessellated to varying levels of detail as a function of how far the grass blade is from the camera.
+As the distance from the camera increases, we reduce the number of tessellated points. This clearly increases the framerate for any number of blades being generated.
+
+## References
+
+- "Responsive Real-Time Grass Rendering for General 3D Scenes" by Klemens Jahrmann and Michael Wimmer
+- [Khronos Vulkan documentation](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/)
+- [Vulkan Tutorial](https://vulkan-tutorial.com/)
+- [Vulkan Guide](https://vkguide.dev/)
+- [Graphics Pipeline](https://www.khronos.org/opengl/wiki)
+- [Vulkan game engine tutorial playlist](https://www.youtube.com/playlist?list=PL8327DO66nu9qYVKLDmdLW_84-yE4auCR)
diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe
index f68db3a..3152cab 100644
Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ
diff --git a/img/cullingOff.gif b/img/cullingOff.gif
new file mode 100644
index 0000000..9a7172e
Binary files /dev/null and b/img/cullingOff.gif differ
diff --git a/img/cullingPerformance.png b/img/cullingPerformance.png
new file mode 100644
index 0000000..90aea68
Binary files /dev/null and b/img/cullingPerformance.png differ
diff --git a/img/distanceCulling.gif b/img/distanceCulling.gif
new file mode 100644
index 0000000..c67e8fb
Binary files /dev/null and b/img/distanceCulling.gif differ
diff --git a/img/distanceTessellation.png b/img/distanceTessellation.png
new file mode 100644
index 0000000..e76ec4a
Binary files /dev/null and b/img/distanceTessellation.png differ
diff --git a/img/frustumCulling.gif b/img/frustumCulling.gif
new file mode 100644
index 0000000..f0dbb11
Binary files /dev/null and b/img/frustumCulling.gif differ
diff --git a/img/grassBlade.png b/img/grassBlade.png
new file mode 100644
index 0000000..1892962
Binary files /dev/null and b/img/grassBlade.png differ
diff --git a/img/grassBlade2.png b/img/grassBlade2.png
new file mode 100644
index 0000000..16291ba
Binary files /dev/null and b/img/grassBlade2.png differ
diff --git a/img/grassBlades.png b/img/grassBlades.png
new file mode 100644
index 0000000..b0b3c5f
Binary files /dev/null and b/img/grassBlades.png differ
diff --git a/img/grassBladesPerformance.png b/img/grassBladesPerformance.png
new file mode 100644
index 0000000..9bc24db
Binary files /dev/null and b/img/grassBladesPerformance.png differ
diff --git a/img/grassBladesPerformance2.png b/img/grassBladesPerformance2.png
new file mode 100644
index 0000000..f1e6665
Binary files /dev/null and b/img/grassBladesPerformance2.png differ
diff --git a/img/grassResult.gif b/img/grassResult.gif
new file mode 100644
index 0000000..f408355
Binary files /dev/null and b/img/grassResult.gif differ
diff --git a/img/orientationCulling.gif b/img/orientationCulling.gif
new file mode 100644
index 0000000..c2a7c77
Binary files /dev/null and b/img/orientationCulling.gif differ
diff --git a/img/quadLevels.png b/img/quadLevels.png
new file mode 100644
index 0000000..270f373
Binary files /dev/null and b/img/quadLevels.png differ
diff --git a/img/tessellation.gif b/img/tessellation.gif
new file mode 100644
index 0000000..3b25983
Binary files /dev/null and b/img/tessellation.gif differ
diff --git a/img/tessellation2.gif b/img/tessellation2.gif
new file mode 100644
index 0000000..562d16c
Binary files /dev/null and b/img/tessellation2.gif differ
diff --git a/img/vulkanGrassPipeline.jpeg b/img/vulkanGrassPipeline.jpeg
new file mode 100644
index 0000000..5048ff6
Binary files /dev/null and b/img/vulkanGrassPipeline.jpeg differ
diff --git a/img/vulkanGrassPipeline.png b/img/vulkanGrassPipeline.png
new file mode 100644
index 0000000..78f4604
Binary files /dev/null and b/img/vulkanGrassPipeline.png differ
diff --git a/img/vulkanPipeline.png b/img/vulkanPipeline.png
new file mode 100644
index 0000000..f611370
Binary files /dev/null and b/img/vulkanPipeline.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/CMakeLists.txt b/src/CMakeLists.txt
index add88d7..05c6696 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -44,7 +44,6 @@ foreach(SHADER_SOURCE ${SHADER_SOURCES})
add_dependencies(vulkan_grass_rendering ${fname}.spv)
endif(WIN32)
- # TODO: Build shaders on not windows
endforeach()
target_link_libraries(vulkan_grass_rendering ${ASSIMP_LIBRARIES} Vulkan::Vulkan glfw)
diff --git a/src/Instance.cpp b/src/Instance.cpp
index 7f6b01c..039c1aa 100644
--- a/src/Instance.cpp
+++ b/src/Instance.cpp
@@ -20,6 +20,7 @@ namespace {
if (ENABLE_VALIDATION) {
extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
+ //extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
return extensions;
diff --git a/src/Renderer.cpp b/src/Renderer.cpp
index b445d04..6d07852 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -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
+
+ // Describe the binding of the descriptor set layout
+ VkDescriptorSetLayoutBinding bladesLayoutBinding = {};
+ bladesLayoutBinding.binding = 0;
+ bladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ bladesLayoutBinding.descriptorCount = 1;
+ bladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ bladesLayoutBinding.pImmutableSamplers = nullptr;
+
+ VkDescriptorSetLayoutBinding 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() {
@@ -207,7 +241,7 @@ void Renderer::CreateDescriptorPool() {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1},
// Models + Blades
- { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) },
+ { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) }, // ??? Why models + blades for image sampler and uniform buffer of modeldescriptorset
// Models + Blades
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) },
@@ -216,6 +250,17 @@ void Renderer::CreateDescriptorPool() {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 },
// TODO: Add any additional types and counts of descriptors you will need to allocate
+
+ // Compute
+ //{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(scene->GetBlades().size() * 3) }, // GetBlades.size() will return number of blades, and each compute descriptor contains 3 storage buffers
+
+ // ??? Can we have VK_DESCRIPTOR_TYPE_STORAGE_BUFFER 3 times each with GetBlades().size instead of above implementation?
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(scene->GetBlades().size()) }, // for Blades buffer
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(scene->GetBlades().size()) }, // for culled Blades buffer
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(scene->GetBlades().size()) }, // for num blades buffer
+
+ // ??? Do we also need to add grass descriptors or is it included in the Models + blades
+
};
VkDescriptorPoolCreateInfo poolInfo = {};
@@ -320,6 +365,56 @@ 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);
+
+ // Bind image and sampler resources to the descriptor
+ //VkDescriptorImageInfo imageInfo = {};
+ //imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ //imageInfo.imageView = scene->GetModels()[i]->GetTextureView();
+ //imageInfo.sampler = scene->GetModels()[i]->GetTextureSampler();
+
+ 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;
+
+ //descriptorWrites[2 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ //descriptorWrites[2 * i + 1].dstSet = grassDescriptorSets[i];
+ //descriptorWrites[2 * i + 1].dstBinding = 1;
+ //descriptorWrites[2 * i + 1].dstArrayElement = 0;
+ //descriptorWrites[2 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ //descriptorWrites[2 * i + 1].descriptorCount = 1;
+ //descriptorWrites[2 * i + 1].pImageInfo = &imageInfo;
+ }
+
+ // Update descriptor sets
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
void Renderer::CreateTimeDescriptorSet() {
@@ -360,6 +455,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()); // ??? size of compute descriptor set is not per buffer?
+
+ // 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; //sizeof(ModelBufferObject); // ??? size
+
+ VkDescriptorBufferInfo culledBladesBufferInfo = {};
+ culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer();
+ culledBladesBufferInfo.offset = 0;
+ culledBladesBufferInfo.range = sizeof(Blade) * NUM_BLADES; //sizeof(ModelBufferObject); // ??? size
+
+ VkDescriptorBufferInfo numBladesBufferInfo = {};
+ numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer();
+ numBladesBufferInfo.offset = 0;
+ numBladesBufferInfo.range = sizeof(BladeDrawIndirect); ; // sizeof(Blade)* NUM_BLADES; //sizeof(ModelBufferObject); // ??? size
+
+ 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() {
@@ -572,7 +734,7 @@ void Renderer::CreateGrassPipeline() {
// Input Assembly
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
- inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
+ inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; // ??? Where do we define patch size - whether we want the patch as a quad of 4 vertices or tri of 3 etc
inputAssembly.primitiveRestartEnable = VK_FALSE;
// Viewports and Scissors (rectangles that define in which regions pixels are stored)
@@ -717,7 +879,7 @@ void Renderer::CreateComputePipeline() {
computeShaderStageInfo.pName = "main";
// TODO: Add the compute dsecriptor set layout you create to this list
- std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout };
+ std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout };
// Create pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
@@ -884,6 +1046,17 @@ void Renderer::RecordComputeCommandBuffer() {
vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 1, 1, &timeDescriptorSet, 0, nullptr);
// TODO: For each group of blades bind its descriptor set and dispatch
+ //for (int i = 0; i < computeDescriptorSets.size(); i++) {
+ // vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr); // ??? loop over all computeDescriptorSets? How many computeDescriptorSets are created?
+ //}
+
+ for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) {
+ // Bind the descriptor set for each model
+ vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[j], 0, nullptr);
+
+ // Dispatch
+ vkCmdDispatch(computeCommandBuffer, NUM_BLADES / 32, 1, 1);
+ }
// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
@@ -948,7 +1121,7 @@ void Renderer::RecordCommandBuffers() {
// Bind the camera descriptor set. This is set 0 in all pipelines so it will be inherited
vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 0, 1, &cameraDescriptorSet, 0, nullptr);
- vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); //VK_SUBPASS_CONTENTS_INLINE - Signalling if you want to use primary(inline) command buffer or secondary command buffer
// Bind the graphics pipeline
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
@@ -976,13 +1149,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 +1231,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..f64e506 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;
@@ -78,5 +81,5 @@ class Renderer {
std::vector framebuffers;
std::vector commandBuffers;
- VkCommandBuffer computeCommandBuffer;
+ VkCommandBuffer computeCommandBuffer; // ?? why single computeCommandBuffer but vector of commandBuffers
};
diff --git a/src/main.cpp b/src/main.cpp
index 8bf822b..96178a3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,4 +1,5 @@
#include
+#include
#include "Instance.h"
#include "Window.h"
#include "Renderer.h"
@@ -79,6 +80,7 @@ int main() {
throw std::runtime_error("Failed to create window surface");
}
+ // create physical device
instance->PickPhysicalDevice({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }, QueueFlagBit::GraphicsBit | QueueFlagBit::TransferBit | QueueFlagBit::ComputeBit | QueueFlagBit::PresentBit, surface);
VkPhysicalDeviceFeatures deviceFeatures = {};
@@ -86,12 +88,14 @@ int main() {
deviceFeatures.fillModeNonSolid = VK_TRUE;
deviceFeatures.samplerAnisotropy = VK_TRUE;
+ // create logical device
device = instance->CreateDevice(QueueFlagBit::GraphicsBit | QueueFlagBit::TransferBit | QueueFlagBit::ComputeBit | QueueFlagBit::PresentBit, deviceFeatures);
- swapChain = device->CreateSwapChain(surface, 5);
+ swapChain = device->CreateSwapChain(surface, 5); // ??? which 5 buffers
camera = new Camera(device, 640.f / 480.f);
+ // create command pool
VkCommandPoolCreateInfo transferPoolInfo = {};
transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
transferPoolInfo.queueFamilyIndex = device->GetInstance()->GetQueueFamilyIndices()[QueueFlags::Transfer];
@@ -139,20 +143,43 @@ int main() {
renderer = new Renderer(device, swapChain, scene, camera);
- glfwSetWindowSizeCallback(GetGLFWWindow(), resizeCallback);
- glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
- glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback);
+ GLFWwindow* window = GetGLFWWindow();
+ glfwSetWindowSizeCallback(window, resizeCallback);
+ glfwSetMouseButtonCallback(window, mouseDownCallback);
+ glfwSetCursorPosCallback(window, mouseMoveCallback);
+
+ double fps = 0;
+ double timebase = 0;
+ int frame = 0;
while (!ShouldQuit()) {
glfwPollEvents();
+
+ frame++;
+ double time = glfwGetTime();
+
+ if (time - timebase > 1.0) {
+ fps = frame / (time - timebase);
+ timebase = time;
+ frame = 0;
+ }
+
scene->UpdateTime();
renderer->Frame();
+
+ std::ostringstream ss;
+ ss << "[";
+ ss.precision(1);
+ ss << std::fixed << fps;
+ ss << " fps] " << applicationName;
+ glfwSetWindowTitle(window, ss.str().c_str());
}
vkDeviceWaitIdle(device->GetVkDevice());
vkDestroyImage(device->GetVkDevice(), grassImage, nullptr);
vkFreeMemory(device->GetVkDevice(), grassImageMemory, nullptr);
+ vkDestroySurfaceKHR(instance->GetVkInstance(), surface, nullptr);
delete scene;
delete plane;
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..bc6a71e 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -1,7 +1,23 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
+// #extension GL_EXT_debug_printf : enable
#define WORKGROUP_SIZE 32
+#define GRAVITY_CONSTANT 4.0
+#define WIND_MAGNITUDE 3.5
+#define PI 3.14
+
+
+#define CULLING true
+#define ORIENTATION_CULLING true
+#define FRUSTUM_CULLING true
+#define DIST_CULLING true
+#define DIST_CULLING_MAX 20.0
+#define DIST_CULLING_N 5
+#define FRUSTUM_CULLING_THRESHOLD 3
+#define ORIENTATION_CULLING_THRESHOLD 0.95
+
+
layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0) uniform CameraBufferObject {
@@ -26,31 +42,185 @@ 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[];
+};
+
+layout(set = 2, binding = 1) buffer CulledBlades {
+ Blade culledBlades[];
+};
+
// The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call
// This is sort of an advanced feature so we've showed you what this buffer should look like
//
-// layout(set = ???, binding = ???) buffer NumBlades {
-// uint vertexCount; // Write the number of blades remaining here
-// uint instanceCount; // = 1
-// uint firstVertex; // = 0
-// uint firstInstance; // = 0
-// } numBlades;
+layout(set = 2, binding = 2) buffer NumBlades {
+ uint vertexCount; // Write the number of blades remaining here
+ uint instanceCount; // = 1
+ uint firstVertex; // = 0
+ uint firstInstance; // = 0
+} numBlades;
bool inBounds(float value, float bounds) {
return (value >= -bounds) && (value <= bounds);
}
+
+bool inFrustum(vec3 p) {
+ vec4 pp = camera.proj * camera.view * vec4(p, 1.0); // project the point to screen space NDC
+ float h = pp.w - FRUSTUM_CULLING_THRESHOLD; // add some tolerance and obtain the homogeneous coordinates
+ // debugPrintfEXT("***** pp.w = %f *****", pp.w );
+ return inBounds(pp.x, h) && inBounds(pp.y, h);
+}
+
+
+float noise2D( vec2 p ) {
+ return fract(sin(dot(p, vec2(127.1, 311.7))) *
+ 43758.5453);
+}
+
+float cosineInterpolate(float a, float b, float t)
+{
+ float cos_t = (1.f - cos(t * PI)) * 0.5f;
+ return mix(a, b, cos_t);
+}
+
+float interpNoise2D(float x, float y) {
+ int intX = int(floor(x));
+ float fractX = fract(x);
+ int intY = int(floor(y));
+ float fractY = fract(y);
+
+ float v1 = noise2D(vec2(intX, intY));
+ float v2 = noise2D(vec2(intX + 1, intY));
+ float v3 = noise2D(vec2(intX, intY + 1));
+ float v4 = noise2D(vec2(intX + 1, intY + 1));
+
+ float i1 = cosineInterpolate(v1, v2, fractX);
+ float i2 = cosineInterpolate(v3, v4, fractX);
+ return cosineInterpolate(i1, i2, fractY);
+}
+
+
+vec3 windNoise(vec3 v0){
+ float n = interpNoise2D(v0.x, v0.z);
+ return vec3(sin(totalTime) + n, 0, sin(totalTime) + n);
+}
+
void main() {
+
+ uint index = gl_GlobalInvocationID.x;
+
// 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[index];
+ vec3 up = blade.up.xyz;
+ vec3 v0 = blade.v0.xyz;
+ vec3 v1 = blade.v1.xyz;
+ vec3 v2 = blade.v2.xyz;
+ float h = blade.v1.w;
+ float stiffness = blade.up.w;
+ float orientation = blade.v0.w;
+
+ /*
+ Calculating gravitational force
+ */
+
+ vec4 D = vec4(0.0f, -1.0f, 0.0f, GRAVITY_CONSTANT);
+ vec3 gravity_environmental = normalize(D.xyz) * D.w;
+
+ vec3 f; // f represents the front direction that is perpendicular to the width of the blade
+
+ vec3 t0 = normalize(v2 - v0); // tangent
+ vec3 t1 = vec3(-1.f, 0.f, 0.f); // bitangent initialized, to be rotated by blade orientation about up vector
+ t1 = cos(orientation) * t1 + sin(orientation) * cross(up, t1) + (1.0 - cos(orientation)) * dot(up, t1) * up; // ??? check the rotation formula again
+ f = normalize(cross(t0, t1));
+
+ vec3 gravity_front = 0.25 * length(gravity_environmental) * f;
+
+ vec3 gravity_force = gravity_environmental + gravity_front;
+
+
+ /*
+ Calculating recovery force
+ */
+
+ vec3 Iv2 = v0 + h * up; // initial v2 position
+ vec3 recovery_force = (Iv2 - v2) * stiffness; // up.w hold stiffness coefficient
+
+
+ /*
+ Calculating wind force
+ */
+
+ vec3 windVector = WIND_MAGNITUDE * abs(sin(totalTime + v0.x)) * windNoise(v0);
+ float fd = 1.f - length(dot(normalize(windVector), normalize(v2-v0)));
+ float fr = dot((v2 - v0), up) / h;
+ float alignmentValue = fd * fr;
+ vec3 wind_force = windVector * alignmentValue;
+
+
+ /*
+ Applying natural forces to grass blade and validating state of control points v1, v2
+ */
+
+ v2 += (recovery_force + gravity_force + wind_force) * deltaTime;
+
+ // v2 must not be pushed beneath the ground
+ v2 = v2 - up * min(dot(up, (v2 - v0)), 0);
+
+ // v1 has to be set according to v2, v1 must be above v0
+ float lproj = length(v2 - v0 - up * dot((v2 - v0), up)); // lproj is projected length, if this is 0 v2 rests in idle position, else v2 is away and v1 gets lower
+ v1 = v0 + h * up * max( 1 - lproj / h , 0.05 * max( lproj / h, 1));
+
+ // length of curve must be equal to height of blade of grass
+ float L0 = distance(v0, v2);
+ float L1 = distance(v0, v1) + distance(v1, v2);
+ float L = 0.25 * ((2 * L0) + (2 * L1));
+ float ratio = h/L; // we will correct and readjust v1 and v2 acc to this ratio
+ v1 = v0 + ratio * (v1 - v0);
+ v2 = v1 + ratio * (v2 - v1);
+
+
+ blades[index].v1.xyz = v1.xyz;
+ blades[index].v2.xyz = v2.xyz;
+
+
+
// 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
+
+
+ if (CULLING) {
+
+ vec3 c = inverse(camera.view)[3].xyz; // Getting camera position in world space
+ // Viewing direction is projected on the plane so that it's coplanar with the blade direction.
+ vec3 viewVec = v0 - c - up * dot(v0 - c, up);
+
+ // Orientation Culling
+ if (ORIENTATION_CULLING && ORIENTATION_CULLING_THRESHOLD < abs(dot(normalize(viewVec), t1)))
+ return;
+
+ // View-Frustum Culling
+ vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2;
+ if (FRUSTUM_CULLING && !inFrustum(v0) && !inFrustum(v2) && !inFrustum(m))
+ return;
+
+ // Distance Culling
+ float dproj = length(viewVec);
+ int n = DIST_CULLING_N;
+ if (DIST_CULLING && index % n < int(floor( n * (1.0 - dproj/DIST_CULLING_MAX))))
+ return;
+ }
+
+ // AtomicAdd returns the value of vertexCount right before the addition which guarantees that it would be the index for the culledBlades array
+ uint idx = atomicAdd(numBlades.vertexCount, 1);
+ culledBlades[idx] = blades[index];
}
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..5a4d072 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -7,11 +7,23 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} camera;
// TODO: Declare fragment shader inputs
+layout(location = 0) in vec3 pos;
+layout(location = 1) in vec3 nor;
layout(location = 0) out vec4 outColor;
void main() {
// TODO: Compute fragment color
+
+ // Lighting Parameters
+ vec3 albedo = vec3(0.1, 0.5, 0.0);
+ vec3 lightPos = vec3(0.0, 100.0, 0.0);
- outColor = vec4(1.0);
+ // Diffuse Lighting
+ vec3 l = normalize(lightPos - pos);
+ float lambert = max(dot(l, normalize(nor)), 0.0);
+
+ vec3 result = albedo + lambert * vec3(0.3);
+
+ outColor = vec4(result, 1.0);
}
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..143642d 100644
--- a/src/shaders/grass.tesc
+++ b/src/shaders/grass.tesc
@@ -1,26 +1,79 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
-layout(vertices = 1) out;
+#define VARYING_TESSELLATION true
+#define TESSLEVELMIN 3
+#define TESSLEVELDEFAULT 3
+#define DIST_MAX 15
+
+layout(vertices = 1) out; // We want tessellation points to be generated for each blade just once, hence we have set layout vertices to 1, which denotes number of blades in output patch
layout(set = 0, binding = 0) uniform CameraBufferObject {
mat4 view;
mat4 proj;
} camera;
+/*
+The Tessellation control shader DOES NOT determine the positions of the tessellated vertices generated.
+If the tessellation patch type is quads for example, imagine that TCS will generate a number of tessellated points equal to what a quad would have, once per blade in this case, since layout specifies output patch size as 1 (number of blades per patch generated in the output - NOT number of vertices per patch).
+Note that these points are not "positioned" anywhere specifically along a quad or so, just generated. A tessellator in the next step would appropriately group it together.
+These points are saved in gl_TessCoords as a 2D array (imagine iterating over points in a tessellated quad).
+The Evaluation shader then reads these points and appropriately positions them to tessellate each blade along a grass blade curve.
+*/
+
+
+/*
+Notice that the primitive generation is not affected by the user-defined outputs of the TCS (or vertex shader if no TCS is active), the TCS's output patch size, or any per-patch TCS outputs besides the tessellation levels. The primitive generation part of the tessellation stage is completely blind to the actual vertex coordinates and other patch data.
+The purpose of the primitive generation system is to determine how many vertices to generate, in which order to generate them, and what kind of primitives to build out of them. The actual per-vertex data for these vertices, such as position, color, etc., is to be generated by the TES, based on information provided by the primitive generator.
+Because of this dichotomy, the primitive generator operates on what could be considered an "abstract patch". It doesn't look at the patch output from the TCS; it thinks only in terms of tessellating an abstract quad, triangle, or "isoline" block.
+Ref: https://www.khronos.org/opengl/wiki/Tessellation#Tessellation_primitive_generation
+*/
+
+
// TODO: Declare tessellation control shader inputs and outputs
+layout(location = 0) in vec4 tcs_v1[];
+layout(location = 1) in vec4 tcs_v2[];
+layout(location = 2) in vec4 tcs_up[];
+
+layout(location = 0) out vec4 tes_v1[];
+layout(location = 1) out vec4 tes_v2[];
+layout(location = 2) out vec4 tes_up[];
+
+in gl_PerVertex
+{
+ vec4 gl_Position;
+} gl_in[gl_MaxPatchVertices];
+
void main() {
// Don't move the origin location of the patch
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+ tes_v1[gl_InvocationID] = tcs_v1[gl_InvocationID];
+ tes_v2[gl_InvocationID] = tcs_v2[gl_InvocationID];
+ tes_up[gl_InvocationID] = tcs_up[gl_InvocationID];
// TODO: Write any shader outputs
+ vec3 c = inverse(camera.view)[3].xyz;
+ vec3 v0 = gl_in[gl_InvocationID].gl_Position.xyz;
+ vec3 up = tes_up[gl_InvocationID].xyz;
+ vec3 viewVec = v0 - c - up * dot(v0 - c, up);
+ float dproj = length(viewVec);
+
+ int n = TESSLEVELDEFAULT;
+
+ if(VARYING_TESSELLATION) {
+ n = TESSLEVELMIN;
+ if(dproj < DIST_MAX) {
+ n = DIST_MAX - int(floor(dproj / 2.0)) + 4;
+ }
+ }
+
// 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] = n;
+ gl_TessLevelInner[1] = n;
+ gl_TessLevelOuter[0] = n;
+ gl_TessLevelOuter[1] = n;
+ gl_TessLevelOuter[2] = n;
+ gl_TessLevelOuter[3] = n;
}
diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese
index 751fff6..4a09fc3 100644
--- a/src/shaders/grass.tese
+++ b/src/shaders/grass.tese
@@ -10,9 +10,66 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
// TODO: Declare tessellation evaluation shader inputs and outputs
+layout(location = 0) in vec4 tes_v1[];
+layout(location = 1) in vec4 tes_v2[];
+layout(location = 2) in vec4 tes_up[];
+
+// The evaluation shader will set frag position for each tessellated vertex point
+// The rasterizer will take care of interpolating between these values to get position per fragment, as input to fragment shader
+layout(location = 0) out vec3 fs_pos;
+layout(location = 1) out vec3 fs_nor;
+
+
+vec3 lerp(vec3 v1, vec3 v2, float u){
+ return (1 - u) * v1 + u * v2;
+}
+
void main() {
+
+ /*
+ We know that TCS does not give positions to points it generates.
+ gl_TessCoord.x and gl_TessCoord.y will give you a fraction of how far away a point is with respect to another point in the imaginary quad patch.
+ It will allow us to use these values to position each tessellated point wrt each other along the cure using lerp.
+ */
+
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
+
+ /*
+ Since the layout in control shader generated only one blade per patch, see how the number of elements
+ in each array input is just one and can be accessed directly by accessing index 0 of input arrays
+ */
+
+ vec3 v0 = gl_in[0].gl_Position.xyz;
+ float orientation = gl_in[0].gl_Position.w;
+
+ vec3 v1 = tes_v1[0].xyz;
+ vec3 v2 = tes_v2[0].xyz;
+ vec3 up = tes_up[0].xyz;
+ float width = tes_v2[0].w; //width
+
+ // De Casteljau's Algorithm
+ vec3 a = lerp(v0, v1, v);
+ vec3 b = lerp(v1, v2, v);
+ vec3 c = lerp(a, b, v);
+
+ // Calculate bitangent vector along the width of the blade
+ vec3 t1 = vec3(-cos(orientation), 0, sin(orientation));
+ normalize(t1);
+
+ vec3 t0 = normalize(b - a);
+
+ fs_nor = normalize(cross(t0, t1));
+
+ vec3 c0 = c - width * t1;
+ vec3 c1 = c + width * t1;
+
+ float t = u + 0.5*v - u*v;
+ vec3 pos = lerp(c0, c1, t);
+
+ fs_pos = pos; // vertex position in world space
+
+ gl_Position = camera.proj * camera.view * vec4(pos, 1.0); // transform to clip space
}
diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert
index db9dfe9..490a852 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -7,11 +7,43 @@ layout(set = 1, binding = 0) uniform ModelBufferObject {
};
// TODO: Declare vertex shader inputs and outputs
+// Reference tutorial: https://vulkan-tutorial.com/Vertex_buffers/Vertex_input_description
+
+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 tcs_v1;
+layout(location = 1) out vec4 tcs_v2;
+layout(location = 2) out vec4 tcs_up;
+
out gl_PerVertex {
vec4 gl_Position;
};
+vec4 multiply(mat4 m, vec4 v) {
+ vec4 ans = m * vec4(v.xyz, 1.0);
+ ans.w = v.w; // copying the 4th value as it is instead of multiplying because it contains additional information like orientation, width, stiffness etc which will be required in evaluation shader
+ return ans;
+}
+
void main() {
// TODO: Write gl_Position and any other shader outputs
+
+ /*
+ Converting from local space to world space next, we can also do it after tesselation but it might be less efficient as more number of points will have to be transformed.
+ Note how we are not transforming into screen space yet as we will have to use world space positions for positioning tessellated points later in evaluation
+ shader.
+ We will then have to transform all tessellated and positioned points into screen space.
+ */
+
+ tcs_v1 = multiply(model, v1);
+ tcs_v2 = multiply(model, v2);
+ tcs_up = multiply(model, up);
+
+ // Store v0 as gl_Position - we want to use v0 as our blade position and other points evaluated with respect to it in evaluation shader
+ gl_Position = multiply(model, v0);
+
}