diff --git a/README.md b/README.md index 47a8c52..69e7bcf 100644 --- a/README.md +++ b/README.md @@ -275,8 +275,29 @@ The following resources may be useful for this project. ## README * A brief description of the project and the specific features you implemented. + * In this project, I implemented the grass representation using tessellation based on next-gen graphics API Vulkan. The tessellation level is based on the distance from the camera to the grass blade. + * Besides the basic grass shading, I also let natural force applying on the grass following 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)". There are three natural forces I added here: gravity, recovery and wind force. + * Furthermore, in order to optimize the performance of grass rendering. I added three culling methods on the scene: orientation culling, view-frustum culling and distance culling. + * orientation culling is based on angle between the direction of the width of the blade and the camera orientation. If they align too much, the blade might not be able to fill the whole pixel, causing rendering problems + * view-frustum culling is based on the idea that if we can't see it, we don't need to draw it. Hence, we check whether the v0(origin of blade), v2(blade tip) and m(mass center of blade) are inside the view-frustum. If not, we cull it out. + * distance culling is based on the idea that if the distance is too far away, we can't see that many blades in the same area very detailed far away from the camera. So cull some of the baldes to improve the performance. * GIFs of your project in its different stages with the different features being added incrementally. -* A performance analysis (described below). + * without any force and culling + * ![](img/grass_without_force.PNG) + * With gravity + * ![](img/grass_with_gravity.PNG) + * With wind force and recovery force + * ![](img/grass_with_wind.gif) + * With view-frustum culling and orientation culling (notice the boundary of window) + * ![](img/frustum_culling.gif) + * With distance culling + * ![](img/distance_cull.gif) +* A performance analysis. + * Time R.S.T then number of blades + * ![](img/blade_num_chart.png) + * culling efficiency + * ![](img/culling_method_efficiency.png) + * From the chart above we can easily see the performance improvement by applying the culling methods. Among them, the distance culling improve the performance most. The frustum culling has a better result when the camera is looking at the corner of the ground. ### Performance Analysis @@ -300,3 +321,16 @@ The template of the comment section of your pull request is attached below, you * Feature 1 * ... * Feedback on the project itself, if any. + + + +### Acknowledgements + +- [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) +- [Vulkan Tutorial](https://vulkan-tutorial.com/) +- [Project 4 Recitation Slides](https://docs.google.com/presentation/d/1WqIPLlli2l5A2QfgPOl1cqqkvSRPjiid9OYzSlkAWxM/edit#slide=id.g25e0d401a4_0_90) +- [Introduction to Vulkan](https://docs.google.com/presentation/d/1zTqdnUV5hGuff6mPutvc9ZOzHJAPbdm4OzkYXTIZTSE/edit#slide=id.p) +- [Tessellation Tutorial](http://in2gpu.com/2014/07/12/tessellation-tutorial-opengl-4-3/) +- Hannah and Josh for helped me build up the compute pipeline +- Yan Dong helped me a lot when I implemented the grass fragment shader as well as debug the compute pipeline and compute shader +- Jie Meng helped me figure out the dynamic tessellation level based on distance to camera \ No newline at end of file diff --git a/img/blade_num_chart.png b/img/blade_num_chart.png new file mode 100644 index 0000000..df79b1a Binary files /dev/null and b/img/blade_num_chart.png differ diff --git a/img/culling_method_efficiency.png b/img/culling_method_efficiency.png new file mode 100644 index 0000000..66bad32 Binary files /dev/null and b/img/culling_method_efficiency.png differ diff --git a/img/debug_dist.gif b/img/debug_dist.gif new file mode 100644 index 0000000..7d6cf5a Binary files /dev/null and b/img/debug_dist.gif differ diff --git a/img/debug_dist_2.gif b/img/debug_dist_2.gif new file mode 100644 index 0000000..548b8ce Binary files /dev/null and b/img/debug_dist_2.gif differ diff --git a/img/distance_cull.gif b/img/distance_cull.gif new file mode 100644 index 0000000..cf0feae Binary files /dev/null and b/img/distance_cull.gif differ diff --git a/img/frustum_culling.gif b/img/frustum_culling.gif new file mode 100644 index 0000000..9b55917 Binary files /dev/null and b/img/frustum_culling.gif differ diff --git a/img/grass_with_gravity.PNG b/img/grass_with_gravity.PNG new file mode 100644 index 0000000..bb66eec Binary files /dev/null and b/img/grass_with_gravity.PNG differ diff --git a/img/grass_with_wind.gif b/img/grass_with_wind.gif new file mode 100644 index 0000000..7f68292 Binary files /dev/null and b/img/grass_with_wind.gif differ diff --git a/img/grass_without_force.PNG b/img/grass_without_force.PNG new file mode 100644 index 0000000..3f48800 Binary files /dev/null and b/img/grass_without_force.PNG differ diff --git a/img/tes_error.PNG b/img/tes_error.PNG new file mode 100644 index 0000000..26a38e4 Binary files /dev/null and b/img/tes_error.PNG differ diff --git a/img/validation_layer_error_vertex_buffer_bind.PNG b/img/validation_layer_error_vertex_buffer_bind.PNG new file mode 100644 index 0000000..3e10ee0 Binary files /dev/null and b/img/validation_layer_error_vertex_buffer_bind.PNG differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..44b5535 100644 --- a/src/Blades.cpp +++ b/src/Blades.cpp @@ -28,6 +28,7 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode currentBlade.v1 = glm::vec4(bladePosition + bladeUp * height, height); // Physical model guide and width (v2) + // v2 is the same as v1 at the beginning float width = MIN_WIDTH + (generateRandomFloat() * (MAX_WIDTH - MIN_WIDTH)); currentBlade.v2 = glm::vec4(bladePosition + bladeUp * height, width); @@ -44,9 +45,10 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode indirectDraw.firstVertex = 0; indirectDraw.firstInstance = 0; - BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory); + //need to modify to eliminate the error shown in console + BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_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::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory); + BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory); } VkBuffer Blades::GetBladesBuffer() const { diff --git a/src/Blades.h b/src/Blades.h index 9bd1eed..a55e96d 100644 --- a/src/Blades.h +++ b/src/Blades.h @@ -4,13 +4,13 @@ #include #include "Model.h" -constexpr static unsigned int NUM_BLADES = 1 << 13; -constexpr static float MIN_HEIGHT = 1.3f; -constexpr static float MAX_HEIGHT = 2.5f; +constexpr static unsigned int NUM_BLADES = 1 << 20; +constexpr static float MIN_HEIGHT = 0.8f; +constexpr static float MAX_HEIGHT = 1.5f; constexpr static float MIN_WIDTH = 0.1f; -constexpr static float MAX_WIDTH = 0.14f; -constexpr static float MIN_BEND = 7.0f; -constexpr static float MAX_BEND = 13.0f; +constexpr static float MAX_WIDTH = 0.18f; +constexpr static float MIN_BEND = 15.0f; +constexpr static float MAX_BEND = 20.0f; struct Blade { // Position and direction diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..8188632 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -60,17 +60,17 @@ void Renderer::CreateRenderPass() { VkAttachmentDescription colorAttachment = {}; colorAttachment.format = swapChain->GetVkImageFormat(); colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; //what to do before rendering + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; //what to do after rendering colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //don't care, we will clear it when we load it + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // ready for presentation using the swap chain after rendering // Create a color attachment reference to be used with subpass VkAttachmentReference colorAttachmentRef = {}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachmentRef.attachment = 0; //index in attachment description array + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; //layout we want the attachment to be during a subpass -- this one is best performance // Depth buffer attachment VkFormat depthFormat = device->GetInstance()->GetSupportedFormat({ VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); @@ -110,8 +110,8 @@ void Renderer::CreateRenderPass() { // Create render pass VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.attachmentCount = static_cast(attachments.size()); //we have two attachementes here + renderPassInfo.pAttachments = attachments.data();//related to the head of array renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.dependencyCount = 1; @@ -128,7 +128,7 @@ void Renderer::CreateCameraDescriptorSetLayout() { uboLayoutBinding.binding = 0; uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; uboLayoutBinding.descriptorCount = 1; - uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL; //appear in all stages, compute and vertex uboLayoutBinding.pImmutableSamplers = nullptr; std::vector bindings = { uboLayoutBinding }; @@ -178,11 +178,12 @@ void Renderer::CreateTimeDescriptorSetLayout() { uboLayoutBinding.binding = 0; uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; uboLayoutBinding.descriptorCount = 1; - uboLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL; //appear in all stages, compute and vertex uboLayoutBinding.pImmutableSamplers = nullptr; std::vector bindings = { uboLayoutBinding }; + // Create the descriptor set layout VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; @@ -196,8 +197,41 @@ 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 + // Remember this is like a class definition stating what types of information // will be stored at each binding + VkDescriptorSetLayoutBinding inputBladesLayoutBinding = {}; + inputBladesLayoutBinding.binding = 0; + inputBladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + inputBladesLayoutBinding.descriptorCount = 1; + inputBladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + inputBladesLayoutBinding.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 = { inputBladesLayoutBinding, culledBladesLayoutBinding, 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"); + } } void Renderer::CreateDescriptorPool() { @@ -216,6 +250,9 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + //for blades, culled blade and number blade? + //Compute shader + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast( 3 * scene->GetBlades().size())}, }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -265,6 +302,7 @@ void Renderer::CreateCameraDescriptorSet() { } void Renderer::CreateModelDescriptorSets() { + //here the resize replace the {size, SetLayout} in tutorial -- determine how many descriptor sets we need modelDescriptorSets.resize(scene->GetModels().size()); // Describe the desciptor set @@ -318,8 +356,47 @@ void Renderer::CreateModelDescriptorSets() { } void Renderer::CreateGrassDescriptorSets() { - // TODO: Create Descriptor sets for the grass. + // TODO: Create Descriptor sets for the grass. -- mostly adopt by CreateModelDescriptorSets // 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 + // here, we also use the model layout for blades + 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"); + } + + //an array of descriptor modifiers + std::vector descriptorWrites(grassDescriptorSets.size()); + + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo bladeBufferInfo = {}; + bladeBufferInfo.buffer = scene->GetModels()[i]->GetModelBuffer(); //why we should use getmodels -- inherit from Model class and use ModelBufferObject in shader too + bladeBufferInfo.offset = 0; + bladeBufferInfo.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; + //only need one of three + descriptorWrites[i].pBufferInfo = &bladeBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -359,7 +436,80 @@ 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 + // 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 desciptor set + // here, we also use the model layout for blades + 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"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + + // Configure the descriptors to refer to buffers -- blades buffer + //helped by Josh and Hannah + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo inputBufferInfo = {}; + inputBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + inputBufferInfo.offset = 0; + inputBufferInfo.range = NUM_BLADES * sizeof(Blade); //DOUBT WHETHER NEED NUM_BLADES + + VkDescriptorBufferInfo culledBufferInfo = {}; + culledBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBufferInfo.offset = 0; + culledBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo indirectBufferInfo = {}; + indirectBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + indirectBufferInfo.offset = 0; + indirectBufferInfo.range = sizeof(BladeDrawIndirect); + + //write to input buffer + 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; + //only need one of three + descriptorWrites[3 * i + 0].pBufferInfo = &inputBufferInfo; + 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; + //only need one of three + descriptorWrites[3 * i + 1].pBufferInfo = &culledBufferInfo; + 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; + //only need one of three + descriptorWrites[3 * i + 2].pBufferInfo = &indirectBufferInfo; + 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() { @@ -456,7 +606,7 @@ void Renderer::CreateGraphicsPipeline() { depthStencil.maxDepthBounds = 1.0f; depthStencil.stencilTestEnable = VK_FALSE; - // Color blending (turned off here, but showing options for learning) + // Color blending (turned off here, but showing options for learning) -- for per attachment buffer // --> Configuration per attached framebuffer VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; @@ -468,7 +618,7 @@ void Renderer::CreateGraphicsPipeline() { colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; - // --> Global color blending settings + // --> Global color blending settings VkPipelineColorBlendStateCreateInfo colorBlending = {}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; @@ -497,7 +647,7 @@ void Renderer::CreateGraphicsPipeline() { // --- Create graphics pipeline --- VkGraphicsPipelineCreateInfo pipelineInfo = {}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineInfo.stageCount = 2; + pipelineInfo.stageCount = 2; //vertex shader and frag shader pipelineInfo.pStages = shaderStages; pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; @@ -510,7 +660,7 @@ void Renderer::CreateGraphicsPipeline() { pipelineInfo.layout = graphicsPipelineLayout; pipelineInfo.renderPass = renderPass; pipelineInfo.subpass = 0; - pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; //for derving from a existing pipeline pipelineInfo.basePipelineIndex = -1; if (vkCreateGraphicsPipelines(logicalDevice, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { @@ -561,6 +711,7 @@ void Renderer::CreateGrassPipeline() { VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + //give us information about what should be in grass.vert auto bindingDescription = Blade::getBindingDescription(); auto attributeDescriptions = Blade::getAttributeDescriptions(); @@ -716,8 +867,8 @@ 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 }; + // TODO: Add the compute descriptor set layout you create to this list + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout }; // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -801,8 +952,10 @@ void Renderer::CreateFrameResources() { // CREATE FRAMEBUFFERS + //same as tutorial, encapsulating by ourselves framebuffers.resize(swapChain->GetCount()); for (size_t i = 0; i < swapChain->GetCount(); i++) { + //we have both color attachment and depth stencil attachment std::vector attachments = { imageViews[i], depthImageView @@ -810,7 +963,7 @@ void Renderer::CreateFrameResources() { VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = renderPass; + framebufferInfo.renderPass = renderPass; //which render pass to be compatible with framebufferInfo.attachmentCount = static_cast(attachments.size()); framebufferInfo.pAttachments = attachments.data(); framebufferInfo.width = swapChain->GetVkExtent().width; @@ -884,6 +1037,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 + // helped by Yan + for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) { + + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[j], 0, nullptr); + vkCmdDispatch(computeCommandBuffer, NUM_BLADES / WORKGROUP_SIZE, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -891,6 +1050,7 @@ void Renderer::RecordComputeCommandBuffer() { } } +//same as createCommandBuffers, combine record and create void Renderer::RecordCommandBuffers() { commandBuffers.resize(swapChain->GetCount()); @@ -973,16 +1133,20 @@ void Renderer::RecordCommandBuffers() { vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipeline); for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) { + //before we culled blade in compute shader, we use this for testing + //VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetBladesBuffer() }; + //after we implement the compute shader 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 +1221,8 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + //clean up compute descriptor layout + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..1f6113b 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,12 +56,17 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; - + //compute descriptor layout + VkDescriptorSetLayout computeDescriptorSetLayout; + VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + //add two new descriptorSet for computing physical force and render grass + std::vector grassDescriptorSets; + std::vector computeDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; @@ -75,8 +80,8 @@ class Renderer { VkImage depthImage; VkDeviceMemory depthImageMemory; VkImageView depthImageView; - std::vector framebuffers; + std::vector framebuffers; //framebuffers for all the images in the swap chain and use the one retrieved std::vector commandBuffers; - VkCommandBuffer computeCommandBuffer; + VkCommandBuffer computeCommandBuffer; //only one compute command in this buffer? }; diff --git a/src/Scene.cpp b/src/Scene.cpp index 86894f2..7b5e885 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -31,6 +31,9 @@ void Scene::UpdateTime() { time.deltaTime = nextDeltaTime.count(); time.totalTime += time.deltaTime; + //print out the delta time + //std::cout << time.deltaTime << std::endl; + memcpy(mappedData, &time, sizeof(Time)); } diff --git a/src/Scene.h b/src/Scene.h index 7699d78..e838eff 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -2,6 +2,7 @@ #include #include +#include #include "Model.h" #include "Blades.h" diff --git a/src/SwapChain.cpp b/src/SwapChain.cpp index 711fec0..0a83396 100644 --- a/src/SwapChain.cpp +++ b/src/SwapChain.cpp @@ -196,6 +196,7 @@ void SwapChain::Recreate() { bool SwapChain::Acquire() { if (ENABLE_VALIDATION) { // the validation layer implementation expects the application to explicitly synchronize with the GPU + // without this synchronization, the queue will be filled up slowly and cause problem vkQueueWaitIdle(device->GetQueue(QueueFlags::Present)); } VkResult result = vkAcquireNextImageKHR(device->GetVkDevice(), vkSwapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); diff --git a/src/main.cpp b/src/main.cpp index 8bf822b..1bb7097 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -104,6 +104,7 @@ int main() { VkImage grassImage; VkDeviceMemory grassImageMemory; + //seems like import from a specific texture Image::FromFile(device, transferCommandPool, "images/grass.jpg", @@ -118,6 +119,7 @@ int main() { float planeDim = 15.f; float halfWidth = planeDim * 0.5f; + //define the ground by four vertices Model* plane = new Model(device, transferCommandPool, { { { -halfWidth, 0.0f, halfWidth }, { 1.0f, 0.0f, 0.0f },{ 1.0f, 0.0f } }, @@ -129,14 +131,17 @@ int main() { ); plane->SetTexture(grassImage); + //create all grass blades Blades* blades = new Blades(device, transferCommandPool, planeDim); vkDestroyCommandPool(device->GetVkDevice(), transferCommandPool, nullptr); + //store the ground and blades into the scene, which only contains the time buffer, what is that for? Scene* scene = new Scene(device); scene->AddModel(plane); scene->AddBlades(blades); + //start rendering renderer = new Renderer(device, swapChain, scene, camera); glfwSetWindowSizeCallback(GetGLFWWindow(), resizeCallback); @@ -149,6 +154,7 @@ int main() { renderer->Frame(); } + //without this synchronizing, the program will crash after we close it because the drawing is still happening vkDeviceWaitIdle(device->GetVkDevice()); vkDestroyImage(device->GetVkDevice(), grassImage, nullptr); diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..7a07114 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -21,6 +21,21 @@ struct Blade { vec4 up; }; +layout(set = 2, binding = 0) buffer Blades { + Blade[] input_blades; +} blades; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade[] output_blades; +} culledBlades; + +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; + // TODO: Add bindings to: // 1. Store the input blades // 2. Write out the culled blades @@ -28,6 +43,7 @@ struct Blade { // 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 +// The blade type is passed by the compute layer set // // layout(set = ???, binding = ???) buffer NumBlades { // uint vertexCount; // Write the number of blades remaining here @@ -43,14 +59,152 @@ bool inBounds(float value, float bounds) { 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 + //for testing + //grab out the data + Blade curr = blades.input_blades[gl_GlobalInvocationID.x]; + float angle = curr.v0.w; + float height = curr.v1.w; + float width = curr.v2.w; + float stiffness = curr.up.w; + + vec3 v0 = vec3(curr.v0.xyz); + vec3 v1 = vec3(curr.v1.xyz); + vec3 v2 = vec3(curr.v2.xyz); + vec3 up = vec3(curr.up.xyz); + + //compute the front and width direction + vec3 front_dir = normalize(vec3(cos(angle), 0.0, sin(angle))); + vec3 width_dir = normalize(cross(up,front_dir)); + //compute gravity + //assume the direction is toward (0, -1 , 0) globally + vec4 gravity = vec4(0,-1,0,1); + vec3 gE = normalize(gravity.xyz) * gravity.w; + vec3 gF = 0.25 * abs(gE) * front_dir; + vec3 g_total = gE + gF; + + //compute recovery + vec3 iv2 = height * up + v0; + vec3 recovery = (iv2 - v2) * stiffness; + + //compute wind + //a very naive wind force function based on v0 + //vec3 wi = vec3(cos(v0.x), 0, sin(v0.z)); + vec3 wi = vec3(cos(totalTime + v0.x * 0.2 + v0.z * 0.3) * 3, 0, 0); + //vec3 wi = vec3(2,0,2); + //directional alignment + float fd = 1 - dot(normalize(wi), normalize(v2 - v0)); + //height ratio + float fh = dot(normalize(up), normalize(v2 - v0)) / height; + vec3 wind = wi * fd * fh; + //apply to v2 + v2 += (g_total + recovery + wind) * deltaTime; + + + + //we need to validate the new pos of v2, update v1 and recalculate the length of bezier + + //v2 above group validation + v2 = v2 - up * min(dot(up,(v2-v0)), 0); + + //v1 recompute + float l_proj = length(v2 - v0 - up * dot((v2 - v0),up)); + v1 = v0 + height * up * max( ( 1 - l_proj / height), 0.05 * max(l_proj/height,1)); //not equal to v0 + + //ensure the length of bezier is correct + //not quite sure about the L + //L0 distance between v0 and v2 + float L0 = length(v2 - v0); + //L1 sum of v0 to v1 and v1 to v2; + float L1 = length(v1 - v0) + length(v2 - v1); + //assume we are 2 degree bezier + float L = (2 * L0 + (3 - 1) * L1) / (3 +1); + float r = height / L; + + //modify the v1 and v2 to the new appropriate position which have the same length as before + v1 = v0 + r * (v1 - v0); + v2 = v1 + r * (v2 - v1); + + //v1 and v2 has been updated -- ready to cull + + //direction cull -- question how to get the orientation of camera from view matrix? + //get the camera orientation by view matrix + vec3 camera_orientation = vec3(camera.view[0][2], camera.view[1][2], camera.view[2][2]); + float dir_cull_thred = 0.9; + if(abs(dot(camera_orientation,width_dir)) > dir_cull_thred) + { + return; + } + + + //view-frustum culling -- q: in curr.v0 v1 the last value is a real value, not 1, do we need to reconstruct to (v0.xyz,1) form? -- I think we need + //compute m + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; + //reconstruct v0, m and v2 + vec4 v0_culling = vec4(v0,1.0); + vec4 m_culling = vec4(m,1.0); + vec4 v2_culling = vec4(v2,1.0); + + v0_culling = camera.proj * camera.view * v0_culling; + m_culling = camera.proj * camera.view * m_culling; + v2_culling = camera.proj * camera.view * v2_culling; + + float tolerance = 0.05; + float v0_thre = v0_culling.w + tolerance; + float m_thre = m_culling.w + tolerance; + float v2_thre = v2_culling.w + tolerance; + + bool v0_pass = inBounds(v0_culling.x, v0_thre) && inBounds(v0_culling.y, v0_thre) && inBounds(v0_culling.z, v0_thre); + bool m_pass = inBounds(m_culling.x, m_thre) && inBounds(m_culling.y, m_thre) && inBounds(m_culling.z, m_thre); + bool v2_pass = inBounds(v2_culling.x, v2_thre) && inBounds(v2_culling.y, v2_thre) && inBounds(v2_culling.z, v2_thre); + + //cull if all three are not passed + if(!(v0_pass && m_pass && v2_pass)) + { + return; + } + + //distance cull -- how to get camera position form view matrix? + // transform the v0 and up to camera space -- not work.. + vec3 v0_cam = vec3(camera.view * vec4(v0,1.0)); + vec3 up_cam = vec3(camera.view * vec4(up,0.0)); + //vec3 camera_pos = vec3(-camera.view[3][0], -camera.view[3][1], -camera.view[3][2]); + //define dmax and n + uint n = 8; + float dmax = 30.0; + + //float d_proj = length(v0 - camera_pos - up * dot((v0 - camera_pos), up)); + float d_proj = length(v0_cam - up_cam * dot(v0_cam , up_cam)); + float dist_threshold = floor(n * ( 1 - (d_proj / dmax))); + if((gl_GlobalInvocationID.x % n) > dist_threshold) + { + return; + } + + // 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 + + //construct the data back to a blade type + Blade result; + result.v0 = vec4(v0, angle); + result.v1 = vec4(v1, height); + result.v2 = vec4(v2, width); + result.up = vec4(up, stiffness); + + + uint index = atomicAdd(numBlades.vertexCount,1); + + //store the modified blade to output array and update vertexCount + //culledBlades.output_blades[index] = blades.input_blades[gl_GlobalInvocationID.x]; + //when it works change to this + blades.input_blades[gl_GlobalInvocationID.x] = result; + culledBlades.output_blades[index] = blades.input_blades[gl_GlobalInvocationID.x]; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..6e8b29c 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,32 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec4 position; +layout(location = 1) in vec3 normal; +layout(location = 2) in vec2 uv; + layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color + //bright green for tip + vec3 bot = vec3(0.1, 0.2, 0); + vec3 tip = vec3(0.3, 0.6, 0); + //interpolate color + vec3 col = mix(bot, tip, uv.y); + + //add light effect -- helped by Yan Dong + vec3 light = normalize(vec3(-1, 1, -1)); + float lambert = dot(normal, light); + //too dark + //lambert = clamp(lambert, 0, 1); + //boost up a little bit by adding an ambient + lambert = clamp(lambert, 0 ,1) + 0.6; + - outColor = vec4(1.0); + //without light effect + //outColor = vec4(col, 1.0); + //with light effect + outColor = vec4(lambert * col, 1.0); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..bee211b 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -9,18 +9,71 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation control shader inputs and outputs +//inputs from vert shader +layout (location = 0) in vec4 tesc_v1[]; +layout (location = 1) in vec4 tesc_v2[]; +layout (location = 2) in vec4 tesc_up[]; +layout (location = 3) in vec4 tesc_front[]; +layout (location = 4) in vec4 tesc_widthdir[]; + +//output to evaluation shader +layout (location = 0) patch out vec4 tese_v1; +layout (location = 1) patch out vec4 tese_v2; +layout (location = 2) patch out vec4 tese_up; +layout (location = 3) patch out vec4 tese_front; +layout (location = 4) patch out vec4 tese_widthdir; +//layout (location = 0) out vec4 tese_v1; +//layout (location = 1) out vec4 tese_v2; +//layout (location = 2) out vec4 tese_up; +//layout (location = 3) out vec4 tese_forward; + void main() { // Don't move the origin location of the patch + //world coord gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; + + // TODO: Write any shader outputs + // pass through the passed in vec4 + tese_v1 = tesc_v1[0]; + tese_v2 = tesc_v2[0]; + tese_up = tesc_up[0]; + tese_front = tesc_front[0]; + tese_widthdir = tesc_widthdir[0]; + // TODO: Set level of tesselation - // gl_TessLevelInner[0] = ??? - // gl_TessLevelInner[1] = ??? - // gl_TessLevelOuter[0] = ??? - // gl_TessLevelOuter[1] = ??? - // gl_TessLevelOuter[2] = ??? - // gl_TessLevelOuter[3] = ??? + + //tessellation on multiple levels depending on depth + //compute the depth in ndc + //https://docs.google.com/presentation/d/1YkDE7YAqoffC9wUmDxFo9WZjiLqWI5SlQRojOeCBPGs/edit#slide=id.g2492ec6f45_0_342 560 slides + //Thanks Jie Meng's help + //get the world position of v0 of this blade from gl_in + vec4 v0_world = gl_in[gl_InvocationID].gl_Position; + v0_world.w = 1.0; //it is a point + //transform from world space to projection space + vec4 v0_proj = camera.proj * camera.view * v0_world; + //map to screen space + v0_proj /= v0_proj.w; + //get depth in normalized screen space + float depth = v0_proj.z; + + float tess_height_min = 3.0; + float tess_height_max = 8.0; + float tess_width = 2.0; + + //divide depth into 3 layers + float tess_height = ceil(mix(tess_height_max, tess_height_min, depth)); + //float tessLevel = tessLevelmax; + + // TODO: Set level of tesselation + gl_TessLevelInner[0] = tess_width; //horizontal + gl_TessLevelInner[1] = tess_height; //vertical + + gl_TessLevelOuter[0] = tess_height; //vert 0 - 3 + gl_TessLevelOuter[1] = tess_width;// vert 3 - 2 + gl_TessLevelOuter[2] = tess_height; //vert 2 - 1 + gl_TessLevelOuter[3] = tess_width; // vert 1 -0 } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..6fb3dcb 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -1,6 +1,11 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +/* + We use the tessellation evaluation shader to compute the + actual shape of the blades using the formula provided in the paper + +*/ layout(quads, equal_spacing, ccw) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -9,10 +14,66 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation evaluation shader inputs and outputs +//inputs are from tessellation.control shader +layout(location = 0) patch in vec4 tese_v1; +layout(location = 1) patch in vec4 tese_v2; +layout(location = 2) patch in vec4 tese_up; +layout(location = 3) patch in vec4 tese_front; +layout (location = 4) patch in vec4 tese_widthdir; +//layout(location = 0) in vec4 tese_v1; +//layout(location = 1) in vec4 tese_v2; +//layout(location = 2) in vec4 tese_up; +//layout(location = 3) in vec4 tese_forward; + +//output to fragment shader for shading +layout(location = 0) out vec4 position; +layout(location = 1) out vec3 normal; +layout(location = 2) out vec2 uv; void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; + + //grab the scalar from tese_v1 and tese_v2 + float height = tese_v1.w; + float width = tese_v2.w; // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + //get 3 control points + vec3 v0 = gl_in[0].gl_Position.xyz; + vec3 v1 = tese_v1.xyz; + vec3 v2 = tese_v2.xyz; + + vec3 a = v0 + v * (v1 - v0); + vec3 b = v1 + v * (v2 - v1); + vec3 c = a + v * (b - a); + //calculate tangent + vec3 t0 = normalize(b - a); + //vec3 width_dir = cross(t0, tese_forward.xyz); + vec3 t1 = vec3(tese_widthdir.xyz); + vec3 width_offset = t1 * width * 0.5; + vec3 c0 = c - width_offset; + vec3 c1 = c + width_offset; + //compute the normal of blade + normal = normalize(cross(t0, t1)); + + //interpolate on the width dir of blade to get basic shape + //triangle shape + //float t = u + 0.5 * v - u * v; + //quadratic + //float t = u - u * v * v; + //quad + //float t = u; + //triangle tip + float threshold = 0.5; + float t = 0.5 + (u - 0.5) * (1 - max(v - threshold, 0) / (1 - threshold)); + + position.xyz = (1.0 - t) * c0 + t * c1; + //transform from world space to screen space + position = camera.proj * camera.view * vec4(position.xyz, 1.0); + + //pass the uv to frag to interpolate color + uv = vec2(u,v); + + gl_Position = position; } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..cd968cb 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,6 +7,17 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { }; // TODO: Declare vertex shader inputs and outputs +//according to blades +layout(location = 0) in vec4 v0; //w is direction +layout(location = 1) in vec4 v1; //w is height +layout(location = 2) in vec4 v2; //w is width +layout(location = 3) in vec4 up; //w is stiffness + +layout (location = 0) out vec4 tesc_v1; +layout (location = 1) out vec4 tesc_v2; +layout (location = 2) out vec4 tesc_up; +layout (location = 3) out vec4 tesc_front; +layout (location = 4) out vec4 tesc_widthdir; out gl_PerVertex { vec4 gl_Position; @@ -14,4 +25,32 @@ out gl_PerVertex { void main() { // TODO: Write gl_Position and any other shader outputs + + float angle = v0.w; + float height = v1.w; + float width = v2.w; + float stiffness = up.w; + + //applying the object transformation to transform to world space + tesc_v1 = model * vec4(v1.xyz, 1.0); + tesc_v1.w = height; + + tesc_v2 = model * vec4(v2.xyz, 1.0); + tesc_v2.w = width; + + //global up(for plane), no need to transform + tesc_up = vec4(normalize(up.xyz), 0.0); + //no need to store stiffness -- only used in compute shader + + //compute the front direction of blades by the angle stores in v0 + vec3 front_dir = vec3(cos(angle), 0.0, sin(angle)); + tesc_front = vec4(normalize(front_dir),0.0); + + //compute the widthdir of blade by cross up and front + vec4 width_dir = vec4(normalize(cross(tesc_up.xyz,front_dir)), 0.0); + tesc_widthdir = width_dir; + + //compute the pos of blade in world space + gl_Position = model * vec4(v0.xyz, 1.0); + }