diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md new file mode 100644 index 0000000..47a8c52 --- /dev/null +++ b/INSTRUCTIONS.md @@ -0,0 +1,302 @@ +Instructions - Vulkan Grass Rendering +======================== + +This is due **Wednesday 10/9, evening at midnight**. + +**QUICK NOTE**: Please use `git clone --recursive` when cloning this repo as there are submodules which need to be cloned as well. + +**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. + +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) + +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 Shehzan or one of the TAs before continuing! + +### Contents + +* `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. +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. +* `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at `v0` +* Orientation: the orientation of the grass blade's face +* Height: the height of the grass blade +* Width: the width of the grass blade's face +* Stiffness coefficient: the stiffness of our grass blade, which will affect the force computations on our blade + +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. + +#### Gravity + +Given a gravity direction, `D.xyz`, and the magnitude of acceleration, `D.w`, we can compute the environmental gravity in +our scene as `gE = normalize(D.xyz) * D.w`. + +We then determine the contribution of the gravity with respect to the front facing direction of the blade, `f`, +as a term called the "front gravity". Front gravity is computed as `gF = (1/4) * ||gE|| * f`. + +We can then determine the total gravity on the grass blade as `g = gE + gF`. + +#### Recovery + +Recovery corresponds to the counter-force that brings our grass blade back into equilibrium. This is derived in the paper using Hooke's law. +In order to determine the recovery force, we need to compare the current position of `v2` to its original position before +simulation started, `iv2`. At the beginning of our simulation, `v1` and `v2` are initialized to be a distance of the blade height along the `up` vector. + +Once we have `iv2`, we can compute the recovery forces as `r = (iv2 - v2) * stiffness`. + +#### 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! + +Once you have a wind direction and a wind alignment term, your total wind force (`w`) will be `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 +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`. + +### 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 +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 +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 + +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. + +If all three points are outside of the view-frustum, we will cull the grass blade. The paper uses a tolerance value for this test so that we are culling +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 +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. +* 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 +are culled with each farther bucket. + +#### Occlusion culling (extra credit) + +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! + +### 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. + +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. + +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! + +** 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. + +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/). + +## Resources + +### Links + +The following resources may be useful for 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) +* [Official Vulkan documentation](https://www.khronos.org/registry/vulkan/) +* [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/README.md b/README.md index 47a8c52..69bcf12 100644 --- a/README.md +++ b/README.md @@ -1,302 +1,131 @@ -Instructions - Vulkan Grass Rendering -======================== +# Vulkan Grass Rendering +**University of Pennsylvania, CIS 565: GPU Programming and Architecture, +Project 4** -This is due **Wednesday 10/9, evening at midnight**. +Caroline Lachanski: [LinkedIn](https://www.linkedin.com/in/caroline-lachanski/), [personal website](http://carolinelachanski.com/) -**QUICK NOTE**: Please use `git clone --recursive` when cloning this repo as there are submodules which need to be cloned as well. +Tested on: Windows 10, i5-6500 @ 3.20GHz 16GB, GTX 1660 (personal computer) -**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. +![](img/thatSureIsGrass.gif) -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. +## Project Description -![](img/grass.gif) ![](img/grass2.gif) +The goal of this project was to utilize Vulkan to implement a real-time grass simulation and render, heavily based on this paper: [Responsive Real-Time Grass Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf). -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! +## Features -**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 Shehzan or one of the TAs before continuing! +### Blade Model -### Contents - -* `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) +![](img/blade_model.jpg) -### Representing Grass as Bezier Curves +For most of the pipeline, each blade of grass is represented as three control points (v0, v1, and v2) of a Bezier curve. Forces are applied on these points in the compute shader, as is culling. In the tessellation evaluation shader, this curve is evaluated using De Casteljau's algorithm. -In this project, grass blades will be 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) +* `v0`: the position of the grass blade on the ground plane geometry +* `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 -We also need to 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` +Each blade has some additional parameters that are needed: + +* `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at v0 * Orientation: the orientation of the grass blade's face * Height: the height of the grass blade * Width: the width of the grass blade's face * Stiffness coefficient: the stiffness of our grass blade, which will affect the force computations on our blade -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) +All of the above data can be packed into four vec4s, such that `v0.w` holds orientation, `v1.w` holds height, `v2.w` holds width, and `up.w` holds the stiffness coefficient. -### 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. +### Forces #### Gravity -Given a gravity direction, `D.xyz`, and the magnitude of acceleration, `D.w`, we can compute the environmental gravity in -our scene as `gE = normalize(D.xyz) * D.w`. - -We then determine the contribution of the gravity with respect to the front facing direction of the blade, `f`, -as a term called the "front gravity". Front gravity is computed as `gF = (1/4) * ||gE|| * f`. +There are two components to the gravity force in this simulation. The first is environmental gravity, `gE`, represented by the typical 9.81 in the negative y-direction. The second component is front gravity, `gF`, which accounts for the blade's elasticity, which causes its tip to bend. `gF` is computed as `0.25 * || gE || * f`, where `f` is the front-facing direction of the blade. -We can then determine the total gravity on the grass blade as `g = gE + gF`. +The total gravity is computed as `gravity = gE + gF`. #### Recovery -Recovery corresponds to the counter-force that brings our grass blade back into equilibrium. This is derived in the paper using Hooke's law. -In order to determine the recovery force, we need to compare the current position of `v2` to its original position before -simulation started, `iv2`. At the beginning of our simulation, `v1` and `v2` are initialized to be a distance of the blade height along the `up` vector. +The recovery force is the counterforce to the other applied forces (pusing the blade to return to its initial upright position) and is based on Hooke's Law. Recovery is calculated as `recovery = (iv2 - v2) * stiffness`, where `iv2` is the original position of `v2` before any simulation and `v2` is the current position. -Once we have `iv2`, we can compute the recovery forces as `r = (iv2 - v2) * stiffness`. +Here's the grass with only gravity and recovery forces applied (no movement, since no forces vary over time): -#### 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. +![](img/stillGrass.PNG) -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! - -Once you have a wind direction and a wind alignment term, your total wind force (`w`) will be `windDirection * windAlignment`. +#### Wind -#### Total force +We can let the wind direction be anything we wish, such as a simple unidirectional wind or a more complex function that varies based on the blade position or wind source position. However, it's important to realize that effect of wind on the blade will depend on the blade's orientation with respect to the wind direction. Additionally, a blade that is standing straight will be more affected by wind than a blade that is pushed to the ground. We thus calculate a `windAlignment` value. The total wind force is then calculated as `wind = windDirection * windAlignment`. -We can 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. +Here's the grass using more basic, directional wind function: -Read section 5.2 of the paper in order to learn how to determine the corrected final positions for `v1` and `v2`. +![](img/directionalWind.gif) -### Culling tests +Here's a more complex wind function, using 2D FBM based on blade position and time: -Although we need to simulate forces on every grass blade at every frame, there are many blades that we won't need to render -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. +![](img/moreComplexWindOverhead.gif) -#### Orientation culling +#### Simulation Correction -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. +The translation for `v2` is computed as `tv2 = (gravity + wind + recovery) * deltaTime`, but simply applying this translation can cause errors. What if this pushes v2 below the ground? How do we prevent the blade of grass from changing length? Corrections are applied to both `v1` and `v2` to ensure the simulation is robust (see paper and/or the compute shader). -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. +### Culling -#### View-frustum culling +Three different culling heuristics are applied to every blade of grass to ensure we only render the blades we need to. -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. +#### Distance Culling -If all three points are outside of the view-frustum, we will cull the grass blade. The paper uses a tolerance value for this test so that we are culling -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. +We want to cull blades that are farther away from the camera, because 1) far away enough blades might be smaller than a pixel, which can cause aliasing artifacts, 2) the farther away a patch of grass is, the less blades it needs to look "full," and 3) at far away distances, too high of a blade density at a low viewing angle can cause z-fighting due to the lack of precision in depth values. -#### Distance culling +Since the blade density increase due to perspective is stronger near the horizon than when viewed from above, the distance from the camera to the blade is projected onto the local plane defined by the blade's `up` vector and then used for distance culling: `d_proj = || v0 โˆ’ c โˆ’ up * ((v0 โˆ’ c) ยท up) ||`, where `c` is the camera position. -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. +We accomplish distance culling by the using the projected distance and assigning it to one of `n` buckets between the camera position and some max distance (any blades beyond this max distance will not be rendered at all). In the first bucket, no blades are culled; in the second, one out of every n blades are culled, and so on. -You are free to 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. +This gif shows distance culling using 3 buckets with a shortened max distance to exaggerate the culling effect: -Define a function such that 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. +![](img/distanceCullingVis.gif) -#### Occlusion culling (extra credit) +#### Orientation Culling -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! +Since blades have zero thickness, when looking at them from the side, they might appear at width that is less than a pixel, which can cause aliasing. We attempt to avoid this by culling blades based on their orientation to the camera. We can use a dot product between the camera's view direction and the blade's orientation direction and cull blades if this value is less than 0.9. -### Tessellating Bezier curves into grass blades +![](img/orientationCullingVis.gif) -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. +#### View Frustum Culling -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. +Blades that are outside of the camera's view frustum wouldn't appear anyway, so we don't bother trying to render them. We check the visibility of three points: `v0`, `v2`, and `m`, where `m` is the approximated midpoint of the Bezier curve, calcualted by: `m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2`. `v1` is not used in the visibility test, since `v1` is really just a Bezier guide that doesn't represent an actual position on the blade of grass. If neither of the three points are within the view frustum, the blade is culled. -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! +I had some difficulty visualizing this heuristic to see if it was working. One hacky way I used was increasing the height of a blade if its true position was outside the view frustum. You can see the blades at the bottom of the gif that go out of view will then suddenly appear since their height has been artificially increased: -** 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. +![](img/frustumCullingVis.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/). +## Analysis -## Resources +We can look at performance (using FPS) as we increase the number of blades (higher on graph = higher FPS = better): -### Links +![](img/FPSvsNumBlades.png) -The following resources may be useful for this project. +As expected, increasing the number of blades results in a lower FPS. Additionally, we can see that using the three culling heuristics above results in a better performance than when not using any culling. At higher blade counts, this performance increase results in about double the FPS. -* [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) -* [Official Vulkan documentation](https://www.khronos.org/registry/vulkan/) -* [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/) +We can also look at the individual effect of each culling heuristic. Since the amount of culling that occurs for a given heuristic can depend on the camera viewing angle, I tested two scenes: one up close, level with the horizon (blue bars), and one zoomed out, viewing the entire patch of grass from above (red bars). Both scenes contained 65,536 blades. +![](img/FPSvsCulling.png) -## Third-Party Code Policy +For the close-up scene, orientation culling provided the biggest benefit, which is unexpected. I would have expected frustum culling to provide the biggest performance boost since I imagined many blades to be outside the camera frustum, but perhaps the camera wasn't as zoomed in as it needed to be to provide that performance increase. One reason orientation culling likely had such a large effect was because the camera was very level with the horizon, meanning a good number of blades would have been culled. -* 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. +In the close-up scene, distance culling barely did better than no culling at all. I would guess this is because I did not change the max distance value I was using in the distance culling calculation, which had been tuned for farther away scenes. Because the camera was so close, most of the blades likely fell in the first bucket, meaning very few were culled. +In the zoomed out scene, we see different trends. Overall, culling does not increase performance as much as in the zoomed in scene. Distance culling becomes the best culling, as a good number of blades will fall into the second and third culling buckets. Frustum culling has little to no effect because all blades were within the camera frustum. Similarly, orientation culling shows little increase in performance due to the angle at which the blades were viewed. -## README +## Bloopers -* 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). +It took a *long* time before I had any grass appearing at all, so I was really happy to get to this stage: -### Performance Analysis +![](img/projectFinishedNoMoreWorkNeeded.PNG) -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 +(The issue: I was indexing into my input arrays in the evaluation shader using gl_PrimitiveID, but since each array was of length 1, only one blade would draw.) -## Submit +When I finally had all the blades showing up, it looked like they were at a house party that had the bass turned up to 11: -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. +![](img/turnTheBassDown.gif) -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: +Changing some parameters makes this effect even worse (better?): -* [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. +![](img/uh.gif) diff --git a/img/FPSvsCulling.png b/img/FPSvsCulling.png new file mode 100644 index 0000000..f9dbd61 Binary files /dev/null and b/img/FPSvsCulling.png differ diff --git a/img/FPSvsNumBlades.png b/img/FPSvsNumBlades.png new file mode 100644 index 0000000..48f8ca5 Binary files /dev/null and b/img/FPSvsNumBlades.png differ diff --git a/img/directionalWind.gif b/img/directionalWind.gif new file mode 100644 index 0000000..7220cb1 Binary files /dev/null and b/img/directionalWind.gif differ diff --git a/img/distanceCullingVis.gif b/img/distanceCullingVis.gif new file mode 100644 index 0000000..10566f8 Binary files /dev/null and b/img/distanceCullingVis.gif differ diff --git a/img/frustumCullingVis.gif b/img/frustumCullingVis.gif new file mode 100644 index 0000000..c227802 Binary files /dev/null and b/img/frustumCullingVis.gif differ diff --git a/img/fullGrass.PNG b/img/fullGrass.PNG new file mode 100644 index 0000000..1f909b6 Binary files /dev/null and b/img/fullGrass.PNG differ diff --git a/img/moreComplexWind.gif b/img/moreComplexWind.gif new file mode 100644 index 0000000..741cd1c Binary files /dev/null and b/img/moreComplexWind.gif differ diff --git a/img/moreComplexWindOverhead.gif b/img/moreComplexWindOverhead.gif new file mode 100644 index 0000000..1774357 Binary files /dev/null and b/img/moreComplexWindOverhead.gif differ diff --git a/img/orientationCullingVis.gif b/img/orientationCullingVis.gif new file mode 100644 index 0000000..42b667d Binary files /dev/null and b/img/orientationCullingVis.gif differ diff --git a/img/projectFinishedNoMoreWorkNeeded.PNG b/img/projectFinishedNoMoreWorkNeeded.PNG new file mode 100644 index 0000000..6f99896 Binary files /dev/null and b/img/projectFinishedNoMoreWorkNeeded.PNG differ diff --git a/img/stillGrass.PNG b/img/stillGrass.PNG new file mode 100644 index 0000000..04b7ddd Binary files /dev/null and b/img/stillGrass.PNG differ diff --git a/img/thatSureIsGrass.gif b/img/thatSureIsGrass.gif new file mode 100644 index 0000000..1d3c834 Binary files /dev/null and b/img/thatSureIsGrass.gif differ diff --git a/img/turnTheBassDown.gif b/img/turnTheBassDown.gif new file mode 100644 index 0000000..f009703 Binary files /dev/null and b/img/turnTheBassDown.gif differ diff --git a/img/uh.gif b/img/uh.gif new file mode 100644 index 0000000..d9e407e Binary files /dev/null and b/img/uh.gif differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..3a0ce55 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -44,8 +44,8 @@ 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_INDIRECT_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, &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..b438e24 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 << 15; 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/Camera.cpp b/src/Camera.cpp index 3afb5b8..7d372e1 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -12,6 +12,7 @@ Camera::Camera(Device* device, float aspectRatio) : device(device) { r = 10.0f; theta = 0.0f; phi = 0.0f; + //cameraBufferObject.viewMatrix = glm::lookAt(glm::vec3(16.0f, 16.0f, 10.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); cameraBufferObject.viewMatrix = glm::lookAt(glm::vec3(0.0f, 1.0f, 10.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); cameraBufferObject.projectionMatrix = glm::perspective(glm::radians(45.0f), aspectRatio, 0.1f, 100.0f); cameraBufferObject.projectionMatrix[1][1] *= -1; // y-coordinate is flipped diff --git a/src/Image.cpp b/src/Image.cpp index 64bfe82..6dad122 100644 --- a/src/Image.cpp +++ b/src/Image.cpp @@ -5,6 +5,7 @@ #include "Device.h" #include "Instance.h" #include "BufferUtils.h" +#include void Image::Create(Device* device, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { // Create Vulkan image @@ -200,7 +201,9 @@ void Image::FromFile(Device* device, VkCommandPool commandPool, const char* path VkDeviceSize imageSize = texWidth * texHeight * 4; if (!pixels) { - throw std::runtime_error("Failed to load texture image"); + //throw std::runtime_error("Failed to load texture image"); + std::cout << stbi_failure_reason() << std::endl; + throw std::runtime_error(stbi_failure_reason()); } // Create staging buffer diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..56f635a 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -17,20 +17,27 @@ Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* c CreateCommandPools(); CreateRenderPass(); + CreateCameraDescriptorSetLayout(); CreateModelDescriptorSetLayout(); + CreateGrassDescriptorSetLayout(); CreateTimeDescriptorSetLayout(); CreateComputeDescriptorSetLayout(); + CreateDescriptorPool(); + CreateCameraDescriptorSet(); CreateModelDescriptorSets(); CreateGrassDescriptorSets(); CreateTimeDescriptorSet(); CreateComputeDescriptorSets(); + CreateFrameResources(); + CreateGraphicsPipeline(); CreateGrassPipeline(); CreateComputePipeline(); + RecordCommandBuffers(); RecordComputeCommandBuffer(); } @@ -172,6 +179,27 @@ void Renderer::CreateModelDescriptorSetLayout() { } } +void Renderer::CreateGrassDescriptorSetLayout() { + 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(); + + if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &grassDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } +} + void Renderer::CreateTimeDescriptorSetLayout() { // Describe the binding of the descriptor set layout VkDescriptorSetLayoutBinding uboLayoutBinding = {}; @@ -195,9 +223,43 @@ void Renderer::CreateTimeDescriptorSetLayout() { } void Renderer::CreateComputeDescriptorSetLayout() { - // TODO: Create the descriptor set layout for the compute pipeline + // DONE: Create the descriptor set layout for the compute pipeline // Remember this is like a class definition stating why types of information // will be stored at each binding + + // Describe the binding of the descriptor set layout + VkDescriptorSetLayoutBinding inBladesLayoutBinding = {}; + inBladesLayoutBinding.binding = 0; + inBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + inBladesLayoutBinding.descriptorCount = 1; + inBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + inBladesLayoutBinding.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 = { inBladesLayoutBinding, 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() { @@ -207,7 +269,7 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1}, // Models + Blades - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , static_cast(scene->GetModels().size()) }, // Models + Blades { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) }, @@ -215,7 +277,10 @@ 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 + // In blades, culled blades, num blades (compute) + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(3 * scene->GetBlades().size()) }, + + // DONE: Add any additional types and counts of descriptors you will need to allocate }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -267,7 +332,7 @@ void Renderer::CreateCameraDescriptorSet() { void Renderer::CreateModelDescriptorSets() { modelDescriptorSets.resize(scene->GetModels().size()); - // Describe the desciptor set + // Describe the descriptor set VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout }; VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; @@ -318,8 +383,44 @@ void Renderer::CreateModelDescriptorSets() { } void Renderer::CreateGrassDescriptorSets() { - // TODO: Create Descriptor sets for the grass. + // DONE: 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[] = { grassDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(grassDescriptorSets.size());; + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(grassDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo 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() { @@ -358,8 +459,76 @@ void Renderer::CreateTimeDescriptorSet() { } void Renderer::CreateComputeDescriptorSets() { - // TODO: Create Descriptor sets for the compute pipeline + // DONE: 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 + + computeDescriptorSets.resize(scene->GetBlades().size()); + + // Describe the descriptor set + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(computeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo inBladesBufferInfo = {}; + inBladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + inBladesBufferInfo.offset = 0; + inBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect); + + descriptorWrites[3 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 0].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 0].dstBinding = 0; + descriptorWrites[3 * i + 0].dstArrayElement = 0; + descriptorWrites[3 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 0].descriptorCount = 1; + descriptorWrites[3 * i + 0].pBufferInfo = &inBladesBufferInfo; + descriptorWrites[3 * i + 0].pImageInfo = nullptr; + descriptorWrites[3 * i + 0].pTexelBufferView = nullptr; + + descriptorWrites[3 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 1].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 1].dstBinding = 1; + descriptorWrites[3 * i + 1].dstArrayElement = 0; + descriptorWrites[3 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 1].descriptorCount = 1; + descriptorWrites[3 * i + 1].pBufferInfo = &culledBladesBufferInfo; + descriptorWrites[3 * i + 1].pImageInfo = nullptr; + descriptorWrites[3 * i + 1].pTexelBufferView = nullptr; + + descriptorWrites[3 * i + 2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 2].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 2].dstBinding = 2; + descriptorWrites[3 * i + 2].dstArrayElement = 0; + descriptorWrites[3 * i + 2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 2].descriptorCount = 1; + descriptorWrites[3 * i + 2].pBufferInfo = &numBladesBufferInfo; + descriptorWrites[3 * i + 2].pImageInfo = nullptr; + descriptorWrites[3 * i + 2].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } void Renderer::CreateGraphicsPipeline() { @@ -654,7 +823,8 @@ void Renderer::CreateGrassPipeline() { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout }; + //std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout }; + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, grassDescriptorSetLayout }; // Pipeline layout: used to specify uniform values VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -716,8 +886,8 @@ 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 }; + // DONE: Add the compute descriptor set layout you create to this list + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout }; // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -883,7 +1053,11 @@ 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 + // DONE: For each group of blades bind its descriptor set and dispatch + for (size_t i = 0; i < scene->GetBlades().size(); ++i) { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2 + i, 1, &computeDescriptorSets[i], 0, nullptr); + vkCmdDispatch(computeCommandBuffer, (int)ceil((NUM_BLADES + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE), 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -926,7 +1100,7 @@ void Renderer::RecordCommandBuffers() { renderPassInfo.renderArea.extent = swapChain->GetVkExtent(); std::array clearValues = {}; - clearValues[0].color = { 0.0f, 0.0f, 0.0f, 1.0f }; + clearValues[0].color = { 0.68f, 0.90f, 0.93f, 1.0f }; clearValues[1].depthStencil = { 1.0f, 0 }; renderPassInfo.clearValueCount = static_cast(clearValues.size()); renderPassInfo.pClearValues = clearValues.data(); @@ -975,14 +1149,15 @@ 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); + // DONE: Uncomment this when the buffers are populated + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); - // TODO: Bind the descriptor set for each grass blades model + // DONE: 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)); + // DONE: Uncomment this when the buffers are populated + vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); } // End render pass @@ -1041,7 +1216,7 @@ void Renderer::Frame() { Renderer::~Renderer() { vkDeviceWaitIdle(logicalDevice); - // TODO: destroy any resources you created + // DONE: destroy any resources you created vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); vkFreeCommandBuffers(logicalDevice, computeCommandPool, 1, &computeCommandBuffer); @@ -1056,7 +1231,9 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, grassDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..4993792 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -17,6 +17,7 @@ class Renderer { void CreateCameraDescriptorSetLayout(); void CreateModelDescriptorSetLayout(); + void CreateGrassDescriptorSetLayout(); void CreateTimeDescriptorSetLayout(); void CreateComputeDescriptorSetLayout(); @@ -55,13 +56,18 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; - VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout grassDescriptorSetLayout; + VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; + VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; + std::vector grassDescriptorSets; VkDescriptorSet timeDescriptorSet; + std::vector computeDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/images/dirt.png b/src/images/dirt.png new file mode 100644 index 0000000..902a3a8 Binary files /dev/null and b/src/images/dirt.png differ diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..2630e64 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include +#include #include "Instance.h" #include "Window.h" #include "Renderer.h" @@ -106,7 +107,9 @@ int main() { VkDeviceMemory grassImageMemory; Image::FromFile(device, transferCommandPool, - "images/grass.jpg", + // Note: for some reason, I had to use absolute paths to load any images besides the given one + "images/grass.jpg", + //"C:/Users/caroline/Documents/CIS_565/Project4-Vulkan-Grass-Rendering/src/images/dirt.png", VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_SAMPLED_BIT, @@ -143,8 +146,29 @@ int main() { glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback); glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback); + double fps = 0; + double timebase = 0; + int frame = 0; + while (!ShouldQuit()) { glfwPollEvents(); + + frame++; + double time = glfwGetTime(); + + if (time - timebase > 1.0) { + fps = frame / (time - timebase); + timebase = time; + frame = 0; + } + + std::ostringstream ss; + ss << "["; + ss.precision(1); + ss << std::fixed << fps; + ss << " fps] "; + glfwSetWindowTitle(GetGLFWWindow(), ss.str().c_str()); + scene->UpdateTime(); renderer->Frame(); } diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..88eaa0f 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -1,6 +1,12 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +#define PI 3.141592653238 + +#define ORIENTATION_CULL 1 +#define FRUSTUM_CULL 1 +#define DISTANCE_CULL 1 + #define WORKGROUP_SIZE 32 layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; @@ -21,36 +27,193 @@ struct Blade { vec4 up; }; -// TODO: Add bindings to: +// DONE: Add bindings to: // 1. Store the input blades // 2. Write out the culled blades // 3. Write the total number of blades remaining +layout(set = 2, binding = 0) buffer InBlades { + Blade inBlades[]; +}; + +layout(set = 2, binding = 1) buffer RemainingBlades { + Blade remainingBlades[]; +}; + // The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call // This is sort of an advanced feature so we've showed you what this buffer should look like -// -// layout(set = ???, binding = ???) buffer NumBlades { -// uint vertexCount; // Write the number of blades remaining here -// uint instanceCount; // = 1 -// uint firstVertex; // = 0 -// uint firstInstance; // = 0 -// } numBlades; +layout(set = 2, binding = 2) buffer NumBlades { + uint vertexCount; // Write the number of blades remaining here + uint instanceCount; // = 1 + uint firstVertex; // = 0 + uint firstInstance; // = 0 +} numBlades; bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } +// 2D Random +float random(vec2 p) { + return fract(sin(dot(p.xy, vec2(12.9898,78.233))) * 43758.5453123); +} + +// 2D Noise based on Morgan McGuire @morgan3d +// https://www.shadertoy.com/view/4dS3Wd +float noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + + // Four corners in 2D of a tile + float a = random(i); + float b = random(i + vec2(1.0, 0.0)); + float c = random(i + vec2(0.0, 1.0)); + float d = random(i + vec2(1.0, 1.0)); + + // Smooth Interpolation with Cubic Hermine Curve + vec2 u = f * f * (3.0 - 2.0 * f); + + // Mix 4 coorners percentages + return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y; +} + +// https://thebookofshaders.com/13/ +#define OCTAVES 6 +float fbm(vec2 p) { + float value = 0.0; + float amplitude = .5; + float frequency = 0.; + + for (int i = 0; i < OCTAVES; i++) { + value += amplitude * noise(p); + p *= 2.0; + amplitude *= 0.5; + } + return value; +} + void main() { // Reset the number of blades to 0 - if (gl_GlobalInvocationID.x == 0) { - // numBlades.vertexCount = 0; + uint threadIdx = gl_GlobalInvocationID.x; + if (threadIdx == 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 + // gather input + vec3 v0 = inBlades[threadIdx].v0.xyz; + vec3 v1 = inBlades[threadIdx].v1.xyz; + vec3 v2 = inBlades[threadIdx].v2.xyz; + vec3 up = inBlades[threadIdx].up.xyz; + float orientation = inBlades[threadIdx].v0.w; + float height = inBlades[threadIdx].v1.w; + float width = inBlades[threadIdx].v2.w; + float stiffness = inBlades[threadIdx].up.w; + + vec3 orientationDir = normalize(vec3(cos(orientation), 0.0, sin(orientation))); // pointing along width of blade + vec3 faceDir = normalize(cross(up, orientationDir)); // pointing out from face of blade + + // DONE: Apply forces on every blade and update the vertices in the buffer + + // gravity force + vec3 gravityDir = vec3(0.f, -1.f, 0.f); + float gravityAcc = 9.81f; + vec3 gE = gravityDir * gravityAcc; + vec3 gF = 0.25 * length(gE) * faceDir; + vec3 gravity = gF + gE; + + // recovery force + vec3 iv2 = v0 + height * up; + vec3 recovery = (iv2 - v2) * stiffness; - // TODO: Cull blades that are too far away or not in the camera frustum and write them + // wind force + // playing around with FBM, noise, and time + vec3 windDir = normalize(vec3(sin(totalTime * fbm(0.5 * v0.xz)), 0.5, cos(totalTime * fbm(0.5 * v0.xz)))); + //windDir = normalize(vec3(sin(fbm(abs(cos(totalTime)) * 0.5 * v0.xz)), 0.5, cos(fbm(abs(sin(totalTime)) * 0.5 * v0.xz)))); + + float dirAlignment = 1.0 - abs(dot(windDir, normalize(v2 - v0))); + float heightRatio = dot(v2 - v0, up) / height; + float windAlignment = dirAlignment * heightRatio; + float windScale = 20.f * noise(abs(sin(totalTime)) * v0.xz); + windScale = 50.f * abs(fbm(v0.xz + vec2(totalTime))); + vec3 wind = windScale * windAlignment * windDir; + + // apply total force + vec3 tv2 = (gravity + recovery + wind) * 0.01; + v2 += tv2; + + // apply correction + v2 = v2 - up * min(dot(up, v2 - v0), 0.0); + + float l_proj = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + height * up * max(1.0 - (l_proj / height), 0.05 * max(l_proj / height, 1.0)); + + float n = 2.0; // degree of bezier curve + float L0 = distance(v2, v0); + float L1 = distance(v2, v1) + distance(v1, v0); + float L = (2.0 * L0 + (n - 1.0) * L1) / (n + 1.0); + float r = height / L; + + vec3 v1_corr = v0 + r * (v1 - v0); + vec3 v2_corr = v1_corr + r * (v2 - v1); + + // update values + inBlades[threadIdx].v1 = vec4(v1_corr, height); + inBlades[threadIdx].v2 = vec4(v2_corr, width); + + // DONE: 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 + + bool cull = false; + mat4 invView = inverse(camera.view); + +#if ORIENTATION_CULL + // orientation culling + vec3 viewDir = normalize(vec3(camera.view[0][2], camera.view[1][2], camera.view[2][2])); + cull = cull || abs(dot(orientationDir, viewDir)) > 0.9; +#endif // #if ORIENTATION_CULL + +#if FRUSTUM_CULL + // view-frustum culling + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; // estimated midpoint of blade + + // project v0, v2, and m to NDC + vec4 v0_NDC = camera.proj * camera.view * vec4(v0, 1.0); + vec4 v2_NDC = camera.proj * camera.view * vec4(v2, 1.0); + vec4 m_NDC = camera.proj * camera.view * vec4(m, 1.0); + + float t = 0.1; // tolerance + float v0Bounds = v0_NDC.w + t; + float v2Bounds = v2_NDC.w + t; + float mBounds = m_NDC.w + t; + bool v0InBounds = inBounds(v0_NDC.x, v0Bounds) && inBounds(v0_NDC.y, v0Bounds) && inBounds(v0_NDC.z, v0Bounds); + bool v2InBounds = inBounds(v2_NDC.x, v2Bounds) && inBounds(v2_NDC.y, v2Bounds) && inBounds(v2_NDC.z, v2Bounds); + bool mInBounds = inBounds(m_NDC.x, mBounds) && inBounds(m_NDC.y, mBounds) && inBounds(m_NDC.z, mBounds); + + // for vis/debug purposes + //if (!v0InBounds && !v2InBounds && !mInBounds) { + // inBlades[threadIdx].v1 += vec4(0.0, 5.0, 0.0, 0.0); + //} + + // cull blade if all 3 points are outside frustum + cull = cull || (!v0InBounds && !v2InBounds && !mInBounds); + +#endif // #if FRUSTUM_CULL + +#if DISTANCE_CULL + // distance culling + vec3 cameraPos = vec3(invView[3][0], invView[3][1], invView[3][2]); + int numBuckets = 3; // number of buckets/levels used in distance culling + float d_max = 50.0; // max distance + float d_proj = length(v0 - cameraPos - up * dot(v0 - cameraPos, up)); + cull = cull || (mod(threadIdx, numBuckets) > floor(numBuckets * (1.0 - d_proj / d_max))); + +#endif // #if DISTANCE_CULL + + // if no culling, increment number of blades and add blade to remaining blades + if (!cull) { + remainingBlades[atomicAdd(numBlades.vertexCount, 1)] = inBlades[threadIdx]; + } } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..ef96f73 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -6,12 +6,27 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare fragment shader inputs +// DONE: Declare fragment shader inputs +layout(location = 0) in vec2 in_uv; +layout(location = 1) in float in_height; layout(location = 0) out vec4 outColor; void main() { - // TODO: Compute fragment color + const float MAX_HEIGHT = 2.5f; - outColor = vec4(1.0); + // DONE: Compute fragment color + float over255 = 1.0 / 255.0; + + // green gradient color + vec3 green1 = vec3(16.0, 82.0, 50.0) * over255; + vec3 green2 = vec3(121.0, 161.0, 98.0) * over255; + vec3 green3 = vec3(169.0, 224.0, 81.0) * over255; + + float t = (in_uv.y * in_height / MAX_HEIGHT); + + vec3 color = mix(mix(green1, green2, t), mix(green2, green3, t), t); + //color = mix(mix(green1, green2, in_uv.y), mix(green2, green3, in_uv.y), in_uv.y); + + outColor = vec4(color, 1.0); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..a4d6d90 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -8,19 +8,35 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation control shader inputs and outputs +// DONE: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4[] in_v0; +layout(location = 1) in vec4[] in_v1; +layout(location = 2) in vec4[] in_v2; +layout(location = 3) in vec4[] in_up; + +layout(location = 0) out vec4[] out_v0; +layout(location = 1) out vec4[] out_v1; +layout(location = 2) out vec4[] out_v2; 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 + // DONE: 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]; + + // DONE: Set level of tessellation + // TODO: base level of tessellation on distance from camera + //mat4 invView = inverse(camera.view); + //vec3 cameraPos = vec3(invView[3][0], invView[3][1], invView[3][2]); + //float dist = length(cameraPos - vec3(camera.proj * camera.view * in_v0[gl_InvocationID])) / 50.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] = 2; + gl_TessLevelInner[1] = 8; + gl_TessLevelOuter[0] = 8; + gl_TessLevelOuter[1] = 2; + gl_TessLevelOuter[2] = 8; + gl_TessLevelOuter[3] = 8; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..ebeb8d6 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -1,6 +1,8 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +#define PI 3.141592653238 + layout(quads, equal_spacing, ccw) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -8,11 +10,46 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation evaluation shader inputs and outputs +// DONE: 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 = 0) out vec2 out_uv; +layout(location = 1) out float out_height; + void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; - // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + // DONE: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + + // gather input + vec3 v0 = in_v0[0].xyz; + vec3 v1 = in_v1[0].xyz; + vec3 v2 = in_v2[0].xyz; + float width = in_v2[0].w; + float orientation = in_v0[0].w; + + // deCasteljau's to evaluate Bezier curve + vec3 a = mix(v0, v1, v); + vec3 b = mix(v1, v2, v); + vec3 c = mix(a, b, v); + + //vec3 t1 = normalize(vec3(cos(orientation + PI), 0.0, sin(orientation + PI))); + vec3 t1 = normalize(vec3(cos(orientation), 0.0, sin(orientation))); // pointing along width of blade + + vec3 c0 = c - width * t1; + vec3 c1 = c + width * t1; + + // for triangle shape + float t = u + 0.5 * v - u * v; + vec4 pos = vec4(mix(c0, c1, t), 1.0); + + // output + out_uv = vec2(u, v); + out_height = in_v1[0].w;; + + gl_Position = camera.proj * camera.view * pos; } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..e1d0c8c 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,27 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { mat4 model; }; -// TODO: Declare vertex shader inputs and outputs +// DONE: Declare vertex shader inputs and outputs +layout(location = 0) in vec4 in_v0; +layout(location = 1) in vec4 in_v1; +layout(location = 2) in vec4 in_v2; +layout(location = 3) in vec4 in_up; + +layout(location = 0) out vec4 out_v0; +layout(location = 1) out vec4 out_v1; +layout(location = 2) out vec4 out_v2; +layout(location = 3) out vec4 out_up; out gl_PerVertex { vec4 gl_Position; }; void main() { - // TODO: Write gl_Position and any other shader outputs + // DONE: Write gl_Position and any other shader outputs + out_v0 = model * in_v0; + out_v1 = model * in_v1; + out_v2 = model * in_v2; + out_up = in_up; + + gl_Position = model * vec4(in_v0.xyz, 1.0); }