diff --git a/README.md b/README.md
index 20ee451..4dadd87 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,80 @@ 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)
+Jason Li ([LinkedIn](https://linkedin.com/in/jeylii))
+* Tested on: Windows 10, Ryzen 5 3600X @ 3.80GHz 32 GB, NVIDIA RTX 4070
-### (TODO: Your README)
+# **Summary**
+This project is a GPU Grass Simulator implemented using C++ and the Vulkan API. It simulates each grass blade based on a physical model defined in [Responseive 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). The simulation includes force simulation, grass blade culling, and different shaders including a tesselation shader.
-*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.
+Example Scene |
+:-------------------------:|
+ |
+
+## **Tesselation Shader**
+Grass blades are modeled as Bezier curves in this simulation, with three control points v0, v1, and v2. In addition, each blade has an "up vector", a width, height, orientation, and stiffness associated with it. In order to efficiently render such a shape, a tessellation shader is used to create the curvature of each grass blade from the three control points. In this simulation, we are using a "triangle-tip" shape, as described in the above reference.
+
+Blade Model |
+:-------------------------:|
+ |
+
+## **Force Simulation**
+
+Three types of forces are simulated on the grass blades:
+- Gravity,
+- Recovery,
+- and Wind.
+
+Each of these physics-based forces is applied to the "tip" of each blade of grass (v2), following the methods given in the [above reference](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf). The other control point v1 is updated based on the new location of v2. A time-varying wind function is used to simulate a gradually changing wind direction which is uniformly applied to each blade of grass. In addition to this, validation is required to ensure that each blade of grass remains in a "good" state, i.e. its control points stay above ground, etc. With just the forces on each blade of grass implemented, our grass simulation looks like this:
+
+Physical Force Simulation |
+:-------------------------:|
+ |
+
+## **Grass Blade Culling**
+After the grass is sufficiently simulated, we can increase the performance of our simulation by culling grass blades that are unnecessary or cause aliasing. In this case, three types of culling were applied:
+- Orientation culling,
+- View-frustum culling,
+- and Distance culling.
+
+### Orientation Culling
+For orientation culling, we cull the grass blades which appear nearly parallel to the camera's view direction. This is because our grass blades are only 2-dimensional, so any grass blades which are parallel or similar to the view will be rendered smaller than the size of a pixel. This can cause undesirable aliasing artifacts. Below is an example of what this looks like in our simulation.
+
+Orientation Culling |
+:-------------------------:|
+ |
+
+### View-frustum Culling
+We also want to cull blades of grass which are outside of our field of view. This is accomplished using view-frustum culling. This method tests if all three control points of a certain blade of grass are located outside the view-frustum. If so, the blade is culled - with some tolerance to allow for grass on the edge of our view to render. An example of what this looks like in our simulation is included below.
+
+View-frustum Culling |
+:-------------------------:|
+ |
+
+### Distance Culling
+For grass blades that are seen from a large distance, they may be rendered as a pixel or smaller. To prevent alisaing from this effect, we use distance culling - culling an increasing number of blades the further the blades' positions are from the camera.
+
+Distance Culling |
+:-------------------------:|
+ |
+
+## **Performance Analysis**
+Below is a performance analysis of our grass simulation when compared across different runs with some changing variables. The analysis was done by choosing a standard angle to view the scene, then running the simulation with features on or off or varying the number of blades. Average FPS was aggregated over periods of 10 seconds. This standard scene is shown below.
+
+Performance Analysis Angle |
+:-------------------------:|
+ |
+
+### **Performance vs. Number of Blades**
+FPS vs. Number of Blades |
+:-------------------------:|
+ |
+
+The graph above compares the frames per second on average for our perforamnce analysis scene when using a different number of grass blades in the scene. As expected, the FPS drops when using a larger number of grass blades. As the number of blades is on a logarithmic scale, we can see that the performance drop is quite significant at larger numbers. This is expected, as more work has to be done per blade. However, note that this simulation does not include any interactions between blades of grass, so the performance drop with such interactions included may be even more significant.
+
+### Performance vs. Usage of Culling
+FPS vs. Culling Method (218 blades of grass) |
+:-------------------------:|
+ |
+
+The chart above compares the frames per second on average for our performance analysis scene with 218 blades of grass when using different methods of grass blade culling. In our specific chosen scene, distance culling seems to be the most significant, as our camera view is quite far away from one edge of the platform. Orientation culling also seems to be effective, as the probability of a random blade of grass being nearly parallel with the camera's view (here we used a coefficient of 0.75) is quite large. View-frustum culling was least effective here, though this is likely due to the limited amount of grass culled in this particular view of the grass scene. The combination of all three culling methods significantly outperforms any individual method, as expected.
\ No newline at end of file
diff --git a/img/fps_vs_blade_culling_method.png b/img/fps_vs_blade_culling_method.png
new file mode 100644
index 0000000..fc8a93a
Binary files /dev/null and b/img/fps_vs_blade_culling_method.png differ
diff --git a/img/fps_vs_num_blades.png b/img/fps_vs_num_blades.png
new file mode 100644
index 0000000..809ab29
Binary files /dev/null and b/img/fps_vs_num_blades.png differ
diff --git a/img/grass_distance_culling.gif b/img/grass_distance_culling.gif
new file mode 100644
index 0000000..22a67ba
Binary files /dev/null and b/img/grass_distance_culling.gif differ
diff --git a/img/grass_example.gif b/img/grass_example.gif
new file mode 100644
index 0000000..35fe858
Binary files /dev/null and b/img/grass_example.gif differ
diff --git a/img/grass_forces.gif b/img/grass_forces.gif
new file mode 100644
index 0000000..b82d8bf
Binary files /dev/null and b/img/grass_forces.gif differ
diff --git a/img/grass_orientation_culling.gif b/img/grass_orientation_culling.gif
new file mode 100644
index 0000000..9321937
Binary files /dev/null and b/img/grass_orientation_culling.gif differ
diff --git a/img/grass_view_culling.gif b/img/grass_view_culling.gif
new file mode 100644
index 0000000..ae60723
Binary files /dev/null and b/img/grass_view_culling.gif differ
diff --git a/img/performance_analysis_scene.png b/img/performance_analysis_scene.png
new file mode 100644
index 0000000..1064e18
Binary files /dev/null and b/img/performance_analysis_scene.png differ
diff --git a/src/Blades.cpp b/src/Blades.cpp
index 80e3d76..230d65c 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_VERTEX_BUFFER_BIT, bladesBuffer, bladesBufferMemory);
+ BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory);
BufferUtils::CreateBufferFromData(device, commandPool, &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..6b97bfd 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 << 18;
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..8caf161 100644
--- a/src/Camera.cpp
+++ b/src/Camera.cpp
@@ -15,10 +15,11 @@ Camera::Camera(Device* device, float aspectRatio) : device(device) {
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
-
BufferUtils::CreateBuffer(device, sizeof(CameraBufferObject), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, buffer, bufferMemory);
vkMapMemory(device->GetVkDevice(), bufferMemory, 0, sizeof(CameraBufferObject), 0, &mappedData);
memcpy(mappedData, &cameraBufferObject, sizeof(CameraBufferObject));
+ UpdateOrbit(10, -20, -2.2);
+
}
VkBuffer Camera::GetBuffer() const {
diff --git a/src/Renderer.cpp b/src/Renderer.cpp
index b445d04..67aaa22 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -195,9 +195,37 @@ void Renderer::CreateTimeDescriptorSetLayout() {
}
void Renderer::CreateComputeDescriptorSetLayout() {
- // TODO: Create the descriptor set layout for the compute pipeline
- // Remember this is like a class definition stating why types of information
- // will be stored at each binding
+ VkDescriptorSetLayoutBinding bladesLayoutBinding = {};
+ bladesLayoutBinding.binding = 0;
+ bladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ bladesLayoutBinding.descriptorCount = 1;
+ bladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ bladesLayoutBinding.pImmutableSamplers = nullptr;
+
+ VkDescriptorSetLayoutBinding culledBladesLayoutBinding = {};
+ culledBladesLayoutBinding.binding = 1;
+ culledBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ culledBladesLayoutBinding.descriptorCount = 1;
+ culledBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ culledBladesLayoutBinding.pImmutableSamplers = nullptr;
+
+ VkDescriptorSetLayoutBinding numBladesLayoutBinding = {};
+ numBladesLayoutBinding.binding = 2;
+ numBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ numBladesLayoutBinding.descriptorCount = 1;
+ numBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ numBladesLayoutBinding.pImmutableSamplers = nullptr;
+
+ std::vector bindings = { bladesLayoutBinding, culledBladesLayoutBinding, numBladesLayoutBinding };
+
+ 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 +244,7 @@ void Renderer::CreateDescriptorPool() {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 },
// TODO: Add any additional types and counts of descriptors you will need to allocate
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(scene->GetBlades().size() * 3)},
};
VkDescriptorPoolCreateInfo poolInfo = {};
@@ -320,6 +349,44 @@ 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
+ // Describe the desciptor set
+ grassDescriptorSets.resize(scene->GetBlades().size());
+
+ 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;
+ }
+
+
+ // Update descriptor sets
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
void Renderer::CreateTimeDescriptorSet() {
@@ -360,6 +427,72 @@ 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
+ computeDescriptorSets.resize(scene->GetBlades().size());
+
+ VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout };
+ VkDescriptorSetAllocateInfo allocInfo = {};
+ allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ allocInfo.descriptorPool = descriptorPool;
+ allocInfo.descriptorSetCount = static_cast(computeDescriptorSets.size());
+ allocInfo.pSetLayouts = layouts;
+
+ // Allocate descriptor sets
+ if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) {
+ throw std::runtime_error("Failed to allocate compute descriptor set");
+ }
+
+ std::vector descriptorWrites(computeDescriptorSets.size() * 3);
+ for (uint32_t i = 0; i < scene->GetBlades().size(); i++)
+ {
+ VkDescriptorBufferInfo bladeBufferInfo = {};
+ bladeBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer();
+ bladeBufferInfo.offset = 0;
+ bladeBufferInfo.range = NUM_BLADES * sizeof(Blade);
+
+ 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 = &bladeBufferInfo;
+ descriptorWrites[3 * i].pImageInfo = nullptr;
+ descriptorWrites[3 * i].pTexelBufferView = nullptr;
+
+ VkDescriptorBufferInfo culledBladesBufferInfo = {};
+ culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer();
+ culledBladesBufferInfo.offset = 0;
+ culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade);
+
+ descriptorWrites[3 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[3 * i + 1].dstSet = computeDescriptorSets[i];
+ descriptorWrites[3 * i + 1].dstBinding = 1;
+ descriptorWrites[3 * i + 1].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;
+
+ VkDescriptorBufferInfo numBladesBufferInfo = {};
+ numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer();
+ numBladesBufferInfo.offset = 0;
+ numBladesBufferInfo.range = sizeof(BladeDrawIndirect);
+
+ descriptorWrites[3 * i + 2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[3 * i + 2].dstSet = computeDescriptorSets[i];
+ descriptorWrites[3 * i + 2].dstBinding = 2;
+ descriptorWrites[3 * i + 2].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() {
@@ -717,7 +850,7 @@ void Renderer::CreateComputePipeline() {
computeShaderStageInfo.pName = "main";
// TODO: Add the compute dsecriptor set layout you create to this list
- std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout };
+ std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout };
// Create pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
@@ -884,6 +1017,11 @@ void Renderer::RecordComputeCommandBuffer() {
vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 1, 1, &timeDescriptorSet, 0, nullptr);
// TODO: For each group of blades bind its descriptor set and dispatch
+ for (uint32_t 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 / WORKGROUP_SIZE), 1, 1);
+ }
+
// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
@@ -973,16 +1111,17 @@ void Renderer::RecordCommandBuffers() {
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipeline);
for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) {
- VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() };
+ VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() }; // from 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 +1196,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..36caa9b 100644
--- a/src/Renderer.h
+++ b/src/Renderer.h
@@ -56,12 +56,15 @@ class Renderer {
VkDescriptorSetLayout cameraDescriptorSetLayout;
VkDescriptorSetLayout modelDescriptorSetLayout;
VkDescriptorSetLayout timeDescriptorSetLayout;
+ VkDescriptorSetLayout computeDescriptorSetLayout;
VkDescriptorPool descriptorPool;
VkDescriptorSet cameraDescriptorSet;
std::vector modelDescriptorSets;
VkDescriptorSet timeDescriptorSet;
+ std::vector grassDescriptorSets;
+ std::vector computeDescriptorSets;
VkPipelineLayout graphicsPipelineLayout;
VkPipelineLayout grassPipelineLayout;
diff --git a/src/Scene.cpp b/src/Scene.cpp
index 86894f2..69dd6b7 100644
--- a/src/Scene.cpp
+++ b/src/Scene.cpp
@@ -34,6 +34,10 @@ void Scene::UpdateTime() {
memcpy(mappedData, &time, sizeof(Time));
}
+float Scene::get_time() {
+ return time.totalTime;
+}
+
VkBuffer Scene::GetTimeBuffer() const {
return timeBuffer;
}
diff --git a/src/Scene.h b/src/Scene.h
index 7699d78..07f2b40 100644
--- a/src/Scene.h
+++ b/src/Scene.h
@@ -42,4 +42,6 @@ high_resolution_clock::time_point startTime = high_resolution_clock::now();
VkBuffer GetTimeBuffer() const;
void UpdateTime();
+ float get_time();
+
};
diff --git a/src/main.cpp b/src/main.cpp
index 8bf822b..9791d40 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,6 +5,8 @@
#include "Camera.h"
#include "Scene.h"
#include "Image.h"
+#include
+#include
Device* device;
SwapChain* swapChain;
@@ -143,10 +145,30 @@ int main() {
glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback);
+ int total_frames = 0;
+ float total_time = 0;
+ float curr_time = 0;
+
while (!ShouldQuit()) {
glfwPollEvents();
scene->UpdateTime();
renderer->Frame();
+
+ total_frames++;
+ float time = scene->get_time();
+ total_time = (time - curr_time);
+ std::ostringstream oss;
+ if (total_time < 10)
+ {
+ oss << "FPS: " << total_frames / total_time << std::endl;
+ }
+ else
+ {
+ total_frames = 0;
+ total_time = 0;
+ curr_time = time;
+ }
+ glfwSetWindowTitle(GetGLFWWindow(), oss.str().c_str());
}
vkDeviceWaitIdle(device->GetVkDevice());
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..272c345 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -2,6 +2,16 @@
#extension GL_ARB_separate_shader_objects : enable
#define WORKGROUP_SIZE 32
+
+#define WIND 8.75
+#define GRAVITY 3.25
+#define RECOVERY 1
+#define ORIENT_CULL 1
+#define DIST_CULL 7
+#define DIST_CULL_MAX 25
+#define VIEW_CULL 1
+#define VIEW_CULL_TOLERANCE 0.15
+
layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0) uniform CameraBufferObject {
@@ -35,22 +45,123 @@ struct Blade {
// uint firstVertex; // = 0
// uint firstInstance; // = 0
// } numBlades;
+layout(set = 2, binding = 0) buffer Blades {
+ Blade blades[];
+};
+
+layout(set = 2, binding = 1) buffer CulledBlades {
+ Blade out_blades[];
+};
+
+layout(set = 2, binding = 2) buffer NumBlades {
+ uint vertexCount; // num blades remaining
+ uint instanceCount; // = 1
+ uint firstVertex; // = 0
+ uint firstInstance; // = 0
+} numBlades;
+
bool inBounds(float value, float bounds) {
return (value >= -bounds) && (value <= bounds);
}
+bool is_in_frustum(vec3 point)
+{
+ vec4 point_prime = camera.proj * camera.view * vec4(point, 1);
+ float h = point_prime.w + VIEW_CULL_TOLERANCE;
+ return (inBounds(point_prime.x, h) && inBounds(point_prime.y, h) && inBounds(point_prime.z, h));
+}
+
void main() {
// Reset the number of blades to 0
if (gl_GlobalInvocationID.x == 0) {
- // numBlades.vertexCount = 0;
+ numBlades.vertexCount = 0;
}
barrier(); // Wait till all threads reach this point
- // TODO: Apply forces on every blade and update the vertices in the buffer
+ uint index = gl_GlobalInvocationID.x;
+ Blade blade = blades[index];
+
+ vec3 v0 = blade.v0.xyz;
+ vec3 v1 = blade.v1.xyz;
+ vec3 v2 = blade.v2.xyz;
+ vec3 up = blade.up.xyz;
+
+ float orient = blade.v0.w;
+ float height = blade.v1.w;
+ float width = blade.v2.w;
+ float stiff = blade.up.w;
+
+#ifdef GRAVITY
+ vec3 grav_env = -1 * normalize(up) * GRAVITY;
+
+ vec3 front = normalize(cross(vec3(cos(orient), 0, sin(orient)), up));
+ vec3 grav_front = 0.25 * length(grav_env) * normalize(front);
+
+ vec3 gravity = grav_env + grav_front;
+#endif
+
+#ifdef RECOVERY
+ vec3 recovery = ((v0 + up * height) - v2) * stiff;
+#endif
+
+#ifdef WIND
+ vec3 wind_function = vec3(1.5, 2.0, 1.0) * sin(0.5 * totalTime) * sin(0.5 * totalTime) * cos(0.5 * totalTime) * WIND;
+ float wind_dir = 1 - (abs(dot(normalize(wind_function), normalize(v2 - v0))));
+ float wind_height_ratio = dot(v2 - v0, up) / height;
+
+ vec3 wind = wind_function * wind_dir * wind_height_ratio;
+#endif
+
+ vec3 total_force = (recovery + gravity + wind) * deltaTime;
+
+ // update v2
+ v2 += total_force;
+ v2 -= (up * min(dot(up, v2 - v0), 0));
+
+ // update v1
+ float l_proj = length(v2 - v0 - up * dot((v2 - v0), up));
+ v1 = v0 + height * up * max((1 - l_proj / height), 0.05 * max(l_proj / height, 1));
+
+ float l_0 = distance(v2, v0);
+ float l_1 = distance(v2, v1) + distance(v1, v0);
+ float bezier_length = (2.f * l_0 + l_1) / 3.f;
+
+ float ratio = height / bezier_length;
+ vec3 temp_v1 = v1;
+ v1 = v0 + ratio * (v1 - v0);
+ v2 = v1 + ratio * (v2 - temp_v1);
+
+ blade.v0 = vec4(v0, orient);
+ blade.v1 = vec4(v1, height);
+ blade.v2 = vec4(v2, width);
+ blade.up = vec4(up, stiff);
+ blades[index] = blade;
+
+ vec3 cam = inverse(camera.view)[3].xyz;
+
+#ifdef ORIENT_CULL
+ if (abs(dot(normalize(cam - v0), vec3(cos(orient), 0, sin(orient)))) > 0.75) {
+ return;
+ }
+#endif
+
+#ifdef DIST_CULL
+ float d_proj = length(v0 - cam - up * (dot((v0 - cam), up)));
+ if (mod(index, DIST_CULL) >= (DIST_CULL * (1 - d_proj / DIST_CULL_MAX))) {
+ return;
+ }
+#endif
+
+#ifdef VIEW_CULL
+ vec3 midpoint = 0.25 * v0 + 0.5 * v1 + 0.25 * v2;
+ if (!(is_in_frustum(v0) && is_in_frustum(v2) && is_in_frustum(midpoint)))
+ {
+ return;
+ }
+#endif
+
+ uint culled_index = atomicAdd(numBlades.vertexCount, 1);
+ out_blades[culled_index] = blade;
- // 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
}
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..248457b 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -7,11 +7,25 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} camera;
// TODO: Declare fragment shader inputs
+layout(location = 0) in vec3 pos;
+layout(location = 1) in vec3 nor;
+layout(location = 2) in float height;
layout(location = 0) out vec4 outColor;
void main() {
// TODO: Compute fragment color
+ vec3 baseColor = vec3(0.4, 0.75, 0.15);
+ vec3 ambientColor = vec3(0.0, 0.17, 0.0);
- outColor = vec4(1.0);
+// vec4 lightPos = inverse(camera.view) * vec4(0.0, 0.0, 0.0, 1.0) - inverse(camera.view) * vec4(pos, 1.0);
+ vec4 lightPos = vec4(10, 10, 20, 1.0);
+ vec4 surfaceNormal = vec4(normalize(nor), 0.0);
+ vec4 vecToLight = normalize(lightPos - vec4(pos, 1.0));
+
+ float diffuse = abs(dot(surfaceNormal, vecToLight));
+ diffuse = clamp(diffuse, 0.0, 1.0);
+ vec3 diffuseColor = baseColor * diffuse + ambientColor;
+
+ outColor = vec4(diffuseColor, 1.0) * (height + 0.25) * 0.85;
}
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..1f5d844 100644
--- a/src/shaders/grass.tesc
+++ b/src/shaders/grass.tesc
@@ -1,5 +1,6 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
+#define TESS_VALUE 20
layout(vertices = 1) out;
@@ -8,19 +9,36 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
mat4 proj;
} camera;
+
+layout(location = 0) in vec4 v0[];
+layout(location = 1) in vec4 v1[];
+layout(location = 2) in vec4 v2[];
+layout(location = 3) in vec4 up[];
+
+layout(location = 0) out vec4 out_v0[];
+layout(location = 1) out vec4 out_v1[];
+layout(location = 2) out vec4 out_v2[];
+layout(location = 3) out vec4 out_up[];
// 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;
+ out_v0[gl_InvocationID] = v0[gl_InvocationID];
+ out_v1[gl_InvocationID] = v1[gl_InvocationID];
+ out_v2[gl_InvocationID] = v2[gl_InvocationID];
+ out_up[gl_InvocationID] = up[gl_InvocationID];
+
+ vec3 cam_pos = inverse(camera.view)[3].xyz;
+ float dist = distance(cam_pos, v0[gl_InvocationID].xyz);
// 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] = ???
+ gl_TessLevelInner[0] = TESS_VALUE;
+ gl_TessLevelInner[1] = TESS_VALUE;
+ gl_TessLevelOuter[0] = TESS_VALUE;
+ gl_TessLevelOuter[1] = TESS_VALUE;
+ gl_TessLevelOuter[2] = TESS_VALUE;
+ gl_TessLevelOuter[3] = TESS_VALUE;
}
diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese
index 751fff6..d19300a 100644
--- a/src/shaders/grass.tese
+++ b/src/shaders/grass.tese
@@ -10,9 +10,41 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
// TODO: Declare tessellation evaluation shader inputs and outputs
+layout(location = 0) in vec4 v0[];
+layout(location = 1) in vec4 v1[];
+layout(location = 2) in vec4 v2[];
+layout(location = 3) in vec4 up[];
+
+layout(location = 0) out vec3 out_pos;
+layout(location = 1) out vec3 out_nor;
+layout(location = 2) 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
+ vec3 v0_3 = v0[0].xyz;
+ vec3 v1_3 = v1[0].xyz;
+ vec3 v2_3 = v2[0].xyz;
+ vec3 up_3 = up[0].xyz;
+
+ vec3 a = v0_3 + (v1_3 - v0_3) * v;
+ vec3 b = v1_3 + (v2_3 - v1_3) * v;
+ vec3 c = a + (b - a) * v;
+
+ vec3 t1 = vec3(cos(v0[0].w), 0.0, sin(v0[0].w));
+ vec3 t0 = normalize(b - a);
+ vec3 c0 = c - v2[0].w * t1;
+ vec3 c1 = c + v2[0].w * t1;
+
+ //float t = u + (0.5 * v) - (u * v);
+ //float t = 0.5 + (u - 0.5) * (1 - (max((v - 0.5), 0)) / (1 - 0.5));
+ //float t = (u - u * v * v);
+ float t = 0.25 + (u - 0.25) * (u - u * v * v);
+
+ out_pos = mix(c0, c1, t);
+ out_nor = normalize(cross(t0, t1));
+ out_height = v;
+
+ gl_Position = camera.proj * camera.view * vec4(out_pos, 1.f);
}
diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert
index db9dfe9..1b9e841 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -6,6 +6,16 @@ layout(set = 1, binding = 0) uniform ModelBufferObject {
mat4 model;
};
+layout(location = 0) in vec4 v0;
+layout(location = 1) in vec4 v1;
+layout(location = 2) in vec4 v2;
+layout(location = 3) in vec4 up;
+
+layout(location = 0) out vec4 out_v0;
+layout(location = 1) out vec4 out_v1;
+layout(location = 2) out vec4 out_v2;
+layout(location = 3) out vec4 out_up;
+
// TODO: Declare vertex shader inputs and outputs
out gl_PerVertex {
@@ -14,4 +24,9 @@ out gl_PerVertex {
void main() {
// TODO: Write gl_Position and any other shader outputs
+ gl_Position = vec4((model * vec4(v0.xyz, 1.0f)).xyz, v0.w);
+ out_v0 = vec4((model * vec4(v0.xyz, 1.0f)).xyz, v0.w);
+ out_v1 = vec4((model * vec4(v1.xyz, 1.0f)).xyz, v1.w);
+ out_v2 = vec4((model * vec4(v2.xyz, 1.0f)).xyz, v2.w);
+ out_up = vec4((model * vec4(up.xyz, 0.0f)).xyz, up.w); //stiffness?
}