diff --git a/README.md b/README.md
index 20ee451..9f330b0 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,86 @@
Vulkan Grass Rendering
==================================
-**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5**
+**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 4**
-* (TODO) YOUR NAME HERE
-* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab)
+* Xiaoyue Ma
+ * [LinkedIn](https://www.linkedin.com/in/xiaoyue-ma-6b268b193/)
+* Tested on: Windows 10, i7-12700H @ 2.30 GHz 16GB, GTX3060 8GB
-### (TODO: Your README)
+# Overview
+
+In this project, I developed a grass simulator and renderer using the Vulkan API, drawing inspiration from 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). The simulation represents each grass blade with three control points: v0, v1, and v2, facilitating efficient tessellation. The core components encompass a vertex shader for Bezier control point transformations, tessellation shaders for grass geometry generation, and a fragment shader for grass blade shading.
+
+
+
+# Table of Contents
+* [Features](#features)
+ * [Physical Force](#force)
+ * [Blades Culling](#culling)
+ * [LOD Tessellation](#lod)
+* [Performance Analysis](#performance)
+
+
+# Features
+## Physical Force
+In our project, grass blades are represented using Bezier curves with three control points: `v0` indicating the blade's position on the geometry, `v1` serving as an upward vector guide above `v0`, and `v2` acting as a directional guide. We simulate forces on these blades while they remain as Bezier curves, primarily applying transformations to `v2` and subsequently adjusting `v1` to ensure the blade's consistent length.
+
+
+### Gravity
+Using a specified gravity direction, `D.xyz`, and acceleration magnitude, `D.w`, the environmental gravity `gE`, is calculated as `gE = normalize(D.xyz) * D.w`. The front-facing gravity influence on the blade, termed "front gravity", is `gF = (1/4) * ||gE|| * f`. The overall gravity acting on the grass blade is then `g = gE + gF`.
+
+### Recovery
+The recovery force, bringing the grass blade back to equilibrium, is based on Hooke's law. It compares the current position of `v2` with its initial position, `iv2`, set at the simulation's start. Initially, `v1` and `v2` are aligned at the blade height along the up vector. The recovery forces are then calculated as `r = (iv2 - v2) * stiffness`.
+
+### Wind
+The wind's direction in the simulation is dynamic, defined by `windFunc (vec3(2.0, 4.0, 2.0) * sin(totalTime * 0.1))`. Grass blades aligned with the wind direction experience a greater impact, represented by the windAlignment term. The cumulative wind force, `w`, is given by `windDirection * windAlignment`.
+
+### Result
+
+
+## Blades Culling
+### Orientation
+
+When the grass blade's front face direction is perpendicular to the view vector, it can cause aliasing artifacts due to the blades having no width and appearing smaller than a pixel. To address this, blades are culled using a dot product test checking for perpendicularity between the view vector and the blade's direction. The threshold for this culling is set at `0.9`.
+
+### View-Frustum
+
+Blades outside the view-frustum are culled since they won't be visible in the frame. To ascertain a grass blade's position in relation to the view-frustum, three points: `v0`, `v2`, and `m = (1/4)v0 * (1/2)v1 * (1/4)v2)` are assessed. If all these points lie outside the view-frustum, the blade is culled.
+
+### Distance
+
+For grass blades at significant distances that appear smaller than a pixel, rendering can introduce artifacts. To counter this, blades are culled based on their distance from the camera, similar to orientation culling.
+
+### Result
+
+
+### LOD Tessellation
+
+To optimize performance, the tessellation level for a grass blade can be adjusted based on its distance from the camera. Blades farther away require less detail, reducing the need for high tessellation levels.
+
+```
+#if DYNAMIC_LOD
+ if (dist > 20.0)
+ LOD = 4;
+ else if (dist > 15.0)
+ LOD = 8;
+ else if (dist > 10.0)
+ LOD = 12;
+ else if (dist > 5.0)
+ LOD = 16;
+#endif
+```
+#### Result
+
+
+
+
+# Performance Analysis
+View-frustum culling offers a minor performance enhancement, as it primarily culls blades at the image's periphery. Distance culling and Orientation culling is more effective based on the camera position. Dynamic LOD significantly improves performance by reducing polygons for distant grass blades. Most rear blades are obscured, and reduced tessellation often goes unnoticed, yielding a substantial speed increase with minimal visual impact.
+
+Utilizing all culling methods amplifies the frame rate to almost 10 times the original. These methods substantially enhance performance while minimally affecting visual quality.
+
+
+
+
-*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.
diff --git a/bin/Debug/vulkan_grass_rendering.exe b/bin/Debug/vulkan_grass_rendering.exe
new file mode 100644
index 0000000..5e9bb21
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..a8fa2ce
Binary files /dev/null and b/bin/Debug/vulkan_grass_rendering.pdb differ
diff --git a/img/chart.png b/img/chart.png
new file mode 100644
index 0000000..9e872b6
Binary files /dev/null and b/img/chart.png differ
diff --git a/img/culling.gif b/img/culling.gif
new file mode 100644
index 0000000..9a18173
Binary files /dev/null and b/img/culling.gif differ
diff --git a/img/force.gif b/img/force.gif
new file mode 100644
index 0000000..75eee2a
Binary files /dev/null and b/img/force.gif differ
diff --git a/img/lod.gif b/img/lod.gif
new file mode 100644
index 0000000..a262da8
Binary files /dev/null and b/img/lod.gif differ
diff --git a/img/result.gif b/img/result.gif
new file mode 100644
index 0000000..2146ac5
Binary files /dev/null and b/img/result.gif 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..ec2bd9e 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -198,6 +198,38 @@ 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 compute descriptor set layout");
+ }
}
void Renderer::CreateDescriptorPool() {
@@ -216,6 +248,8 @@ void Renderer::CreateDescriptorPool() {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 },
// TODO: Add any additional types and counts of descriptors you will need to allocate
+ // bladesĄ˘culledBlades and numBlades
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(3 * scene->GetBlades().size()) }
};
VkDescriptorPoolCreateInfo poolInfo = {};
@@ -320,6 +354,39 @@ 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;
+
+ 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,6 +427,69 @@ 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;
+
+ if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) {
+ throw std::runtime_error("Failed to allocate descriptor set");
+ }
+
+ std::vector descriptorWrites(3 * computeDescriptorSets.size());
+
+ for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) {
+ VkDescriptorBufferInfo 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 +847,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 +1014,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 (int i = 0; i < computeDescriptorSets.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, &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 +1193,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..fdc6a24 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/main.cpp b/src/main.cpp
index 8bf822b..bdd5bea 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -67,7 +67,7 @@ namespace {
int main() {
static constexpr char* applicationName = "Vulkan Grass Rendering";
- InitializeWindow(640, 480, applicationName);
+ InitializeWindow(800, 600, applicationName);
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
@@ -90,7 +90,7 @@ int main() {
swapChain = device->CreateSwapChain(surface, 5);
- camera = new Camera(device, 640.f / 480.f);
+ camera = new Camera(device, 1080.f / 960.f);
VkCommandPoolCreateInfo transferPoolInfo = {};
transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
@@ -138,7 +138,7 @@ int main() {
scene->AddBlades(blades);
renderer = new Renderer(device, swapChain, scene, camera);
-
+ GLFWwindow* window = GetGLFWWindow();
glfwSetWindowSizeCallback(GetGLFWWindow(), resizeCallback);
glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback);
@@ -161,6 +161,7 @@ int main() {
delete renderer;
delete swapChain;
delete device;
+ vkDestroySurfaceKHR(instance->GetVkInstance(), surface, nullptr);
delete instance;
DestroyWindow();
return 0;
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..233cd45 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -2,11 +2,22 @@
#extension GL_ARB_separate_shader_objects : enable
#define WORKGROUP_SIZE 32
+
+#define GRAVITY_ON 1
+#define RECOVER_ON 1
+#define WIND_ON 1
+
+#define ORIE_CULL_ON 1
+#define DIST_CULL_ON 1
+#define VIEW_CULL_ON 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;
+ vec3 pos;
+
} camera;
layout(set = 1, binding = 0) uniform Time {
@@ -23,34 +34,135 @@ struct Blade {
// TODO: Add bindings to:
// 1. Store the input blades
-// 2. Write out the culled blades
-// 3. Write the total number of blades remaining
+layout(set = 2, binding = 0) buffer Blades {
+ Blade blades[];
+} ;
+// 2. Write out the culled blades
+layout(set = 2, binding = 1) buffer CulledBlades {
+ Blade culled_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;
+
+// 3. Write the total number of blades remaining
+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);
}
+// helper for frustum culling
+bool inFrustum(vec3 point)
+{
+ mat4 vp = camera.proj * camera.view;
+ vec4 pp = vp * vec4(point, 1.0f);
+ float tolerance = 0.2f;
+ float h = pp.w + tolerance;
+ return inBounds(pp.x, h) && inBounds(pp.y, h) && inBounds(pp.z, h);
+}
+
+#define DIST_CULL_MAX 30
+#define GRAVITY vec4(0, -1, 0, 4.9)
+#define windFunc (vec3(2.0, 4.0, 2.0) * sin(totalTime))
+
+
+
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 id = gl_GlobalInvocationID.x;
+ Blade blade = blades[id];
+ vec3 v0 = blade.v0.xyz;
+ vec3 v1 = blade.v1.xyz;
+ vec3 v2 = blade.v2.xyz;
+ vec3 up = blade.up.xyz;
+
+ float direction = blade.v0.w;
+ float height = blade.v1.w;
+ float width = blade.v2.w;
+ float stiffness = blade.up.w;
+ vec3 orient = vec3(cos(direction), 0, sin(direction));
+
+ // gravity
+ vec3 gravity = vec3(0,0,0);
+ #if GRAVITY_ON
+ vec3 gE = normalize(GRAVITY.xyz) * GRAVITY.w;
+ vec3 frontDir = normalize(cross(orient, up));
+ vec3 gF = 0.25f * length(gE) * frontDir;
+ gravity = gE + gF;
+ #endif
+
+ // recovery
+ vec3 recovery = vec3(0,0,0);
+ #if RECOVER_ON
+ vec3 iv2 = v0 + up * height;
+ recovery = (iv2 - v2) * stiffness;
+ #endif
+
+ // wind
+ vec3 wind = vec3(0,0,0);
+ #if WIND_ON
+ float windDirection = 1 - abs(dot(normalize(windFunc), normalize(v2 - v0)));
+ float windAlign = dot((v2 - v0), up) / height;
+ wind = windFunc * windDirection * windAlign;
+ #endif
+
+ vec3 totalForce = gravity + recovery + wind;
+
+ v2 += totalForce * deltaTime;
+ v2 -= up * min(dot(up, v2 - v0), 0);
+
+ float Lproj = length(v2 - v0 - up * dot(v2 - v0, up));
+ v1 = v0 + height * up * max(1 - Lproj / height, 0.05 * max(Lproj/height, 1));
+
+ float L0 = length(v2 - v0);
+ float L1 = length(v2 - v1) + length(v1 - v0);
+ float L = (2 * L0 + (2 - 1) * L1) / (2 + 1);
+ float r = height / L;
+
+ v1 = v0 + r * (v1 - v0);
+ v2 = v1 + r * (v2 - v1);
+
+ blades[id].v1 = vec4(v1, blade.v1.w);
+ blades[id].v2 = vec4(v2, blade.v2.w);
// 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
-}
+ // Reset the number of blades to 0
+
+ vec3 cam_pos = inverse(camera.view)[3].xyz;
+
+ #if ORIE_CULL_ON
+ if (abs(dot(normalize(cam_pos - v0), orient)) >= 0.9)
+ return;
+ #endif
+
+ #if VIEW_CULL_ON
+ vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2;
+ if(!(inFrustum(v0) && inFrustum(v2) && inFrustum(m)))
+ return;
+ #endif
+
+ #if DIST_CULL_ON
+ vec3 dir = v0 - cam_pos;
+ float dProj = length(dir - up * dot(dir, up));
+ if (mod(id, 10.0) >= (10.0 * (1 - dProj / DIST_CULL_MAX)))
+ return;
+ #endif
+
+ culled_blades[atomicAdd(numBlades.vertexCount, 1)] = blades[id];
+}
\ No newline at end of file
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..fd64891 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -7,11 +7,22 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} camera;
// TODO: Declare fragment shader inputs
+layout(location = 0) in vec3 in_pos;
+layout(location = 1) in vec3 in_nor;
layout(location = 0) out vec4 outColor;
void main() {
// TODO: Compute fragment color
+ vec3 baseColor = vec3(0.4, 0.7, 0.2);
+ vec3 ambient = vec3(0.2, 0.3, 0.1);
+
+ vec4 light_pos = vec4(10.0, 30.0, 0.0, 1.0);
+ vec4 nor = vec4(normalize(in_nor), 0.0);
+ vec4 L = normalize(light_pos - vec4(in_pos, 1.0f));
- outColor = vec4(1.0);
+ float diffuse = max(dot(L, nor), 0.0);
+ vec3 color = ambient + diffuse * baseColor;
+
+ outColor = vec4(color, 1.0);
}
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..559c412 100644
--- a/src/shaders/grass.tesc
+++ b/src/shaders/grass.tesc
@@ -8,19 +8,50 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
mat4 proj;
} camera;
+# define TESS_GENERAL_LEVEL 20
+# define DYNAMIC_LOD 1
+
// 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 = 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];
+
+ vec3 cam_pos = inverse(camera.view)[3].xyz;
+ float dist = distance(cam_pos, in_v0[gl_InvocationID].xyz);
// TODO: Set level of tesselation
- // gl_TessLevelInner[0] = ???
- // gl_TessLevelInner[1] = ???
- // gl_TessLevelOuter[0] = ???
- // gl_TessLevelOuter[1] = ???
- // gl_TessLevelOuter[2] = ???
- // gl_TessLevelOuter[3] = ???
+ int LOD = TESS_GENERAL_LEVEL;
+
+#if DYNAMIC_LOD
+ if (dist > 20.0)
+ LOD = 4;
+ else if (dist > 15.0)
+ LOD = 8;
+ else if (dist > 10.0)
+ LOD = 12;
+ else if (dist > 5.0)
+ LOD = 16;
+#endif
+
+ gl_TessLevelInner[0] = LOD;
+ gl_TessLevelInner[1] = LOD;
+ gl_TessLevelOuter[0] = LOD;
+ gl_TessLevelOuter[1] = LOD;
+ gl_TessLevelOuter[2] = LOD;
+ gl_TessLevelOuter[3] = LOD;
}
diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese
index 751fff6..1d92142 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 = 3) in vec4 in_up[];
+
+layout(location = 0) out vec3 out_pos;
+layout(location = 1) out vec3 out_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 v0 = in_v0[0].xyz;
+ vec3 v1 = in_v1[0].xyz;
+ vec3 v2 = in_v2[0].xyz;
+ vec3 up = in_up[0].xyz;
+
+ vec3 a = mix(v0, v1, v);
+ vec3 b = mix(v1, v2, v);
+ vec3 c = mix(a, b, v);
+ vec3 t0 = normalize(b - a);
+
+ float theta = in_v0[0].w;
+ vec3 t1 = vec3(cos(theta), 0.0, sin(theta));
+
+ float width = in_v2[0].w;
+ vec3 c0 = c - width * t1;
+ vec3 c1 = c + width * t1;
+
+ float t = u + 0.5 * v - u * v;
+ vec3 out_pos = mix(c0, c1, t);
+
+ out_nor = normalize(cross(t0, t1));
+
+ 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..d91ff6f 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -7,6 +7,15 @@ 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;
+
+layout(location = 0) out vec4 out_v0;
+layout(location = 1) out vec4 out_v1;
+layout(location = 2) out vec4 out_v2;
+layout(location = 3) out vec4 out_up;
out gl_PerVertex {
vec4 gl_Position;
@@ -14,4 +23,10 @@ out gl_PerVertex {
void main() {
// TODO: Write gl_Position and any other shader outputs
+
+ gl_Position = model * vec4(in_v0.xyz, 1.f);
+ out_v0 = vec4((model * vec4(in_v0.xyz, 1.f)).xyz, in_v0.w);
+ out_v1 = vec4((model * vec4(in_v1.xyz, 1.f)).xyz, in_v1.w);
+ out_v2 = vec4((model * vec4(in_v2.xyz, 1.f)).xyz, in_v2.w);
+ out_up = vec4((model * vec4(in_up.xyz, 0.f)).xyz, in_up.w);
}