diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3d5ec50
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/out
+.vs/
+/build
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 860a279..51af884 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -60,7 +60,7 @@ ELSE(WIN32)
link_libraries(${XCB_LIBRARIES} ${VULKAN_LIB})
ENDIF(WIN32)
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/")
+# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/")
add_subdirectory(external)
add_subdirectory(src)
diff --git a/CMakeSettings.json b/CMakeSettings.json
new file mode 100644
index 0000000..b58599c
--- /dev/null
+++ b/CMakeSettings.json
@@ -0,0 +1,27 @@
+{
+ "configurations": [
+ {
+ "name": "x64-Debug",
+ "generator": "Ninja",
+ "configurationType": "Debug",
+ "inheritEnvironments": [ "msvc_x64_x64" ],
+ "buildRoot": "${projectDir}\\out\\build\\${name}",
+ "installRoot": "${projectDir}\\out\\install\\${name}",
+ "cmakeCommandArgs": "",
+ "buildCommandArgs": "",
+ "ctestCommandArgs": ""
+ },
+ {
+ "name": "x64-Release",
+ "generator": "Ninja",
+ "configurationType": "Release",
+ "buildRoot": "${projectDir}\\out\\build\\${name}",
+ "installRoot": "${projectDir}\\out\\install\\${name}",
+ "cmakeCommandArgs": "",
+ "buildCommandArgs": "",
+ "ctestCommandArgs": "",
+ "inheritEnvironments": [ "msvc_x64_x64" ],
+ "variables": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 20ee451..6db9f06 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,62 @@
Vulkan Grass Rendering
-==================================
+=======================
+In this project, I implemented a real-time Vulkan Grass Simulation based on this [paper](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf).
**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)
+* Mengxuan Huang
+ * [LinkedIn](https://www.linkedin.com/in/mengxuan-huang-52881624a/)
+* Tested on: Windows 11, i9-13980HX @ 2.22GHz 64.0 GB, RTX4090-Laptop 16384MB
+-----------------------------
+## Overview
+
+
+
-### (TODO: Your README)
+In this project, I used three control points($v_0, v_1, v_2$) and its $width$, $height$, and $direction$ to represent a grass.The control point $v_0$ act as the root of the grass and will not move during the simulation. All forces are applied on $v_2$. Control point $v_1$ is used to preserve the length of the grass. The result is shown in the gif below.
-*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.
+
+
+
+
+## Features
+- Force Simulation
+- Culling
+
+### Force Simulation
+-------------------------------------------------
+There are three kinds of forces applied to grass in the simulation:
+- Gravity
+- Recovery
+- Wind
+
+#### Gravity
+The Gravity term includes the *environmental gravity* $g_E$ and the *front gravity* $g_F$, where
+`gF = gF = (1/4) * ||gE|| * f`. And `f` if the vector perpendicular to the direction of grass.
+
+#### Recovery
+Recovery is used to represent the force the grass recover to the origin position, which is defined as `r = (v0 + up * height - v2) * stiffness`.
+
+#### Wind
+Wind can be any analytic functions that represent wind waves moving through 3D space. In my simulation, I used a combination of `sin` and `cos` functions: `windMagnitude = u * sin(v) - v * cos(u)`. Besides, total wind force `w = windMagnitude * windDirection * windAlignment`.
+
+#### Total Force
+Having the above foces, the total force `f = gE + gF + r + w`. Affter appling the total force to the control point $v_2$, it is also necessary to compute the position of $v_1$ and correct the position of $v_1$ and $v_2$ to preserve the length of grass.
+
+### Culling
+-------------------------------------------------
+There are three kinds of culling used to improve the performance:
+- Orienation Culling (cull grass that are align with view direction. )
+- View Frustrum Culling (cull grass that are outside the view Frustrum. )
+- Distance Culling (cull grass based on distance.)
+
+|Orientation Culling|Frustrum Culling|Distance Culling|
+|:-----------:|:-------------:|:-----------:|
+||||
+
+## Performance Analysis
+
+
+
+
+As shown in the graph, with the increase of the number of grass, the compute + graphics pipline time increases. And the time drop significantly after culling blades before rendering. According to the graph, distance culling improve the performance most.
\ No newline at end of file
diff --git a/img/cube_demo.png b/img/cube_demo.png
deleted file mode 100644
index 21e928e..0000000
Binary files a/img/cube_demo.png and /dev/null differ
diff --git a/img/distance.gif b/img/distance.gif
new file mode 100644
index 0000000..44ca6fc
Binary files /dev/null and b/img/distance.gif differ
diff --git a/img/frustrum.gif b/img/frustrum.gif
new file mode 100644
index 0000000..86d16f1
Binary files /dev/null and b/img/frustrum.gif differ
diff --git a/img/graph.png b/img/graph.png
new file mode 100644
index 0000000..29246fb
Binary files /dev/null and b/img/graph.png differ
diff --git a/img/my_grass.gif b/img/my_grass.gif
new file mode 100644
index 0000000..fd876aa
Binary files /dev/null and b/img/my_grass.gif differ
diff --git a/img/orientation.gif b/img/orientation.gif
new file mode 100644
index 0000000..9bd4672
Binary files /dev/null and b/img/orientation.gif differ
diff --git a/src/Blades.cpp b/src/Blades.cpp
index 80e3d76..dde104b 100644
--- a/src/Blades.cpp
+++ b/src/Blades.cpp
@@ -6,7 +6,9 @@ float generateRandomFloat() {
return rand() / (float)RAND_MAX;
}
-Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Model(device, commandPool, {}, {}) {
+Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim)
+ : Model(device, commandPool, {}, {})
+{
std::vector blades;
blades.reserve(NUM_BLADES);
@@ -45,7 +47,7 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode
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::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/Renderer.cpp b/src/Renderer.cpp
index b445d04..0f4f100 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -198,6 +198,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
+
+ VkDescriptorSetLayoutBinding in_blade_layout_binding = {};
+ in_blade_layout_binding.binding = 0;
+ in_blade_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ in_blade_layout_binding.descriptorCount = 1;
+ in_blade_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ in_blade_layout_binding.pImmutableSamplers = nullptr;
+
+ VkDescriptorSetLayoutBinding out_blade_layout_binding = {};
+ out_blade_layout_binding.binding = 1;
+ out_blade_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ out_blade_layout_binding.descriptorCount = 1;
+ out_blade_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ out_blade_layout_binding.pImmutableSamplers = nullptr;
+
+ VkDescriptorSetLayoutBinding num_blade_layout_binding = {};
+ num_blade_layout_binding.binding = 2;
+ num_blade_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ num_blade_layout_binding.descriptorCount = 1;
+ num_blade_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ num_blade_layout_binding.pImmutableSamplers = nullptr;
+
+ std::vector bindings = {
+ in_blade_layout_binding,
+ out_blade_layout_binding,
+ num_blade_layout_binding
+ };
+
+ // 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 compute descriptor set layout");
+ }
}
void Renderer::CreateDescriptorPool() {
@@ -216,6 +253,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->GetModels().size() + scene->GetBlades().size())},
};
VkDescriptorPoolCreateInfo poolInfo = {};
@@ -320,6 +358,42 @@ 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());
+
+ 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 descriptor set");
+ }
+
+ std::vector descriptorWrites(grassDescriptorSets.size());
+
+ for (int i = 0; i < grassDescriptorSets.size(); ++i)
+ {
+ VkDescriptorBufferInfo modelBufferInfo = {};
+ modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer();
+ modelBufferInfo.offset = 0;
+ modelBufferInfo.range = sizeof(ModelBufferObject);
+
+ descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[i].dstSet = grassDescriptorSets[i];
+ descriptorWrites[i].dstBinding = 0;
+ descriptorWrites[i].dstArrayElement = 0;
+ descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ descriptorWrites[i].descriptorCount = 1;
+ descriptorWrites[i].pBufferInfo = &modelBufferInfo;
+ descriptorWrites[i].pTexelBufferView = nullptr;
+ descriptorWrites[i].pImageInfo = nullptr;
+ }
+
+ // Update descriptor sets
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
void Renderer::CreateTimeDescriptorSet() {
@@ -360,6 +434,71 @@ 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(3 * computeDescriptorSets.size());
+
+ for (uint32_t i = 0; i < computeDescriptorSets.size(); ++i) {
+ VkDescriptorBufferInfo in_blade_buffer_info = {};
+ in_blade_buffer_info.buffer = scene->GetBlades()[i]->GetBladesBuffer();
+ in_blade_buffer_info.offset = 0;
+ in_blade_buffer_info.range = NUM_BLADES * sizeof(Blade);
+
+ VkDescriptorBufferInfo out_blade_buffer_info = {};
+ out_blade_buffer_info.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer();
+ out_blade_buffer_info.offset = 0;
+ out_blade_buffer_info.range = NUM_BLADES * sizeof(Blade);
+
+ VkDescriptorBufferInfo num_blade_buffer_info{};
+ num_blade_buffer_info.buffer = scene->GetBlades()[i]->GetNumBladesBuffer();
+ num_blade_buffer_info.offset = 0;
+ num_blade_buffer_info.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 = &in_blade_buffer_info;
+ 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 = &out_blade_buffer_info;
+ 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 = &num_blade_buffer_info;
+ 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() {
@@ -665,7 +804,7 @@ void Renderer::CreateGrassPipeline() {
pipelineLayoutInfo.pPushConstantRanges = 0;
if (vkCreatePipelineLayout(logicalDevice, &pipelineLayoutInfo, nullptr, &grassPipelineLayout) != VK_SUCCESS) {
- throw std::runtime_error("Failed to create pipeline layout");
+ throw std::runtime_error("Failed to create grass pipeline layout");
}
// Tessellation state
@@ -717,7 +856,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,7 +1023,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
-
+ for (auto& descriptor_set : computeDescriptorSets)
+ {
+ vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &descriptor_set, 0, nullptr);
+ vkCmdDispatch(computeCommandBuffer, std::max(NUM_BLADES / WORKGROUP_SIZE, unsigned int(1)), 1, 1);
+ }
+
// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
throw std::runtime_error("Failed to record compute command buffer");
@@ -958,12 +1102,12 @@ void Renderer::RecordCommandBuffers() {
VkBuffer vertexBuffers[] = { scene->GetModels()[j]->getVertexBuffer() };
VkDeviceSize offsets[] = { 0 };
vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);
-
+
vkCmdBindIndexBuffer(commandBuffers[i], scene->GetModels()[j]->getIndexBuffer(), 0, VK_INDEX_TYPE_UINT32);
-
+
// Bind the descriptor set for each model
vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 1, 1, &modelDescriptorSets[j], 0, nullptr);
-
+
// Draw
std::vector indices = scene->GetModels()[j]->getIndices();
vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0);
@@ -976,13 +1120,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
@@ -1004,7 +1149,7 @@ void Renderer::Frame() {
computeSubmitInfo.pCommandBuffers = &computeCommandBuffer;
if (vkQueueSubmit(device->GetQueue(QueueFlags::Compute), 1, &computeSubmitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
- throw std::runtime_error("Failed to submit draw command buffer");
+ throw std::runtime_error("Failed to submit compute command buffer");
}
if (!swapChain->Acquire()) {
@@ -1057,6 +1202,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..139cc33 100644
--- a/src/Renderer.h
+++ b/src/Renderer.h
@@ -56,12 +56,16 @@ class Renderer {
VkDescriptorSetLayout cameraDescriptorSetLayout;
VkDescriptorSetLayout modelDescriptorSetLayout;
VkDescriptorSetLayout timeDescriptorSetLayout;
+ VkDescriptorSetLayout computeDescriptorSetLayout;
VkDescriptorPool descriptorPool;
VkDescriptorSet cameraDescriptorSet;
std::vector modelDescriptorSets;
VkDescriptorSet timeDescriptorSet;
+ std::vector computeDescriptorSets;
+
+ std::vector grassDescriptorSets;
VkPipelineLayout graphicsPipelineLayout;
VkPipelineLayout grassPipelineLayout;
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..19e2909 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -26,31 +26,175 @@ struct Blade {
// 2. Write out the culled blades
// 3. Write the total number of blades remaining
+layout(set = 2, binding = 0) buffer InBlades{
+ Blade in_blades[];
+};
+
+layout(set = 2, binding = 1) buffer OutBlades{
+ Blade out_blades[];
+};
+
// 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);
+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;
+
+// global controls
+// ***********************************
+const float G = 4.8f;
+const float TimeScale = 1.5f;
+const float WindScale = 2.f;
+const float OrientationThreshold = 0.9f;
+const float ViewFrustrumThreshold = 0.3f;
+const int DistanceCullLevels = 10;
+const float DistanceCullMax = 20.f;
+
+#define ENABLE_ORIENTATION_CULL 1
+#define ENABLE_FRUSTRUM_CULL 1
+#define ENABLE_DISTANCE_CULL 1
+// ***********************************
+
+bool inBounds(vec3 value, float bounds) {
+ return (value.x >= -bounds) && (value.x <= bounds) &&
+ (value.y >= -bounds) && (value.y <= bounds) &&
+ (value.z >= -1.f) && (value.z <= 1.f);
+}
+
+vec3 GetForce(in const Blade blade)
+{
+ const float t = TimeScale * totalTime;
+
+ // attributes
+ const float direction = blade.v0.w;
+ const float height = blade.v1.w;
+ const float stiffness = blade.up.w;
+ const vec3 gE = vec3(0, -G, 0);
+ const vec3 width = vec3(cos(direction), 0.f, sin(direction));
+ // gravity
+ const vec3 gF = 0.25f * G * normalize(cross(width, blade.up.xyz));
+ const vec3 g = gF + gE;
+
+ // recovery
+ const vec3 iv2 = blade.v0.xyz + blade.up.xyz * height;
+ const vec3 r = (iv2 - blade.v2.xyz) * stiffness;
+
+ // wind function
+ const vec3 w_d = normalize(vec3(1, 0, 1));
+ const float w_m = blade.v0.y * sin(blade.v0.x + t) - blade.v0.x * cos(blade.v0.y + t);
+
+ const float fd = 1.f - abs(dot(w_d, normalize(blade.v2.xyz - blade.v0.xyz)));
+ const float fr = dot(blade.v2.xyz - blade.v0.xyz, blade.up.xyz) / height;
+
+ const vec3 w = WindScale * w_d * w_m * fd * fr;
+
+ // total force
+ return g + r + w;
+}
+
+void ValidateBlade(inout Blade blade)
+{
+ // validate v2 and v1
+ blade.v2.xyz -= blade.up.xyz * min(dot(blade.up.xyz, blade.v2.xyz - blade.v0.xyz), 0.f);
+
+ const float l_proj = length(blade.v2.xyz - blade.v0.xyz - dot(blade.v2.xyz - blade.v0.xyz, blade.up.xyz) * blade.up.xyz);
+
+ const float height = blade.v1.w;
+ blade.v1.xyz = blade.v0.xyz + height * blade.up.xyz * max(1.f - l_proj / height, 0.05f * max(l_proj / height, 1.f));
+
+ // correct the length
+ // L = 2L0 + (n - 1)L1 / (n + 1)
+ const float L = (2.f * distance(blade.v2.xyz, blade.v0.xyz) + (distance(blade.v1.xyz, blade.v0.xyz) + distance(blade.v2.xyz, blade.v1.xyz))) / 3.f;
+ const float r = height / L;
+
+ const vec3 temp_v1 = blade.v0.xyz + r * (blade.v1.xyz - blade.v0.xyz);
+ blade.v2.xyz = temp_v1 + r * (blade.v2.xyz - blade.v1.xyz);
+
+ blade.v1.xyz = temp_v1;
+}
+
+void UpdateBlade(inout Blade blade)
+{
+ // Apply forces on every blade and update the vertices in the buffer
+ vec3 f = GetForce(blade);
+ f = clamp(f, vec3(-100.f), vec3(100.f));
+ // total force
+ blade.v2.xyz += f * deltaTime;
+
+ ValidateBlade(blade);
+}
+
+
+bool CullTest(const in Blade blade)
+{
+ const vec3 vec_dir = normalize(vec3(cos(blade.v0.w), 0.f, sin(blade.v0.w)));
+
+ // Orientation Test
+ vec3 forward = transpose(camera.view)[2].xyz;
+ forward.y = 0.f;
+ forward = normalize(forward);
+#if ENABLE_ORIENTATION_CULL
+ if(abs(dot(vec_dir, forward)) > OrientationThreshold ) return true;
+#endif
+ // View-Frustum Test
+ mat4 vp = camera.proj * camera.view;
+ vec4 v0_cam = camera.view * vec4(blade.v0.xyz, 1.f);
+ vec4 v0_ndc = camera.proj * v0_cam;
+ v0_ndc /= v0_ndc.w;
+
+ vec4 v2_ndc = vp * vec4(blade.v2.xyz, 1.f);
+ v2_ndc /= v2_ndc.w;
+
+ const vec3 m = 0.25f * (blade.v0.xyz + blade.v2.xyz) + 0.5f * blade.v1.xyz;
+ vec4 m_ndc = vp * vec4(m, 1.f);
+ m_ndc /= m_ndc.w;
+
+ const float h = .7f + ViewFrustrumThreshold;
+#if ENABLE_FRUSTRUM_CULL
+ if(!inBounds(v0_ndc.xyz, h) && !inBounds(v2_ndc.xyz, h) && !inBounds(m_ndc.xyz, h)) return true;
+#endif
+ // Distance Test
+ const vec3 up = (camera.view * vec4(blade.up.xyz, 0.f)).xyz;
+ const float d_proj = length(v0_cam.xyz - dot(v0_cam.xyz, up) * up);
+#if ENABLE_DISTANCE_CULL
+ if(gl_GlobalInvocationID.x % DistanceCullLevels > floor(float(DistanceCullLevels) * (1.f - d_proj / DistanceCullMax))) return true;
+#endif
+ return false;
}
void main() {
+ //if(gl_GlobalInvocationID.x >= numBlades.vertexCount) return;
+
// 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
+
+ // global index
+ const uint index = gl_GlobalInvocationID.x;
+
+ Blade blade = in_blades[index];
+
+ UpdateBlade(blade);
+
+
- // TODO: Apply forces on every blade and update the vertices in the buffer
+ // For now, simply passthrough the compute shader
+ in_blades[index] = blade;
+
- // TODO: Cull blades that are too far away or not in the camera frustum and write them
+ // Cull blades that are too far away or not in the camera frustum and write them
// to the culled blades buffer
// Note: to do this, you will need to use an atomic operation to read and update numBlades.vertexCount
// You want to write the visible blades to the buffer without write conflicts between threads
+
+
+ if(CullTest(blade)) return;
+
+ out_blades[atomicAdd(numBlades.vertexCount, 1)] = blade;
}
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..9ff2eaa 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -8,10 +8,23 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
// TODO: Declare fragment shader inputs
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 normal;
+layout(location = 2) in vec2 uv;
+
layout(location = 0) out vec4 outColor;
-void main() {
- // TODO: Compute fragment color
+void main()
+{
+ // lambert
+
+ // use view dir as light dir
+ const vec3 light_dir = normalize(transpose(camera.view)[2].xyz);
+
+ const vec3 top = vec3(0.1f, 0.9f, 0.1f);
+ const vec3 buttom = vec3(0.1f, 0.4f, 0.1f);
+
+ const vec3 base_color = mix(buttom, top, uv.y);
- outColor = vec4(1.0);
+ outColor = vec4(base_color * abs(dot(light_dir, normal)), 1.0);
}
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..dffd7c5 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
+//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[];
-void main() {
+layout(location = 0) out vec4 out_v0[];
+layout(location = 1) out vec4 out_v1[];
+layout(location = 2) out vec4 out_v2[];
+layout(location = 3) out vec4 out_up[];
+
+void main()
+{
// Don't move the origin location of the patch
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
- // TODO: Write any shader outputs
+ out_v0[gl_InvocationID] = in_v0[gl_InvocationID];
+ out_v1[gl_InvocationID] = in_v1[gl_InvocationID];
+ out_v2[gl_InvocationID] = in_v2[gl_InvocationID];
+ out_up[gl_InvocationID] = in_up[gl_InvocationID];
// TODO: Set level of tesselation
- // gl_TessLevelInner[0] = ???
- // gl_TessLevelInner[1] = ???
- // gl_TessLevelOuter[0] = ???
- // gl_TessLevelOuter[1] = ???
- // gl_TessLevelOuter[2] = ???
- // gl_TessLevelOuter[3] = ???
+ if(gl_InvocationID == 0)
+ {
+ gl_TessLevelInner[0] = 5;
+ gl_TessLevelInner[1] = 5;
+ gl_TessLevelOuter[0] = 5;
+ gl_TessLevelOuter[1] = 5;
+ gl_TessLevelOuter[2] = 5;
+ gl_TessLevelOuter[3] = 5;
+ }
}
diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese
index 751fff6..b45020d 100644
--- a/src/shaders/grass.tese
+++ b/src/shaders/grass.tese
@@ -10,9 +10,46 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
// TODO: Declare tessellation evaluation shader inputs and outputs
+layout(location = 0) in vec4 in_v0[];
+layout(location = 1) in vec4 in_v1[];
+layout(location = 2) in vec4 in_v2[];
+layout(location = 3) in vec4 in_up[];
+
+layout(location = 0) out vec3 position;
+layout(location = 1) out vec3 normal;
+layout(location = 2) out vec2 uv;
+
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 p0 = in_v0[0].xyz;
+ vec3 p1 = in_v1[0].xyz;
+ vec3 p2 = in_v2[0].xyz;
+
+ float direction = in_v0[0].w;
+ //float height = in_v1[0].w;
+ float width = in_v2[0].w;
+ //float stiffness = in_up[0].w;
+
+ vec3 p00 = mix(p0, p1, v);
+ vec3 p01 = mix(p1, p2, v);
+ vec3 p10 = mix(p00, p01, v);
+
+ vec3 dir_vec = vec3(cos(direction), 0, sin(direction));
+ width = mix(width, 0.f, v);
+
+ float t = u + 0.5f * v - u * v;
+
+ vec3 p = mix(p10 - width * dir_vec, p10 + width * dir_vec, t);
+
+ normal = normalize(cross(p01 - p00, dir_vec));
+ vec4 pos = camera.proj * camera.view * vec4(p, 1.f);
+ position = pos.xyz;
+
+ uv = gl_TessCoord.xy;
+
+ gl_Position = pos;
}
diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert
index db9dfe9..5bedec3 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -7,11 +7,19 @@ layout(set = 1, binding = 0) uniform ModelBufferObject {
};
// 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;
-out gl_PerVertex {
- vec4 gl_Position;
-};
+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
+ out_v0 = vec4(vec3(model * vec4(in_v0.xyz, 1.f)), in_v0.w);
+ out_v1 = vec4(vec3(model * vec4(in_v1.xyz, 1.f)), in_v1.w);
+ out_v2 = vec4(vec3(model * vec4(in_v2.xyz, 1.f)), in_v2.w);
+ out_up = vec4(vec3(model * vec4(in_up.xyz, 1.f)), in_up.w);
}