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..0260d44 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,67 @@ 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)
+* Dongying Liu
+* [LinkedIn](https://www.linkedin.com/in/dongying-liu/), [personal website](https://vivienliu1998.wixsite.com/portfolio)
+* Tested on: Windows 11, i7-11700 @ 2.50GHz, NVIDIA GeForce RTX 3060
-### (TODO: Your README)
+# Project Description
+
+
+
+
+
+In this project, I used Vulkan to implemented a grass simulator and renderer. The grass simulation is based on this paper, [Responsive Real-Time Grass Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf).
+
+# Grass Representing and Forces Simulation
+## Grass Representing
+
+
+
+
+
+In this project, grass blades is represeted as a Beizier curves with three control points as the picture shows above.
+
+* `v0`: the position of the grass blade on the geomtry
+* `v1`: a Bezier curve guide that is always "above" `v0` with respect to the grass blade's up vector (explained soon)
+* `v2`: a physical guide for which we simulate forces on
+
+Up, orientation, height, width and stiffness coefficient are stored as well to represent and simulate the grass:
+
+* `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at `v0`
+* Orientation: the orientation of the grass blade's face
+* Height: the height of the grass blade
+* Width: the width of the grass blade's face
+* Stiffness coefficient: the stiffness of our grass blade, which will affect the force computations on our blade
+
+## Forces Simulation
+
+Based on the paper, three force simulations are implemented.
+| No Force | Gravity |
+| ----------- | ----------- |
+|
|
|
+
+| Gravity & Recovery | Gravity & Recovery & Wind |
+| ----------- | ----------- |
+|
|
|
+
+## Culling and Performance
+
+There are many blades that don't need to be rendered due to a variety of reasons. Based on the paper, three culling were implemented.
+
+| Orientation Culling | View-frustum Culling | Distance Culling |
+| ----------- | ----------- | ----------- |
+|
|
|
|
+
+Orientation culling culls the blades which the front face direction of the grass blades is perpendicular to the camera view director.
+
+View-frustum culling culls the that are outside of the view-frustum.
+
+Distance culling culls the grass blades that are far away from the camera postion. Further the distance from the camera, fewer the grass blades will be.
+
+The following picture shows the FPS(the higher the better) with each culling be done. With the number of grass simulated raise, the FPS went down. However, we can tell that the culling function improved the performace a lot.
+
+
+
+
-*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.
diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe
index f68db3a..d9fd805 100644
Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ
diff --git a/img/distance_culling.gif b/img/distance_culling.gif
new file mode 100644
index 0000000..a13d7bf
Binary files /dev/null and b/img/distance_culling.gif differ
diff --git a/img/frustum_culling.gif b/img/frustum_culling.gif
new file mode 100644
index 0000000..ed78da8
Binary files /dev/null and b/img/frustum_culling.gif differ
diff --git a/img/gravity.jpg b/img/gravity.jpg
new file mode 100644
index 0000000..2fd7b8c
Binary files /dev/null and b/img/gravity.jpg differ
diff --git a/img/noforce.jpg b/img/noforce.jpg
new file mode 100644
index 0000000..f4a99ca
Binary files /dev/null and b/img/noforce.jpg differ
diff --git a/img/orint_culling.gif b/img/orint_culling.gif
new file mode 100644
index 0000000..c7d8681
Binary files /dev/null and b/img/orint_culling.gif differ
diff --git a/img/performance.png b/img/performance.png
new file mode 100644
index 0000000..a854bf9
Binary files /dev/null and b/img/performance.png differ
diff --git a/img/recovery.jpg b/img/recovery.jpg
new file mode 100644
index 0000000..edcec23
Binary files /dev/null and b/img/recovery.jpg differ
diff --git a/img/result.gif b/img/result.gif
new file mode 100644
index 0000000..627a269
Binary files /dev/null and b/img/result.gif differ
diff --git a/img/result_with_culling.gif b/img/result_with_culling.gif
new file mode 100644
index 0000000..751f369
Binary files /dev/null and b/img/result_with_culling.gif differ
diff --git a/img/result_with_culling_15.gif b/img/result_with_culling_15.gif
new file mode 100644
index 0000000..6a9aa6e
Binary files /dev/null and b/img/result_with_culling_15.gif differ
diff --git a/src/Renderer.cpp b/src/Renderer.cpp
index b445d04..b689c4f 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -198,6 +198,38 @@ void Renderer::CreateComputeDescriptorSetLayout() {
// TODO: Create the descriptor set layout for the compute pipeline
// Remember this is like a class definition stating why types of information
// will be stored at each binding
+ VkDescriptorSetLayoutBinding bladesLayoutBinding = {};
+ bladesLayoutBinding.binding = 0;
+ bladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ bladesLayoutBinding.descriptorCount = 1;
+ bladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ bladesLayoutBinding.pImmutableSamplers = nullptr;
+
+ VkDescriptorSetLayoutBinding culledBladesLayoutBinding = {};
+ culledBladesLayoutBinding.binding = 1;
+ culledBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ culledBladesLayoutBinding.descriptorCount = 1;
+ culledBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ culledBladesLayoutBinding.pImmutableSamplers = nullptr;
+
+ VkDescriptorSetLayoutBinding numBladesLayoutBinding = {};
+ numBladesLayoutBinding.binding = 2;
+ numBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ numBladesLayoutBinding.descriptorCount = 1;
+ numBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ numBladesLayoutBinding.pImmutableSamplers = nullptr;
+
+ std::vector bindings = { bladesLayoutBinding, culledBladesLayoutBinding, numBladesLayoutBinding };
+
+ // Create the descriptor set layout
+ VkDescriptorSetLayoutCreateInfo layoutInfo = {};
+ layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ layoutInfo.bindingCount = static_cast(bindings.size());
+ layoutInfo.pBindings = bindings.data();
+
+ if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) {
+ throw std::runtime_error("Failed to create descriptor set layout");
+ }
}
void Renderer::CreateDescriptorPool() {
@@ -216,6 +248,8 @@ void Renderer::CreateDescriptorPool() {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 },
// TODO: Add any additional types and counts of descriptors you will need to allocate
+ // blades
+ {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(3 * scene->GetBlades().size())}
};
VkDescriptorPoolCreateInfo poolInfo = {};
@@ -320,6 +354,42 @@ void Renderer::CreateModelDescriptorSets() {
void Renderer::CreateGrassDescriptorSets() {
// TODO: Create Descriptor sets for the grass.
// This should involve creating descriptor sets which point to the model matrix of each group of grass blades
+ grassDescriptorSets.resize(scene->GetBlades().size());
+
+ // Describe the desciptor set
+ VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout };
+ VkDescriptorSetAllocateInfo allocInfo = {};
+ allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ allocInfo.descriptorPool = descriptorPool;
+ allocInfo.descriptorSetCount = static_cast(grassDescriptorSets.size());
+ allocInfo.pSetLayouts = layouts;
+
+ // Allocate descriptor sets
+ if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) {
+ throw std::runtime_error("Failed to allocate descriptor set");
+ }
+
+ std::vector descriptorWrites(grassDescriptorSets.size());
+
+ for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) {
+ VkDescriptorBufferInfo modelBufferInfo = {};
+ modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer();
+ modelBufferInfo.offset = 0;
+ modelBufferInfo.range = sizeof(ModelBufferObject);
+
+ descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[i].dstSet = grassDescriptorSets[i];
+ descriptorWrites[i].dstBinding = 0;
+ descriptorWrites[i].dstArrayElement = 0;
+ descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ descriptorWrites[i].descriptorCount = 1;
+ descriptorWrites[i].pBufferInfo = &modelBufferInfo;
+ descriptorWrites[i].pImageInfo = nullptr;
+ descriptorWrites[i].pTexelBufferView = nullptr;
+ }
+
+ // Update descriptor sets
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
void Renderer::CreateTimeDescriptorSet() {
@@ -359,7 +429,77 @@ 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
+ // 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) {
+ // Bind three blades buffer to the descriptor
+
+ VkDescriptorBufferInfo bladesBufferInfo = {};
+ bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer();
+ bladesBufferInfo.offset = 0;
+ bladesBufferInfo.range = sizeof(Blade) * NUM_BLADES;
+
+ VkDescriptorBufferInfo culledBladesBufferInfo = {};
+ culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer();
+ culledBladesBufferInfo.offset = 0;
+ culledBladesBufferInfo.range = sizeof(Blade) * NUM_BLADES;
+
+ VkDescriptorBufferInfo numBladesBufferInfo = {};
+ numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer();
+ numBladesBufferInfo.offset = 0;
+ numBladesBufferInfo.range = sizeof(BladeDrawIndirect);
+
+ descriptorWrites[3 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[3 * i + 0].dstSet = computeDescriptorSets[i];
+ descriptorWrites[3 * i + 0].dstBinding = 0;
+ descriptorWrites[3 * i + 0].dstArrayElement = 0;
+ descriptorWrites[3 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[3 * i + 0].descriptorCount = 1;
+ descriptorWrites[3 * i + 0].pBufferInfo = &bladesBufferInfo;
+ descriptorWrites[3 * i + 0].pImageInfo = nullptr;
+ descriptorWrites[3 * i + 0].pTexelBufferView = nullptr;
+
+ descriptorWrites[3 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[3 * i + 1].dstSet = computeDescriptorSets[i];
+ descriptorWrites[3 * i + 1].dstBinding = 1;
+ descriptorWrites[3 * i + 1].dstArrayElement = 0;
+ descriptorWrites[3 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[3 * i + 1].descriptorCount = 1;
+ descriptorWrites[3 * i + 1].pBufferInfo = &culledBladesBufferInfo;
+ descriptorWrites[3 * i + 1].pImageInfo = nullptr;
+ descriptorWrites[3 * i + 1].pTexelBufferView = nullptr;
+
+ descriptorWrites[3 * i + 2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[3 * i + 2].dstSet = computeDescriptorSets[i];
+ descriptorWrites[3 * i + 2].dstBinding = 2;
+ descriptorWrites[3 * i + 2].dstArrayElement = 0;
+ descriptorWrites[3 * i + 2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[3 * i + 2].descriptorCount = 1;
+ descriptorWrites[3 * i + 2].pBufferInfo = &numBladesBufferInfo;
+ descriptorWrites[3 * i + 2].pImageInfo = nullptr;
+ descriptorWrites[3 * i + 2].pTexelBufferView = nullptr;
+ }
+
+ // Update descriptor sets
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
+
}
void Renderer::CreateGraphicsPipeline() {
@@ -717,7 +857,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 +1024,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 (int i = 0; i < scene->GetBlades().size(); ++i) {
+ vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr);
+ vkCmdDispatch(computeCommandBuffer, (NUM_BLADES / WORKGROUP_SIZE) + 1, 1, 1);
+ }
// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
@@ -976,13 +1120,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
diff --git a/src/Renderer.h b/src/Renderer.h
index 95e025f..728ab34 100644
--- a/src/Renderer.h
+++ b/src/Renderer.h
@@ -56,12 +56,15 @@ class Renderer {
VkDescriptorSetLayout cameraDescriptorSetLayout;
VkDescriptorSetLayout modelDescriptorSetLayout;
VkDescriptorSetLayout timeDescriptorSetLayout;
+ VkDescriptorSetLayout computeDescriptorSetLayout;
VkDescriptorPool descriptorPool;
VkDescriptorSet cameraDescriptorSet;
std::vector modelDescriptorSets;
VkDescriptorSet timeDescriptorSet;
+ std::vector computeDescriptorSets;
+ std::vector grassDescriptorSets;
VkPipelineLayout graphicsPipelineLayout;
VkPipelineLayout grassPipelineLayout;
diff --git a/src/main.cpp b/src/main.cpp
index 8bf822b..4b23129 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,6 +5,7 @@
#include "Camera.h"
#include "Scene.h"
#include "Image.h"
+#include
Device* device;
SwapChain* swapChain;
@@ -67,7 +68,7 @@ namespace {
int main() {
static constexpr char* applicationName = "Vulkan Grass Rendering";
- InitializeWindow(640, 480, applicationName);
+ InitializeWindow(920, 560, applicationName);
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
@@ -139,14 +140,34 @@ int main() {
renderer = new Renderer(device, swapChain, scene, camera);
+ GLFWwindow* window = GetGLFWWindow();
glfwSetWindowSizeCallback(GetGLFWWindow(), resizeCallback);
glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
glfwSetCursorPosCallback(GetGLFWWindow(), 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] ";
+ glfwSetWindowTitle(window, ss.str().c_str());
}
vkDeviceWaitIdle(device->GetVkDevice());
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..97e1f0d 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -4,6 +4,10 @@
#define WORKGROUP_SIZE 32
layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
+#define ORIENTATION_CULLING 1
+#define FRUSTUM_CULLING 1
+#define DISTANCE_CULLING 1
+
layout(set = 0, binding = 0) uniform CameraBufferObject {
mat4 view;
mat4 proj;
@@ -26,31 +30,137 @@ struct Blade {
// 2. Write out the culled blades
// 3. Write the total number of blades remaining
+// for blades
+layout(set = 2, binding = 0) buffer Blades {
+ Blade blades[];
+};
+
+// for culledBlades
+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 ptInBounds(vec3 p, float bounds){
+ return (p.x >= -bounds) && (p.x <= bounds)
+ && (p.y >= -bounds) && (p.y <= bounds)
+ && (p.z >= -bounds) && (p.z <= bounds);
+}
+
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[gl_GlobalInvocationID.x];
+ vec3 v0 = blade.v0.xyz;
+ vec3 v1 = blade.v1.xyz;
+ vec3 v2 = blade.v2.xyz;
+ vec3 up = blade.up.xyz;
+ float orientation = blade.v0.w;
+ float height = blade.v1.w;
+ float width = blade.v2.w;
+ float stiffness = blade.up.w;
+ vec3 right = normalize(vec3(cos(orientation), 0, sin(orientation)));
+ vec3 front = normalize(cross(up, right));
+
// TODO: Apply forces on every blade and update the vertices in the buffer
+ // gravity
+ vec3 gE = vec3(0.0, -9.8, 0.0);
+ vec3 gF = 0.25 * length(gE) * front;
+ vec3 g = gE + gF;
+
+ // recovery
+ vec3 initV2 = v0 + height * up;
+ vec3 recovery = (initV2 - v2) * stiffness;
+
+ // wind
+ vec3 wind = sin(totalTime) * vec3(1.0, 0.0, 1.0) * 2.0;
+ float fd = 1.0 - abs(dot(normalize(wind), normalize(v2 - v0)));
+ float fr = dot((v2 - v0), up) / height;
+ float alignmentValue = fd * fr;
+ vec3 w = wind * alignmentValue;
+
+ // update v2
+ v2 = v2 + (g + recovery + w) * deltaTime;
+ // check v2 and ground
+ v2 = v2 - up * min(up * (v2 - v0), 0);
+ // update v1
+ float lproj = length(v2 - v0 - up*((v2 - v0)*up));
+ v1 = v0 + height * up * max(1 - lproj / height, 0.05 * max(lproj / height, 1));
+ // validation
+ float L0 = length(v2 - v0);
+ float L1 = length(v2 - v1) + length(v1 - v0);
+ float n = 2.0;
+ float L = (2 * L0 + (n - 1) * L1) / (n + 1);
+ float r = height / L;
+ vec3 v1corr = v0 + r * (v1 - v0);
+ vec3 v2corr = v1corr + r * (v2 - v1);
+
+ // write new v1, v2 to the bladesBuffer
+ blade.v1.xyz = v1corr;
+ blade.v2.xyz = v2corr;
+
+ 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
+
+#if ORIENTATION_CULLING
+ // orientation culling
+ vec3 cameraForward = normalize(vec3(camera.view[0][2], camera.view[1][2], camera.view[2][2]));
+ float oriCondition = abs(dot(front, cameraForward));
+ if(oriCondition < 0.6){
+ return;
+ }
+#endif
+
+#if FRUSTUM_CULLING
+ // view-frustum culling
+ mat4 VP = camera.proj * camera.view;
+ vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2;
+ vec4 v0NDC = VP * vec4(v0, 1.0);
+ vec4 v2NDC = VP * vec4(v2, 1.0);
+ vec4 mNDC = VP * vec4(m, 1.0);
+ float tolerance = 0.5;
+ bool v0IsInBounds = ptInBounds(v0NDC.xyz, v0NDC.w + tolerance);
+ bool v2IsInBounds = ptInBounds(v2NDC.xyz, v2NDC.w + tolerance);
+ bool mIsInBounds = ptInBounds(mNDC.xyz,mNDC.w + tolerance);
+ if(!v0IsInBounds && !v2IsInBounds && !mIsInBounds){
+ return;
+ }
+#endif
+
+#if DISTANCE_CULLING
+ // distance culling
+ float dMax = 30.0;
+ int distanceN = 10;
+ //vec3 cameraPos = vec3(camera.view[3][0], camera.view[3][1], camera.view[3][2]) * -1.0;
+ vec3 cameraPos = vec3(inverse(camera.view)* vec4(0.f, 0.f, 0.f, 1.f));
+ float dproj = length(v0 - cameraPos - up * (dot(v0 - cameraPos, up)));
+ if((gl_GlobalInvocationID.x % distanceN) > (distanceN * (1.0 - dproj / dMax))){
+ return;
+ }
+#endif
+
+
+ culledBlades[atomicAdd(numBlades.vertexCount, 1)] = blade;
}
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..9fa3331 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -7,11 +7,19 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} camera;
// TODO: Declare fragment shader inputs
+layout(location = 0) in vec3 n;
+layout(location = 1) in float height;
layout(location = 0) out vec4 outColor;
void main() {
// TODO: Compute fragment color
+ vec3 tipCol = vec3(0.0, 1.0, 0.0);
+ vec3 bottomCol = vec3(0.0, 0.2, 0.0);
- outColor = vec4(1.0);
+ vec3 lightDir = vec3(0.0, 0.0, 1.0);
+ float lightIntensity = 20.0;
+ vec3 col = mix(bottomCol, tipCol, height);// * dot(normalize(lightDir), n) * lightIntensity;
+
+ outColor = vec4(col, 1.0);
}
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..91122e8 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 in_v0[];
+layout(location = 1) in vec4 in_v1[];
+layout(location = 2) in vec4 in_v2[];
+layout(location = 3) in vec4 in_up[];
+
+layout(location = 0) out vec4 out_v0[];
+layout(location = 1) out vec4 out_v1[];
+layout(location = 2) out vec4 out_v2[];
+layout(location = 3) out vec4 out_up[];
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
+ out_v0[gl_InvocationID] = in_v0[gl_InvocationID];
+ out_v1[gl_InvocationID] = in_v1[gl_InvocationID];
+ out_v2[gl_InvocationID] = in_v2[gl_InvocationID];
+ out_up[gl_InvocationID] = in_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] = 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..e70a827 100644
--- a/src/shaders/grass.tese
+++ b/src/shaders/grass.tese
@@ -9,10 +9,39 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} camera;
// TODO: Declare tessellation evaluation shader inputs and outputs
+layout(location = 0) in vec4 in_v0[];
+layout(location = 1) in vec4 in_v1[];
+layout(location = 2) in vec4 in_v2[];
+layout(location = 3) in vec4 in_up[];
+
+layout(location = 0) out vec3 n;
+layout(location = 1) out float height;
void main() {
float u = gl_TessCoord.x;
float v = gl_TessCoord.y;
// TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade
+ float width = in_v2[0].w;
+
+ vec3 v0 = in_v0[0].xyz;
+ vec3 v1 = in_v1[0].xyz;
+ vec3 v2 = in_v2[0].xyz;
+ vec3 up = in_up[0].xyz;
+ vec3 t1 = normalize(vec3(sin(in_v0[0].w), 0.0, cos(in_v0[0].w)));
+
+ 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);
+ n = normalize(cross(t0, t1));
+
+ float t = u + 0.5 * v - u * v;
+ vec4 pos = vec4(mix(c0, c1, t), 1.0);
+
+ height = v;
+ gl_Position = camera.proj * camera.view * pos;
}
diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert
index db9dfe9..c97fc16 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -7,6 +7,15 @@ layout(set = 1, binding = 0) uniform ModelBufferObject {
};
// TODO: Declare vertex shader inputs and outputs
+layout(location = 0) in vec4 in_v0;
+layout(location = 1) in vec4 in_v1;
+layout(location = 2) in vec4 in_v2;
+layout(location = 3) in vec4 in_up;
+
+layout(location = 0) out vec4 out_v0;
+layout(location = 1) out vec4 out_v1;
+layout(location = 2) out vec4 out_v2;
+layout(location = 3) out vec4 out_up;
out gl_PerVertex {
vec4 gl_Position;
@@ -14,4 +23,10 @@ out gl_PerVertex {
void main() {
// TODO: Write gl_Position and any other shader outputs
+ // in_v0.w is orientation might not be 1.0
+ gl_Position = model * vec4(in_v0.xyz, 1.0);
+ out_v0 = model * in_v0;
+ out_v1 = model * in_v1;
+ out_v2 = model * in_v2;
+ out_up = model * in_up;
}