diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c57396 --- /dev/null +++ b/.gitignore @@ -0,0 +1,562 @@ +*.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/ +bin/ +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..ebc7cd1 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,92 @@ 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) +* Han Yang + * [LinkedIn](https://www.linkedin.com/in/han-yang-0031231a3/), [personal website](https://bdwhst.wixsite.com/portfolio), etc. +* Tested on: Windows 11, i9-12900HX @ 2.30GHz 16GB, RTX4080 laptop 12GB -### (TODO: Your README) +## Final Result + +![](./img/grass-finished.gif) + +## Principles and Analysis + +In our model, each individual blade of grass is simulated using a dedicated GPU thread, ensuring a rich, dynamic appearance to the grassy landscape. + +### Geometry + +The geometry of each blade is foundational to its visual representation: + +- **Control Points:** Every blade of grass is constructed using three pivotal control points. These points define the shape and curvature of the blade. + +- **Tessellation:** + - Leveraging tessellation, we dynamically generate a multitude of discrete points. + - These points are derived from the bezier curve established by the control points, ensuring the blade's realistic, curved appearance. + - The tessellated structure grants the flexibility of having varying levels of detail, potentially boosting performance without significantly compromising visual fidelity. + +### Simulation + +Our grass simulation, essential for a lifelike depiction, is grounded in three primary forces: + +1. **Gravity Force:** + - An omnipresent downward force, gravity ensures each blade has a natural 'droop', reflecting the real-world pull. + - The strength of the gravity force can be modulated to simulate conditions like a waterlogged field or a dry, arid landscape. +2. **Wind Force:** + - This dynamic force introduces motion, causing the grass to sway and rustle. + - By altering the direction and magnitude of the wind force, various atmospheric conditions, from a gentle breeze to a raging storm, can be emulated. + - Here I used Perlin noise to simulate the wind field +3. **Recovery Force:** + - After any perturbation, such as a strong gust of wind, the recovery force works to restore the grass blade to its original stance. + - It ensures that after any disturbance, the grass doesn't remain permanently deformed. + +After we computed the new position by using these forces, we need a validation stage to ensure we get a robust and valid simulation for the control points. + +### Culling + +Culling is a crucial optimization technique, ensuring only relevant blades are rendered: + +#### Direction Culling + +- **Parallel Check:** If a blade's orientation is almost parallel to the viewer's line of sight, it's likely to contribute negligibly to the scene. + - Such blades are culled to improve performance. + +![](./img/direction_culling.gif) + +#### View Frustum Culling + +- **Visibility Check:** The viewer's field of vision, or frustum, is a defined volume. Blades of grass outside this volume are not visible. + - By checking a few key points on each blade against this volume, we can determine its visibility. + - Blades found outside are culled, ensuring computational resources are focused only on visible entities. + +![](./img/viewfrustrum_culling.gif) + +#### Distance Culling + +- **Proximity Based:** Beyond a certain distance, blades become virtually indistinguishable to the viewer. + - We set a threshold distance, beyond which blades are culled. +- **Fading Mechanism:** + - Instead of abruptly culling blades at the threshold, a fading mechanism is used. + - We cull a percentage of blades according to their projected distance to the camera. + +![](./img/dist_culling.gif) + +### Distance Based Tessellation + +We set the tessellation level of the blade according to its distance to the camera, the further it gets, the less tessellation level will it be. + +![](./img/dist_based_tess.png) + +### Performance + +We tested the program under different conditions, and recorded average FPS for each case. Here we set max tessellation level to 10, min tessellation level to 2, and resolution to 800x800. + +| Condition \ Num of Blades | 1<<14 | 1<<15 | 1<<16 | 1<<17 | 1<<18 | 1<<19 | +| -------------------------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | +| With culling and distance based tessellation | 3334 | 2132 | 1124 | 598 | 311 | 160 | +| With culling | 2720 | 1461 | 779 | 375 | 183 | 111 | +| Naïve | 1107 | 542 | 289 | 141 | 74 | 38 | + +![](./img/perf.png) + +We can see that both culling and distance based tessellation can greatly improve our program's FPS. The improvement of culling is roughly 150%, and the improvement of distance based tessellation is roughly 40%. -*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 deleted file mode 100644 index f68db3a..0000000 Binary files a/bin/Release/vulkan_grass_rendering.exe and /dev/null differ diff --git a/img/direction_culling.gif b/img/direction_culling.gif new file mode 100644 index 0000000..b0e620c Binary files /dev/null and b/img/direction_culling.gif differ diff --git a/img/dist_based_tess.png b/img/dist_based_tess.png new file mode 100644 index 0000000..9130898 Binary files /dev/null and b/img/dist_based_tess.png differ diff --git a/img/dist_culling.gif b/img/dist_culling.gif new file mode 100644 index 0000000..60bbcfe Binary files /dev/null and b/img/dist_culling.gif differ diff --git a/img/grass-finished.gif b/img/grass-finished.gif new file mode 100644 index 0000000..d3db069 Binary files /dev/null and b/img/grass-finished.gif differ diff --git a/img/perf.png b/img/perf.png new file mode 100644 index 0000000..c663b73 Binary files /dev/null and b/img/perf.png differ diff --git a/img/viewfrustrum_culling.gif b/img/viewfrustrum_culling.gif new file mode 100644 index 0000000..6f891ab Binary files /dev/null and b/img/viewfrustrum_culling.gif differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..6d80ff0 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -45,8 +45,9 @@ 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::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory); + 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::CreateBuffer(device, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, numBladesBuffer, numBladesBufferMemory); + BufferUtils::CopyToHostVisibleMemory(device, &indirectDraw, numBladesBufferMemory, sizeof(indirectDraw)); } VkBuffer Blades::GetBladesBuffer() const { @@ -61,6 +62,11 @@ VkBuffer Blades::GetNumBladesBuffer() const { return numBladesBuffer; } +VkDeviceMemory Blades::GetNumBladesMemory() const +{ + return numBladesBufferMemory; +} + Blades::~Blades() { vkDestroyBuffer(device->GetVkDevice(), bladesBuffer, nullptr); vkFreeMemory(device->GetVkDevice(), bladesBufferMemory, nullptr); diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..8339a63 100644 --- a/src/Blades.h +++ b/src/Blades.h @@ -1,14 +1,15 @@ #pragma once #include +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES #include #include #include "Model.h" -constexpr static unsigned int NUM_BLADES = 1 << 13; +constexpr static unsigned int NUM_BLADES = 1 << 16; constexpr static float MIN_HEIGHT = 1.3f; constexpr static float MAX_HEIGHT = 2.5f; -constexpr static float MIN_WIDTH = 0.1f; -constexpr static float MAX_WIDTH = 0.14f; +constexpr static float MIN_WIDTH = 0.08f; +constexpr static float MAX_WIDTH = 0.1f; constexpr static float MIN_BEND = 7.0f; constexpr static float MAX_BEND = 13.0f; @@ -69,6 +70,14 @@ struct BladeDrawIndirect { uint32_t firstInstance; }; +struct ComputePushConstant { + glm::vec4 G; + glm::vec4 wind_Params;//time frequency, position freq, + int numBlades; + float maxCullDist; + int numCullLevels; +}; + class Blades : public Model { private: VkBuffer bladesBuffer; @@ -84,5 +93,6 @@ class Blades : public Model { VkBuffer GetBladesBuffer() const; VkBuffer GetCulledBladesBuffer() const; VkBuffer GetNumBladesBuffer() const; + VkDeviceMemory GetNumBladesMemory() const; ~Blades(); }; diff --git a/src/BufferUtils.cpp b/src/BufferUtils.cpp index acf617e..df433c2 100644 --- a/src/BufferUtils.cpp +++ b/src/BufferUtils.cpp @@ -90,3 +90,11 @@ void BufferUtils::CreateBufferFromData(Device* device, VkCommandPool commandPool vkDestroyBuffer(device->GetVkDevice(), stagingBuffer, nullptr); vkFreeMemory(device->GetVkDevice(), stagingBufferMemory, nullptr); } + +void BufferUtils::CopyToHostVisibleMemory(Device* device, void* srcData, VkDeviceMemory dstMemory, VkDeviceSize size) +{ + void* data; + vkMapMemory(device->GetVkDevice(), dstMemory, 0, size, 0, &data); + memcpy(data, srcData, static_cast(size)); + vkUnmapMemory(device->GetVkDevice(), dstMemory); +} diff --git a/src/BufferUtils.h b/src/BufferUtils.h index 04e784a..e86448e 100644 --- a/src/BufferUtils.h +++ b/src/BufferUtils.h @@ -7,4 +7,5 @@ namespace BufferUtils { void CreateBuffer(Device* device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory); void CopyBuffer(Device* device, VkCommandPool commandPool, VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size); void CreateBufferFromData(Device* device, VkCommandPool commandPool, void* bufferData, VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsage, VkBuffer& buffer, VkDeviceMemory& bufferMemory); + void CopyToHostVisibleMemory(Device* device, void* srcData, VkDeviceMemory dstMemory, VkDeviceSize size); } diff --git a/src/Camera.cpp b/src/Camera.cpp index 3afb5b8..8b90b78 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -37,7 +37,6 @@ void Camera::UpdateOrbit(float deltaX, float deltaY, float deltaZ) { glm::mat4 finalTransform = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f)) * rotation * glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 1.0f, r)); cameraBufferObject.viewMatrix = glm::inverse(finalTransform); - memcpy(mappedData, &cameraBufferObject, sizeof(CameraBufferObject)); } diff --git a/src/Camera.h b/src/Camera.h index 6b10747..070b217 100644 --- a/src/Camera.h +++ b/src/Camera.h @@ -1,6 +1,6 @@ #pragma once - +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES #include #include "Device.h" diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..10db2ac 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -5,6 +5,7 @@ #include "Blades.h" #include "Camera.h" #include "Image.h" +#include "BufferUtils.h" static constexpr unsigned int WORKGROUP_SIZE = 32; @@ -198,6 +199,39 @@ 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 + if (scene->GetBlades().size() != 1) throw; + VkDescriptorSetLayoutBinding outLayoutBinding = {}; + outLayoutBinding.binding = 0; + outLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + outLayoutBinding.descriptorCount = 1; + outLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + outLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding inLayoutBinding = {}; + inLayoutBinding.binding = 1; + inLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + inLayoutBinding.descriptorCount = 1; + inLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + inLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding indirectLayoutBinding = {}; + indirectLayoutBinding.binding = 2; + indirectLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + indirectLayoutBinding.descriptorCount = 1; + indirectLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + indirectLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { outLayoutBinding, inLayoutBinding, indirectLayoutBinding }; + + // 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 +250,7 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2 }, }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -360,6 +395,70 @@ 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 + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, &computeDescriptorSet) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + if (scene->GetBlades().size() != 1) throw; + + // Configure the descriptors to refer to buffers + VkDescriptorBufferInfo outBufferInfo = {}; + outBufferInfo.buffer = scene->GetBlades()[0]->GetCulledBladesBuffer(); + outBufferInfo.offset = 0; + outBufferInfo.range = VK_WHOLE_SIZE; + + VkDescriptorBufferInfo inBufferInfo = {}; + inBufferInfo.buffer = scene->GetBlades()[0]->GetBladesBuffer(); + inBufferInfo.offset = 0; + inBufferInfo.range = VK_WHOLE_SIZE; + + VkDescriptorBufferInfo indirectBufferInfo = {}; + indirectBufferInfo.buffer = scene->GetBlades()[0]->GetNumBladesBuffer(); + indirectBufferInfo.offset = 0; + indirectBufferInfo.range = VK_WHOLE_SIZE; + + std::array descriptorWrites = {}; + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = computeDescriptorSet; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &outBufferInfo; + descriptorWrites[0].pImageInfo = nullptr; + descriptorWrites[0].pTexelBufferView = nullptr; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = computeDescriptorSet; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pBufferInfo = &inBufferInfo; + descriptorWrites[1].pImageInfo = nullptr; + descriptorWrites[1].pTexelBufferView = nullptr; + + descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[2].dstSet = computeDescriptorSet; + descriptorWrites[2].dstBinding = 2; + descriptorWrites[2].dstArrayElement = 0; + descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[2].descriptorCount = 1; + descriptorWrites[2].pBufferInfo = &indirectBufferInfo; + descriptorWrites[2].pImageInfo = nullptr; + descriptorWrites[2].pTexelBufferView = nullptr; + + + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -600,6 +699,7 @@ void Renderer::CreateGrassPipeline() { rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; + //rasterizer.polygonMode = VK_POLYGON_MODE_LINE; rasterizer.polygonMode = VK_POLYGON_MODE_FILL; rasterizer.lineWidth = 1.0f; rasterizer.cullMode = VK_CULL_MODE_NONE; @@ -654,7 +754,7 @@ void Renderer::CreateGrassPipeline() { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout }; + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout }; // Pipeline layout: used to specify uniform values VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -717,15 +817,20 @@ 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 }; + + VkPushConstantRange pushConstantRange{}; + pushConstantRange.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + pushConstantRange.offset = 0; + pushConstantRange.size = sizeof(ComputePushConstant); // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = static_cast(descriptorSetLayouts.size()); pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts.data(); - pipelineLayoutInfo.pushConstantRangeCount = 0; - pipelineLayoutInfo.pPushConstantRanges = 0; + pipelineLayoutInfo.pushConstantRangeCount = 1; + pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; if (vkCreatePipelineLayout(logicalDevice, &pipelineLayoutInfo, nullptr, &computePipelineLayout) != VK_SUCCESS) { throw std::runtime_error("Failed to create pipeline layout"); @@ -883,7 +988,18 @@ void Renderer::RecordComputeCommandBuffer() { // Bind descriptor set for time uniforms vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 1, 1, &timeDescriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSet, 0, nullptr); + // TODO: For each group of blades bind its descriptor set and dispatch + if (scene->GetBlades().size() != 1) throw; + ComputePushConstant consts{}; + consts.G = glm::vec4(0.0, -1.0, 0.0, 9.8); + consts.wind_Params = glm::vec4(1.0f, 3.0f, 10.0f, 100.0f); + consts.numBlades = NUM_BLADES; + consts.maxCullDist = 30.0f; + consts.numCullLevels = 10; + vkCmdPushConstants(computeCommandBuffer, computePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(ComputePushConstant), &consts); + vkCmdDispatch(computeCommandBuffer, (NUM_BLADES + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE, 1, 1); // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -971,18 +1087,18 @@ void Renderer::RecordCommandBuffers() { // Bind the grass pipeline vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipeline); - + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 0, 1, &cameraDescriptorSet, 0, nullptr); 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); + 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, &modelDescriptorSets[0], 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 @@ -1006,7 +1122,8 @@ void Renderer::Frame() { if (vkQueueSubmit(device->GetQueue(QueueFlags::Compute), 1, &computeSubmitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { throw std::runtime_error("Failed to submit draw command buffer"); } - + //vkQueueWaitIdle(device->GetQueue(QueueFlags::Compute)); + //vkDeviceWaitIdle(device->GetVkDevice()); if (!swapChain->Acquire()) { RecreateFrameResources(); return; @@ -1057,6 +1174,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..a893392 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,12 +56,14 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + VkDescriptorSet computeDescriptorSet; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..d9fa6bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include +#include #include "Instance.h" #include "Window.h" #include "Renderer.h" @@ -10,6 +11,9 @@ Device* device; SwapChain* swapChain; Renderer* renderer; Camera* camera; +const int window_width = 800; +const int window_height = 800; + namespace { void resizeCallback(GLFWwindow* window, int width, int height) { @@ -67,7 +71,7 @@ namespace { int main() { static constexpr char* applicationName = "Vulkan Grass Rendering"; - InitializeWindow(640, 480, applicationName); + InitializeWindow(window_width, window_height, applicationName); unsigned int glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -90,7 +94,7 @@ int main() { swapChain = device->CreateSwapChain(surface, 5); - camera = new Camera(device, 640.f / 480.f); + camera = new Camera(device, window_width * 1.0f / window_height); VkCommandPoolCreateInfo transferPoolInfo = {}; transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; @@ -153,15 +157,18 @@ int main() { vkDestroyImage(device->GetVkDevice(), grassImage, nullptr); vkFreeMemory(device->GetVkDevice(), grassImageMemory, nullptr); - + delete scene; delete plane; delete blades; delete camera; delete renderer; delete swapChain; + vkDestroySurfaceKHR(instance->GetVkInstance(), surface, nullptr); + DestroyWindow(); delete device; delete instance; - DestroyWindow(); + std::cout << "Press any key to exit..." << std::endl; + getchar(); return 0; } diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..92f8d09 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -12,7 +12,8 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { layout(set = 1, binding = 0) uniform Time { float deltaTime; float totalTime; -}; +} time; + struct Blade { vec4 v0; @@ -26,31 +27,181 @@ struct Blade { // 2. Write out the culled blades // 3. Write the total number of blades remaining +layout(std140, set = 2, binding = 0) buffer BladesOut { + Blade outdata[ ]; +}; + +layout(std140, set = 2, binding = 1) buffer BladesIn { + Blade indata[ ]; +}; + // 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(std140, 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; + +layout(push_constant) uniform PushConstants { + vec4 G; + vec4 wind_Params; + int numBlades; + float maxCullDist; + int numCullLevels; +} pcs; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +//Perlin noise from https://github.com/SuboptimalEng/shader-tutorials/blob/main/05-perlin-noise/shader.frag +vec2 randomVecOnGrids(uvec2 corner) +{ + const float omega=0.1f; + vec2 grad=vec2(dot(corner, vec2(562.23f,734.58)),dot(corner, vec2(213.86f,456.12f))); + grad = sin(grad); + grad = grad*347892.0f+time.totalTime*pcs.wind_Params.x; + return sin(grad); +} + +vec2 smoothCubic(vec2 x) { + return x * x * (3.0 - x * 2.0); +} + +float perlin(vec2 xy) +{ + vec2 uv = fract(xy); + uvec2 cell = uvec2(xy-uv); + vec2 grad00 = randomVecOnGrids(cell+uvec2(0,0)); + vec2 grad01 = randomVecOnGrids(cell+uvec2(0,1)); + vec2 grad10 = randomVecOnGrids(cell+uvec2(1,0)); + vec2 grad11 = randomVecOnGrids(cell+uvec2(1,1)); + + vec2 vecTo00 = uv + vec2(0.0, 0.0); + vec2 vecTo01 = uv + vec2(0.0, 1.0); + vec2 vecTo10 = uv + vec2(1.0, 0.0); + vec2 vecTo11 = uv + vec2(1.0, 1.0); + + float dot00 = dot(vecTo00, grad00); + float dot01 = dot(vecTo01, grad01); + float dot10 = dot(vecTo10, grad10); + float dot11 = dot(vecTo11, grad11); + + uv = smoothCubic(uv); + return mix(mix(dot00, dot01, uv.x), mix(dot10, dot11, uv.x), uv.y); +} + + + +vec3 getWindInfluence(vec3 pos) +{ + const float frequency = pcs.wind_Params.y; + const float amplitude = pcs.wind_Params.z; + float windX = amplitude * perlin(vec2(pos.x * frequency, pos.z * frequency)); + float windZ = amplitude * perlin(vec2((pos.x + pcs.wind_Params.w) * frequency, (pos.z + pcs.wind_Params.w) * frequency)); + return vec3(windX,0,windZ); +} + +float windFr(vec3 v2, vec3 v0, vec3 up, float h) +{ + return dot(v2-v0,up)/h; +} + +float windFd(vec3 v2, vec3 v0) +{ + return 1-dot(normalize(getWindInfluence(v0)),normalize(v2-v0)); +} + +vec3 getWindForce(vec3 v2, vec3 v0, vec3 up, float h) +{ + return windFr(v2,v0,up,h)*windFd(v2,v0)*getWindInfluence(v0); +} + +bool checkInViewFrustrum(vec3 x) +{ + vec4 clipPos = camera.proj * camera.view * vec4(x, 1.0f); + float hClip = clipPos.w+1e-2f; + return inBounds(clipPos.x,hClip)&&inBounds(clipPos.y,hClip)&&inBounds(clipPos.z,hClip); +} + void main() { - // Reset the number of blades to 0 - if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + uint index = gl_GlobalInvocationID.x; + + if(index >= pcs.numBlades) return; + + if (index == 0) { + numBlades.vertexCount = 0; } - barrier(); // Wait till all threads reach this point + barrier(); + Blade tmp = indata[index]; + vec3 v0 = tmp.v0.xyz; + vec3 cv1 = tmp.v1.xyz; + vec3 cv2 = tmp.v2.xyz; + vec3 up = tmp.up.xyz; + float stiffness = tmp.up.w; + float h = tmp.v1.w; + + //Apply Forces + vec3 gE = normalize(pcs.G.xyz)*pcs.G.w; + float angle = tmp.v0.w; + vec3 side = vec3(cos(angle),0.0,sin(angle)); + vec3 front = cross(up, side); + vec3 gF = 0.25*length(gE)*front; + vec3 G = gE+gF; + + vec3 iv2 = v0+up*h; + vec3 R = (iv2-cv2)*stiffness; + vec3 W = getWindForce(cv2,v0,up,h); + vec3 tv2 = (R+W+G)*time.deltaTime; + //vec3 tv2 = vec3(0.0f); + vec3 nv2 = cv2+tv2; + + //Validation + nv2 = nv2 - tmp.up.xyz*min(0,dot(up,nv2-v0)); + float lproj = length(nv2-v0-up*dot(up,nv2-v0)); + vec3 nv1 = v0 + h*up*max(1-lproj/h,0.05*max(lproj/h,1)); + float L1 = length(v0-nv1)+length(nv1-nv2); + float L0 = length(v0-nv2); + float L = (2*L0+L1)/3.0; + float r = h/max(L,1e-3f); + vec3 cnv1 = v0+r*(nv1-v0); + vec3 cnv2 = cnv1+r*(nv2-nv1); + + //Update data + indata[index].v2.xyz = cnv2; + indata[index].v1.xyz = cnv1; + + //Culling + bool culled = false; + vec3 dirc = normalize(vec3(camera.view * vec4(side,0.0f))); + vec3 dirb = normalize(vec3(camera.view * vec4(v0,1.0f))); + culled = culled || (abs(dot(dirc,dirb))>0.9f?true:false); + + vec3 m = 0.25*v0+0.5*cnv1+0.25*cnv2; + bool inViewFrustrum = checkInViewFrustrum(v0)||checkInViewFrustrum(m)||checkInViewFrustrum(cnv2); + culled = culled || (!inViewFrustrum); - // TODO: Apply forces on every blade and update the vertices in the buffer + vec3 posCamSpace = vec3(camera.view*vec4(v0,1.0f)); + vec3 upCamSpace = vec3(camera.view*vec4(up,0.0f)); + vec3 viewLeft = cross(upCamSpace,posCamSpace); + vec3 viewFront = normalize(cross(viewLeft, upCamSpace)); + float dproj = dot(viewFront, posCamSpace); + int n=pcs.numCullLevels; + if((index%n)>floor(n*(1-dproj/pcs.maxCullDist))) culled = true; + + if(!culled) + { + uint nIdx = atomicAdd(numBlades.vertexCount, 1); + outdata[nIdx] = indata[index]; + } + - // 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 + /* + outdata[index] = indata[index]; + if (index == 0) { + numBlades.vertexCount = pcs.numBlades; + }*/ } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..501ebd0 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,15 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs - +layout(location = 0) in vec2 inUV; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - - outColor = vec4(1.0); + float u = abs(inUV.x-0.5f)*2.0f; + vec3 col = mix(vec3(0.208,0.469,0.231),vec3(0.0,0.6,0.0),u); + col = mix(col,vec3(0.1,0.3,0.1), 1.0-inUV.y); + + outColor = vec4(col,1.0f); + //outColor = vec4(1.0f); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..81d0512 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -1,7 +1,6 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable -layout(vertices = 1) out; layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; @@ -9,18 +8,37 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation control shader inputs and outputs +layout(vertices = 1) out; + +layout (location = 0) in vec3 inV0[]; +layout (location = 1) in vec3 inV1[]; +layout (location = 2) in vec3 inV2[]; +layout (location = 3) in vec3 in_dir_height_width[]; + +layout (location = 0) out vec3 outV0[]; +layout (location = 1) out vec3 outV1[]; +layout (location = 2) out vec3 outV2[]; +layout (location = 3) out vec3 out_dir_height_width[]; 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 + outV0[gl_InvocationID] = inV0[gl_InvocationID]; + outV1[gl_InvocationID] = inV1[gl_InvocationID]; + outV2[gl_InvocationID] = inV2[gl_InvocationID]; + out_dir_height_width[gl_InvocationID] = in_dir_height_width[gl_InvocationID]; + const float min_dist=2.0f; + const float max_dist=20.0f; + float dist = length(vec3(camera.view*vec4(inV0[gl_InvocationID],1.0f))); + int levels = int(mix(10.0,3.0,clamp((dist-min_dist)/(max_dist-min_dist),0.0,1.0))); // 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] = levels; + gl_TessLevelInner[1] = levels; + gl_TessLevelOuter[0] = levels; + gl_TessLevelOuter[1] = levels; + gl_TessLevelOuter[2] = levels; + gl_TessLevelOuter[3] = levels; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..71124aa 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,30 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation evaluation shader inputs and outputs +layout (location = 0) in vec3 inV0[]; +layout (location = 1) in vec3 inV1[]; +layout (location = 2) in vec3 inV2[]; +layout (location = 3) in vec3 in_dir_height_width[]; + +layout(location = 0) out vec2 outUV; void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; - + + float angle = in_dir_height_width[0].x; + vec3 t1 = vec3(cos(angle),0.0f,sin(angle)); + vec3 a = inV0[0]+v*(inV1[0]-inV0[0]); + vec3 b = inV1[0]+v*(inV2[0]-inV1[0]); + vec3 c = a+v*(b-a); + vec3 c0 = c-t1*in_dir_height_width[0].z; + vec3 c1 = c+t1*in_dir_height_width[0].z; + //vec3 t0 = normalize(b-a); + //vec3 n = normalize(cross(t0, t1)); + //float t = u; + float t = u+0.5*v-u*v; // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + gl_Position = vec4((1-t)*c0+t*c1,1.0f); + gl_Position = camera.proj * camera.view * gl_Position; + outUV = vec2(u,v); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..b5c467f 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -2,16 +2,31 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable -layout(set = 1, binding = 0) uniform ModelBufferObject { - mat4 model; -}; + + // TODO: Declare vertex shader inputs and outputs +layout(location = 0) in vec4 v0; +layout(location = 1) in vec4 v1; +layout(location = 2) in vec4 v2; +layout(location = 3) in vec4 up; -out gl_PerVertex { - vec4 gl_Position; -}; +layout (location = 0) out vec3 outV0; +layout (location = 1) out vec3 outV1; +layout (location = 2) out vec3 outV2; +layout (location = 3) out vec3 out_dir_height_width; +//out gl_PerVertex { +// vec4 gl_Position; +//}; void main() { - // TODO: Write gl_Position and any other shader outputs + //TODO: Write gl_Position and any other shader outputs + vec4 worldV0 = vec4(v0.xyz, 1.0f); + vec4 worldV1 = vec4(v1.xyz, 1.0f); + vec4 worldV2 = vec4(v2.xyz, 1.0f); + //gl_Position = worldV0; + outV0 = worldV0.xyz; + outV1 = worldV1.xyz; + outV2 = worldV2.xyz; + out_dir_height_width = vec3(v0.w,v1.w,v2.w); }