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..b7ad890 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,98 @@ 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) +Jiajun Li -### (TODO: Your README) +Linkedin: [link](https://www.linkedin.com/in/jiajun-li-5063a4217/) -*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. +Tested on: Windows 10, i7-12700 @ 2.10GHz, 32GB, RTX3080 12GB + +CUDA Compute Capability: 8.6 + +## **Overview** + +![](img/overview.gif) + +This project implements an efficient grass rendering using Vulkan based on [this paper](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf). + +## **Grass Model** + +![](img/blade_model.jpg) + +This project uses a Bezier curve to represent grass. The control points are: + +* **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 +* **v2**: a physical guide for which we simulate forces on + +Additionally, we have these information to control grass's looking and behavior: + +* **up**: the blade's up vector +* **Orientation**: the orientation of grass's face +* **Height and Width**: grass's shapes +* **Stifness**: describes how stiff grass will react to forces + +## **Simulate Forces** + +There are three type of forces in this simulation: gravity, recovery forces and wind. + +### **Gravity** + +![](img/gravity.png) + +Gravity consists of two components: a downward earth gravity gE and a front face gravity gF. The face gravity serves as a bending direction guide for the grass. Currently since gravity is the only force in the scene, grass will lay flat on the ground. + +### **Recovery** + +![](img/recovery.png) + +Recovery forces is the counterforce from the previous applied forces, and its direction points to the current position from the grass initial position. This way grass will be simulated as a mass-spring system and will produce a realisic feeling. With recovery forces added, the grass now can stand on the ground. + +### **Wind** + +![](img/wind.gif) + +Wind force is the custom part of this project and can be varied in different implementations. In my implementaion, I applied an cosine wave forces in the direction (0.2, 0, 0.5) and add some 2D perlin noise so that the grass wave will look a little less 'uniform'. + + +### **Grass Culling** + +There are three types of culling implemented: orientation culling, view frustum culling and distance culling. + +### **Orientation Culling** + +![](img/orientation_culling.gif) + +Orientatioin culling method takes the dot product of grass width direction and view direction. If the dot product is greater than a certain threshold (the threshold in the above gif is 0.9), then it means grass is almost perpendicular to the camera and thus will be culled. + +### **View Frustum Culling** + +![](img/frustum_culling.gif) + +View Frustum Culling will cull grass outside the view frustum, which is determined by transforming the grass to NDC coordinates and check if the 2d point will be outsider a certain screen width. In the above example, the culling screen width is set to 80% percent of the sceen width so we can see that grass near the 10% percent of the edges in both left and right sides are culled. + +### **Distance Culling** + +![](img/distance_culling.gif) + +Distance Culling will cull grass based on the distance between grass and camera. It designates grass into a serise of baskets and apply different portions of culling based on their distance(closer baskets will have less culling). In the above example, the max culling distance is 20.0 and the culling number N is 8. + +## **Performance Analysis** + +### **How culling improve performance** + +![](img/analysis_1.png) + +In the above testing, all three culling are enabled and the camera is placed close to the center of the grass and the culling distance is set to a small number to ensure all three culling methods will have effects. + +As we can see from the chart that FPS drops when grass number increasese. Moreover, for all situations, tests enabling culling have around 3 times higher average FPS than tests where no culling applied. This shows that culling does help incrase performance in an effective way. + +### **Impact of each culling method** + +![](img/analysis_2.png) + + The camera is placed near the center of the grass plane again so all culling methods will actually have effects. The face culling threshold is 0.9, the view frustum culling width is 110% and the distance culling distance is 20 and N is 8. + +As we can see from the chart, frustum culling has the overall best performance, followed by distance culling, then the orientation culling. They all have improved performance than not culling in the previous culling analysis. + +One thing to notice is that the culling result greatly depends on camera position and culling configurations so the above result should only apply to this perticular setting. diff --git a/diff_culling.xlsx b/diff_culling.xlsx new file mode 100644 index 0000000..dc6ca8b Binary files /dev/null and b/diff_culling.xlsx differ diff --git a/diff_grass_num.xlsx b/diff_grass_num.xlsx new file mode 100644 index 0000000..e29dcdc Binary files /dev/null and b/diff_grass_num.xlsx differ diff --git a/img/analysis_1.png b/img/analysis_1.png new file mode 100644 index 0000000..f7477db Binary files /dev/null and b/img/analysis_1.png differ diff --git a/img/analysis_2.png b/img/analysis_2.png new file mode 100644 index 0000000..434854e Binary files /dev/null and b/img/analysis_2.png differ diff --git a/img/distance_culling.gif b/img/distance_culling.gif new file mode 100644 index 0000000..d31a12f 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..c5897a0 Binary files /dev/null and b/img/frustum_culling.gif differ diff --git a/img/gravity.png b/img/gravity.png new file mode 100644 index 0000000..3d6293e Binary files /dev/null and b/img/gravity.png differ diff --git a/img/orientation_culling.gif b/img/orientation_culling.gif new file mode 100644 index 0000000..f2e2cb0 Binary files /dev/null and b/img/orientation_culling.gif differ diff --git a/img/overview.gif b/img/overview.gif new file mode 100644 index 0000000..a77dd80 Binary files /dev/null and b/img/overview.gif differ diff --git a/img/recovery.png b/img/recovery.png new file mode 100644 index 0000000..4947ed0 Binary files /dev/null and b/img/recovery.png differ diff --git a/img/wind.gif b/img/wind.gif new file mode 100644 index 0000000..b62b93d Binary files /dev/null and b/img/wind.gif differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..92148c2 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -44,8 +44,10 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode indirectDraw.firstVertex = 0; 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, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, bladesBuffer, bladesBufferMemory); + BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory); + //BufferUtils::CreateBufferFromData(device, commandPool, 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); } diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..9779c20 100644 --- a/src/Blades.h +++ b/src/Blades.h @@ -4,11 +4,11 @@ #include #include "Model.h" -constexpr static unsigned int NUM_BLADES = 1 << 13; +constexpr static unsigned int NUM_BLADES = 1 << 14; // 1 << 13 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.13f; // 0.1f +constexpr static float MAX_WIDTH = 0.16f; // 0.14f constexpr static float MIN_BEND = 7.0f; constexpr static float MAX_BEND = 13.0f; diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..2fb12e9 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,40 @@ void Renderer::CreateComputeDescriptorSetLayout() { // TODO: Create the descriptor set layout for the compute pipeline // Remember this is like a class definition stating why types of information // will be stored at each binding + + 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 +250,9 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + + // For compute shader + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(scene->GetBlades().size() * 3) }, }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,6 +357,46 @@ 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 descriptor 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 grass descriptor set"); + } + + std::vector descriptorWrites(grassDescriptorSets.size()); + + for (size_t i = 0; i < scene->GetBlades().size(); ++i) + { + VkDescriptorBufferInfo grassBufferInfo{}; + grassBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + grassBufferInfo.offset = 0; + grassBufferInfo.range = sizeof(ModelBufferObject); + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = grassDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = &grassBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +437,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 + + std::vector blades = scene->GetBlades(); + + // Describe the desciptor set + computeDescriptorSets.resize(blades.size()); + 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"); + } + + // Write descriptor sets + std::vector descriptorWrites(3 * blades.size()); // 3 types of buffers + + for (uint32_t i = 0; i < blades.size(); ++i) + { + // Grass blades + VkDescriptorBufferInfo bladesBufferInfo = {}; + bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + bladesBufferInfo.offset = 0; + bladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + 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 = &bladesBufferInfo; + descriptorWrites[3 * i + 0].pImageInfo = nullptr; + descriptorWrites[3 * i + 0].pTexelBufferView = nullptr; + + // Cull blades + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + 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; + + // Number of blades + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect); + + 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 descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +865,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 = {}; @@ -884,6 +1037,11 @@ 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 (uint32_t j = 0; j < scene->GetBlades().size(); ++j) + { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[j], 0, nullptr); + vkCmdDispatch(computeCommandBuffer, NUM_BLADES / 32, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1134,14 @@ void Renderer::RecordCommandBuffers() { VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() }; VkDeviceSize offsets[] = { 0 }; // TODO: Uncomment this when the buffers are populated - // vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); // TODO: Bind the descriptor set for each grass blades model + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &grassDescriptorSets[j], 0, nullptr); // Draw // TODO: Uncomment this when the buffers are populated - // vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); + vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); } // End render pass @@ -1057,6 +1216,8 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + //vkDestroyDescriptorSetLayout(logicalDevice, grassDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..36caa9b 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 grassDescriptorSets; + std::vector computeDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..d9dcf81 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,7 +67,7 @@ namespace { int main() { static constexpr char* applicationName = "Vulkan Grass Rendering"; - InitializeWindow(640, 480, applicationName); + InitializeWindow(1280, 768, applicationName); unsigned int glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -164,4 +164,4 @@ int main() { delete instance; DestroyWindow(); return 0; -} +} \ No newline at end of file diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..71cc18b 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -1,6 +1,31 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +// Force Settings +#define ENABLE_GRAVITY true +#define ENABLE_RECOVERY true +#define ENABLE_WIND true + +#define GRAVITY 4.0 + +#define WIND_DIR normalize(vec3(0.2, 0, 0.5)) +#define WIND_STRENGTH 5.0 +#define WING_FREQUENCY 0.5 +#define WIND_LENGTH 0.2 + +// Culling Settings +#define ENABLE_ORIENTATION_CULLING true +#define ENABLE_FRUSTUM_CULLING true +#define ENABLE_DIESANCE_CULLING true + +#define FACE_CULLING_THRESHOLD 0.9 + +#define FRUSTUM_CULLING_THRESHOLD 1.1 + +#define DISTANCE_CULLING_MAX_DIST 20.0 + +#define DISTANCE_CULLING_N 8 + #define WORKGROUP_SIZE 32 layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; @@ -26,31 +51,205 @@ struct Blade { // 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 Blades +{ + Blade blades[]; +}; + +layout(set = 2, binding = 1) buffer CulledBlades +{ + Blade culledBlades[]; +}; + +layout(set = 2, binding = 2) buffer NumBlades +{ + uint vertexCount; // Write the number of blades remaining here + uint instanceCount; // = 1 + uint firstVertex; // = 0 + uint firstInstance; // = 0 + } numBlades; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } -void main() { + +vec2 random2(vec2 p) +{ + return fract(sin(vec2(dot(p, vec2(127.1f, 311.7f)), + dot(p, vec2(269.5f,183.3f)))) + * 43758.5453f); +} + +float surflet(vec2 p, vec2 gridPoint) +{ + // Compute the distance between p and the grid point along each axis, and warp it with a + // quintic function so we can smooth our cells + vec2 t2 = abs(p - gridPoint); + //vec2 t = vec2(1.f) - 6.f * pow(t2, vec2(5.f)) + 15.f * pow(t2, vec2(4.f)) - 10.f * pow(t2, vec2(3.f)); + vec2 t; + t.x = 1.f - 6.f*t2.x*t2.x*t2.x*t2.x*t2.x + 15.f*t2.x*t2.x*t2.x*t2.x - 10.f*t2.x*t2.x*t2.x; + t.y = 1.f - 6.f*t2.y*t2.y*t2.y*t2.y*t2.y + 15.f*t2.y*t2.y*t2.y*t2.y - 10.f*t2.y*t2.y*t2.y; + // Get the random vector for the grid point (assume we wrote a function random2 + // that returns a vec2 in the range [0, 1]) + vec2 gradient = random2(gridPoint) * 2.f - vec2(1.f,1.f); + // Get the vector from the grid point to P + vec2 diff = p - gridPoint; + // Get the value of our height field by dotting grid->P with our gradient + float height = dot(diff, gradient); + // Scale our height field (i.e. reduce it) by our polynomial falloff function + return height * t.x * t.y; +} + +float perlin2D(vec2 uv) +{ + float surfletSum = 0.f; + // Iterate over the four integer corners surrounding uv + for(int dx = 0; dx <= 1; ++dx) { + for(int dy = 0; dy <= 1; ++dy) { + surfletSum += surflet(uv, floor(uv) + vec2(dx, dy)); + } + } + return surfletSum; +} + +void main() +{ // Reset the number of blades to 0 - if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + if (gl_GlobalInvocationID.x == 0) + { + numBlades.vertexCount = 0; } barrier(); // Wait till all threads reach this point // TODO: Apply forces on every blade and update the vertices in the buffer + Blade blade = blades[gl_GlobalInvocationID.x]; + + vec3 eye = vec3(inverse(camera.view) * vec4(0.f, 0.f, 0.f, 1.f)); + vec3 up = blade.up.xyz; + vec3 v0 = blade.v0.xyz; + vec3 v1 = blade.v1.xyz; + vec3 v2 = blade.v2.xyz; + float theta = blade.v0.w; + float height = blade.v1.w; + float k = blade.up.w; // stiffness coefficient + + //vec3 bitangent = vec3(cos(theta), 0, -sin(theta)); + vec3 bitangent = vec3(-cos(theta), 0, sin(theta)); + + // Forces + vec3 recovery = vec3(0.0); + vec3 gravity = vec3(0.0); + vec3 wind = vec3(0.0); + + // 1. Recovery + if(ENABLE_RECOVERY) + { + vec3 i_v2 = v0 + up * height; + recovery = (i_v2 - v2) * k; + } + + // 2. Gravity + if(ENABLE_GRAVITY) + { + vec3 gE = vec3(0.0, -1.0, 0.0) * GRAVITY; + vec3 nor = cross(up, bitangent); + vec3 gF = 0.25 * length(gE) * nor; + gravity = gE + gF; + } + + // 3. Wind + if(ENABLE_WIND) + { + //wind = WIND_DIR * WIND_STRENGTH * cos(WING_FREQUENCY * totalTime + WIND_LENGTH * v0); + wind = WIND_DIR * WIND_STRENGTH * cos(WING_FREQUENCY * totalTime + WIND_LENGTH * v0 + perlin2D(v0.xz)); + } + + // Total force + vec3 deltaPos = (recovery + gravity + wind) * deltaTime; + + // Update v2 + v2 += deltaPos; + v2 -= up * min(dot(up, v2 - v0), 0); // state validation + + // Update v1 + float lproj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + height * up * max(1 - lproj / height, 0.05 * max(lproj/height, 1)); + + // Ensure length of curve is valid + float L0 = length(v2 - v0); + float L1 = length(v2 - v1) + length(v1 - v0); + float L = (2 * L0 + L1) / 3; + float r = height / L; + + v1 = v0 + r * (v1 - v0); + v2 = v1 + r * (v2 - v1); + + // Update buffer + blades[gl_GlobalInvocationID.x].v1.xyz = v1.xyz; + blades[gl_GlobalInvocationID.x].v2.xyz = v2.xyz; + + barrier(); + // 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(ENABLE_ORIENTATION_CULLING || ENABLE_FRUSTUM_CULLING || ENABLE_DIESANCE_CULLING) + { + // Orientation culling + if(ENABLE_ORIENTATION_CULLING) + { + + vec3 viewDir = eye - v0; + if(abs(dot(normalize(viewDir), normalize(bitangent))) > FACE_CULLING_THRESHOLD) + { + return; + } + } + + // Frustum culling + if(ENABLE_FRUSTUM_CULLING) + { + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; + mat4 viewProj = camera.proj * camera.view; + + // Points to NDC + vec4 normalizedV0 = viewProj * vec4(v0, 1.f); + vec4 normalizedM = viewProj * vec4(m, 1.f); + vec4 normalizedV2 = viewProj * vec4(v2, 1.f); + normalizedV0 /= normalizedV0.w; + normalizedM /= normalizedM.w; + normalizedV2 /= normalizedV2.w; + + bool testV0 = inBounds(normalizedV0.x, FRUSTUM_CULLING_THRESHOLD) && + inBounds(normalizedV0.y, FRUSTUM_CULLING_THRESHOLD); + bool testM = inBounds(normalizedM.x, FRUSTUM_CULLING_THRESHOLD) && + inBounds(normalizedM.y, FRUSTUM_CULLING_THRESHOLD); + bool testV2 = inBounds(normalizedV2.x, FRUSTUM_CULLING_THRESHOLD) && + inBounds(normalizedV2.y, FRUSTUM_CULLING_THRESHOLD); + + if(!testV0 && !testM && !testV2) + { + return; + } + } + + // Distance culling + if(ENABLE_DIESANCE_CULLING) + { + float dProj = length(v0 - eye - up * dot((v0 - eye), up)); + + if(mod(gl_GlobalInvocationID.x, DISTANCE_CULLING_N) >= + (DISTANCE_CULLING_N * (1 - dProj / DISTANCE_CULLING_MAX_DIST))) + { + return; + } + } + } + + uint addIndex = atomicAdd(numBlades.vertexCount, 1); + culledBlades[addIndex] = blades[gl_GlobalInvocationID.x]; +} \ No newline at end of file diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..a8117fa 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -1,3 +1,5 @@ + + #version 450 #extension GL_ARB_separate_shader_objects : enable @@ -8,10 +10,22 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 in_pos; +layout(location = 1) in vec3 in_nor; + layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - outColor = vec4(1.0); -} + float ambient = 0.15; + + vec3 lightPos = vec3(100.0, 100.0, 0.0); + vec3 dir = normalize(lightPos - in_pos); + float lambert = max(dot(dir, normalize(in_nor)), 0.0); + + float cofficient = min(lambert + ambient, 1.0); + vec3 col = cofficient * vec3(0.0, 1.0, 0.0); + + outColor = vec4(col, 1.0); +} \ No newline at end of file diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..1eeb7b7 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -1,6 +1,10 @@ + + #version 450 #extension GL_ARB_separate_shader_objects : enable +#define TL 8 // Tessellation Layer + layout(vertices = 1) out; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -10,17 +14,39 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { // 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[]; + +//in gl_PerVertex +//{ +// vec4 gl_Position; +//} gl_in[gl_MaxPatchVertices]; + + +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] = TL; + gl_TessLevelInner[1] = TL; + gl_TessLevelOuter[0] = TL; + gl_TessLevelOuter[1] = TL; + gl_TessLevelOuter[2] = TL; + gl_TessLevelOuter[3] = TL; } + diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..eaea747 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -1,3 +1,5 @@ + + #version 450 #extension GL_ARB_separate_shader_objects : enable @@ -9,10 +11,51 @@ 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 out_pos; +layout(location = 1) out vec3 out_nor; + +//vec3 lerp3(vec3 v1, vec3 v2, float u) +//{ +// return (1 - u) * v1 + u * v2; +//} 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 + + vec3 v0 = in_v0[0].xyz; + vec3 v1 = in_v1[0].xyz; + vec3 v2 = in_v2[0].xyz; + vec3 up = in_up[0].xyz; + float width = in_v2[0].w; + float theta = in_v0[0].w; + + vec3 t1 = normalize(vec3(-cos(theta), 0, sin(theta))); + + // De Casteljau's algorithm + vec3 a = mix(v0, v1, v); + vec3 b = mix(v1, v2, v); + vec3 c = mix(a, b, v); + + vec3 t0 = normalize(b - a); + + vec3 c0 = c - width * t1; + vec3 c1 = c + width * t1; + + // New position + float t = u + 0.5 * v - u * v; + out_pos = mix(c0, c1, t); + + // New normal + out_nor = normalize(cross(t0, t1)); + + gl_Position = camera.proj * camera.view * vec4(out_pos, 1.0); } + diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..11004da 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -1,17 +1,41 @@ + #version 450 #extension GL_ARB_separate_shader_objects : enable -layout(set = 1, binding = 0) uniform ModelBufferObject { +layout(set = 1, binding = 0) uniform ModelBufferObject +{ mat4 model; }; // TODO: Declare vertex shader inputs and outputs -out gl_PerVertex { - vec4 gl_Position; -}; +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; +//}; -void main() { + +void main() +{ // TODO: Write gl_Position and any other shader outputs + + // v0 to be the vertex position + gl_Position = model * vec4(in_v0.xyz, 1.f); + + // To world space + out_v0 = vec4((model * vec4(in_v0.xyz, 1.f)).xyz, in_v0.w); + out_v1 = vec4((model * vec4(in_v1.xyz, 1.f)).xyz, in_v1.w); + out_v2 = vec4((model * vec4(in_v2.xyz, 1.f)).xyz, in_v2.w); + out_up = vec4((model * vec4(in_up.xyz, 0.f)).xyz, in_up.w); }