diff --git a/README.md b/README.md index fc0e545..3e94a1f 100644 --- a/README.md +++ b/README.md @@ -1,128 +1,33 @@ -Instructions - Vulkan Grass Rendering -======================== +Vulkan Grass Rendering +====================== -This is due **Sunday 11/4, evening at midnight**. +**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 6** -**Summary:** -In this project, you will use Vulkan to implement a grass simulator and renderer. You will -use compute shaders to perform physics calculations on Bezier curves that represent individual -grass blades in your application. Since rendering every grass blade on every frame will is fairly -inefficient, you will also use compute shaders to cull grass blades that don't contribute to a given frame. -The remaining blades will be passed to a graphics pipeline, in which you will write several shaders. -You will write a vertex shader to transform Bezier control points, tessellation shaders to dynamically create -the grass geometry from the Bezier curves, and a fragment shader to shade the grass blades. +* Liam Dugan -- Fall 2018 + * [LinkedIn](https://www.linkedin.com/in/liam-dugan-95a961135/), [personal website](http://liamdugan.com/) +* Tested on: Windows 10, Intel(R) Xeon(R) CPU E5-2687W v3 @ 3.10GHz 32GB, TITAN V 28.4GB (Lab Computer) -The base code provided includes all of the basic Vulkan setup, including a compute pipeline that will run your compute -shaders and two graphics pipelines, one for rendering the geometry that grass will be placed on and the other for -rendering the grass itself. Your job will be to write the shaders for the grass graphics pipeline and the compute pipeline, -as well as binding any resources (descriptors) you may need to accomplish the tasks described in this assignment. + -![](img/grass.gif) ![](img/grass2.gif) +What is Grass Rendering? +============= +This project is an implementation of the 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). -You are not required to use this base code if you don't want -to. You may also change any part of the base code as you please. -**This is YOUR project.** The above .gifs are just examples that you -can use as a reference to compare to. Feel free to get creative with your implementations! + -**Important:** -- If you are not in CGGT/DMD, you may replace this project with a GPU compute -project. You MUST get this pre-approved by Ottavio before continuing! +It involves two different rendering passes. First there's a compute pass which calculates the forces exerted on the blades and decides whether or not the given blade should be culled, then the second render pass tessellates the culled grass blades to different levels of detail based on the bezier curve points' distance from the camera and renders them with simple Lambertian shading to get the final blade output. -### Contents +## Representing Grass as Bezier Curves -* `src/` C++/Vulkan source files. - * `shaders/` glsl shader source files - * `images/` images used as textures within graphics pipelines -* `external/` Includes and static libraries for 3rd party libraries. -* `img/` Screenshots and images to use in your READMEs - -### Installing Vulkan - -In order to run a Vulkan project, you first need to download and install the [Vulkan SDK](https://vulkan.lunarg.com/). -Make sure to run the downloaded installed as administrator so that the installer can set the appropriate environment -variables for you. - -Once you have done this, you need to make sure your GPU driver supports Vulkan. Download and install a -[Vulkan driver](https://developer.nvidia.com/vulkan-driver) from NVIDIA's website. - -Finally, to check that Vulkan is ready for use, go to your Vulkan SDK directory (`C:/VulkanSDK/` unless otherwise specified) -and run the `cube.exe` example within the `Bin` directory. IF you see a rotating gray cube with the LunarG logo, then you -are all set! - -### Running the code - -While developing your grass renderer, you will want to keep validation layers enabled so that error checking is turned on. -The project is set up such that when you are in `debug` mode, validation layers are enabled, and when you are in `release` mode, -validation layers are disabled. After building the code, you should be able to run the project without any errors. You will see a plane with a grass texture on it to begin with. - -![](img/cube_demo.png) - -## Requirements - -**Ask on the mailing list for any clarifications.** - -In this project, you are given the following code: - -* The basic setup for a Vulkan project, including the swapchain, physical device, logical device, and the pipelines described above. -* Structs for some of the uniform buffers you will be using. -* Some buffer creation utility functions. -* A simple interactive camera using the mouse. - -You need to implement the following features/pipeline stages: - -* Compute shader (`shaders/compute.comp`) -* Grass pipeline stages - * Vertex shader (`shaders/grass.vert') - * Tessellation control shader (`shaders/grass.tesc`) - * Tessellation evaluation shader (`shaders/grass.tese`) - * Fragment shader (`shaders/grass.frag`) -* Binding of any extra descriptors you may need - -See below for more guidance. - -## Base Code Tour - -Areas that you need to complete are -marked with a `TODO` comment. Functions that are useful -for reference are marked with the comment `CHECKITOUT`. - -* `src/main.cpp` is the entry point of our application. -* `src/Instance.cpp` sets up the application state, initializes the Vulkan library, and contains functions that will create our -physical and logical device handles. -* `src/Device.cpp` manages the logical device and sets up the queues that our command buffers will be submitted to. -* `src/Renderer.cpp` contains most of the rendering implementation, including Vulkan setup and resource creation. You will -likely have to make changes to this file in order to support changes to your pipelines. -* `src/Camera.cpp` manages the camera state. -* `src/Model.cpp` manages the state of the model that grass will be created on. Currently a plane is hardcoded, but feel free to -update this with arbitrary model loading! -* `src/Blades.cpp` creates the control points corresponding to the grass blades. There are many parameters that you can play with -here that will change the behavior of your rendered grass blades. -* `src/Scene.cpp` manages the scene state, including the model, blades, and simualtion time. -* `src/BufferUtils.cpp` provides helper functions for creating buffers to be used as descriptors. - -We left out descriptions for a couple files that you likely won't have to modify. Feel free to investigate them to understand their -importance within the scope of the project. - -## Grass Rendering - -This project is an implementation of the 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). -Please make sure to use this paper as a primary resource while implementing your grass renderers. It does a great job of explaining -the key algorithms and math you will be using. Below is a brief description of the different components in chronological order of how your renderer will -execute, but feel free to develop the components in whatever order you prefer. - -We recommend starting with trying to display the grass blades without any forces on them before trying to add any forces on the blades themselves. Here is an example of what that may look like: - -![](img/grass_basic.gif) - -### Representing Grass as Bezier Curves - -In this project, grass blades will be represented as Bezier curves while performing physics calculations and culling operations. +Grass blades are represented as Bezier curves while performing physics calculations and culling operations. Each Bezier curve has three control points. * `v0`: the position of the grass blade on the geomtry * `v1`: a Bezier curve guide that is always "above" `v0` with respect to the grass blade's up vector (explained soon) * `v2`: a physical guide for which we simulate forces on -We also need to store per-blade characteristics that will help us simulate and tessellate our grass blades correctly. + + +We also store per-blade characteristics that will help us simulate and tessellate our grass blades correctly. * `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at `v0` * Orientation: the orientation of the grass blade's face * Height: the height of the grass blade @@ -132,20 +37,8 @@ We also need to store per-blade characteristics that will help us simulate and t We can pack all this data into four `vec4`s, such that `v0.w` holds orientation, `v1.w` holds height, `v2.w` holds width, and `up.w` holds the stiffness coefficient. -![](img/blade_model.jpg) - -### Simulating Forces - -In this project, you will be simulating forces on grass blades while they are still Bezier curves. This will be done in a compute -shader using the compute pipeline that has been created for you. Remember that `v2` is our physical guide, so we will be -applying transformations to `v2` initially, then correcting for potential errors. We will finally update `v1` to maintain the appropriate -length of our grass blade. - -#### Binding Resources - -In order to update the state of your grass blades on every frame, you will need to create a storage buffer to maintain the grass data. -You will also need to pass information about how much time has passed in the simulation and the time since the last frame. To do this, -you can extend or create descriptor sets that will be bound to the compute pipeline. +## Simulating Forces +In this project, the forces on grass blades are simulated in a compute shader while they are still Bezier curves. The simulated forces are: #### Gravity @@ -167,41 +60,33 @@ Once we have `iv2`, we can compute the recovery forces as `r = (iv2 - v2) * stif #### Wind -In order to simulate wind, you are at liberty to create any wind function you want! In order to have something interesting, -you can make the function depend on the position of `v0` and a function that changes with time. Consider using some combination -of sine or cosine functions. - -Your wind function will determine a wind direction that is affecting the blade, but it is also worth noting that wind has a larger impact on -grass blades whose forward directions are parallel to the wind direction. The paper describes this as a "wind alignment" term. We won't go -over the exact math here, but use the paper as a reference when implementing this. It does a great job of explaining this! +In order to simulate wind, a sine function based on the x and y position of `v0` along with the `totalTime` passed is used to calculate the magnitude of the wind vector. This wind direction has a larger impact on +grass blades whose forward directions are parallel to the wind direction. The paper describes this as a "wind alignment" term. -Once you have a wind direction and a wind alignment term, your total wind force (`w`) will be `windDirection * windAlignment`. +Once we have a wind direction and a wind alignment term, the total wind force (`w`) is calculated by doing `windDirection * windAlignment`. #### Total force -We can then determine a translation for `v2` based on the forces as `tv2 = (gravity + recovery + wind) * deltaTime`. However, we can't simply +We then determine a translation for `v2` based on the forces as `tv2 = (gravity + recovery + wind) * deltaTime`. However, we can't simply apply this translation and expect the simulation to be robust. Our forces might push `v2` under the ground! Similarly, moving `v2` but leaving `v1` in the same position will cause our grass blade to change length, which doesn't make sense. -Read section 5.2 of the paper in order to learn how to determine the corrected final positions for `v1` and `v2`. +We use section 5.2 of the paper in order to learn how to determine the corrected final positions for `v1` and `v2`. -### Culling tests - -Although we need to simulate forces on every grass blade at every frame, there are many blades that we won't need to render +## Culling tests +Although forces are simulated on every grass blade at every frame, there are many blades that do not need to be rendered due to a variety of reasons. Here are some heuristics we can use to cull blades that won't contribute positively to a given frame. -#### Orientation culling - -Consider the scenario in which the front face direction of the grass blade is perpendicular to the view vector. Since our grass blades +### Orientation culling + Consider the scenario in which the front face direction of the grass blade is perpendicular to the view vector. Since our grass blades won't have width, we will end up trying to render parts of the grass that are actually smaller than the size of a pixel. This could lead to aliasing artifacts. In order to remedy this, we can cull these blades! Simply do a dot product test to see if the view vector and front face direction of -the blade are perpendicular. The paper uses a threshold value of `0.9` to cull, but feel free to use what you think looks best. - -#### View-frustum culling +the blade are perpendicular. For this we use a threshold value of `0.8` to cull. -We also want to cull blades that are outside of the view-frustum, considering they won't show up in the frame anyway. To determine if +### View-frustum culling + We also want to cull blades that are outside of the view-frustum, considering they won't show up in the frame anyway. To determine if a grass blade is in the view-frustum, we want to compare the visibility of three points: `v0, v2, and m`, where `m = (1/4)v0 * (1/2)v1 * (1/4)v2`. Notice that we aren't using `v1` for the visibility test. This is because the `v1` is a Bezier guide that doesn't represent a position on the grass blade. We instead use `m` to approximate the midpoint of our Bezier curve. @@ -210,43 +95,54 @@ If all three points are outside of the view-frustum, we will cull the grass blad blades a little more conservatively. This can help with cases in which the Bezier curve is technically not visible, but we might be able to see the blade if we consider its width. -#### Distance culling - -Similarly to orientation culling, we can end up with grass blades that at large distances are smaller than the size of a pixel. This could lead to additional +### Distance culling + Similarly to orientation culling, we can end up with grass blades that at large distances are smaller than the size of a pixel. This could lead to additional artifacts in our renders. In this case, we can cull grass blades as a function of their distance from the camera. -You are free to define two parameters here. +We define two parameters here. * A max distance afterwhich all grass blades will be culled. * A number of buckets to place grass blades between the camera and max distance into. -Define a function such that the grass blades in the bucket closest to the camera are kept while an increasing number of grass blades +The grass blades in the bucket closest to the camera are kept while an increasing number of grass blades are culled with each farther bucket. -#### Occlusion culling (extra credit) +## Tessellating Bezier curves with varying levels of detail +In this project, we pass in each Bezier curve as a single patch to be processed by your grass graphics pipeline. We tessellate this patch into a quad with a quadratic shape + +In the tessellation control shader, we set the base level of inner and outer tessellation for when the blades are at `MAX_DISTANCE` to be 2.0 and we set the highest level of tessellation to be `6.0` when the blades are at distance 0 and interpolate between them. + +Performance Analysis +============= +### Blade Culling +The performance we got for blade culling far outshined the performance we got for non-culling, despite having to do much more work in the compute shader. Without culling the blade count we were barely even able to render 4 million blades and we encountered substantial physics bugs on 8 million. As for the approach with culling, we were able to get a stable 25fps on 8 million blades and we hit a mak of 32 million blades (data point not shown). -This type of culling only makes sense if our scene has additional objects aside from the plane and the grass blades. We want to cull grass blades that -are occluded by other geometry. Think about how you can use a depth map to accomplish this! +The reason that culling improves performance so substantially here is the avoidance of entire extra pipeline calls. Despite spending much longer amount of time in the compute shader, that time is made up for by that blade never entering the grass blade render pass. -### Tessellating Bezier curves into grass blades + -In this project, you should pass in each Bezier curve as a single patch to be processed by your grass graphics pipeline. You will tessellate this patch into -a quad with a shape of your choosing (as long as it looks sufficiently like grass of course). The paper has some examples of grass shapes you can use as inspiration. +### Workgroup Size +The optimal workgroup size, as we expected, was 32. The other workgoup sizes gave similar performance, but I believe 32 won out because it is the traditional size of a warp on NVIDIA hardware and so it was likely the GPU was easily able to handle scheduling logical blocks of 32 threads each. -In the tessellation control shader, specify the amount of tessellation you want to occur. Remember that you need to provide enough detail to create the curvature of a grass blade. +![](img/Workgroup.png) -The generated vertices will be passed to the tessellation evaluation shader, where you will place the vertices in world space, respecting the width, height, and orientation information -of each blade. Once you have determined the world space position of each vector, make sure to set the output `gl_Position` in clip space! +From Humble Beginnings +============= +#### The First output I got from the grass blade shader (my orientation vector was off) +![](img/firstOutput.png) -** Extra Credit**: Tessellate to varying levels of detail as a function of how far the grass blade is from the camera. For example, if the blade is very far, only generate four vertices in the tessellation control shader. +#### The First tessellated blades, very naive implementation +![](img/initial.gif) -To build more intuition on how tessellation works, I highly recommend playing with the [helloTessellation sample](https://github.com/CIS565-Fall-2018/Vulkan-Samples/tree/master/samples/5_helloTessellation) -and reading this [tutorial on tessellation](http://in2gpu.com/2014/07/12/tessellation-tutorial-opengl-4-3/). +#### The First blades given an orientation +![](img/progress.gif) -## Resources +#### Initial implementation of the wind function (lambert shader broken) +![](img/windy.gif) -### Links +Credits +============= -The following resources may be useful for this project. +The following resources were very useful in the creation of this project * [Responsive Real-Time Grass Grass Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf) * [CIS565 Vulkan samples](https://github.com/CIS565-Fall-2018/Vulkan-Samples) @@ -254,47 +150,3 @@ The following resources may be useful for this project. * [Vulkan tutorial](https://vulkan-tutorial.com/) * [RenderDoc blog on Vulkan](https://renderdoc.org/vulkan-in-30-minutes.html) * [Tessellation tutorial](http://in2gpu.com/2014/07/12/tessellation-tutorial-opengl-4-3/) - - -## Third-Party Code Policy - -* Use of any third-party code must be approved by asking on our Google Group. -* If it is approved, all students are welcome to use it. Generally, we approve - use of third-party code that is not a core part of the project. For example, - for the path tracer, we would approve using a third-party library for loading - models, but would not approve copying and pasting a CUDA function for doing - refraction. -* Third-party code **MUST** be credited in README.md. -* Using third-party code without its approval, including using another - student's code, is an academic integrity violation, and will, at minimum, - result in you receiving an F for the semester. - - -## README - -* A brief description of the project and the specific features you implemented. -* GIFs of your project in its different stages with the different features being added incrementally. -* A performance analysis (described below). - -### Performance Analysis - -The performance analysis is where you will investigate how... -* Your renderer handles varying numbers of grass blades -* The improvement you get by culling using each of the three culling tests - -## Submit - -If you have modified any of the `CMakeLists.txt` files at all (aside from the -list of `SOURCE_FILES`), mentions it explicity. -Beware of any build issues discussed on the Google Group. - -Open a GitHub pull request so that we can see that you have finished. -The title should be "Project 6: YOUR NAME". -The template of the comment section of your pull request is attached below, you can do some copy and paste: - -* [Repo Link](https://link-to-your-repo) -* (Briefly) Mentions features that you've completed. Especially those bells and whistles you want to highlight - * Feature 0 - * Feature 1 - * ... -* Feedback on the project itself, if any. diff --git a/img/Performance.png b/img/Performance.png new file mode 100644 index 0000000..967def9 Binary files /dev/null and b/img/Performance.png differ diff --git a/img/Workgroup.png b/img/Workgroup.png new file mode 100644 index 0000000..0f69451 Binary files /dev/null and b/img/Workgroup.png differ diff --git a/img/buckets.gif b/img/buckets.gif new file mode 100644 index 0000000..7d662b5 Binary files /dev/null and b/img/buckets.gif differ diff --git a/img/cube_demo.png b/img/cube_demo.png deleted file mode 100644 index 21e928e..0000000 Binary files a/img/cube_demo.png and /dev/null differ diff --git a/img/final.gif b/img/final.gif new file mode 100644 index 0000000..f5e6298 Binary files /dev/null and b/img/final.gif differ diff --git a/img/firstOutput.png b/img/firstOutput.png new file mode 100644 index 0000000..b7d0d4f Binary files /dev/null and b/img/firstOutput.png differ diff --git a/img/grass.gif b/img/frustumculling.gif similarity index 54% rename from img/grass.gif rename to img/frustumculling.gif index 78f008e..249cab9 100644 Binary files a/img/grass.gif and b/img/frustumculling.gif differ diff --git a/img/grass2.gif b/img/grass2.gif deleted file mode 100644 index 3f14616..0000000 Binary files a/img/grass2.gif and /dev/null differ diff --git a/img/grass_basic.gif b/img/grass_basic.gif deleted file mode 100644 index 3b04705..0000000 Binary files a/img/grass_basic.gif and /dev/null differ diff --git a/img/initial.gif b/img/initial.gif new file mode 100644 index 0000000..187d192 Binary files /dev/null and b/img/initial.gif differ diff --git a/img/manyBlades.gif b/img/manyBlades.gif new file mode 100644 index 0000000..66c57a6 Binary files /dev/null and b/img/manyBlades.gif differ diff --git a/img/progress.gif b/img/progress.gif new file mode 100644 index 0000000..1bdbffe Binary files /dev/null and b/img/progress.gif differ diff --git a/img/start.png b/img/start.png new file mode 100644 index 0000000..62fb824 Binary files /dev/null and b/img/start.png differ diff --git a/img/viewFrustumCulling.gif b/img/viewFrustumCulling.gif new file mode 100644 index 0000000..1d28759 Binary files /dev/null and b/img/viewFrustumCulling.gif differ diff --git a/img/windy.gif b/img/windy.gif new file mode 100644 index 0000000..9edac51 Binary files /dev/null and b/img/windy.gif differ diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..914dc1d 100644 --- a/src/Blades.h +++ b/src/Blades.h @@ -4,7 +4,7 @@ #include #include "Model.h" -constexpr static unsigned int NUM_BLADES = 1 << 13; +constexpr static unsigned int NUM_BLADES = 1 << 14; constexpr static float MIN_HEIGHT = 1.3f; constexpr static float MAX_HEIGHT = 2.5f; constexpr static float MIN_WIDTH = 0.1f; diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..520a7f5 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -20,19 +20,23 @@ Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* c CreateCameraDescriptorSetLayout(); CreateModelDescriptorSetLayout(); CreateTimeDescriptorSetLayout(); - CreateComputeDescriptorSetLayout(); + CreateComputeBladesDescriptorSetLayout(); + CreateComputeCulledBladesDescriptorSetLayout(); + CreateComputeNumBladesDescriptorSetLayout(); CreateDescriptorPool(); CreateCameraDescriptorSet(); CreateModelDescriptorSets(); CreateGrassDescriptorSets(); CreateTimeDescriptorSet(); - CreateComputeDescriptorSets(); + CreateComputeBladesDescriptorSets(); + CreateComputeCulledBladesDescriptorSets(); + CreateComputeNumBladesDescriptorSets(); CreateFrameResources(); CreateGraphicsPipeline(); CreateGrassPipeline(); CreateComputePipeline(); + RecordComputeCommandBuffer(); RecordCommandBuffers(); - RecordComputeCommandBuffer(); } void Renderer::CreateCommandPools() { @@ -194,10 +198,70 @@ void Renderer::CreateTimeDescriptorSetLayout() { } } -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 +void Renderer::CreateComputeBladesDescriptorSetLayout() { + // input blade data + 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; + + std::vector bindings = { bladesLayoutBinding }; + + // 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, &computeBladesDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } +} + +void Renderer::CreateComputeCulledBladesDescriptorSetLayout() { + // culled blade data + VkDescriptorSetLayoutBinding culledBladesLayoutBinding = {}; + culledBladesLayoutBinding.binding = 0; + culledBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledBladesLayoutBinding.descriptorCount = 1; + culledBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + culledBladesLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { culledBladesLayoutBinding }; + + // 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, &computeCulledBladesDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } +} + +void Renderer::CreateComputeNumBladesDescriptorSetLayout() { + // num blade data + VkDescriptorSetLayoutBinding numBladesLayoutBinding = {}; + numBladesLayoutBinding.binding = 0; + numBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBladesLayoutBinding.descriptorCount = 1; + numBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numBladesLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { 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, &computeNumBladesDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } } void Renderer::CreateDescriptorPool() { @@ -215,14 +279,21 @@ void Renderer::CreateDescriptorPool() { // Time (compute) { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, - // TODO: Add any additional types and counts of descriptors you will need to allocate + // Blades (compute) + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) }, + + // Culled Blades (compute) + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) }, + + // Num Blades (compute) + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) }, }; VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = static_cast(poolSizes.size()); poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = 5; + poolInfo.maxSets = 7; if (vkCreateDescriptorPool(logicalDevice, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("Failed to create descriptor pool"); @@ -318,8 +389,68 @@ 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 + + // So this one is the camera buffer object + VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + uboLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { uboLayoutBinding }; + + // 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(); + + VkDescriptorSetLayout grassDescriptorSetLayout; + + if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &grassDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } + + // Create Descriptor sets for the grass. + grassDescriptorSets.resize(scene->GetBlades().size()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { grassDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(scene->GetBlades().size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + + std::vector descriptorWrites(grassDescriptorSets.size()); + + // and we for sure at least have to loop through all of these + // literally the only descriptor set we want to create is for the model matrix of each set of blades + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo modelBufferInfo = {}; + modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + modelBufferInfo.offset = 0; + modelBufferInfo.range = sizeof(ModelBufferObject); + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = grassDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = &modelBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -357,9 +488,132 @@ void Renderer::CreateTimeDescriptorSet() { vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } -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 +void Renderer::CreateComputeBladesDescriptorSets() { + // Create the input blade Descriptor set for the compute pipeline + computeBladesDescriptorSets.resize(scene->GetBlades().size()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { computeBladesDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(scene->GetBlades().size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + VkResult Kek_debug = vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeBladesDescriptorSets.data()); + if (Kek_debug != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(scene->GetBlades().size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo computeBladeBufferInfo = {}; + computeBladeBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + computeBladeBufferInfo.offset = 0; + computeBladeBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo bufferInfos[] = { computeBladeBufferInfo }; + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = computeBladesDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = bufferInfos; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); +} + +void Renderer::CreateComputeCulledBladesDescriptorSets() { + // Create culled blade descriptor sets for the compute pipeline + computeCulledBladesDescriptorSets.resize(scene->GetBlades().size()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { computeCulledBladesDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(scene->GetBlades().size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + VkResult kek_debug = vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeCulledBladesDescriptorSets.data()); + if (kek_debug != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(scene->GetBlades().size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo computeCulledBladeBufferInfo = {}; + computeCulledBladeBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + computeCulledBladeBufferInfo.offset = 0; + computeCulledBladeBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo bufferInfos[] = { computeCulledBladeBufferInfo }; + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = computeCulledBladesDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = bufferInfos; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); +} + +void Renderer::CreateComputeNumBladesDescriptorSets() { + // Create num blades descriptor sets for the compute pipeline + computeNumBladesDescriptorSets.resize(scene->GetBlades().size()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { computeNumBladesDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(scene->GetBlades().size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeNumBladesDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(scene->GetBlades().size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo computeNumBladeBufferInfo = {}; + computeNumBladeBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + computeNumBladeBufferInfo.offset = 0; + computeNumBladeBufferInfo.range = sizeof(BladeDrawIndirect); + + VkDescriptorBufferInfo bufferInfos[] = { computeNumBladeBufferInfo }; + + descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[i].dstSet = computeNumBladesDescriptorSets[i]; + descriptorWrites[i].dstBinding = 0; + descriptorWrites[i].dstArrayElement = 0; + descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[i].descriptorCount = 1; + descriptorWrites[i].pBufferInfo = bufferInfos; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -716,8 +970,7 @@ void Renderer::CreateComputePipeline() { computeShaderStageInfo.module = computeShaderModule; 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, computeBladesDescriptorSetLayout, computeCulledBladesDescriptorSetLayout, computeNumBladesDescriptorSetLayout }; // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -883,7 +1136,16 @@ 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 + for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) { + + // Bind the descriptor set for each model + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeBladesDescriptorSets[j], 0, nullptr); + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 3, 1, &computeCulledBladesDescriptorSets[j], 0, nullptr); + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 4, 1, &computeNumBladesDescriptorSets[j], 0, nullptr); + } + + vkCmdDispatch(computeCommandBuffer, NUM_BLADES / WORKGROUP_SIZE, 1, 1); // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -975,14 +1237,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); + // Populate Buffers + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); - // TODO: Bind the descriptor set for each grass blades model + // Bind the descriptor set for each grass blades model + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &modelDescriptorSets[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 @@ -1041,8 +1303,6 @@ void Renderer::Frame() { Renderer::~Renderer() { vkDeviceWaitIdle(logicalDevice); - // TODO: destroy any resources you created - vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); vkFreeCommandBuffers(logicalDevice, computeCommandPool, 1, &computeCommandBuffer); @@ -1057,6 +1317,9 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeBladesDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeCulledBladesDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeNumBladesDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..69aecc2 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -18,7 +18,9 @@ class Renderer { void CreateCameraDescriptorSetLayout(); void CreateModelDescriptorSetLayout(); void CreateTimeDescriptorSetLayout(); - void CreateComputeDescriptorSetLayout(); + void CreateComputeBladesDescriptorSetLayout(); + void CreateComputeCulledBladesDescriptorSetLayout(); + void CreateComputeNumBladesDescriptorSetLayout(); void CreateDescriptorPool(); @@ -26,7 +28,9 @@ class Renderer { void CreateModelDescriptorSets(); void CreateGrassDescriptorSets(); void CreateTimeDescriptorSet(); - void CreateComputeDescriptorSets(); + void CreateComputeBladesDescriptorSets(); + void CreateComputeCulledBladesDescriptorSets(); + void CreateComputeNumBladesDescriptorSets(); void CreateGraphicsPipeline(); void CreateGrassPipeline(); @@ -56,11 +60,18 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeBladesDescriptorSetLayout; + VkDescriptorSetLayout computeCulledBladesDescriptorSetLayout; + VkDescriptorSetLayout computeNumBladesDescriptorSetLayout; VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; + std::vector computeBladesDescriptorSets; + std::vector computeCulledBladesDescriptorSets; + std::vector computeNumBladesDescriptorSets; + std::vector grassDescriptorSets; VkDescriptorSet timeDescriptorSet; VkPipelineLayout graphicsPipelineLayout; diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..cd021ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,6 +66,8 @@ namespace { } int main() { + //while (!GetAsyncKeyState(VK_F12)); + static constexpr char* applicationName = "Vulkan Grass Rendering"; InitializeWindow(640, 480, applicationName); diff --git a/src/notes.txt b/src/notes.txt new file mode 100644 index 0000000..02f1e3b --- /dev/null +++ b/src/notes.txt @@ -0,0 +1,150 @@ +Notes on the writeup (and I guess also the paper): + +So basically we're doing two things. We render the grass and we also build a simulator for having wind forces on the grass. + +The rendering is made in tesselation shaders (etc.) and the physics simulation happens in the compute shaders + +The files are like: +shaders/compute.comp -- computes the physics calculations of wind on the bezier curve +shaders/grass.vert -- computes the transform (?) +shaders/grass.tesc -- controls the tessellation of the grass +shaders/grass.tese -- evaluates the tessellation of the grass (i.e going from a single bezier curve to an actual set of vertices) + +Bezier Curve Representation: + +We will basically be storing three vertices (vec3) one up vector (vec3) then 4 parameters (orientation, height, width and stiffness). +We can pack them together so that's only 4 vec4s when passing through vertex shader +v0.w = orientation, +v1.w = height, +v2.w = width, +up.w = stiffness + +We simulate the forces given the bezier curve input -- we really only apply forces to v2 and then use v1 to maintain the appropriate height/length of blade +v0 is our base ground position so that's not going to change with something like wind. + +We are in charge of maintaining the storage and uploading of all the grass data :( so we gotta make a buffer. +The buffer should include: -- amount of time passed in simulation + -- amount of time since last frame (in order to do both they say to extend/create descriptor sets + that will be bound to the compute pipeline, but what the hell does that mean) + +Okay so the forces that we have to implement in the compute shader are threefold: + +Gravity, Recovery, and WIND + +Gravity -- Given a gravity vector D (direction xyz magnitude w): gE = normalize(D.xyz) * D.w + - then we compute the "Front gravity" (f=front facing direction of the blade): gF = (1/4) * ||gE|| * f + - g = gE + gF + +Recovery -- Basically we gotta find the initial value of v1/v2 (let that be iv2). (probably just going to walk along the up vector by height of blade) from v0. + - Once we have iv2, then we're golden: r = (iv2 - v2) * stiffness + +Wind -- Try to do some sine or cosine function (maybe one that depends on the position v0 & changes with time) + +--------------------------- +Total TODOs in the codebase: +Renderer.cpp - +198 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 +218 // TODO: Add any additional types and counts of descriptors you will need to allocate +320 // 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 +360 // 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 +719 // TODO: Add the compute dsecriptor set layout you create to this list +886 // TODO: For each group of blades bind its descriptor set and dispatch +978 // 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 + + // Draw + // TODO: Uncomment this when the buffers are populated + // vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); +1044 // TODO: destroy any resources you created + +compute.comp - +24 // 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; + +50 // TODO: Apply forces on every blade and update the vertices in the buffer + + // 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 + +grass.frag - +9 // TODO: Declare fragment shader inputs +14 // TODO: Compute fragment color + +grass.tesc - +11 // TODO: Declare tessellation control shader inputs and outputs +17 // TODO: Write any shader outputs + + // TODO: Set level of tesselation + // gl_TessLevelInner[0] = ??? + // gl_TessLevelInner[1] = ??? + // gl_TessLevelOuter[0] = ??? + // gl_TessLevelOuter[1] = ??? + // gl_TessLevelOuter[2] = ??? + // gl_TessLevelOuter[3] = ??? + +grass.tese - +11 // TODO: Declare tessellation evaluation shader inputs and outputs +17 // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + +grass.vert - +9 // TODO: Declare vertex shader inputs and outputs +16 // TODO: Write gl_Position and any other shader outputs + +So in order to allocate the grass descriptor set we need:: +vkDescriptorSetLayout +vkDescriptorSetAllovateInfo + +then call vkAllocateDescriptorSets + +VkDescriptorBufferInfo + +// Structure specifying descriptor buffer info + typedef struct VkDescriptorBufferInfo { + VkBuffer buffer; + VkDeviceSize offset; + VkDeviceSize range; + } VkDescriptorBufferInfo; + +std::array + +//Structure specifying the parameters of a descriptor set write operation + typedef struct VkWriteDescriptorSet { + VkStructureType sType; + const void* pNext; + VkDescriptorSet dstSet; + uint32_t dstBinding; + uint32_t dstArrayElement; + uint32_t descriptorCount; + VkDescriptorType descriptorType; + const VkDescriptorImageInfo* pImageInfo; + const VkDescriptorBufferInfo* pBufferInfo; + const VkBufferView* pTexelBufferView; + } VkWriteDescriptorSet; + +then call vkUpdateDescriptorSets +Update the contents of a descriptor set object + VkDevice device, + uint32_t descriptorWriteCount, + const VkWriteDescriptorSet* pDescriptorWrites, + uint32_t descriptorCopyCount, + const VkCopyDescriptorSet* pDescriptorCopies); ~ null \ No newline at end of file diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..1c2d1e2 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,14 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define GRAVITY_ACCEL 5 +#define WIND_AMPLITUDE 10 + +#define ORIENTATION_TOLERANCE 0.8 +#define VIEW_FRUSTUM_TOLERANCE 2.0 +#define MAX_DISTANCE 40.0 +#define NUM_BUCKETS 30 + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -21,20 +29,20 @@ 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 Blades { + Blade data[]; +} bladeData; + +layout(set = 3, binding = 0) buffer CulledBlades { + Blade data[]; +} culledBladeData; + +layout(set = 4, binding = 0) 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); @@ -43,14 +51,111 @@ bool inBounds(float value, float bounds) { void main() { // Reset the number of blades to 0 if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + numBlades.vertexCount = 0; } barrier(); // Wait till all threads reach this point - // TODO: Apply forces on every blade and update the vertices in the buffer + // Apply forces on every blade and update the vertices in the buffer + Blade b = bladeData.data[gl_GlobalInvocationID.x]; + + // Extract the blade constants + float orientation = b.v0.w; + float height = b.v1.w; + float width = b.v2.w; + float stiffness = b.up.w; + + // Extract the blade vertices + vec3 v0 = b.v0.xyz; + vec3 v1 = b.v1.xyz; + vec3 v2 = b.v2.xyz; + vec3 up = b.up.xyz; + + // calculate the front facing direction of the blade + // NOTE: we're assuming here that "orientation" is an angle in radians + // relative to the vector vec3(1.0, 0.0, 0.0); + vec3 front = vec3(cos(orientation), 0.0, sin(orientation)); + + // calculate gravity + vec3 gravityDir = vec3(0.0, -1.0, 0.0); + vec3 gravity = normalize(gravityDir) * GRAVITY_ACCEL; + vec3 frontGravity = 0.25 * length(gravity) * front; + vec3 g = gravity + frontGravity; + + // Calculate Recovery + vec3 iv2 = v0 + up * height; + vec3 r = (iv2 - v2) * stiffness; + + // Calculate Wind Influence (we're doing a simple ripple here) + float windStrength = WIND_AMPLITUDE * sin(v0.x + v0.z + (totalTime / 2.5)); + vec3 windVector = abs(windStrength) * vec3(-1.0, 0.0, 0.0); + + // now get the directional alignment and the height ratio + float directionalAlignment = 1 - abs(dot((windVector) / length(windVector), (v2 - v0) / length(v2 - v0))); + float heightRatio = (dot((v2 - v0), up) / height); + float alignmentValue = directionalAlignment * heightRatio; + vec3 w = alignmentValue * windVector; - // 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 + // now calculate how much v2 moves based on the forces and the delta time + vec3 tv2 = (g + r + w) * deltaTime; + vec3 newv2 = v2 + tv2; + + // validate that v2 hasn't sunk beneath the 3d model + newv2 = newv2 - (up * min(dot(up, (newv2 - v0)), 0.0)); + + // calculate the new value for v1 + float proj = length(newv2 - v0 - (dot(up, (newv2 - v0)) * up)); + vec3 newv1 = v0 + height * up * max(1 - (proj/height), 0.05 * max(proj/height, 1.0)); + + // approximate the length of our bezier curve + float n = 1.0; // the degree of our bezier curve + float L0 = length(newv2 - v0); + float L1 = length(newv2 - newv1) + length(newv1 - v0); + float L = (2 * L0 + (n-1) * L1) / (n+1); + + // correct the points to prevent that length from changing + float ratio = height / L; + vec3 v1corr = v0 + ratio * (newv1 - v0); + vec3 v2corr = v1corr + ratio * (newv2 - newv1); + + // Update the blades with their new v1 and v2 position + bladeData.data[gl_GlobalInvocationID.x].v1 = vec4(v1corr, height); + bladeData.data[gl_GlobalInvocationID.x].v2 = vec4(v2corr, width); + + //// Perform orientation cull test + bool isFacingCamera = abs(dot(camera.view * vec4(cross(up, front), 0.0), vec4(0.0, 0.0, -1.0, 0.0))) > ORIENTATION_TOLERANCE; + + //// Perform view frustum cull test + // get midpoint of the blade + vec3 midpoint = (1.0/4.0) * v0 * (1.0/2.0) * v1corr * (1.0/4.0) * v2corr; + + // get the vectors in clip space + vec4 v0Clip = camera.proj * camera.view * vec4(v0, 1.0); + vec4 v2Clip = camera.proj * camera.view * vec4(v2corr, 1.0); + vec4 mClip = camera.proj * camera.view * vec4(midpoint, 1.0); + + // determine if they are in view + bool isv0InView = inBounds(v0Clip.x, v0Clip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(v0Clip.y, v0Clip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(v0Clip.z, v0Clip.w + VIEW_FRUSTUM_TOLERANCE); + bool isv2InView = inBounds(v2Clip.x, v2Clip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(v2Clip.y, v2Clip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(v2Clip.z, v2Clip.w + VIEW_FRUSTUM_TOLERANCE); + bool ismInView = inBounds(mClip.x, mClip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(mClip.y, mClip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(mClip.z, mClip.w + VIEW_FRUSTUM_TOLERANCE); + + // the blade is in view only if all of them are + bool isInView = isv0InView && isv2InView && ismInView; + + //// Perform Distance cull test + vec4 v0View = camera.view * vec4(v0, 1.0); + vec4 upView = normalize(camera.view * vec4(up, 0.0)); + float distanceFromCam = length(v0View); //- (dot(v0View, vec4(up, 0.0)) * vec4(up, 0.0))); + + // sample based on bucket if blade is culled or not + bool isCloseEnough = (gl_GlobalInvocationID.x % NUM_BUCKETS) < floor(NUM_BUCKETS * (1.0 - (distanceFromCam / MAX_DISTANCE))); + + // If the blade passes all of these tests add it to the culledBladeData + if (isFacingCamera && isInView && isCloseEnough) { + uint count = atomicAdd(numBlades.vertexCount, 1); + barrier(); + culledBladeData.data[count].v0 = vec4(v0, orientation); + culledBladeData.data[count].v1 = vec4(v1corr, height); + culledBladeData.data[count].v2 = vec4(v2corr, width); + culledBladeData.data[count].up = vec4(up, stiffness); + } } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..92e728d 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -6,12 +6,10 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare fragment shader inputs +layout(location = 0) in float lightIntensity; layout(location = 0) out vec4 outColor; void main() { - // TODO: Compute fragment color - - outColor = vec4(1.0); + outColor = lightIntensity * vec4(34.0 / 255.0, 139.0 / 255.0, 34.0 / 255.0, 0.0); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..2734fac 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -8,19 +8,30 @@ 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 outv1[]; +layout(location = 1) in vec4 outv2[]; +layout(location = 2) in vec4 outup[]; + +layout(location = 0) out vec4 v1_ES_in[]; +layout(location = 1) out vec4 v2_ES_in[]; +layout(location = 2) out vec4 v3_ES_in[]; void main() { // Don't move the origin location of the patch - gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; + gl_out[gl_InvocationID].gl_Position = gl_in[0].gl_Position; + + v1_ES_in[gl_InvocationID] = outv1[gl_InvocationID]; + v2_ES_in[gl_InvocationID] = outv2[gl_InvocationID]; + v3_ES_in[gl_InvocationID] = outup[gl_InvocationID]; - // TODO: Write any shader outputs + // Set level of tessellation based on distance away + float LOD = 2.0 + ((40.0 - length(camera.view * vec4(gl_in[0].gl_Position.xyz, 1.0))) / 10.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] = LOD; + gl_TessLevelInner[1] = LOD; + gl_TessLevelOuter[0] = LOD; + gl_TessLevelOuter[1] = LOD; + gl_TessLevelOuter[2] = LOD; + gl_TessLevelOuter[3] = LOD; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..1087b04 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -8,11 +8,51 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 v1_ES_in[]; +layout(location = 1) in vec4 v2_ES_in[]; +layout(location = 2) in vec4 v3_ES_in[]; + +layout(location = 0) out float lightIntensity[]; 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 = gl_in[0].gl_Position.xyz; + vec3 v1 = v1_ES_in[0].xyz; + vec3 v2 = v2_ES_in[0].xyz; + vec3 up = v3_ES_in[0].xyz; + + float orientation = gl_in[0].gl_Position.w; + float height = v1_ES_in[0].w; + float width = v2_ES_in[0].w; + vec3 t1 = vec3(cos(orientation), 0.0, sin(orientation)); + + vec3 a = v0 + v * (v1 - v0); + vec3 b = v1 + v * (v2 - v1); + vec3 c = a + v * (b - a); + vec3 c0 = c - (width * t1); + vec3 c1 = c + (width * t1); + vec3 t0 = (b - a) / (length(b - a)); + vec3 n = cross(t0, t1) / (length(cross(t0, t1))); + vec3 frontVec = normalize(cross(up, t1)); + + // calculate interpolation parameter for triangle tip + //float t = 0.5 + (u - 0.5) * (1 - (max(v - 0.75, 0)/(1-0.75))); + + // calculate interpolation parameter for quad + //float t = u; + + // calculate interpolation parameter for triangle + //float t = u + (0.5*v) - (u*v); + + // calculate interpolation parameter for quadratic + float t = u - (u * v * v); + + // calculate lambertian shading term to pass to frag shader + vec4 posView = camera.view * vec4(mix(c0, c1, t), 1.0); + + lightIntensity[0] = abs(dot(normalize(posView), normalize(camera.view * vec4(n, 0.0)))); + + gl_Position = camera.proj * posView; } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..5d46cdd 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -1,4 +1,3 @@ - #version 450 #extension GL_ARB_separate_shader_objects : enable @@ -6,12 +5,22 @@ 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; + +layout(location = 0) out vec4 outv1; +layout(location = 1) out vec4 outv2; +layout(location = 2) out vec4 outup; out gl_PerVertex { vec4 gl_Position; }; void main() { - // TODO: Write gl_Position and any other shader outputs + gl_Position = model * v0; + outv1 = model * v1; + outv2 = model * v2; + outup = model * up; }