diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1191f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,561 @@ +*.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..55bbb9f 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,55 @@ 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) +* Akiko Zhu + * [LinkedIn](https://www.linkedin.com/in/geming-akiko-zhu-b6705a255/) +* Tested on: Windows 11, i9-12900H @ 2.50GHz 16GB, RTX 3070Ti 8GB (Personal) -### (TODO: Your README) +## Overview +![](images/demo.gif) -*DO NOT* leave the README to the last minute! It is a crucial part of the -project, and we will not be able to grade you without a good README. +This is a Vulkan-based grass simulation project. The algorithm and detailed implementation are referenced to 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). + +## Implementation +### Grass Blade Representation +![](img/blade_model.jpg) + +- The grass blades are represented as Bezier curves, each Bezier curve has three control points v0, v1, and v2. +- Besides, we stored per-blade characteristics such as normal, orientation, height, width, and stiffness in the blade structure so that we can efficiently simulate and tessellate the grass blades. + +### Force +- Gravity + - We assume the gravity value is 9.8 and the direction of the gravity is the negative direction of the Y-axis. + +- Recovery + - This is a counter-force that brings the grass blade back into equilibrium. + +- Wind + - We use cosine and sine functions, to simulate the strength of the natural wind, making each cluster of grass blades swing like being blown by the wind + + ![](images/wind.gif) + +### Culling +- Orientation Culling + - Because the grass blade doesn't have thickness in this simulation, we don't want the side of the grass blade to cause any artifacts. Therefore, we cull the rendering if the grass pixel is less than 0.1. + + | Without O.C. | With O.C. | + |-|-| + |![](images/ori_0.gif)|![](images/ori_1.gif)| + +- View-frustum Culling + - In order to improve the render performance, we can cull the grass blades that is not in the camera view + | Without V.F.C. | With V.F.C. | + |-|-| + |![](images/view_0.gif)|![](images/view_1.gif)| +- Distance Culling + - In addition, we can cull the grass blades that are too far from the camera. + | Without D.C. | With D.C. | + |-|-| + |![](images/view_1.gif)|![](images/dist_1.gif)| + +## Performance Analysis +- 2^17 grass blades in the test scene + ![](images/Cull.png) + As shown in the diagram, the combination of "Orientation, View-Frustum, and Distance" culling leads to an efficient performance improvement when rendering the grass simulation. diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe index f68db3a..c36fcd9 100644 Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ diff --git a/images/Cull.png b/images/Cull.png new file mode 100644 index 0000000..5a2315d Binary files /dev/null and b/images/Cull.png differ diff --git a/images/demo.gif b/images/demo.gif new file mode 100644 index 0000000..edcf4f6 Binary files /dev/null and b/images/demo.gif differ diff --git a/images/dist_1.gif b/images/dist_1.gif new file mode 100644 index 0000000..67fa32f Binary files /dev/null and b/images/dist_1.gif differ diff --git a/images/ori_0.gif b/images/ori_0.gif new file mode 100644 index 0000000..a80c5a5 Binary files /dev/null and b/images/ori_0.gif differ diff --git a/images/ori_1.gif b/images/ori_1.gif new file mode 100644 index 0000000..72fb927 Binary files /dev/null and b/images/ori_1.gif differ diff --git a/images/view_0.gif b/images/view_0.gif new file mode 100644 index 0000000..9232786 Binary files /dev/null and b/images/view_0.gif differ diff --git a/images/view_1.gif b/images/view_1.gif new file mode 100644 index 0000000..d3bd19e Binary files /dev/null and b/images/view_1.gif differ diff --git a/images/wind.gif b/images/wind.gif new file mode 100644 index 0000000..e5837f8 Binary files /dev/null and b/images/wind.gif differ diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..a401f74 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -195,9 +195,40 @@ void Renderer::CreateTimeDescriptorSetLayout() { } void Renderer::CreateComputeDescriptorSetLayout() { - // TODO: Create the descriptor set layout for the compute pipeline + // *DONE: 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 bladeInputBinding = {}; + bladeInputBinding.binding = 0; + bladeInputBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladeInputBinding.descriptorCount = 1; + bladeInputBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladeInputBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding bladeOutputBinding = {}; + bladeOutputBinding.binding = 1; + bladeOutputBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladeOutputBinding.descriptorCount = 1; + bladeOutputBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladeOutputBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding numBladeBinding = {}; + numBladeBinding.binding = 2; + numBladeBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBladeBinding.descriptorCount = 1; + numBladeBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numBladeBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { bladeInputBinding, bladeOutputBinding, numBladeBinding }; + // 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 +247,8 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + // debug { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(3 * scene->GetBlades().size()) }, }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -318,8 +351,43 @@ void Renderer::CreateModelDescriptorSets() { } void Renderer::CreateGrassDescriptorSets() { - // TODO: Create Descriptor sets for the grass. + // Create Descriptor sets for the grass. // This should involve creating descriptor sets which point to the model matrix of each group of grass blades + size_t n = scene->GetBlades().size(); + bladeDescriptorSets.resize(n); + + VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(bladeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, bladeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + std::vector descriptorWrites(n); + // loop over blades and prepare descriptor writes + for (size_t i = 0; i < n; ++i) { + VkDescriptorBufferInfo modelBufferInfo = {}; + modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + modelBufferInfo.offset = 0; + modelBufferInfo.range = sizeof(ModelBufferObject); + + descriptorWrites[i] = {}; + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = bladeDescriptorSets[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() { @@ -358,8 +426,76 @@ 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 + // Create Descriptor sets for the compute pipeline + // The descriptors point to Storage buffers which will hold the grass blades, the culled grass blades, and the output number of grass blades + size_t n = scene->GetBlades().size(); + computeDescriptorSets.resize(n); + + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + + // debug + //allocInfo.descriptorSetCount = 1; + 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 (size_t i = 0; i < n; ++i) { + VkDescriptorBufferInfo bladeBufferInfo = {}; + bladeBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + bladeBufferInfo.offset = 0; + bladeBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect); + + descriptorWrites[3 * i + 0] = {}; + 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].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 0].descriptorCount = 1; + descriptorWrites[3 * i + 0].pBufferInfo = &bladeBufferInfo; + descriptorWrites[3 * i + 0].pImageInfo = nullptr; + descriptorWrites[3 * i + 0].pTexelBufferView = nullptr; + + descriptorWrites[3 * i + 1] = {}; + 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].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] = {}; + 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].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 all descriptor sets at once + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +853,12 @@ void Renderer::CreateComputePipeline() { computeShaderStageInfo.pName = "main"; // TODO: Add the compute dsecriptor set layout you create to this list - std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout }; + std::vector descriptorSetLayouts = + { + cameraDescriptorSetLayout, + timeDescriptorSetLayout, + computeDescriptorSetLayout + }; // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -882,8 +1023,19 @@ 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 + // debug + /* + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSet, 0, nullptr); + vkCmdDispatch(computeCommandBuffer, (NUM_BLADES + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE, 1, 1); + */ + + for (size_t 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) / WORKGROUP_SIZE, 1, 1); + } + // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -975,14 +1127,14 @@ 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); + + // Bind the descriptor set for each grass blades model + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &bladeDescriptorSets[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 +1209,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..9f85e65 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,11 +56,16 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; + VkDescriptorSet computeDescriptorSet; // debug std::vector modelDescriptorSets; + std::vector bladeDescriptorSets; + std::vector computeDescriptorSets; + VkDescriptorSet timeDescriptorSet; VkPipelineLayout graphicsPipelineLayout; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..bd84c8a 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,9 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define D_GRAVITY vec4(0.0, -1.0, 0.0, 9.8) + + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -12,7 +15,7 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { layout(set = 1, binding = 0) uniform Time { float deltaTime; float totalTime; -}; +} time; struct Blade { vec4 v0; @@ -21,36 +24,135 @@ struct Blade { vec4 up; }; -// TODO: Add bindings to: -// 1. Store the input blades -// 2. Write out the culled blades -// 3. Write the total number of blades remaining - -// The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call -// This is sort of an advanced feature so we've showed you what this buffer should look like -// -// layout(set = ???, binding = ???) buffer NumBlades { -// uint vertexCount; // Write the number of blades remaining here -// uint instanceCount; // = 1 -// uint firstVertex; // = 0 -// uint firstInstance; // = 0 -// } numBlades; +layout(set = 2, binding = 0) buffer BladeInputBuffer { + Blade blades[]; +} bladeInputBuffer; + +layout(set = 2, binding = 1) buffer BladeOutputBuffer { + Blade culledBlades[]; +} bladeOutputBuffer; + +layout(set = 2, binding = 2) buffer NumBlades { + uint vertexCount; + uint instanceCount; + uint firstVertex; + uint firstInstance; +} numBlades; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +// Culling based on dist and view frustum +bool inFrustum(vec4 position) { + vec4 clipPos = camera.proj * camera.view * position; + float a = 1.1; + return inBounds(clipPos.x / clipPos.w, a) && + inBounds(clipPos.y / clipPos.w, a) && + inBounds(clipPos.z / clipPos.w, a); +} + +vec3 getWindDirection(vec3 position) { + float i_Time = time.totalTime; + float windStrengthX = 0.0; + float windStrengthZ = 5.0; + float windStrengthY = 0.1; + float freqX = 1.0; + float freqZ = 1.0; + float scale = 1.5; + + float windX = windStrengthX * sin(freqX * position.z + i_Time * scale); + float windZ = windStrengthZ * cos(freqZ * position.x + i_Time * scale); + float windY = windStrengthY * sin(i_Time * 0.2); + + //debug + return vec3(windX, windY, windZ); + return vec3(windX, windY, windZ); + +} + void main() { // Reset the number of blades to 0 if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + numBlades.vertexCount = 0; } barrier(); // Wait till all threads reach this point - // TODO: Apply forces on every blade and update the vertices in the buffer + // fetch data + Blade blade = bladeInputBuffer.blades[gl_GlobalInvocationID.x]; + vec3 v0 = vec3(blade.v0); + vec3 v1 = vec3(blade.v1); + vec3 v2 = vec3(blade.v2); + vec3 up = vec3(blade.up); + float orientation = blade.v0.w; + float height = blade.v1.w; + float width = blade.v2.w; + float stiffCoe = blade.up.w; // stiffness coefficient + + // Apply forces on every blade and update the vertices in the buffer + // Gravity + vec3 D_xyz = vec3(D_GRAVITY.xyz); + float D_w = D_GRAVITY.w; + vec3 gE = normalize(D_xyz) * D_w; + vec3 f = cross(up, vec3(cos(orientation), 0.0, sin(orientation))); + vec3 gF = (0.25) * length(gE) * f; // front gravity + vec3 G = gE + gF; // total gravity + + // Recovery + vec3 iv2 = v0 + height * up; + vec3 R = (iv2 - v2) * stiffCoe; // recovery force + + // Wind + vec3 w = getWindDirection(v0); + float fd = 1 - abs(dot(normalize(w), normalize(v2 - v0))); + float fr = dot((v2 - v0), up) / height; + vec3 W = w * fd * fr; + + // total force + vec3 tv2 = v2 + (G + R + W) * time.deltaTime; + + // validation + tv2 = tv2 - up * min(up * (tv2 - v1), 0); + float lproj = length(tv2 - v0 - up * dot((tv2 - v0), up)); + v1 = v0 + height * up * max(1.0 - lproj / height, 0.05 * max(lproj / height, 1.0)); + v2 = tv2; + float l = (length(v0 - v2) + 2 * (length(v0 - v1) + length(v1 - v2))) / 3; + float r = height / l; + vec3 v1c = v0 + r * (v1 - v0); + v2 = v1c + r * (v2 - v1); + v1 = v1c; + + // update blade + blade.v1 = vec4(v1, height); + blade.v2 = vec4(v2, width); + bladeInputBuffer.blades[gl_GlobalInvocationID.x] = blade; + + // Orientation Culling + vec3 viewWidth = vec3(camera.view * vec4(cos(orientation), 0.0, sin(orientation), 0.0)); + if (abs(normalize(viewWidth).z) > 0.9) { + return; + } + + // View-frustum Culling + vec4 pV0 = vec4(v0, 1.0); + vec4 pV2 = vec4(v2, 1.0); + vec4 pM = vec4(0.25 * v0 + 0.5 * v1 + 0.25 * v2, 1.0); + if (!inFrustum(pV0) || !inFrustum(pV2) || !inFrustum(pM)) { + return; + } + + // Distance Cull + vec3 CameraPos = vec3(camera.view * vec4(v0, 1.0)); + vec3 CameraUp = vec3(camera.view * vec4(up, 0.0)); + vec3 CameraLeft = cross(CameraUp, CameraPos); + vec3 CameraFront = normalize(cross(CameraLeft, CameraUp)); + float dproj = dot(CameraFront, CameraPos); + int dist_cull_level = 10; + int dist_cull_max = 20; + if ((gl_GlobalInvocationID.x % dist_cull_level) > floor(dist_cull_level * (1.0 - dproj / dist_cull_max))) { + return; + } - // 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 + // update culled blades + bladeOutputBuffer.culledBlades[atomicAdd(numBlades.vertexCount, 1)] = blade; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..c41b251 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -6,12 +6,14 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare fragment shader inputs +layout(location = 0) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec2 i_uv; layout(location = 0) out vec4 outColor; void main() { - // TODO: Compute fragment color - - outColor = vec4(1.0); + vec3 green1 = vec3(0.0, 100.0, 0.0); + vec3 green2 = vec3(50.0, 251.0, 0.0); + outColor = vec4(mix(green1 / 255.0, green2 / 255.0, i_uv.y), 1.0); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..1640d26 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -8,19 +8,35 @@ 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 i_v0[]; +layout(location = 1) in vec4 i_v1[]; +layout(location = 2) in vec4 i_v2[]; +layout(location = 3) in vec4 i_up[]; + +layout(location = 0) out vec4 o_v0[]; +layout(location = 1) out vec4 o_v1[]; +layout(location = 2) out vec4 o_v2[]; +layout(location = 3) out vec4 o_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 + // Write shader outputs + o_v0[gl_InvocationID] = i_v0[gl_InvocationID]; + o_v1[gl_InvocationID] = i_v1[gl_InvocationID]; + o_v2[gl_InvocationID] = i_v2[gl_InvocationID]; + o_up[gl_InvocationID] = i_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] = ??? + // Set level of tesselation + int level = 5; + if (gl_InvocationID == 0) { + gl_TessLevelInner[0] = level; + gl_TessLevelInner[1] = level; + gl_TessLevelOuter[0] = level; + gl_TessLevelOuter[1] = level; + gl_TessLevelOuter[2] = level; + gl_TessLevelOuter[3] = level; + } } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..b306527 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -8,11 +8,41 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation evaluation shader inputs and outputs +// Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 i_v0[]; +layout(location = 1) in vec4 i_v1[]; +layout(location = 2) in vec4 i_v2[]; +layout(location = 3) in vec4 i_up[]; + +layout(location = 0) out vec3 o_position; +layout(location = 1) out vec3 o_normal; +layout(location = 2) out vec2 o_uv; void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + // fetch data + vec3 v0 = i_v0[0].xyz; + vec3 v1 = i_v1[0].xyz; + vec3 v2 = i_v2[0].xyz; + vec3 up = i_up[0].xyz; + + float orientation = i_v0[0].w; + float height = i_v1[0].w; + float width = i_v2[0].w; + float stiffCoe = i_up[0].w; // stiffness coefficient + // De-Casteljau + vec3 a = mix(v0, v1, v); + vec3 b = mix(v1, v2, v); + vec3 t_direction_0 = normalize(b - a); + vec3 t_direction_1 = normalize(vec3(cos(orientation), 0.0, sin(orientation))); + float t = (0.5 * v + u) - (v * u); + vec4 pos = vec4(mix(mix(a, b, v) - width * t_direction_1, mix(a, b, v) + width * t_direction_1, t), 1.0); + + gl_Position = camera.proj * camera.view * pos; + o_position = vec3(gl_Position); + o_uv = vec2(u, v); + o_normal = cross(t_direction_0, t_direction_1); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..856b603 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -6,12 +6,25 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { mat4 model; }; -// TODO: Declare vertex shader inputs and outputs +// Declare vertex shader inputs and outputs +layout(location = 0) in vec4 i_v0; +layout(location = 1) in vec4 i_v1; +layout(location = 2) in vec4 i_v2; +layout(location = 3) in vec4 i_up; + +layout(location = 0) out vec4 o_v0; +layout(location = 1) out vec4 o_v1; +layout(location = 2) out vec4 o_v2; +layout(location = 3) out vec4 o_up; + out gl_PerVertex { vec4 gl_Position; }; void main() { - // TODO: Write gl_Position and any other shader outputs + o_v0 = vec4(vec3(model * vec4(i_v0.xyz, 1.0)), i_v0.w); + o_v1 = vec4(vec3(model * vec4(i_v1.xyz, 1.0)), i_v1.w); + o_v2 = vec4(vec3(model * vec4(i_v2.xyz, 1.0)), i_v2.w); + o_up = vec4(vec3(model * vec4(i_up.xyz, 1.0)), i_up.w); }