diff --git a/README.md b/README.md index 20ee451..c4577f8 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,36 @@ 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) +* Shixuan Fang +* Tested on: Windows 11, i7-12700kf, RTX3080 Ti (Personal) -### (TODO: Your README) +## Project Overview -*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. +

+ +

+ +In this project, I implemented a physically-based grass renderer and simulator using Vulkan. This project is based on this paper, [Responsive Real-Time Grass Rendering for General 3D Scenes](https://publik.tuwien.ac.at/files/publik_261737.pdf). In this paper, each blade of grass is simulated using three control points ```v0```, ```v1```, ```v2``` and also other useful informations for tessellation. + + + +Also, blades are influenced by three forces, ```Gravity```, ```Recovery```, and ```Wind```. + +| Gravity | Gravity+Recovery | Gravity+Recovery+wind| +| ------------- |-------------: | :----------:| +| | | | + + +In order to reduce the number of blades that needed to render, I also implemented three culling methods, which are ```Orientation Culling```, ```View-Frustum Culling```, and ```Distance Culling```. + +| Orientation Culling | View-Frustum Culling | Distance Culling| +| ------------- |-------------: | :----------:| +| | | | + +Orientation culling removes blades that have front face direction perpendicular to the view vector; View-frustum culling removes blades that out of the view-frustum using ```v0, v2, and m``` where ```m = (1/4)v0 * (1/2)v1 * (1/4)v2```; and Distance culling simply removes blades that are too far from the camera. + +## Performace + + + +It's actually hard to compare these three culling methods since they remove blades of grass considering different values, and they all depend on camera position. When camera is near to the plane, then view-frustum and orientation cullings take most part, while when camera is far from the plane, distance culling removes lost of blades. Data in this chart comes from same camera position with ```blades number == 2^17```. In general, having all three culling methods activated can significantly improve performance. diff --git a/img/chart.jpg b/img/chart.jpg new file mode 100644 index 0000000..d34e745 Binary files /dev/null and b/img/chart.jpg differ diff --git a/img/dist_culling.gif b/img/dist_culling.gif new file mode 100644 index 0000000..8e8c145 Binary files /dev/null and b/img/dist_culling.gif differ diff --git a/img/gra+rec+wind.gif b/img/gra+rec+wind.gif new file mode 100644 index 0000000..3710507 Binary files /dev/null and b/img/gra+rec+wind.gif differ diff --git a/img/gra+reco.gif b/img/gra+reco.gif new file mode 100644 index 0000000..fa183ae Binary files /dev/null and b/img/gra+reco.gif differ diff --git a/img/gra.gif b/img/gra.gif new file mode 100644 index 0000000..fbdd38e Binary files /dev/null and b/img/gra.gif differ diff --git a/img/open.gif b/img/open.gif new file mode 100644 index 0000000..6ccf2eb Binary files /dev/null and b/img/open.gif differ diff --git a/img/orien.gif b/img/orien.gif new file mode 100644 index 0000000..5b98b1f Binary files /dev/null and b/img/orien.gif differ diff --git a/img/test.jpg b/img/test.jpg new file mode 100644 index 0000000..081053b Binary files /dev/null and b/img/test.jpg differ diff --git a/img/view.gif b/img/view.gif new file mode 100644 index 0000000..309b573 Binary files /dev/null and b/img/view.gif differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..9a8a3a8 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -45,7 +45,7 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode indirectDraw.firstInstance = 0; BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory); - BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory); + BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory); BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory); } diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..fc23ba7 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 << 17; 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..973f4ef 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,43 @@ void Renderer::CreateComputeDescriptorSetLayout() { // TODO: Create the descriptor set layout for the compute pipeline // Remember this is like a class definition stating why types of information // will be stored at each binding + + VkDescriptorSetLayoutBinding inputbladeLayoutBinding = {}; + inputbladeLayoutBinding.binding = 0; + inputbladeLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + inputbladeLayoutBinding.descriptorCount = 1; + inputbladeLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + inputbladeLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding culledbladeLayoutBinding = {}; + culledbladeLayoutBinding.binding = 1; + culledbladeLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledbladeLayoutBinding.descriptorCount = 1; + culledbladeLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + culledbladeLayoutBinding.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 = { + inputbladeLayoutBinding, + culledbladeLayoutBinding, + numBladesLayoutBinding + }; + + // Create the descriptor set layout + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout in CreateComputeDescriptorSetLayout"); + } } void Renderer::CreateDescriptorPool() { @@ -216,6 +253,8 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + // compute buffer, each blade 3 + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(3 * scene->GetBlades().size()) } }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,8 +359,44 @@ 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()); + + // Describe the desciptor set + VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(grassDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set in CreateGrassDescriptorSets"); + } + + 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; + } + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), + descriptorWrites.data(), 0, nullptr); } + void Renderer::CreateTimeDescriptorSet() { // Describe the desciptor set VkDescriptorSetLayout layouts[] = { timeDescriptorSetLayout }; @@ -360,6 +435,73 @@ 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()); + + // Describe the descriptor set + VkDescriptorSetLayout layouts[] = { computeDescriptorSetLayout }; + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(computeDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + + // Allocate descriptor sets + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set in CreateComputeDescriptorSets"); + } + + 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; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +859,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 = {}; @@ -728,7 +870,7 @@ void Renderer::CreateComputePipeline() { pipelineLayoutInfo.pPushConstantRanges = 0; if (vkCreatePipelineLayout(logicalDevice, &pipelineLayoutInfo, nullptr, &computePipelineLayout) != VK_SUCCESS) { - throw std::runtime_error("Failed to create pipeline layout"); + throw std::runtime_error("Failed to create pipeline layout in CreateComputePipeline"); } // Create compute pipeline @@ -884,6 +1026,12 @@ void Renderer::RecordComputeCommandBuffer() { vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 1, 1, &timeDescriptorSet, 0, nullptr); // TODO: For each group of blades bind its descriptor set and dispatch + //use vkCmdDispatch + 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 +1124,13 @@ 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 @@ -1058,6 +1206,8 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); + vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); vkDestroyRenderPass(logicalDevice, renderPass, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..36caa9b 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..b853cf5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "Camera.h" #include "Scene.h" #include "Image.h" +#include Device* device; SwapChain* swapChain; @@ -67,7 +68,7 @@ namespace { int main() { static constexpr char* applicationName = "Vulkan Grass Rendering"; - InitializeWindow(640, 480, applicationName); + InitializeWindow(1280, 960, applicationName); unsigned int glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -143,7 +144,20 @@ int main() { glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback); glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback); + + double previousTime = glfwGetTime(); + int frameCount = 0; + while (!ShouldQuit()) { + double currentTime = glfwGetTime(); + frameCount++; + if (currentTime - previousTime >= 1.0) + { + std::cout << frameCount << std::endl; + frameCount = 0; + previousTime = currentTime; + } + glfwPollEvents(); scene->UpdateTime(); renderer->Frame(); @@ -161,6 +175,9 @@ int main() { delete renderer; delete swapChain; delete device; + + //based on Ed + vkDestroySurfaceKHR(instance->GetVkInstance(), surface, nullptr); delete instance; DestroyWindow(); return 0; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..bc02c10 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -23,34 +23,151 @@ struct Blade { // TODO: Add bindings to: // 1. Store the input blades +layout(set = 2, binding = 0) buffer InputBlades { + Blade in_blades[]; +} ; // 2. Write out the culled blades +layout(set = 2, binding = 1) buffer CulledBlades { + Blade culled_blades[]; +} ; // 3. Write the total number of blades remaining // 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; +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); } +//some helper functions +bool pointInFrustum(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); +} + +//some magic numbers... +#define Dmax 30 +#define Low_Level 10 +#define GRAVITY vec4(0, -1, 0, 5) +#define windFunc (vec3(-3.0, -5.0, 4.0) * sin(totalTime)) + +//performance check +#define GRAV 1 +#define RECO 1 +#define WIND 1 + +#define ORIE_CULL 1 +#define VIEW_CULL 1 +#define DIST_CULL 1 + 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 b = in_blades[id]; + vec3 v0 = b.v0.xyz; + vec3 v1 = b.v1.xyz; + vec3 v2 = b.v2.xyz; + vec3 up = b.up.xyz; + + float direction = b.v0.w; + float height = b.v1.w; + float width = b.v2.w; + float stiffness = b.up.w; + vec3 orient = vec3(cos(direction), 0, sin(direction)); + + //Gravity + #if GRAV + vec3 gE = normalize(GRAVITY.xyz) * GRAVITY.w; + vec3 gF = 0.25f * length(gE) * normalize(cross(orient, up)); + vec3 gravity = gE + gF; + #else + vec3 gravity = vec3(0,0,0); + #endif + //Recovery + #if RECO + vec3 iv2 = v0 + up * height; + vec3 recovery = (iv2 - v2) * stiffness; + #else + vec3 recovery = vec3(0,0,0); + #endif + + //Wind + #if WIND + float Fd = 1 - abs(dot(normalize(windFunc), normalize(v2 - v0))); + float Fr = dot((v2 - v0), up) / height; + vec3 wind = windFunc * Fd * Fr; + #else + vec3 wind = vec3(0,0,0); + #endif + + vec3 totalForce = gravity + recovery + wind; + v2 = v2 + totalForce * deltaTime; + + //state validation + v2 = 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); + + + in_blades[id].v1 = vec4(v1, b.v1.w); + in_blades[id].v2 = vec4(v2, b.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 eye_pos = vec3(camera.view[0][3], camera.view[1][3],camera.view[2][3]); + vec3 eye_pos = inverse(camera.view)[3].xyz; + //Orientation culling + #if ORIE_CULL + vec3 Dirc = eye_pos - v0; + vec3 Dirb = orient; + if (abs(dot(normalize(Dirc), normalize(Dirb))) >= 0.9) + return; + #endif + + //View-frustum culling + + #if VIEW_CULL + vec3 m = (1/4) * v0 * (1/2) * v1 * (1/4) * v2; + //If the test results in false for all three points, the blade is culled. + if(!(pointInFrustum(v0) && pointInFrustum(v2) && pointInFrustum(m))) + return; + + #endif + + //Distance culling + #if DIST_CULL + float Dproj = length(v0 - eye_pos - up * dot((v0 - eye_pos), up)); + uint n = Low_Level; + if (mod(id, n) >= (n * (1 - Dproj / Dmax))) + return; + #endif + + culled_blades[atomicAdd(numBlades.vertexCount, 1)] = in_blades[id]; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..e0cb234 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,19 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 ps_position; +layout(location = 1) in vec3 ps_normal; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - - outColor = vec4(1.0); + vec3 baseColor = vec3(0.1, 0.8, 0.1); + vec3 ambient = vec3(0.1, 0.3, 0.1); + vec4 light_pos = vec4(10.0, 80.0, 10.0, 1.0); + vec4 normal = vec4(normalize(ps_normal), 0.0); + vec4 L = normalize(light_pos - vec4(ps_position, 1.0f)); + float diffuse = max(dot(L, normal), 0.0); + vec3 final = ambient + diffuse * baseColor; + outColor = vec4(final, 1.0); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..e635ad8 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -9,18 +9,31 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4 v0_in[]; +layout(location = 1) in vec4 v1_in[]; +layout(location = 2) in vec4 v2_in[]; +layout(location = 3) in vec4 up_in[]; + +layout(location = 0) out vec4 v0_out[]; +layout(location = 1) out vec4 v1_out[]; +layout(location = 2) out vec4 v2_out[]; +layout(location = 3) out vec4 up_out[]; 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 + v0_out[gl_InvocationID] = v0_in[gl_InvocationID]; + v1_out[gl_InvocationID] = v1_in[gl_InvocationID]; + v2_out[gl_InvocationID] = v2_in[gl_InvocationID]; + up_out[gl_InvocationID] = up_in[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] = ??? + gl_TessLevelInner[0] = 10.0; + gl_TessLevelInner[1] = 10.0; + gl_TessLevelOuter[0] = 10.0; + gl_TessLevelOuter[1] = 10.0; + gl_TessLevelOuter[2] = 10.0; + gl_TessLevelOuter[3] = 10.0; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..8b84b88 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,42 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation evaluation shader inputs and outputs +layout(location = 0) in vec4 v0_in[]; +layout(location = 1) in vec4 v1_in[]; +layout(location = 2) in vec4 v2_in[]; +layout(location = 3) in vec4 up_in[]; + + +layout(location = 0) out vec3 ps_position; +layout(location = 1) out vec3 ps_normal; 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 = v0_in[0].xyz; + vec3 v1 = v1_in[0].xyz; + vec3 v2 = v2_in[0].xyz; + vec3 up = up_in[0].xyz; + + float direction = v0_in[0].w; + vec3 t1 = normalize(vec3(cos(direction), 0.0f, sin(direction))); + float width = v2_in[0].w; + + vec3 a = v0 + v * (v1 - v0); + vec3 b = v1 + v * (v2 - v1); + vec3 c = a + v * (b - a); + vec3 c0 = c - width * t1; + vec3 c1 = c + width * t1; + vec3 t0 = normalize(b - a); + vec3 n = normalize(cross(t0, t1)); + + float t = u + 0.5 * v - u * v; + vec3 p = (1-t) * c0 + t * c1; + + gl_Position = camera.proj * camera.view * vec4(p, 1.0f); + + ps_normal = n; + ps_position = p; } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..67801eb 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -8,10 +8,25 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { // TODO: Declare vertex shader inputs and outputs +layout(location = 0) in vec4 v0_in; +layout(location = 1) in vec4 v1_in; +layout(location = 2) in vec4 v2_in; +layout(location = 3) in vec4 up_in; + +layout(location = 0) out vec4 v0_out; +layout(location = 1) out vec4 v1_out; +layout(location = 2) out vec4 v2_out; +layout(location = 3) out vec4 up_out; + out gl_PerVertex { vec4 gl_Position; }; void main() { // TODO: Write gl_Position and any other shader outputs + gl_Position = model * vec4(v0_in.xyz, 1.0); + v0_out = model * v0_in; + v1_out = model * v1_in; + v2_out = model * v2_in; + up_out = model * up_in; }