diff --git a/CMakeLists.txt b/CMakeLists.txt
index 36d5dcb..44e7a61 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
project(cis565_project5_vulkan_grass_rendering)
diff --git a/README.md b/README.md
index 20ee451..f822856 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,41 @@ 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)
+* Qirui (Chiray) Fu
+ * [personal website](https://qiruifu.github.io/)
+* Tested on my own laptop: Windows 11, i5-13500HX @ 2.5GHz 16GB, GTX 4060 8GB
-### (TODO: Your README)
+### README
-*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.
+
+
+
+
+### Description
+
+In this project, I implement a tool to render and simulate behaviours of grasses in Vulkan. There are three parts in this project: rendering, simulating forces and culling. The grass blades are represented by Bezier curves and all forces are considered on three control points for blades. Furthermore, we can optimize the performance using some culling techniques.
+
+### Technique Details
+
+#### Forces
+
+We have 3 kinds of forces to consider in this project: gravity, recovery and wind. They are not really physics based, we just apply the effects of them on the positions of control points. For example, the gravity has two parts: environmental gravity and front gravity. It's not physicallly precise but we can simulate the behaviours of curved blades.
+
+#### Culling
+
+We have 3 ways for culling so we don't need to render all grasses, which can improve the performance a lot. The first one is intuitive: our view is a frustum, all blades out of this view-frustum should not be rendered. The second is orientation culling, which means the blades who are parallal with view vector are not visible because they don't have width. The last one is if a blade is too far from the camera, we are not supposed to render it. We will discuss later how these methods can improve the performance.
+
+### Performance Analysis
+#### Number of Blades
+
+This figure is generated with all culling methods on:
+
+
+The FPS decreases exponentially as the number of grass blades increases. I believe that the computational complexity is roughly `O(N)` or slightly higher. From $2^{13}$ to $2^{20}$, the number of blades increases by a factor of 128, while FPS drops from 6000 to 40 — about a 150 times decrease. This suggests that the main performance bottleneck scales linearly with the number of blades.
+
+#### Culling Methods
+
+The influence of 3 culling methods are shown here (# of blades: $2^{15}$):
+
+
+We can tell that distance culling has the most significant influence on the performance. However, it will also lead to some obvious artifacts: some grasses disapper when you move the camera. View-frustum culling doesn't improve FPS a lot, I believe the reason is most grasses are located in the frustum. If we build a bigger scene with more objects, this method is expected to have better performance.
\ No newline at end of file
diff --git a/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe
index f68db3a..fbfbeed 100644
Binary files a/bin/Release/vulkan_grass_rendering.exe and b/bin/Release/vulkan_grass_rendering.exe differ
diff --git a/external/GLFW/CMakeLists.txt b/external/GLFW/CMakeLists.txt
index 56c1f38..917fc9a 100644
--- a/external/GLFW/CMakeLists.txt
+++ b/external/GLFW/CMakeLists.txt
@@ -1,19 +1,9 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(GLFW C)
set(CMAKE_LEGACY_CYGWIN_WIN32 OFF)
-if (NOT CMAKE_VERSION VERSION_LESS "3.0")
- # Until all major package systems have moved to CMake 3,
- # we stick with the older INSTALL_NAME_DIR mechanism
- cmake_policy(SET CMP0042 OLD)
-endif()
-
-if (NOT CMAKE_VERSION VERSION_LESS "3.1")
- cmake_policy(SET CMP0054 NEW)
-endif()
-
set(GLFW_VERSION_MAJOR "3")
set(GLFW_VERSION_MINOR "3")
set(GLFW_VERSION_PATCH "0")
diff --git a/img/p1.jpg b/img/p1.jpg
new file mode 100644
index 0000000..21515dd
Binary files /dev/null and b/img/p1.jpg differ
diff --git a/img/p2.jpg b/img/p2.jpg
new file mode 100644
index 0000000..ab40fbf
Binary files /dev/null and b/img/p2.jpg differ
diff --git a/img/screen-gif.gif b/img/screen-gif.gif
new file mode 100644
index 0000000..78e8d1f
Binary files /dev/null and b/img/screen-gif.gif differ
diff --git a/img/screenshot.png b/img/screenshot.png
new file mode 100644
index 0000000..cc5f637
Binary files /dev/null and b/img/screenshot.png differ
diff --git a/src/Blades.h b/src/Blades.h
index 9bd1eed..b438e24 100644
--- a/src/Blades.h
+++ b/src/Blades.h
@@ -4,7 +4,7 @@
#include
#include "Model.h"
-constexpr static unsigned int NUM_BLADES = 1 << 13;
+constexpr static unsigned int NUM_BLADES = 1 << 15;
constexpr static float MIN_HEIGHT = 1.3f;
constexpr static float MAX_HEIGHT = 2.5f;
constexpr static float MIN_WIDTH = 0.1f;
diff --git a/src/Renderer.cpp b/src/Renderer.cpp
index b445d04..325bbf2 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -198,6 +198,37 @@ 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 bladesInfoLayoutBinding = {};
+ bladesInfoLayoutBinding.binding = 2;
+ bladesInfoLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ bladesInfoLayoutBinding.descriptorCount = 1;
+ bladesInfoLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ bladesInfoLayoutBinding.pImmutableSamplers = nullptr;
+
+ std::vector bindings = { bladesLayoutBinding, culledBladesLayoutBinding, bladesInfoLayoutBinding };
+
+ 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 +247,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(3 * scene->GetBlades().size()) }
};
VkDescriptorPoolCreateInfo poolInfo = {};
@@ -320,6 +352,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
+
+ int cnt_blade = scene->GetBlades().size();
+ bladeDescriptorSets.resize(cnt_blade);
+
+ VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout };
+ VkDescriptorSetAllocateInfo allocInfo = {};
+ allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ allocInfo.descriptorPool = descriptorPool;
+ allocInfo.descriptorSetCount = static_cast(bladeDescriptorSets.size());
+ allocInfo.pSetLayouts = layouts;
+
+ if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, bladeDescriptorSets.data()) != VK_SUCCESS) {
+ throw std::runtime_error("Failed to allocate descriptor set");
+ }
+
+ std::vector descriptorWrites(bladeDescriptorSets.size());
+
+ for (uint32_t i = 0; i < cnt_blade; 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 = bladeDescriptorSets[i];
+ descriptorWrites[i].dstBinding = 0;
+ descriptorWrites[i].dstArrayElement = 0;
+ descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ descriptorWrites[i].descriptorCount = 1;
+ descriptorWrites[i].pBufferInfo = &modelBufferInfo;
+ descriptorWrites[i].pImageInfo = nullptr;
+ descriptorWrites[i].pTexelBufferView = nullptr;
+ }
+
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
void Renderer::CreateTimeDescriptorSet() {
@@ -360,6 +427,70 @@ 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
+ int cnt_blade = scene->GetBlades().size();
+ computeDescriptorSets.resize(cnt_blade);
+
+ 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;
+
+ 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 < cnt_blade; ++i) {
+ VkDescriptorBufferInfo bladesBufferInfo = {};
+ bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer();
+ bladesBufferInfo.offset = 0;
+ bladesBufferInfo.range = NUM_BLADES * sizeof(Blade);
+
+ VkDescriptorBufferInfo culledBladesBufferInfo = {};
+ culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer();
+ culledBladesBufferInfo.offset = 0;
+ culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade);
+
+ VkDescriptorBufferInfo numBladesBufferInfo = {};
+ numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer();
+ numBladesBufferInfo.offset = 0;
+ numBladesBufferInfo.range = sizeof(BladeDrawIndirect);
+
+ descriptorWrites[3 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[3 * i + 0].dstSet = computeDescriptorSets[i];
+ descriptorWrites[3 * i + 0].dstBinding = 0;
+ descriptorWrites[3 * i + 0].dstArrayElement = 0;
+ descriptorWrites[3 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[3 * i + 0].descriptorCount = 1;
+ descriptorWrites[3 * i + 0].pBufferInfo = &bladesBufferInfo;
+ descriptorWrites[3 * i + 0].pImageInfo = nullptr;
+ descriptorWrites[3 * i + 0].pTexelBufferView = nullptr;
+
+ descriptorWrites[3 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[3 * i + 1].dstSet = computeDescriptorSets[i];
+ descriptorWrites[3 * i + 1].dstBinding = 1;
+ descriptorWrites[3 * i + 1].dstArrayElement = 0;
+ descriptorWrites[3 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[3 * i + 1].descriptorCount = 1;
+ descriptorWrites[3 * i + 1].pBufferInfo = &culledBladesBufferInfo;
+ descriptorWrites[3 * i + 1].pImageInfo = nullptr;
+ descriptorWrites[3 * i + 1].pTexelBufferView = nullptr;
+
+ descriptorWrites[3 * i + 2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[3 * i + 2].dstSet = computeDescriptorSets[i];
+ descriptorWrites[3 * i + 2].dstBinding = 2;
+ descriptorWrites[3 * i + 2].dstArrayElement = 0;
+ descriptorWrites[3 * i + 2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[3 * i + 2].descriptorCount = 1;
+ descriptorWrites[3 * i + 2].pBufferInfo = &numBladesBufferInfo;
+ descriptorWrites[3 * i + 2].pImageInfo = nullptr;
+ descriptorWrites[3 * i + 2].pTexelBufferView = nullptr;
+ }
+
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
void Renderer::CreateGraphicsPipeline() {
@@ -717,7 +848,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 +1015,10 @@ 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) {
@@ -976,13 +1111,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, &bladeDescriptorSets[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 +1193,7 @@ Renderer::~Renderer() {
vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr);
vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr);
+ vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr);
vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr);
@@ -1064,4 +1201,4 @@ Renderer::~Renderer() {
DestroyFrameResources();
vkDestroyCommandPool(logicalDevice, computeCommandPool, nullptr);
vkDestroyCommandPool(logicalDevice, graphicsCommandPool, nullptr);
-}
+}
\ No newline at end of file
diff --git a/src/Renderer.h b/src/Renderer.h
index 95e025f..2da6df0 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 bladeDescriptorSets;
+ std::vector computeDescriptorSets;
VkPipelineLayout graphicsPipelineLayout;
VkPipelineLayout grassPipelineLayout;
diff --git a/src/main.cpp b/src/main.cpp
index 8bf822b..f692ded 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -143,8 +143,40 @@ int main() {
glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback);
+
+ // The code for printing fps is from :
+ // https://github.com/TianhongZhou/Project5-Vulkan-Grass-Rendering/blob/main/src/main.cpp
+
+ double lastTitleUpdate = glfwGetTime();
+ double lastFrameTime = lastTitleUpdate;
+ int frames = 0;
+ double smoothMs = -1.0;
+
while (!ShouldQuit()) {
glfwPollEvents();
+
+ double now = glfwGetTime();
+ double dt = now - lastFrameTime;
+ lastFrameTime = now;
+ frames++;
+
+ double ms = dt * 1000.0;
+ smoothMs = (smoothMs < 0.0) ? ms : (0.9 * smoothMs + 0.1 * ms);
+
+ if (now - lastTitleUpdate >= 1.0) {
+ double sec = now - lastTitleUpdate;
+ double fps = frames / sec;
+
+ char title[160];
+ std::snprintf(title, sizeof(title),
+ "%s %.1f FPS (%.2f ms)",
+ applicationName, fps, smoothMs);
+ glfwSetWindowTitle(GetGLFWWindow(), title);
+
+ frames = 0;
+ lastTitleUpdate = now;
+ }
+
scene->UpdateTime();
renderer->Frame();
}
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..f64ea9b 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -36,6 +36,21 @@ struct Blade {
// uint firstInstance; // = 0
// } numBlades;
+layout(set = 2, binding = 0) buffer Blades {
+ Blade blades[];
+};
+
+layout(set = 2, binding = 1) buffer CulledBlades {
+ Blade culledBlades[];
+};
+
+layout(set = 2, binding = 2) buffer BladeInfo {
+ uint vertexCount;
+ uint instanceCount;
+ uint firstVertex;
+ uint firstInstance;
+} bladeInfo;
+
bool inBounds(float value, float bounds) {
return (value >= -bounds) && (value <= bounds);
}
@@ -43,7 +58,7 @@ bool inBounds(float value, float bounds) {
void main() {
// Reset the number of blades to 0
if (gl_GlobalInvocationID.x == 0) {
- // numBlades.vertexCount = 0;
+ bladeInfo.vertexCount = 0;
}
barrier(); // Wait till all threads reach this point
@@ -53,4 +68,86 @@ void main() {
// 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
+
+ Blade blade = blades[gl_GlobalInvocationID.x];
+
+ const float g = 5.0;
+ const float wind = 1.0;
+ const float wind_freq = 1.0;
+
+ const float orientation = 0.4;
+ const int bucket = 5;
+ const float max_dis = 15.0;
+
+ vec3 v0 = blade.v0.xyz;
+ vec3 v1 = blade.v1.xyz;
+ vec3 v2 = blade.v2.xyz;
+ vec3 up = blade.up.xyz;
+ float blade_orient = blade.v0.w;
+ float height = blade.v1.w;
+ float stiffness = blade.up.w;
+
+ // gravity
+ vec3 t1 = normalize(vec3(-cos(blade_orient), 0.0, sin(blade_orient)));
+ vec3 f = normalize(cross(t1, up));
+ vec3 g_total = vec3(0.0, -g, 0.0) + 0.25 * g * f;
+
+ // stiffness
+ vec3 iv2 = v0 + height * up;
+ vec3 re = (iv2 - v2) * stiffness;
+
+ // wind
+ vec3 windDir = wind * vec3(cos(wind_freq * v0.x * totalTime), 0.0, sin(wind_freq * v0.z * totalTime));
+ float fd = 1.0 - abs(dot(normalize(windDir), normalize(v2 - v0)));
+ float fr = dot(v2 - v0, up) / height;
+ vec3 w = windDir * (fd * fr);
+
+ v2 += (g_total + re + w) * deltaTime;
+ v2 -= up * min(dot(up, v2 - v0), 0.0);
+
+ float lproj = length(v2 - v0 - up * dot(v2 - v0, up));
+ v1 = v0 + height * up * max(1.0 - lproj / height, 0.05 * max(lproj / height, 1.0));
+
+ float L0 = distance(v0, v2);
+ float L1 = distance(v0, v1) + distance(v1, v2);
+ float r = height / ((2.0 * L0 + L1) / 3.0);
+
+ v1 = v0 + r * (v1 - v0);
+ v2 = v1 + r * (v2 - v1);
+
+ blade.v1.xyz = v1;
+ blade.v2.xyz = v2;
+
+ blades[gl_GlobalInvocationID.x] = blade;
+
+ // frustum
+ vec4 ccs_v0 = camera.proj * camera.view * vec4(v0, 1.0);
+ vec4 ccs_v2 = camera.proj * camera.view * vec4(v2, 1.0);
+ vec4 ccs_m = camera.proj * camera.view * vec4((1.0/4.0)*v0 + (1.0/2.0)*v1 + (1.0/4.0)*v2, 1.0);
+
+ bool v0_in = abs(ccs_v0.x) <= ccs_v0.w && abs(ccs_v0.y) <= ccs_v0.w && abs(ccs_v0.z) <= ccs_v0.w;
+ bool v2_in = abs(ccs_v2.x) <= ccs_v2.w && abs(ccs_v2.y) <= ccs_v2.w && abs(ccs_v2.z) <= ccs_v2.w;
+ bool m_in = abs(ccs_m.x) <= ccs_m.w && abs(ccs_m.y) <= ccs_m.w && abs(ccs_m.z) <= ccs_m.w;
+
+ if (!v0_in && !v2_in && !m_in) {
+ return;
+ }
+
+ // orientation
+ vec3 camPos = inverse(camera.view)[3].xyz;
+ vec3 camToBlade = v0 - camPos;
+ vec3 viewDir = camToBlade - up * dot(camToBlade, up);
+
+ float angle = abs(dot(normalize(viewDir), t1));
+ if (angle < orientation) {
+ return;
+ }
+
+ // distance
+ float dproj = length(viewDir);
+ if ((gl_GlobalInvocationID.x % bucket) < int(floor(bucket * (1.0 - dproj / max_dis)))) {
+ return;
+ }
+
+ culledBlades[atomicAdd(bladeInfo.vertexCount, 1)] = blade;
}
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..7891c70 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -8,10 +8,15 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
// TODO: Declare fragment shader inputs
+layout(location = 0) in float fs_v;
+layout(location = 1) in vec3 fs_nor;
layout(location = 0) out vec4 outColor;
void main() {
// TODO: Compute fragment color
- outColor = vec4(1.0);
+ vec3 rootCol = vec3(35.0, 82.0, 47.0) / 255.0;
+ vec3 tipCol = vec3(110.0, 212.0, 137.0) / 255.0;
+ vec3 baseCol = mix(rootCol, tipCol, fs_v);
+ outColor = vec4(baseCol, 1.0);
}
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..dab4551 100644
--- a/src/shaders/grass.tesc
+++ b/src/shaders/grass.tesc
@@ -10,17 +10,30 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
// TODO: Declare tessellation control shader inputs and outputs
+layout(location = 0) in vec4[] in_v0;
+layout(location = 1) in vec4[] in_v1;
+layout(location = 2) in vec4[] in_v2;
+
+layout(location = 0) out vec4[] out_v0;
+layout(location = 1) out vec4[] out_v1;
+layout(location = 2) out vec4[] out_v2;
+
void main() {
// Don't move the origin location of the patch
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
// TODO: Write any shader outputs
+ out_v0[gl_InvocationID] = in_v0[gl_InvocationID];
+ out_v1[gl_InvocationID] = in_v1[gl_InvocationID];
+ out_v2[gl_InvocationID] = in_v2[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] = ???
+ float density = 6.0;
+ gl_TessLevelInner[0] = density;
+ gl_TessLevelInner[1] = density;
+ gl_TessLevelOuter[0] = density;
+ gl_TessLevelOuter[1] = density;
+ gl_TessLevelOuter[2] = density;
+ gl_TessLevelOuter[3] = density;
+ gl_TessLevelInner[0] = density;
}
diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese
index 751fff6..8378aac 100644
--- a/src/shaders/grass.tese
+++ b/src/shaders/grass.tese
@@ -9,10 +9,40 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} 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 = 0) out float fs_v;
+layout(location = 1) out vec3 fs_nor;
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 base = in_v0[0].xyz;
+ vec3 tip = in_v1[0].xyz;
+ vec3 bend = in_v2[0].xyz;
+
+ float orientation = in_v0[0].w;
+ float width = in_v2[0].w;
+
+ vec3 lower = mix(base, tip, v);
+ vec3 upper = mix(tip, bend, v);
+ vec3 center = mix(lower, upper, v);
+
+ vec3 tangent = normalize(upper - lower);
+ vec3 sideDir = normalize(vec3(-cos(orientation), 0.0, sin(orientation)));
+
+ vec3 leftEdge = center - width * sideDir;
+ vec3 rightEdge = center + width * sideDir;
+
+ float t = u + 0.5 * v - u * v;
+ vec3 pos = mix(leftEdge, rightEdge, t);
+
+ fs_v = v;
+ fs_nor = normalize(cross(tangent, sideDir));
+
+ gl_Position = camera.proj * camera.view * vec4(pos, 1.0);
}
diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert
index db9dfe9..5c8021c 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -7,6 +7,13 @@ 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 = 0) out vec4 out_v0;
+layout(location = 1) out vec4 out_v1;
+layout(location = 2) out vec4 out_v2;
out gl_PerVertex {
vec4 gl_Position;
@@ -14,4 +21,17 @@ out gl_PerVertex {
void main() {
// TODO: Write gl_Position and any other shader outputs
+ vec4 model_v0 = model * vec4(in_v0.xyz, 1.0);
+ vec4 model_v1 = model * vec4(in_v1.xyz, 1.0);
+ vec4 model_v2 = model * vec4(in_v2.xyz, 1.0);
+
+ model_v0.w = in_v0.w;
+ model_v1.w = in_v1.w;
+ model_v2.w = in_v2.w;
+
+ out_v0 = model_v0;
+ out_v1 = model_v1;
+ out_v2 = model_v2;
+
+ gl_Position = out_v0;
}