diff --git a/README.md b/README.md
index 20ee451..cc6294a 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,112 @@ Vulkan Grass Rendering
**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5**
-* (TODO) YOUR NAME HERE
-* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab)
+* Di Lu
+ * [LinkedIn](https://www.linkedin.com/in/di-lu-0503251a2/)
+ * [personal website](https://www.dluisnothere.com/)
+* Tested on: Windows 11, i7-12700H @ 2.30GHz 32GB, NVIDIA GeForce RTX 3050 Ti
-### (TODO: Your README)
+## Introduction
-*DO NOT* leave the README to the last minute! It is a crucial part of the
-project, and we will not be able to grade you without a good README.
+This is my first Vulkan project, which simulates grass movement in an outdoor environment based on 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)
+
+This simulation is broken into three parts: Tessellation of the grass blade into some particular shape (in my case, a quadric knife shape), physics calcuations in a compute shader for grass movements, and culling unnecessary blades for performance. For the physics element of the simulation, the following three environmental forces are considered:
+- Recovery: A force based on stiffness of a blade that counteracts external forces of gravity and wind
+- Gravity: Gravitational force from the ground
+- Wind: Force applied by wind and air
+- Result Validation (to clamp the recalculated blade shape's length and position)
+- **Note that the paper also specifies Collision force, but we don't take that into account here**
+
+![](img/diGrass2.gif)
+
+## Core Features and Results
+
+### 1. Grass Shape Tessellation
+
+At the initial stage of the project, there is only a patch of dirt with no visible grass on it. This is because all grass blades initially are represented by one vertex. In order for grass blades to appear, we must tessellate the vertex into a surface curve that represents the grass blade's shape. Based on Real-Time Grass Rendering, grass can be suggested to follow these possible curves:
+
+
+
+For my project, I chose #3, the quadric "blade" shape for my grass.
+
+Furthermore, each grass blade is controlled by a series of control points illustrated by the following diagram:
+
+![](img/blade_model.jpg)
+
+- `v0`: fixed base point of a grass blade
+- `v1`: this point is "interpolated"/calculated as a response to `v2` and `v0`
+- `v2`: this point gets moved based on the environmental forces
+- `up`: The blade's up vector which sprouts from the ground upward in the Y direction. This is the same as the normal of the pane.
+
+Each of the above points are represented as `vec4s` where the element _w_ (fourth element) represents the following:
+
+- `v0.w - theta`: angle of rotation of the grass blade. This is different for every blade of grass which will give variety to the grass patterns.
+- `v1.w - blade height`: height of the grass blade
+- `v2.w - blade width`: width of the base of the blade
+- `up.w - blade stiffness`: stiffness of the blade/resistance towards the environmental forces
+
+Here is the simulation without any forces:
+
+![](img/grass.png)
+
+### 2. Environmental Forces
+
+There are three environmental forces that impact the grass blade:
+
+- Recovery: A force based on stiffness of a blade that counteracts external forces of gravity and wind
+- Gravity: Gravitational force from the ground
+- Wind: Force applied by wind and air
+
+#### Gravity
+
+With only gravity active, all grass blades will collapse to the ground. Gravitational force is a combination of `frontal gravity` and `downward gravity`. Environmental gravity acts along a vector of influence which is orthogonal to the width of the blade of the grass (along the face forward direction of the grass). Here is an image of the result with just gravity:
+
+![](img/gravity.png)
+
+#### Resistance
+
+Resistance factor is calculated by taking into account a stiffness constant defined by each grass blade. We essentially render the blade between the fully upstanding position and the collapsed ground position.
+
+![](img/resistance.png)
+
+#### Wind
+
+Wind factor is calculated by using a procedural sinusoidal wave and fbm noise to calculate some noise factor applied to the vector displacement.
+
+![](img/wind.gif)
+
+### 3. Culling
+
+#### Rotational Culling
+
+Rotational Culling removes blades such that its face-forward is exacty orthongal to the camera view vector towards the blade. This is because a blade has no thickness, so at that angle, there is no reason to send it to the render pipeline.
+
+![](img/rotation.gif)
+
+#### Frustum Culling
+
+Frustum Culling removes blades that are outside of your viewport. With my implementation, there is a slight border where I remove blades within the frame as well so you can see the effect more clearly.
+
+![](img/frustum.gif)
+
+#### Distance Culling
+
+Distance Culling removes blades that are outside of a certain distance.
+
+![](img/distance.gif)
+
+## Performance Analysis
+
+**Performance Measurements vs. Number of Blades**
+
+![](img/fpsNumBlades.png)
+
+Based on the measurement of FPS above, increasing the number of blades will decrease FPS, as expected.
+
+**Performance Measurements vs. Different Culling Methods over an increasing number of blades**
+
+![](img/lastChart.png)
+
+This chart above is a comparison of performance between NO culling, Frustum Culling, Orientation Culling, Distance Culling, and ALL culling. However, the results are a little bit difficult to generalize. This is because each of the three culling methods work best for different scenarios/setups. For example, Frustum culling works best when you are really zoomed in on a patch of few blades. Orientation culling can be more accurately understood by the graph above since all the grass blades are oriented randomly. Distance culling works best when you can compare the results between being far away from the scene vs. being close up. However, it's easy to see from above that having ALL culling turned on is generally better than having No Culling at all.
+
+## Bloopers! :)
diff --git a/bin/Debug/vulkan_grass_rendering.exe b/bin/Debug/vulkan_grass_rendering.exe
new file mode 100644
index 0000000..1f67220
Binary files /dev/null and b/bin/Debug/vulkan_grass_rendering.exe differ
diff --git a/bin/Debug/vulkan_grass_rendering.pdb b/bin/Debug/vulkan_grass_rendering.pdb
new file mode 100644
index 0000000..be2c4fc
Binary files /dev/null and b/bin/Debug/vulkan_grass_rendering.pdb differ
diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe
index f68db3a..655b457 100644
Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ
diff --git a/img/bladeShape.png b/img/bladeShape.png
new file mode 100644
index 0000000..cf20dcb
Binary files /dev/null and b/img/bladeShape.png differ
diff --git a/img/diGrass.gif b/img/diGrass.gif
new file mode 100644
index 0000000..1d784df
Binary files /dev/null and b/img/diGrass.gif differ
diff --git a/img/diGrass2.gif b/img/diGrass2.gif
new file mode 100644
index 0000000..799beb2
Binary files /dev/null and b/img/diGrass2.gif differ
diff --git a/img/distance.gif b/img/distance.gif
new file mode 100644
index 0000000..f4f5a60
Binary files /dev/null and b/img/distance.gif differ
diff --git a/img/fpsNumBlades.png b/img/fpsNumBlades.png
new file mode 100644
index 0000000..37504d0
Binary files /dev/null and b/img/fpsNumBlades.png differ
diff --git a/img/frustum.gif b/img/frustum.gif
new file mode 100644
index 0000000..cbbcf57
Binary files /dev/null and b/img/frustum.gif differ
diff --git a/img/grass.png b/img/grass.png
new file mode 100644
index 0000000..960f468
Binary files /dev/null and b/img/grass.png differ
diff --git a/img/gravity.png b/img/gravity.png
new file mode 100644
index 0000000..fb9cafc
Binary files /dev/null and b/img/gravity.png differ
diff --git a/img/lastChart.png b/img/lastChart.png
new file mode 100644
index 0000000..2563309
Binary files /dev/null and b/img/lastChart.png differ
diff --git a/img/resistance.png b/img/resistance.png
new file mode 100644
index 0000000..cb3348e
Binary files /dev/null and b/img/resistance.png differ
diff --git a/img/rotation.gif b/img/rotation.gif
new file mode 100644
index 0000000..37ed06d
Binary files /dev/null and b/img/rotation.gif differ
diff --git a/img/wind.gif b/img/wind.gif
new file mode 100644
index 0000000..05d8d75
Binary files /dev/null and b/img/wind.gif differ
diff --git a/src/Blades.cpp b/src/Blades.cpp
index 80e3d76..92a9986 100644
--- a/src/Blades.cpp
+++ b/src/Blades.cpp
@@ -44,9 +44,9 @@ 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, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory);
+ BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, bladesBuffer, bladesBufferMemory);
+ BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory);
+ BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory);
}
VkBuffer Blades::GetBladesBuffer() const {
diff --git a/src/Blades.h b/src/Blades.h
index 9bd1eed..24a76ba 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 << 22;
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..b3d3598 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -6,6 +6,8 @@
#include "Camera.h"
#include "Image.h"
+#include
+
static constexpr unsigned int WORKGROUP_SIZE = 32;
Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* camera)
@@ -31,8 +33,9 @@ Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* c
CreateGraphicsPipeline();
CreateGrassPipeline();
CreateComputePipeline();
- RecordCommandBuffers();
RecordComputeCommandBuffer();
+ RecordCommandBuffers();
+
}
void Renderer::CreateCommandPools() {
@@ -145,6 +148,9 @@ void Renderer::CreateCameraDescriptorSetLayout() {
}
void Renderer::CreateModelDescriptorSetLayout() {
+ // model represents anything that is geometry which require model matrix and texture sampler
+
+ // this is what will eventually represent a matrix.
VkDescriptorSetLayoutBinding uboLayoutBinding = {};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
@@ -152,6 +158,7 @@ void Renderer::CreateModelDescriptorSetLayout() {
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
uboLayoutBinding.pImmutableSamplers = nullptr;
+ // this will eventaully be sampler
VkDescriptorSetLayoutBinding samplerLayoutBinding = {};
samplerLayoutBinding.binding = 1;
samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
@@ -198,6 +205,43 @@ 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
+
+ // TOASK: I think we need both samplerLayoutBinding and uboLayoutBinding for compute.
+
+ // Uniform buffer object can be anything
+ VkDescriptorSetLayoutBinding grassLayoutBinding = {};
+ grassLayoutBinding.binding = 0;
+ grassLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ grassLayoutBinding.descriptorCount = 1;
+ grassLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ grassLayoutBinding.pImmutableSamplers = nullptr;
+
+ VkDescriptorSetLayoutBinding culledGrassLayoutBinding = {};
+ culledGrassLayoutBinding.binding = 1;
+ culledGrassLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ culledGrassLayoutBinding.descriptorCount = 1;
+ culledGrassLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ culledGrassLayoutBinding.pImmutableSamplers = nullptr;
+
+ VkDescriptorSetLayoutBinding numGrassLayoutBinding = {};
+ numGrassLayoutBinding.binding = 2;
+ numGrassLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ numGrassLayoutBinding.descriptorCount = 1;
+ numGrassLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ numGrassLayoutBinding.pImmutableSamplers = nullptr;
+
+ // TOASK: does compute need both? I know grass does.
+ std::vector bindings = { grassLayoutBinding, culledGrassLayoutBinding, numGrassLayoutBinding };
+
+ // Create the descriptor set layout
+ VkDescriptorSetLayoutCreateInfo layoutInfo = {};
+ layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ layoutInfo.bindingCount = static_cast(bindings.size());
+ layoutInfo.pBindings = bindings.data();
+
+ if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) {
+ throw std::runtime_error("Failed to create descriptor set layout");
+ }
}
void Renderer::CreateDescriptorPool() {
@@ -216,6 +260,8 @@ void Renderer::CreateDescriptorPool() {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 },
// TODO: Add any additional types and counts of descriptors you will need to allocate
+ //{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(3 * scene->GetBlades().size()) }
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(scene->GetBlades().size() * 3) },
};
VkDescriptorPoolCreateInfo poolInfo = {};
@@ -294,9 +340,10 @@ void Renderer::CreateModelDescriptorSets() {
imageInfo.imageView = scene->GetModels()[i]->GetTextureView();
imageInfo.sampler = scene->GetModels()[i]->GetTextureSampler();
+ // process attaches vars to the graphics pipeline
descriptorWrites[2 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[2 * i + 0].dstSet = modelDescriptorSets[i];
- descriptorWrites[2 * i + 0].dstBinding = 0;
+ descriptorWrites[2 * i + 0].dstBinding = 0; // handles specified by .frag and .vert
descriptorWrites[2 * i + 0].dstArrayElement = 0;
descriptorWrites[2 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[2 * i + 0].descriptorCount = 1;
@@ -320,6 +367,41 @@ void Renderer::CreateModelDescriptorSets() {
void Renderer::CreateGrassDescriptorSets() {
// TODO: Create Descriptor sets for the grass.
// This should involve creating descriptor sets which point to the model matrix of each group of grass blades
+ grassDescriptorSets.resize(scene->GetBlades().size());
+
+ // Describe the desciptor set
+ VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout };
+ VkDescriptorSetAllocateInfo allocInfo = {};
+ allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ allocInfo.descriptorPool = descriptorPool;
+ allocInfo.descriptorSetCount = static_cast(grassDescriptorSets.size());
+ allocInfo.pSetLayouts = layouts;
+
+ // Allocate descriptor sets
+ if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) {
+ throw std::runtime_error("Failed to allocate grass descriptor set");
+ }
+
+ std::vector descriptorWrites(grassDescriptorSets.size());
+
+ for (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;
+ }
+
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
void Renderer::CreateTimeDescriptorSet() {
@@ -360,8 +442,83 @@ void Renderer::CreateTimeDescriptorSet() {
void Renderer::CreateComputeDescriptorSets() {
// TODO: Create Descriptor sets for the compute pipeline
// The descriptors should point to Storage buffers which will hold the grass blades, the culled grass blades, and the output number of grass blades
+ // TOASK: What storage buffers are these??
+ computeDescriptorSets.resize(scene->GetBlades().size());
+
+ // Describe the desciptor 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 compute descriptor set");
+ }
+
+ std::vector descriptorWrites(3 * computeDescriptorSets.size());
+
+ // GetBlades() returns all groups of blades.
+ // GetCulledBlades() returns all groups of culled blades.
+ // GetNumBladesBuffer() returns all groups of numblades???
+ for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) {
+ VkDescriptorBufferInfo grassBufferInfo = {};
+ grassBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer();
+ grassBufferInfo.offset = 0;
+ grassBufferInfo.range = NUM_BLADES * sizeof(Blade);//sizeof(grassBufferInfo.buffer);
+
+ VkDescriptorBufferInfo culledGrassBufferInfo = {};
+ culledGrassBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer();
+ culledGrassBufferInfo.offset = 0;
+ culledGrassBufferInfo.range = NUM_BLADES * sizeof(Blade);//sizeof(culledGrassBufferInfo.buffer);
+
+ VkDescriptorBufferInfo numGrassBufferInfo = {};
+ numGrassBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer();
+ numGrassBufferInfo.offset = 0;
+ numGrassBufferInfo.range = sizeof(BladeDrawIndirect);
+
+ // normal grass bladess
+ descriptorWrites[3 * i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[3 * i].dstSet = computeDescriptorSets[i];
+ descriptorWrites[3 * i].dstBinding = 0;
+ //descriptorWrites[3 * i].dstArrayElement = 0;
+ descriptorWrites[3 * i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[3 * i].descriptorCount = 1;
+ descriptorWrites[3 * i].pBufferInfo = &grassBufferInfo;
+ descriptorWrites[3 * i].pImageInfo = nullptr;
+ descriptorWrites[3 * i].pTexelBufferView = nullptr;
+
+ // culled grass blades
+ 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 = &culledGrassBufferInfo;
+ descriptorWrites[3 * i + 1].pImageInfo = nullptr;
+ descriptorWrites[3 * i + 1].pTexelBufferView = nullptr;
+
+
+ //output number of grass blades
+ 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 = &numGrassBufferInfo;
+ descriptorWrites[3 * i + 2].pImageInfo = nullptr;
+ descriptorWrites[3 * i + 2].pTexelBufferView = nullptr;
+ }
+
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
+
}
+
void Renderer::CreateGraphicsPipeline() {
VkShaderModule vertShaderModule = ShaderModule::Create("shaders/graphics.vert.spv", logicalDevice);
VkShaderModule fragShaderModule = ShaderModule::Create("shaders/graphics.frag.spv", logicalDevice);
@@ -717,7 +874,9 @@ void Renderer::CreateComputePipeline() {
computeShaderStageInfo.pName = "main";
// TODO: Add the compute dsecriptor set layout you create to this list
- std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout };
+ std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, // 0
+ timeDescriptorSetLayout, // 1
+ computeDescriptorSetLayout}; // 2
// Create pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
@@ -884,7 +1043,12 @@ void Renderer::RecordComputeCommandBuffer() {
vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 1, 1, &timeDescriptorSet, 0, nullptr);
// TODO: For each group of blades bind its descriptor set and dispatch
-
+ // TOASK: What is a group of blades???
+ for (int i = 0; i < scene->GetBlades().size(); ++i) {
+ vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr);
+ vkCmdDispatch(computeCommandBuffer, NUM_BLADES / 32, 1, 1);
+ }
+
// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
throw std::runtime_error("Failed to record compute command buffer");
@@ -976,13 +1140,14 @@ void Renderer::RecordCommandBuffers() {
VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() };
VkDeviceSize offsets[] = { 0 };
// TODO: Uncomment this when the buffers are populated
- // vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);
+ vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);
// TODO: Bind the descriptor set for each grass blades model
+ vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &grassDescriptorSets[j], 0, nullptr);
// Draw
// TODO: Uncomment this when the buffers are populated
- // vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect));
+ vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect));
}
// End render pass
@@ -1057,6 +1222,7 @@ Renderer::~Renderer() {
vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr);
+ vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr);
vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr);
diff --git a/src/Renderer.h b/src/Renderer.h
index 95e025f..287de20 100644
--- a/src/Renderer.h
+++ b/src/Renderer.h
@@ -19,6 +19,7 @@ class Renderer {
void CreateModelDescriptorSetLayout();
void CreateTimeDescriptorSetLayout();
void CreateComputeDescriptorSetLayout();
+ // void CreateGrassDescriptorSetLayout();
void CreateDescriptorPool();
@@ -56,12 +57,16 @@ class Renderer {
VkDescriptorSetLayout cameraDescriptorSetLayout;
VkDescriptorSetLayout modelDescriptorSetLayout;
VkDescriptorSetLayout timeDescriptorSetLayout;
-
+ VkDescriptorSetLayout grassDescriptorSetLayout;
+ VkDescriptorSetLayout computeDescriptorSetLayout;
+
VkDescriptorPool descriptorPool;
VkDescriptorSet cameraDescriptorSet;
std::vector modelDescriptorSets;
VkDescriptorSet timeDescriptorSet;
+ std::vector grassDescriptorSets;
+ std::vector computeDescriptorSets;
VkPipelineLayout graphicsPipelineLayout;
VkPipelineLayout grassPipelineLayout;
@@ -80,3 +85,94 @@ class Renderer {
std::vector commandBuffers;
VkCommandBuffer computeCommandBuffer;
};
+
+//#pragma once
+//
+//#include "Device.h"
+//#include "SwapChain.h"
+//#include "Scene.h"
+//#include "Camera.h"
+//
+//class Renderer {
+//public:
+// Renderer() = delete;
+// Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* camera);
+// ~Renderer();
+//
+// void CreateCommandPools();
+//
+// void CreateRenderPass();
+//
+// void CreateCameraDescriptorSetLayout();
+// void CreateModelDescriptorSetLayout();
+// void CreateTimeDescriptorSetLayout();
+// void CreateComputeDescriptorSetLayout();
+//
+// void CreateDescriptorPool();
+//
+// void CreateCameraDescriptorSet();
+// void CreateModelDescriptorSets();
+// void CreateGrassDescriptorSets();
+// void CreateTimeDescriptorSet();
+// void CreateComputeDescriptorSets();
+//
+// void CreateGraphicsPipeline();
+// void CreateGrassPipeline();
+// void CreateComputePipeline();
+//
+// void CreateFrameResources();
+// void DestroyFrameResources();
+// void RecreateFrameResources();
+//
+// void RecordCommandBuffers();
+// void RecordComputeCommandBuffer();
+//
+// void Frame();
+//
+//private:
+// Device* device;
+// VkDevice logicalDevice;
+// SwapChain* swapChain;
+// Scene* scene;
+// Camera* camera;
+//
+// VkCommandPool graphicsCommandPool;
+// VkCommandPool computeCommandPool;
+//
+// VkRenderPass renderPass;
+//
+// VkDescriptorSetLayout cameraDescriptorSetLayout;
+// VkDescriptorSetLayout modelDescriptorSetLayout;
+// VkDescriptorSetLayout timeDescriptorSetLayout;
+//
+// // Di added
+// VkDescriptorSetLayout grassDescriptorSetLayout;
+// VkDescriptorSetLayout computeDescriptorSetLayout;
+//
+// VkDescriptorPool descriptorPool;
+//
+// VkDescriptorSet cameraDescriptorSet;
+// std::vector modelDescriptorSets;
+// VkDescriptorSet timeDescriptorSet;
+//
+// // Di Added
+// std::vector grassDescriptorSets;
+// std::vector computeDescriptorSets;
+//
+// VkPipelineLayout graphicsPipelineLayout;
+// VkPipelineLayout grassPipelineLayout;
+// VkPipelineLayout computePipelineLayout;
+//
+// VkPipeline graphicsPipeline;
+// VkPipeline grassPipeline;
+// VkPipeline computePipeline;
+//
+// std::vector imageViews;
+// VkImage depthImage;
+// VkDeviceMemory depthImageMemory;
+// VkImageView depthImageView;
+// std::vector framebuffers;
+//
+// std::vector commandBuffers;
+// VkCommandBuffer computeCommandBuffer;
+//};
diff --git a/src/main.cpp b/src/main.cpp
index 8bf822b..cdd4e2c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,6 +5,7 @@
#include "Camera.h"
#include "Scene.h"
#include "Image.h"
+#include
Device* device;
SwapChain* swapChain;
@@ -65,20 +66,145 @@ namespace {
}
}
+//int main() {
+// static constexpr char* applicationName = "Vulkan Grass Rendering";
+// InitializeWindow(640, 480, applicationName);
+//
+// unsigned int glfwExtensionCount = 0;
+// const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
+//
+// Instance* instance = new Instance(applicationName, glfwExtensionCount, glfwExtensions);
+//
+// VkSurfaceKHR surface;
+// if (glfwCreateWindowSurface(instance->GetVkInstance(), GetGLFWWindow(), nullptr, &surface) != VK_SUCCESS) {
+// throw std::runtime_error("Failed to create window surface");
+// }
+//
+// instance->PickPhysicalDevice({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }, QueueFlagBit::GraphicsBit | QueueFlagBit::TransferBit | QueueFlagBit::ComputeBit | QueueFlagBit::PresentBit, surface);
+//
+// VkPhysicalDeviceFeatures deviceFeatures = {};
+// deviceFeatures.tessellationShader = VK_TRUE;
+// deviceFeatures.fillModeNonSolid = VK_TRUE;
+// deviceFeatures.samplerAnisotropy = VK_TRUE;
+//
+// device = instance->CreateDevice(QueueFlagBit::GraphicsBit | QueueFlagBit::TransferBit | QueueFlagBit::ComputeBit | QueueFlagBit::PresentBit, deviceFeatures);
+//
+// swapChain = device->CreateSwapChain(surface, 5);
+//
+// camera = new Camera(device, 640.f / 480.f);
+//
+// VkCommandPoolCreateInfo transferPoolInfo = {};
+// transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+// transferPoolInfo.queueFamilyIndex = device->GetInstance()->GetQueueFamilyIndices()[QueueFlags::Transfer];
+// transferPoolInfo.flags = 0;
+//
+// VkCommandPool transferCommandPool;
+// if (vkCreateCommandPool(device->GetVkDevice(), &transferPoolInfo, nullptr, &transferCommandPool) != VK_SUCCESS) {
+// throw std::runtime_error("Failed to create command pool");
+// }
+//
+// VkImage grassImage;
+// VkDeviceMemory grassImageMemory;
+// Image::FromFile(device,
+// transferCommandPool,
+// "images/grass.jpg",
+// VK_FORMAT_R8G8B8A8_UNORM,
+// VK_IMAGE_TILING_OPTIMAL,
+// VK_IMAGE_USAGE_SAMPLED_BIT,
+// VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+// VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+// grassImage,
+// grassImageMemory
+// );
+//
+// float planeDim = 15.f;
+// float halfWidth = planeDim * 0.5f;
+// Model* plane = new Model(device, transferCommandPool,
+// {
+// { { -halfWidth, 0.0f, halfWidth }, { 1.0f, 0.0f, 0.0f },{ 1.0f, 0.0f } },
+// { { halfWidth, 0.0f, halfWidth }, { 0.0f, 1.0f, 0.0f },{ 0.0f, 0.0f } },
+// { { halfWidth, 0.0f, -halfWidth }, { 0.0f, 0.0f, 1.0f },{ 0.0f, 1.0f } },
+// { { -halfWidth, 0.0f, -halfWidth }, { 1.0f, 1.0f, 1.0f },{ 1.0f, 1.0f } }
+// },
+// { 0, 1, 2, 2, 3, 0 }
+// );
+// plane->SetTexture(grassImage);
+//
+// Blades* blades = new Blades(device, transferCommandPool, planeDim);
+//
+// vkDestroyCommandPool(device->GetVkDevice(), transferCommandPool, nullptr);
+//
+// Scene* scene = new Scene(device);
+// scene->AddModel(plane);
+// scene->AddBlades(blades);
+//
+// renderer = new Renderer(device, swapChain, scene, camera);
+//
+// glfwSetWindowSizeCallback(GetGLFWWindow(), resizeCallback);
+// glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
+// glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback);
+//
+// double fps = 0;
+// double timebase = 0;
+// int frame = 0;
+//
+// while (!ShouldQuit()) {
+// glfwPollEvents();
+//
+// frame++;
+// double curTime = glfwGetTime();
+//
+// if ((time - timebase) > 1.0) {
+// fps = frame / (time - timebase);
+// timebase = time;
+// frame = 0;
+// }
+//
+// scene->UpdateTime();
+// renderer->Frame();
+//
+// std::ostringstream ss;
+// ss << "[";
+// ss.precision(1);
+// ss << std::fixed << fps;
+// ss << " fps] " << applicationName;
+// glfwSetWindowTitle(window, ss.str().c_str());
+// }
+//
+// vkDeviceWaitIdle(device->GetVkDevice());
+//
+// vkDestroyImage(device->GetVkDevice(), grassImage, nullptr);
+// vkFreeMemory(device->GetVkDevice(), grassImageMemory, nullptr);
+//
+// delete scene;
+// delete plane;
+// delete blades;
+// delete camera;
+// delete renderer;
+// delete swapChain;
+// delete device;
+// delete instance;
+// DestroyWindow();
+// return 0;
+//}
+
int main() {
static constexpr char* applicationName = "Vulkan Grass Rendering";
- InitializeWindow(640, 480, applicationName);
+ InitializeWindow(1280, 960, applicationName);
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
+ //NOTE: First thing is to create an instance -> connection between application and the Vulkan library
Instance* instance = new Instance(applicationName, glfwExtensionCount, glfwExtensions);
+ //NOTE: Connection between Vulkan and the windows system to present result to the screen
VkSurfaceKHR surface;
if (glfwCreateWindowSurface(instance->GetVkInstance(), GetGLFWWindow(), nullptr, &surface) != VK_SUCCESS) {
throw std::runtime_error("Failed to create window surface");
}
+ //NOTE: Need to select a physical GPU device to use
instance->PickPhysicalDevice({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }, QueueFlagBit::GraphicsBit | QueueFlagBit::TransferBit | QueueFlagBit::ComputeBit | QueueFlagBit::PresentBit, surface);
VkPhysicalDeviceFeatures deviceFeatures = {};
@@ -86,8 +212,10 @@ int main() {
deviceFeatures.fillModeNonSolid = VK_TRUE;
deviceFeatures.samplerAnisotropy = VK_TRUE;
+ //NOTE: Create the logical device here and connecting to the physical device
device = instance->CreateDevice(QueueFlagBit::GraphicsBit | QueueFlagBit::TransferBit | QueueFlagBit::ComputeBit | QueueFlagBit::PresentBit, deviceFeatures);
+ //NOTE: Swap chain is essentially a queue of images that are waiting to be presented to the screen
swapChain = device->CreateSwapChain(surface, 5);
camera = new Camera(device, 640.f / 480.f);
@@ -128,7 +256,8 @@ int main() {
{ 0, 1, 2, 2, 3, 0 }
);
plane->SetTexture(grassImage);
-
+
+ // NOTE: Blades class contains ALL blades (i.e. vector is contained within the class)
Blades* blades = new Blades(device, transferCommandPool, planeDim);
vkDestroyCommandPool(device->GetVkDevice(), transferCommandPool, nullptr);
@@ -139,14 +268,37 @@ int main() {
renderer = new Renderer(device, swapChain, scene, camera);
- glfwSetWindowSizeCallback(GetGLFWWindow(), resizeCallback);
- glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
- glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback);
+ GLFWwindow* window = GetGLFWWindow();
+ glfwSetWindowSizeCallback(window, resizeCallback);
+ glfwSetMouseButtonCallback(window, mouseDownCallback);
+ glfwSetCursorPosCallback(window, 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;
+ }
+
scene->UpdateTime();
renderer->Frame();
+
+ std::ostringstream ss;
+ ss << "[";
+ ss.precision(1);
+ ss << std::fixed << fps;
+ ss << " fps] " << applicationName;
+ glfwSetWindowTitle(window, ss.str().c_str());
}
vkDeviceWaitIdle(device->GetVkDevice());
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..dd424b8 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -1,56 +1,230 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-#define WORKGROUP_SIZE 32
-layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
-
-layout(set = 0, binding = 0) uniform CameraBufferObject {
- mat4 view;
- mat4 proj;
-} camera;
-
-layout(set = 1, binding = 0) uniform Time {
- float deltaTime;
- float totalTime;
-};
-
-struct Blade {
- vec4 v0;
- vec4 v1;
- vec4 v2;
- 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;
-
-bool inBounds(float value, float bounds) {
- return (value >= -bounds) && (value <= bounds);
-}
-
-void main() {
- // Reset the number of blades to 0
- if (gl_GlobalInvocationID.x == 0) {
- // numBlades.vertexCount = 0;
- }
- barrier(); // Wait till all threads reach this point
-
- // TODO: Apply forces on every blade and update the vertices in the buffer
-
- // 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
-}
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+#define WORKGROUP_SIZE 32
+#define CULLING 1
+
+layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
+
+layout(set = 0, binding = 0) uniform CameraBufferObject {
+ mat4 view;
+ mat4 proj;
+} camera;
+
+layout(set = 1, binding = 0) uniform Time {
+ float deltaTime;
+ float totalTime;
+};
+
+struct Blade {
+ vec4 v0;
+ vec4 v1;
+ vec4 v2;
+ 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 = 2, binding = 0) buffer Blades {
+ Blade blades[];
+};
+
+layout(set = 2, binding = 1) buffer CulledBlades {
+ Blade culledBlades[];
+};
+
+layout(set = 2, binding = 2) buffer NumBlades {
+ uint vertexCount; // Write the number of blades remaining here
+ uint instanceCount; // = 1
+ uint firstVertex; // = 0
+ uint firstInstance; // = 0
+} numBlades;
+
+
+bool inBounds(float value, float bounds) {
+ return (value >= -bounds) && (value <= bounds);
+}
+
+// Took this from my 566 Hw 1
+float random3D(vec3 p, float time) {
+ return cos(float(time) * 0.005) * sin(length(vec3(
+ dot(p, vec3(126.1, 316.8, 106.2)),
+ dot(p, vec3(266.5, 186.3, 206.4)),
+ dot(p, vec3(166.4, 246.2, 126.5))
+ ) * 0.01 ));
+}
+
+float interpolateNoise3D(vec3 p, float time)
+{
+ int intX = int(floor(p.x));
+ float fractX = fract(p.x);
+ int intY = int(floor(p.y));
+ float fractY = fract(p.y);
+ int intZ = int(floor(p.z));
+ float fractZ = fract(p.z);
+
+ float v1 = random3D(vec3(intX, intY, intZ), time);
+ float v2 = random3D(vec3(intX + 1, intY, intZ), time);
+ float v3 = random3D(vec3(intX, intY + 1, intZ), time);
+ float v4 = random3D(vec3(intX + 1, intY + 1, intZ), time);
+ float v5 = random3D(vec3(intX, intY, intZ + 1), time);
+ float v6 = random3D(vec3(intX + 1, intY, intZ + 1), time);
+ float v7 = random3D(vec3(intX, intY + 1, intZ + 1), time);
+ float v8 = random3D(vec3(intX + 1, intY + 1, intZ + 1), time);
+
+
+ float i1 = mix(v1, v2, fractX);
+ float i2 = mix(v3, v4, fractY);
+ float i3 = mix(v5, v6, fractY);
+ float i4 = mix(v7, v8, fractZ);
+ float i5 = mix(v1, v3, fractZ);
+ float i6 = mix(v2, v4, fractX);
+ float i7 = mix(v5, v7, fractZ);
+ float i8 = mix(v6, v8, fractX);
+
+ float mix1 = mix(mix(i1, i2, fractZ), mix(i3, i4, fractX), fractY);
+ float mix2 = mix(mix(i5, i6, fractX), mix(i7, i8, fractY), fractZ);
+ float finalMix = mix(mix1, mix2, fractX);
+ return finalMix;
+}
+
+float fbmNoise(vec3 p, float time)
+{
+ float total = 0.0;
+ float persistence = 0.5;
+ float frequency = 1.0;
+ float amplitude = 2.0;
+ int octaves = 5;
+
+ for (int i = 1; i <= octaves; i++) {
+ total += amplitude * interpolateNoise3D(frequency * p, time);
+ frequency *= 2.0;
+ amplitude *= persistence;
+ }
+ return total;
+}
+
+vec3 sinWave(vec3 p, float time)
+{
+ float noise = fbmNoise(p, time);
+ vec3 returnVec = 10 * vec3(sin(time) * (1 - noise), 0, cos(time) * noise);
+ return returnVec;
+}
+
+void main() {
+ // Reset the number of blades to 0
+ if (gl_GlobalInvocationID.x == 0) {
+ numBlades.vertexCount = 0;
+ }
+ barrier(); // Wait till all threads reach this point
+
+ uint idx = gl_GlobalInvocationID.x;
+ vec3 v0 = vec3(blades[idx].v0);
+ vec3 v1 = vec3(blades[idx].v1);
+ vec3 v2 = vec3(blades[idx].v2);
+ vec3 up = vec3(blades[idx].up);
+
+ float theta = blades[idx].v0.w;
+ float bladeHeight = blades[idx].v1.w;
+ float width = blades[idx].v2.w;
+ float stiffness = blades[idx].up.w;
+
+ vec3 bitangent = vec3(width * cos(theta), 0, width * sin(theta));
+
+ // recovery
+ vec3 initv2 = v0 + bladeHeight * up;
+ vec3 recovery = (initv2 - v2) * stiffness;
+
+ // gravity
+ float gravEnviro = length(vec3(0.0, -1.0, 0.0) * 9.8);
+ vec3 normal = cross(up, bitangent);
+ vec3 gravFront = 0.25 * gravEnviro * normal;
+ vec3 totalGravity = gravFront + gravEnviro;
+
+ // wind
+ vec3 windDirection = sinWave(v0, totalTime);
+ float fd = 1 - length(dot(normalize(windDirection), normalize(v2 - v0)));
+ float fr = dot(normalize(v2 - v0), up) / bladeHeight;
+ float windAlignment = 3 * fd * fr;
+
+ vec3 wind = windDirection * windAlignment;
+
+ //vec3 displacementV2 = (recovery + totalGravity + wind) * deltaTime;
+ vec3 displacementV2 = (totalGravity + recovery + wind) * deltaTime;
+
+ // calculate position based on the force
+ v2 += displacementV2;
+
+ //blade length checking
+ v2 = v2 - up * min(dot(up, (v2 - v0)), 0.0);
+
+ float lProj = length(v2 - v0 - up * dot((v2 - v0), up));
+ v1 = v0 + bladeHeight * up * max(1 - (lProj / bladeHeight), 0.05 * max(lProj / bladeHeight, 1));
+
+ // Length checking
+ float l0 = length(v2 - v0);
+ float l1 = length(v2 - v1) + length(v1 - v0);
+ float len = (2 * l0 + 2 * l1) / 4;
+ float r = bladeHeight / len;
+
+ v1 = v0 + r * (v1 - v0);
+ v2 = v1 + r * (v2 - v1);
+
+ blades[idx].v1.xyz = v1.xyz;
+ blades[idx].v2.xyz = v2.xyz;
+
+#if CULLING
+
+ bool orientationCull = false;
+ bool viewFrustumCull = false;
+ bool distanceCull = false;
+
+ vec3 cameraPos = vec3(inverse(camera.view)[3]);
+
+ // vector from camera to the blade point
+ vec3 camToBladeVec = v0 - cameraPos - up * dot(v0 - cameraPos, up);
+
+ // orientation culling
+ float orientation = abs(dot(camToBladeVec, bitangent));
+
+ if (orientation > 0.9) {
+ // do not render
+ return;
+ }
+
+
+ // view frustum culling
+ vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2;
+ vec4 pprime = camera.proj * camera.view * vec4(m, 1.0);
+ float h = pprime.w * 0.95;
+ bool v = inBounds(pprime.x, h) && inBounds(pprime.y, h);
+
+ if (!v) {
+ // do not render
+ return;
+ }
+
+ // distance culling
+ float projDist = length(camToBladeVec);
+ int maxDistLevel = 30;
+ int n = 5;
+
+ float bladeCullFactor = floor(n * (1 - projDist / maxDistLevel));
+
+ if (idx % n >= bladeCullFactor) {
+ // do not render
+ return;
+ }
+
+#endif
+
+ uint originalVertCount = atomicAdd(numBlades.vertexCount, 1);
+ culledBlades[originalVertCount] = blades[idx];
+}
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..8e53332 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -1,17 +1,36 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(set = 0, binding = 0) uniform CameraBufferObject {
- mat4 view;
- mat4 proj;
-} camera;
-
-// TODO: Declare fragment shader inputs
-
-layout(location = 0) out vec4 outColor;
-
-void main() {
- // TODO: Compute fragment color
-
- outColor = vec4(1.0);
-}
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(set = 0, binding = 0) uniform CameraBufferObject {
+ mat4 view;
+ mat4 proj;
+} camera;
+
+// TODO: Declare fragment shader inputs
+layout(location = 0) in vec4 in_pos;
+layout(location = 1) in vec4 in_nor;
+layout(location = 2) in float verticality;
+
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ // TODO: Compute fragment color
+ vec4 green = vec4(0.34, 0.64, 0.0, 1.0);
+
+ vec3 lightPos = vec3(20.0, 20.0, 20.0);
+
+ // Lambertian reflection
+ vec3 lightVec = normalize(lightPos - vec3(in_pos));
+ float diffuseTerm = dot(normalize(vec3(in_nor)), normalize(lightVec));
+
+ // Avoid negative lighting values
+ diffuseTerm = clamp(diffuseTerm, 0, 1);
+
+ float ambientTerm = 0.2;
+
+ float lightIntensity = diffuseTerm + ambientTerm;
+
+ float heightLerp = (1 - verticality) * 0.3 + verticality * 0.8;
+ outColor = heightLerp * green;
+
+}
\ No newline at end of file
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..d732fe8 100644
--- a/src/shaders/grass.tesc
+++ b/src/shaders/grass.tesc
@@ -1,26 +1,43 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(vertices = 1) out;
-
-layout(set = 0, binding = 0) uniform CameraBufferObject {
- mat4 view;
- mat4 proj;
-} camera;
-
-// TODO: Declare tessellation control shader inputs and outputs
-
-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
-
- // TODO: Set level of tesselation
- // gl_TessLevelInner[0] = ???
- // gl_TessLevelInner[1] = ???
- // gl_TessLevelOuter[0] = ???
- // gl_TessLevelOuter[1] = ???
- // gl_TessLevelOuter[2] = ???
- // gl_TessLevelOuter[3] = ???
-}
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(vertices = 1) out;
+
+layout(set = 0, binding = 0) uniform CameraBufferObject {
+ mat4 view;
+ mat4 proj;
+} camera;
+
+// TODO: Declare tessellation control shader inputs and outputs
+
+// These are arrays because your patch might be starting from multiple vertices.
+// For our purposes, we only start with 1 vertex so all of these arrays only contain one vec4
+
+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[];
+
+void main() {
+ // Don't move the origin location of the patch
+ gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+
+ // TODO: Write any shader outputs
+ out_v0[gl_InvocationID] = in_v0[gl_InvocationID];
+ out_v1[gl_InvocationID] = in_v1[gl_InvocationID];
+ out_v2[gl_InvocationID] = in_v2[gl_InvocationID];
+ out_up[gl_InvocationID] = in_up[gl_InvocationID];
+
+ // TODO: Set level of tesselation
+ gl_TessLevelInner[0] = 4;
+ gl_TessLevelInner[1] = 4;
+ gl_TessLevelOuter[0] = 4;
+ gl_TessLevelOuter[1] = 4;
+ gl_TessLevelOuter[2] = 4;
+ gl_TessLevelOuter[3] = 4;
+}
diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese
index 751fff6..50d0f5b 100644
--- a/src/shaders/grass.tese
+++ b/src/shaders/grass.tese
@@ -1,18 +1,67 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(quads, equal_spacing, ccw) in;
-
-layout(set = 0, binding = 0) uniform CameraBufferObject {
- mat4 view;
- mat4 proj;
-} camera;
-
-// TODO: Declare tessellation evaluation shader inputs and outputs
-
-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
-}
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(quads, equal_spacing, ccw) in;
+
+layout(set = 0, binding = 0) uniform CameraBufferObject {
+ mat4 view;
+ mat4 proj;
+} camera;
+
+// TODO: Declare tessellation evaluation shader inputs and outputs
+layout(location = 0) in vec4 in_v0[];
+layout(location = 1) in vec4 in_v1[];
+layout(location = 2) in vec4 in_v2[];
+layout(location = 3) in vec4 in_up[];
+
+layout(location = 0) out vec4 finalPos;
+layout(location = 1) out vec4 finalNor;
+layout(location = 2) out float verticality;
+
+void main() {
+ // Attempt to rotate using theta
+ float theta = in_v0[0].w;
+
+ // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade
+ float u = gl_TessCoord.x;
+ float v = gl_TessCoord.y;
+ verticality = v;
+
+ // Using barycentric output coordinates from the control shader, figure out what the value of the surface is at this bary centric point.
+ // Assuming they're just triangles... have current position of the grass blade, and the up vector defines the up, then how do we get the width??
+ // How do we parametrize the damn blade itself?
+
+ // The blade parametrization is in the grass paper I'm stupid
+ vec3 v0 = vec3(in_v0[0]);
+ vec3 v1 = vec3(in_v1[0]);
+ vec3 v2 = vec3(in_v2[0]);
+
+ // for now, set width of blade to a constant
+ float width = 0.2f;
+
+ // for now, set tangent of blade to x
+ vec3 t1 = normalize(vec3(width * cos(theta), 0.f, width * sin(theta))); //vec3(1, 0, 0); // tangent
+
+ 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));
+
+ // calculate position for a quadric grass shape
+ float t = u - (u * v * v);
+
+ // Displace the center of the blade by a little bit
+ vec3 nor = vec3(0.f, 0.f, 1.f);
+ vec3 displace = width * nor * (0.5 - abs(u - 0.5) * (1 - v));
+
+ // for now, hard code finalNor
+ finalPos = vec4((1 - t) * c0 + t * c1 + displace, 1.f);
+ finalNor = vec4(nor, 0.f);
+
+ gl_Position = camera.proj * camera.view * finalPos;
+}
\ No newline at end of file
diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert
index db9dfe9..1151ec9 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -1,17 +1,32 @@
-
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(set = 1, binding = 0) uniform ModelBufferObject {
- mat4 model;
-};
-
-// TODO: Declare vertex shader inputs and outputs
-
-out gl_PerVertex {
- vec4 gl_Position;
-};
-
-void main() {
- // TODO: Write gl_Position and any other shader outputs
-}
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(set = 1, binding = 0) uniform ModelBufferObject {
+ mat4 model;
+};
+
+// TODO: Declare vertex shader inputs and outputs
+
+layout(location = 0) in vec4 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;
+
+void main() {
+ // TODO: Write gl_Position and any other shader outputs
+ gl_Position = model * vec4(vec3(in_v0), 1.0f);
+ out_v0 = model * vec4(vec3(in_v0), 1.f);
+ out_v1 = model * vec4(vec3(in_v1), 1.f);
+ out_v2 = model * vec4(vec3(in_v2), 1.f);
+ out_up = model * vec4(vec3(in_up), 1.f);
+
+ out_v0.w = in_v0.w;
+ out_v1.w = in_v1.w;
+ out_v2.w = in_v2.w;
+ out_up.w = in_up.w;
+}
\ No newline at end of file