diff --git a/README.md b/README.md
index 20ee451..6153856 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,111 @@ 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)
+* Jiangman(Lobi) Zhao
+ * [Lobi Zhao - LinkedIn](https://www.linkedin.com/in/lobizhao/), [Lobi Zhao - personal website](https://lobizhao.github.io/).
+* Tested on: Windows 11 Pro, i5-10600KF @ 4.10GHz 32GB, RTX 3080 10GB
+
+
+

+
+ Overlook
+
+
+## Features Implemented
+
+### 1. Grass Blade Representation
+- Bezier Curve Model: Each grass blade is represented as a quadratic Bezier curve with three control points (v0, v1, v2)
+- Physical Properties: Height, width, orientation, and stiffness coefficient for each blade
+- Dynamic Simulation: Blades respond to gravity, recovery forces, and wind in real-time
+
+### 2. Forces Simulation
+
+

+
+
Grass procedural wind forces
+
+
+#### Gravity Force
+- Environmental Gravity: Downward force simulating natural gravity
+- Front Gravity: Additional force based on blade orientation to create natural bending
+- Combined gravity creates realistic drooping effect
+
+#### Recovery Force
+- Restores blade to original upright position based on stiffness coefficient
+- Higher stiffness = faster recovery to vertical position
+- Simulates the elastic properties of real grass
+
+#### Wind Force
+- Procedural Wind Field: Multi-octave noise-based wind simulation
+- Spatial Variation: Different wind strength across the field using hash-based noise
+- Temporal Animation: Wind direction rotates over time with turbulence
+- Wind Alignment: Blades bend more when wind direction aligns with blade orientation
+- Height-based Effect: Wind affects blade tips more than roots
+
+### 3. Culling Optimizations
+
+

+
+
Distance Culling
+
+
+
+

+
+
View Frustum
+
+
+Implemented three types of culling to dramatically improve performance:
+
+InitializeWindow(1080, 768)
+
+NUM_BLADES 1^15
+
+

+
+
Culling type comparsion
+
+
+#### Orientation Culling
+- Culls grass blades that are nearly parallel to the view direction
+- Removes blades that appear as thin lines from the camera's perspective
+- Threshold: `dot(bladeDirection, viewDirection) > 0.9`
+
+#### View-Frustum Culling
+- Culls blades outside the camera's view frustum
+- Tests three points per blade: root (v0), tip (v2), and midpoint (m)
+- Only renders blades visible on screen
+
+#### Distance Culling with LOD
+- Culls blades beyond maximum distance (30 units)
+- Progressive culling: farther blades have higher probability of being culled
+- 10 distance buckets for gradual density reduction
+
+### 4. Blade Count Performance Analysis
+InitializeWindow(1080, 768)
+
+

+
+
FPS vs Number of Grass Blades
+
+
+**Analysis:**
+- The system maintains excellent performance (>1000 FPS) up to 32,768 blades
+- Performance degrades approximately linearly with blade count in log scale
+- At 2^17 (131K blades), FPS drops to ~540, still maintaining real-time performance
+- Beyond 1 million blades (2^20), performance becomes impractical for real-time applications
+- The RTX 3080 can handle up to ~500K blades while maintaining playable framerates (>60 FPS)
+
+### 4. Extra Credit - Tessellation-based LOD
+
+

+
+
Tessellation LOD
+
+
+- Dynamic tessellation level based on distance to camera
+- Near Distance: 5 vertical segments, 7 horizontal segments (35 vertices)
+- Far Distance: 1 vertical segment, 3 horizontal segments (3 vertices)
+- Middle Range: Linear interpolation between near and far
-### (TODO: Your 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.
diff --git a/img/Culling Type.png b/img/Culling Type.png
new file mode 100644
index 0000000..1fe3f52
Binary files /dev/null and b/img/Culling Type.png differ
diff --git a/img/Lod.gif b/img/Lod.gif
new file mode 100644
index 0000000..78ec903
Binary files /dev/null and b/img/Lod.gif differ
diff --git a/img/NUM_BLADES base Run FPS.png b/img/NUM_BLADES base Run FPS.png
new file mode 100644
index 0000000..bd864fb
Binary files /dev/null and b/img/NUM_BLADES base Run FPS.png differ
diff --git a/img/culling.gif b/img/culling.gif
new file mode 100644
index 0000000..796a5c9
Binary files /dev/null and b/img/culling.gif differ
diff --git a/img/headImg.gif b/img/headImg.gif
new file mode 100644
index 0000000..695d7bb
Binary files /dev/null and b/img/headImg.gif differ
diff --git a/img/noise.gif b/img/noise.gif
new file mode 100644
index 0000000..85edcb7
Binary files /dev/null and b/img/noise.gif differ
diff --git a/img/view.gif b/img/view.gif
new file mode 100644
index 0000000..591dcbf
Binary files /dev/null and b/img/view.gif differ
diff --git a/src/Blades.h b/src/Blades.h
index 9bd1eed..8978356 100644
--- a/src/Blades.h
+++ b/src/Blades.h
@@ -4,11 +4,12 @@
#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;
-constexpr static float MAX_WIDTH = 0.14f;
+constexpr static float MIN_WIDTH = 0.08f;
+constexpr static float MAX_WIDTH = 0.12f;
constexpr static float MIN_BEND = 7.0f;
constexpr static float MAX_BEND = 13.0f;
diff --git a/src/Renderer.cpp b/src/Renderer.cpp
index b445d04..9f0a9ac 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -197,7 +197,40 @@ 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
+ //stored each binding
+ VkDescriptorSetLayoutBinding inputBladesBinding = {};
+ inputBladesBinding.binding = 0;
+ inputBladesBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ inputBladesBinding.descriptorCount = 1;
+ inputBladesBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ inputBladesBinding.pImmutableSamplers = nullptr;
+
+ //buffer for cull blade
+ VkDescriptorSetLayoutBinding culledBladesBinding = {};
+ culledBladesBinding.binding = 1;
+ culledBladesBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ culledBladesBinding.descriptorCount = 1;
+ culledBladesBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ culledBladesBinding.pImmutableSamplers = nullptr;
+
+ //buffer for num of blades
+ VkDescriptorSetLayoutBinding numBladesBinding = {};
+ numBladesBinding.binding = 2;
+
+ numBladesBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ numBladesBinding.descriptorCount = 1;
+ numBladesBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ numBladesBinding.pImmutableSamplers = nullptr;
+ std::vector bindings = { inputBladesBinding, culledBladesBinding, numBladesBinding };
+
+ 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,13 +249,15 @@ void Renderer::CreateDescriptorPool() {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 },
// TODO: Add any additional types and counts of descriptors you will need to allocate
+ //buffer for compute shader
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(3 * scene->GetBlades().size()) }
};
VkDescriptorPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = static_cast(poolSizes.size());
poolInfo.pPoolSizes = poolSizes.data();
- poolInfo.maxSets = 5;
+ poolInfo.maxSets = 5 + static_cast(scene->GetBlades().size()) * 2;
if (vkCreateDescriptorPool(logicalDevice, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
throw std::runtime_error("Failed to create descriptor pool");
@@ -320,6 +355,36 @@ 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 sets");
+ }
+
+ for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) {
+ VkDescriptorBufferInfo modelBufferInfo = {};
+ modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer();
+ modelBufferInfo.offset = 0;
+ modelBufferInfo.range = sizeof(ModelBufferObject);
+
+ VkWriteDescriptorSet descriptorWrite = {};
+ descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrite.dstSet = grassDescriptorSets[i];
+ descriptorWrite.dstBinding = 0;
+ descriptorWrite.dstArrayElement = 0;
+ descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ descriptorWrite.descriptorCount = 1;
+ descriptorWrite.pBufferInfo = &modelBufferInfo;
+
+ vkUpdateDescriptorSets(logicalDevice, 1, &descriptorWrite, 0, nullptr);
+ }
}
void Renderer::CreateTimeDescriptorSet() {
@@ -360,6 +425,63 @@ 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 compute descriptor sets");
+ }
+
+ for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) {
+ VkDescriptorBufferInfo inputBladesInfo = {};
+ inputBladesInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer();
+ inputBladesInfo.offset = 0;
+ inputBladesInfo.range = VK_WHOLE_SIZE;
+
+ VkDescriptorBufferInfo culledBladesInfo = {};
+ culledBladesInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer();
+ culledBladesInfo.offset = 0;
+ culledBladesInfo.range = VK_WHOLE_SIZE;
+
+ VkDescriptorBufferInfo numBladesInfo = {};
+ numBladesInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer();
+ numBladesInfo.offset = 0;
+ numBladesInfo.range = VK_WHOLE_SIZE;
+
+ std::array descriptorWrites = {};
+
+ descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[0].dstSet = computeDescriptorSets[i];
+ descriptorWrites[0].dstBinding = 0;
+ descriptorWrites[0].dstArrayElement = 0;
+ descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[0].descriptorCount = 1;
+ descriptorWrites[0].pBufferInfo = &inputBladesInfo;
+
+ descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[1].dstSet = computeDescriptorSets[i];
+ descriptorWrites[1].dstBinding = 1;
+ descriptorWrites[1].dstArrayElement = 0;
+ descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[1].descriptorCount = 1;
+ descriptorWrites[1].pBufferInfo = &culledBladesInfo;
+
+ descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[2].dstSet = computeDescriptorSets[i];
+ descriptorWrites[2].dstBinding = 2;
+ descriptorWrites[2].dstArrayElement = 0;
+ descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ descriptorWrites[2].descriptorCount = 1;
+ descriptorWrites[2].pBufferInfo = &numBladesInfo;
+
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
+ }
}
void Renderer::CreateGraphicsPipeline() {
@@ -716,8 +838,7 @@ void Renderer::CreateComputePipeline() {
computeShaderStageInfo.module = computeShaderModule;
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 +1005,13 @@ 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 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);
+
+ uint32_t numWorkGroups = (NUM_BLADES + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
+ vkCmdDispatch(computeCommandBuffer, numWorkGroups, 1, 1);
+ }
// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
@@ -975,14 +1103,14 @@ void Renderer::RecordCommandBuffers() {
for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) {
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
+ // 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
@@ -1042,6 +1170,7 @@ Renderer::~Renderer() {
vkDeviceWaitIdle(logicalDevice);
// TODO: destroy any resources you created
+ vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr);
vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data());
vkFreeCommandBuffers(logicalDevice, computeCommandPool, 1, &computeCommandBuffer);
diff --git a/src/Renderer.h b/src/Renderer.h
index 95e025f..d3a8756 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;
+ std::vector grassDescriptorSets;
VkDescriptorSet timeDescriptorSet;
+ std::vector computeDescriptorSets;
VkPipelineLayout graphicsPipelineLayout;
VkPipelineLayout grassPipelineLayout;
diff --git a/src/main.cpp b/src/main.cpp
index 8bf822b..8443337 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(1080, 768, 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 / 768.f);
VkCommandPoolCreateInfo transferPoolInfo = {};
transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..49f2693 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -25,32 +25,167 @@ struct Blade {
// 1. Store the input blades
// 2. Write out the culled blades
// 3. Write the total number of blades remaining
+//blades
+layout(set = 2, binding = 0) buffer InputBlades {
+ Blade blades[];
+} inputBlades;
-// 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;
+//culled blades
+layout(set = 2, binding = 1) buffer CulledBlades {
+ Blade blades[];
+} culledBlades;
+
+//number of blades
+layout(set = 2, binding = 2) buffer NumBlades {
+ uint vertexCount;
+ uint instanceCount;
+ uint firstVertex;
+ uint firstInstance;
+} numBlades;
bool inBounds(float value, float bounds) {
return (value >= -bounds) && (value <= bounds);
}
+//simple hash-based noise for wind variation
+float hash(vec2 p) {
+ return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
+}
+
+float noise(vec2 p) {
+ vec2 i = floor(p);
+ vec2 f = fract(p);
+ f = f * f * (3.0 - 2.0 * f);
+
+ float a = hash(i);
+ float b = hash(i + vec2(1.0, 0.0));
+ float c = hash(i + vec2(0.0, 1.0));
+ float d = hash(i + vec2(1.0, 1.0));
+
+ return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
+}
+
void main() {
- // Reset the number of blades to 0
- if (gl_GlobalInvocationID.x == 0) {
- // numBlades.vertexCount = 0;
+ uint index = gl_GlobalInvocationID.x;
+ //reset the number
+ if (index == 0) {
+ numBlades.vertexCount = 0;
}
- barrier(); // Wait till all threads reach this point
+ barrier();
// TODO: Apply forces on every blade and update the vertices in the buffer
+ if (index >= inputBlades.blades.length()) {
+ return;
+ }
+
+ Blade blade = inputBlades.blades[index];
+ //root
+ vec3 v0 = blade.v0.xyz;
+ //mid
+ vec3 v1 = blade.v1.xyz;
+ //for top
+ vec3 v2 = blade.v2.xyz;
+ //up
+ vec3 up = blade.up.xyz;
+
+ float orientation = blade.v0.w;
+ float height = blade.v1.w;
+ float width = blade.v2.w;
+ float stiffness = blade.up.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
+ //gravity section
+ vec4 D = vec4(0.0, -1.0, 0.0, 9.8);
+ vec3 gE = normalize(D.xyz) * D.w;
+ //force
+ vec3 f = normalize(cross(up, vec3(sin(orientation), 0.0, cos(orientation))));
+ vec3 gF = 0.25 * length(gE) * f;
+ vec3 gravity = gE + gF;
+ vec3 iv2 = v0 + up * height;
+ vec3 recovery = (iv2 - v2) * stiffness;
+
+ //wind speed
+ float windSpeed = 0.85;
+
+ float spatialNoise = noise(v0.xz * 0.1);
+ float timeOffset = spatialNoise * 6.28318;
+
+ float windTime = totalTime * windSpeed + timeOffset;
+ vec3 windDir = vec3(sin(windTime), 0.0, cos(windTime));
+
+ float turbulence = noise(v0.xz * 0.3 + totalTime * 0.22) * 0.5 +
+ noise(v0.xz * 0.6 + totalTime * 0.44) * 0.25;
+ float windStrength = 3.0 * (0.8 + turbulence * 0.4);
+
+ //direction blade to wind
+ float fd = 1.0 - abs(dot(normalize(windDir), normalize(v2 - v0)));
+ float currentHeight = dot(v2 - v0, up);
+ float fr = clamp(currentHeight / height, 0.0, 1.0);
+
+ float windAlign = fd * fr * fr;
+ vec3 wind = windDir * windAlign * windStrength;
+ //combinded all
+ v2 += (gravity + recovery + wind) * deltaTime;
+
+ v2 = v2 - up * min(dot(up, v2 - v0), 0.0);
+
+ float lproj = length(v2 - v0 - up * dot(v2 - v0, up));
+ float v1Height = height * max(1.0 - lproj / height, 0.05 * max(lproj / height, 1.0));
+ v1 = v0 + v1Height * up;
+
+ float L0 = distance(v2, v0);
+ float L1 = distance(v2, v1) + distance(v1, v0);
+ float L = (2.0 * L0 + (L1 - L0)) / 3.0;
+ float r = height / L;
+
+ vec3 v1_corrected = v0 + r * (v1 - v0);
+ vec3 v2_corrected = v1_corrected + r * (v2 - v1);
+
+ //update blade
+ blade.v1.xyz = v1_corrected;
+ blade.v2.xyz = v2_corrected;
+ inputBlades.blades[index] = blade;
+
+ //culling test
+ bool visible = true;
+ //culling
+ vec3 viewDir = normalize(v0 - (inverse(camera.view) * vec4(0, 0, 0, 1)).xyz);
+ if (abs(dot(f, viewDir)) > 0.9) {
+ visible = false;
+ }
+ // View-frustum culling
+ vec3 m = 0.25 * v0 + 0.5 * v1_corrected + 0.25 * v2_corrected;
+ vec4 v0_clip = camera.proj * camera.view * vec4(v0, 1.0);
+ vec4 v2_clip = camera.proj * camera.view * vec4(v2_corrected, 1.0);
+ vec4 m_clip = camera.proj * camera.view * vec4(m, 1.0);
+
+ //culling
+ //1.0 = default
+ //0.5 = aggressive
+ //0.0 = cull at exact screen edge
+ float tolerance = 0.9;
+ if (!inBounds(v0_clip.x / v0_clip.w, tolerance) || !inBounds(v0_clip.y / v0_clip.w, tolerance) || v0_clip.z / v0_clip.w < 0.0 || v0_clip.z / v0_clip.w > 1.0) {
+ if (!inBounds(v2_clip.x / v2_clip.w, tolerance) || !inBounds(v2_clip.y / v2_clip.w, tolerance) || v2_clip.z / v2_clip.w < 0.0 || v2_clip.z / v2_clip.w > 1.0) {
+ if (!inBounds(m_clip.x / m_clip.w, tolerance) || !inBounds(m_clip.y / m_clip.w, tolerance) || m_clip.z / m_clip.w < 0.0 || m_clip.z / m_clip.w > 1.0) {
+ visible = false;
+ }
+ }
+ }
+
+ //distance culling
+ float dist = length(v0 - (inverse(camera.view) * vec4(0, 0, 0, 1)).xyz);
+ //set distance
+ float maxDist = 30.0;
+ if (dist > maxDist) {
+ visible = false;
+ } else {
+ int bucket = int((dist / maxDist) * 10.0);
+ if (bucket > 0 && (index % 10) < uint(bucket)) {
+ visible = false;
+ }
+ }
+
+ if (visible) {
+ uint idx = atomicAdd(numBlades.vertexCount, 1);
+ culledBlades.blades[idx] = blade;
+ }
}
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..55fab1a 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -7,11 +7,20 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} camera;
// TODO: Declare fragment shader inputs
+layout(location = 0) in vec3 in_normal;
+layout(location = 1) in float in_height;
layout(location = 0) out vec4 outColor;
void main() {
// TODO: Compute fragment color
-
- outColor = vec4(1.0);
+ //directional lighting
+ vec3 lightDir = normalize(vec3(0.5, 1.0, 0.4));
+ float diffuse = max(dot(normalize(in_normal), lightDir), 0.0) * 0.5 + 0.5;
+
+ //grass clo
+ vec3 baseColor = vec3(0.1, 0.4, 0.1);
+ vec3 tipColor = vec3(0.3, 0.8, 0.2);
+ vec3 grassColor = mix(baseColor, tipColor, in_height);
+ outColor = vec4(grassColor * diffuse, 1.0);
}
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..f597380 100644
--- a/src/shaders/grass.tesc
+++ b/src/shaders/grass.tesc
@@ -7,20 +7,49 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
mat4 view;
mat4 proj;
} camera;
-
// TODO: Declare tessellation control shader inputs and outputs
+//control points from vertex shader
+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[];
+
+//control points to tessellation evaluation shader
+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
+ //control points through
+ 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];
+
+ //distance tessellation
+ vec3 cameraPos = (inverse(camera.view) * vec4(0, 0, 0, 1)).xyz;
+ vec3 bladePos = in_v0[gl_InvocationID].xyz;
+ float dist = length(bladePos - cameraPos);
+
+ //LOD
+ float nearDist = 5.0;
+ float farDist = 20.0;
+ float lodFactor = clamp(1.0 - (dist - nearDist) / (farDist - nearDist), 0.0, 1.0);
+
+ float verticalSegments = mix(1.0, 5.0, lodFactor);
+ float horizontalSegments = mix(1.0, 7.0, lodFactor);
+
+ gl_TessLevelInner[0] = verticalSegments;
+ gl_TessLevelInner[1] = horizontalSegments;
+
+ gl_TessLevelOuter[0] = horizontalSegments;
+ gl_TessLevelOuter[1] = verticalSegments;
+ gl_TessLevelOuter[2] = horizontalSegments;
+ gl_TessLevelOuter[3] = verticalSegments;
- // TODO: Set level of tesselation
- // gl_TessLevelInner[0] = ???
- // gl_TessLevelInner[1] = ???
- // gl_TessLevelOuter[0] = ???
- // gl_TessLevelOuter[1] = ???
- // gl_TessLevelOuter[2] = ???
- // gl_TessLevelOuter[3] = ???
}
diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese
index 751fff6..370b676 100644
--- a/src/shaders/grass.tese
+++ b/src/shaders/grass.tese
@@ -9,10 +9,61 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} camera;
// TODO: Declare tessellation evaluation shader inputs and outputs
+// Input: Bezier control points from tessellation control shader
+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[];
+
+// Output: Pass data to fragment shader
+layout(location = 0) out vec3 out_normal;
+layout(location = 1) out float out_height;
+
+out gl_PerVertex {
+ vec4 gl_Position;
+};
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;
+
+ float orientation = in_v0[0].w;
+ float height = in_v1[0].w;
+ float width = in_v2[0].w;
+
+ //compute Bezier curve position along height
+ //(1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2
+ float t = v;
+ vec3 bezierPos = (1.0 - t) * (1.0 - t) * v0 +
+ 2.0 * (1.0 - t) * t * v1 +
+ t * t * v2;
+
+ vec3 tangent = 2.0 * (1.0 - t) * (v1 - v0) + 2.0 * t * (v2 - v1);
+ if (length(tangent) > 0.0001) {
+ tangent = normalize(tangent);
+ } else {
+ tangent = normalize(v2 - v0);
+ }
+
+ float cosTheta = cos(orientation);
+ float sinTheta = sin(orientation);
+ vec3 facing = normalize(cross(up, vec3(sinTheta, 0.0, cosTheta)));
+
+ //triangular blade
+
+ float widthCoeff = 1.0 - v;
+ float currentWidth = width * widthCoeff;
+
+ //width offset
+ vec3 worldPos = bezierPos + (u - 0.5) * currentWidth * facing;
+ gl_Position = camera.proj * camera.view * vec4(worldPos, 1.0);
+ //normal and height
+ out_normal = normalize(cross(tangent, facing));
+ out_height = v;
}
diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert
index db9dfe9..cd6723e 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -7,6 +7,21 @@ layout(set = 1, binding = 0) uniform ModelBufferObject {
};
// TODO: Declare vertex shader inputs and outputs
+// Bezier control points from vertex buffer
+// for orientaion root positon
+layout(location = 0) in vec4 v0;
+// for height
+layout(location = 1) in vec4 v1;
+// for width
+layout(location = 2) in vec4 v2;
+// for stiffness paramenter
+layout(location = 3) in vec4 up;
+
+// Output
+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 +29,12 @@ out gl_PerVertex {
void main() {
// TODO: Write gl_Position and any other shader outputs
+ // Pass through v0 position as gl_Position
+ gl_Position = vec4(v0.xyz, 1.0);
+
+ // Pass Bezier control points to tessellation stage
+ out_v0 = v0;
+ out_v1 = v1;
+ out_v2 = v2;
+ out_up = up;
}