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; }