diff --git a/README.md b/README.md index 20ee451..b3db6fc 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,21 @@ 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) +* Yian Chen + * [LinkedIn](https://www.linkedin.com/in/yian-chen-33a31a1a8/), [personal website](https://sydianandrewchen.github.io/) etc. +* Tested on: Windows 10, AMD Ryzen 5800 HS with Radeon Graphics CPU @ 3.20GHz 16GB, NVIDIA GeForce RTX3060 Laptop 8GB -### (TODO: Your README) +### Result -*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 grass project, we have implemented a grass renderer based on [this paper](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf). In this paper, multiple sorts of physical effect have been simulated, including gravity, restoratve, and wind forces. + + + +![Alt text](img/image.png) + + +### Performance Analysis + +In this project, we have conducted performance analysis for this program under different number of blades with/without optimization. + +![](./img/fps_under_num_of_blades.png) \ No newline at end of file diff --git a/img/fps_under_num_of_blades.png b/img/fps_under_num_of_blades.png new file mode 100644 index 0000000..b50f06c Binary files /dev/null and b/img/fps_under_num_of_blades.png differ diff --git a/img/image.png b/img/image.png new file mode 100644 index 0000000..073521e Binary files /dev/null and b/img/image.png differ diff --git a/src/Blades.cpp b/src/Blades.cpp index 80e3d76..0142372 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_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_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/Renderer.cpp b/src/Renderer.cpp index b445d04..9dfa6b6 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,37 @@ 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 grassInLayoutBinding = {}; + grassInLayoutBinding.binding = 0; + grassInLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + grassInLayoutBinding.descriptorCount = 1; + grassInLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + grassInLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding grassOutLayoutBinding = {}; + grassOutLayoutBinding.binding = 1; + grassOutLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + grassOutLayoutBinding.descriptorCount = 1; + grassOutLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + grassOutLayoutBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding numRemainLayoutBinding = {}; + numRemainLayoutBinding.binding = 2; + numRemainLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numRemainLayoutBinding.descriptorCount = 1; + numRemainLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numRemainLayoutBinding.pImmutableSamplers = nullptr; + + std::vector bindings = { grassInLayoutBinding, grassOutLayoutBinding, numRemainLayoutBinding }; + + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create compute descriptor set layout"); + } } void Renderer::CreateDescriptorPool() { @@ -216,6 +247,7 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER , static_cast(3 * scene->GetBlades().size()) } }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,6 +352,35 @@ 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 }; // cf. CreateModelDescriptorSetLayout() binding 0 + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(grassDescriptorSets.size()); + allocInfo.pSetLayouts = layouts; + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, grassDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate grass descriptor set"); + } + + std::vector descriptorWrites(grassDescriptorSets.size()); + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo modelBufferInfo = {}; + modelBufferInfo.buffer = scene->GetBlades()[i]->GetModelBuffer(); + modelBufferInfo.offset = 0; + modelBufferInfo.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 = &modelBufferInfo; + descriptorWrites[i].pImageInfo = nullptr; + descriptorWrites[i].pTexelBufferView = nullptr; + } + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +421,67 @@ 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->GetModels().size()); + // storage buffers to hold the grass blades, the culled grass blades, and the output number of grass 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; + if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + std::vector descriptorWrites(3 * computeDescriptorSets.size()); + for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + VkDescriptorBufferInfo bladesBufferInfo = {}; + bladesBufferInfo.buffer = scene->GetBlades()[i]->GetBladesBuffer(); + bladesBufferInfo.offset = 0; + bladesBufferInfo.range = NUM_BLADES * sizeof(Blade); // buffer size + + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = NUM_BLADES * sizeof(Blade); + + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect); + + descriptorWrites[3 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 0].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 0].dstBinding = 0; + descriptorWrites[3 * i + 0].dstArrayElement = 0; + descriptorWrites[3 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 0].descriptorCount = 1; + descriptorWrites[3 * i + 0].pBufferInfo = &bladesBufferInfo; + descriptorWrites[3 * i + 0].pImageInfo = nullptr; + descriptorWrites[3 * i + 0].pTexelBufferView = nullptr; + + descriptorWrites[3 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 1].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 1].dstBinding = 1; + descriptorWrites[3 * i + 1].dstArrayElement = 0; + descriptorWrites[3 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 1].descriptorCount = 1; + descriptorWrites[3 * i + 1].pBufferInfo = &culledBladesBufferInfo; + descriptorWrites[3 * i + 1].pImageInfo = nullptr; + descriptorWrites[3 * i + 1].pTexelBufferView = nullptr; + + descriptorWrites[3 * i + 2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 2].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 2].dstBinding = 2; + descriptorWrites[3 * i + 2].dstArrayElement = 0; + descriptorWrites[3 * i + 2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 2].descriptorCount = 1; + descriptorWrites[3 * i + 2].pBufferInfo = &numBladesBufferInfo; + descriptorWrites[3 * i + 2].pImageInfo = nullptr; + descriptorWrites[3 * i + 2].pTexelBufferView = nullptr; + } + + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } void Renderer::CreateGraphicsPipeline() { @@ -480,7 +602,7 @@ void Renderer::CreateGraphicsPipeline() { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout }; + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout, computeDescriptorSetLayout }; // Pipeline layout: used to specify uniform values VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -717,7 +839,7 @@ void Renderer::CreateComputePipeline() { computeShaderStageInfo.pName = "main"; // TODO: Add the compute dsecriptor set layout you create to this list - std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout }; + std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout, computeDescriptorSetLayout }; // Create pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; @@ -884,6 +1006,10 @@ 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 (uint32_t i = 0; i < scene->GetBlades().size(); ++i) { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &computeDescriptorSets[i], 0, nullptr); + vkCmdDispatch(computeCommandBuffer, NUM_BLADES / WORKGROUP_SIZE + 1, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1102,14 @@ void Renderer::RecordCommandBuffers() { VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() }; VkDeviceSize offsets[] = { 0 }; // TODO: Uncomment this when the buffers are populated - // vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); // TODO: Bind the descriptor set for each grass blades model + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipelineLayout, 1, 1, &grassDescriptorSets[j], 0, nullptr); // Draw // TODO: Uncomment this when the buffers are populated - // vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); + vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); } // End render pass @@ -1057,6 +1184,7 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..1bf3dba 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,11 +56,15 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; - + VkDescriptorSetLayout grassDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; + VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; + std::vector grassDescriptorSets; + std::vector computeDescriptorSets; VkDescriptorSet timeDescriptorSet; VkPipelineLayout graphicsPipelineLayout; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..db37729 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,8 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define HAS_WIND 1 + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -23,34 +25,172 @@ struct Blade { // TODO: Add bindings to: // 1. Store the input blades +layout(set = 2, binding = 0) buffer Blades { + Blade blades[]; +}; + // 2. Write out the culled blades +layout(set = 2, binding = 1) buffer CulledBlades { + Blade culledBlades[]; +}; + // 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); } +bool insideViewFrustum(vec4 point, mat4 vp){ + float tolerance = 1.0f; + + vec4 point_ = vp * point; + + float h = point_.w ; + //float h = point_.w + tolerance; + + return inBounds(point_.x, h) && inBounds(point_.y, h) && inBounds(point_.z, h); +} + +float hash(float n) { return fract(sin(n) * 125.54); } + +vec3 random3(vec3 c) { + float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0))); + vec3 r; + r.z = fract(512.0*j); + j *= .125; + r.x = fract(512.0*j); + j *= .125; + r.y = fract(512.0*j); + return r-0.5; +} + +vec3 hash3( vec2 p ){ + vec3 q = vec3( dot(p,vec2(127.1,311.7)), + dot(p,vec2(269.5,183.3)), + dot(p,vec2(419.2,371.9)) ); + return fract(sin(q)*43758.5453); +} + +float hash2(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); } + 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 + Blade blade = blades[gl_GlobalInvocationID.x]; + + vec3 v0 = blade.v0.xyz; + vec3 v1 = blade.v1.xyz; + vec3 v2 = blade.v2.xyz; + 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; + + vec3 t1 = normalize(vec3(cos(orientation), 0.f, sin(orientation))); + + // TODO: Apply forces on every blade and update the vertices in the buffer + + // Gravity + vec4 D = vec4(0.f, -1.f, 0.f, 5.f); + vec3 gE = normalize(D.xyz) * D.w; + vec3 f = normalize(cross(t1, up)); + vec3 gF = 0.75f * length(gE) * f; + + vec3 gravity = gE + gF; + + // Recovery + vec3 iv2 = v0 + height * up; + vec3 recovery = (iv2 - v2) * stiffness; + +#if HAS_WIND + // Wind + vec3 wind = hash3(v0.xz) * cos(totalTime + int((v0.x + v0.z) / 7.f)) * 6 + hash2(v0.xz) * 2; + wind.y = 0; + + float fd = 1 - abs(dot((normalize(wind)), normalize(v2 - v0))); + float fr = dot(v2 - v0, up) / height; + + wind = wind * fd * fr; +#else + vec3 wind = vec3(0.f); +#endif + // Total Force + vec3 totalForce = gravity + recovery + wind; + + // Apply froce to control points + v2 = v2 + deltaTime * totalForce; + + // Ensure v2 above the local plane + v2 = v2 - up * min(dot(up, (v2 - v0)), 0); + + // Length (v2, v0) projected onto the ground + float l_proj = length(v2 - v0 - up * dot((v2 - v0), up)); + + // Ensure v0 != v1 + v1 = v0 + height * up * max(1 - (l_proj / height), 0.05 * max((l_proj / height), 1)); + + // Ensure lenth of curve < height of blade + float L0 = length(v2 - v0); + float L1 = length(v1 - v0) + length(v2 - v1); + float n = 2.f; + float L = (2.f * L0 + (n - 1) * L1) / (n + 1); + + // Correction + float r = height / L; + v1 = v0 + r * (v1 - v0); + v2 = v1 + r * (v2 - v1); + + blade.v1.xyz = v1.xyz; + blade.v2.xyz = v2.xyz; + blades[gl_GlobalInvocationID.x] = blade; // 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 + + // Orientation culling + vec3 c = inverse(camera.view)[3].xyz; + vec3 viewDir = (v0 - c - up * dot((v0 - c), up)); + vec3 widthDir = t1; + + if(abs(dot(normalize(viewDir), widthDir)) > 0.9){ + return; + } + + // View-frustum culling + mat4 matViewProj = camera.proj * camera.view; + vec3 m = 0.25f * v0 + 0.5f * v1 + 0.25f * v2; + + float tolerance_ratio = 1.0f; + if(!insideViewFrustum(vec4(v0, tolerance_ratio), matViewProj) || !insideViewFrustum(vec4(v2, tolerance_ratio), matViewProj) || !insideViewFrustum(vec4(m, tolerance_ratio), matViewProj)){ + return; + } + + // Distance test + float proj_distance = length(viewDir); + + int levels = 10; + float max_distance = 40.f; + if(mod(gl_GlobalInvocationID.x, levels) > floor(levels * (1.f - proj_distance / max_distance))){ + return; + } + + culledBlades[atomicAdd(numBlades.vertexCount, 1)] = blade; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..3f00890 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -6,12 +6,15 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare fragment shader inputs +layout(location = 0) in vec2 in_uv; layout(location = 0) out vec4 outColor; +// simple flat color gradient void main() { - // TODO: Compute fragment color + // note in_uv.y -> corresponds to height, .x width of each blade model + vec3 rootColor = vec3(0.0, 0.0, 0.2); + vec3 tipColor = vec3(0.1, 0.8, 0.2); - outColor = vec4(1.0); + outColor = vec4(mix(rootColor, tipColor, in_uv.y), 1.); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..26a5b6d 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -8,19 +8,31 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation control shader inputs and outputs +// https://ogldev.org/www/tutorial30/tutorial30.html - consider TESS_LVL based on cam dist +layout(location = 0) in vec4[] in_v0; // just 1 element per arr +layout(location = 1) in vec4[] in_v1; +layout(location = 2) in vec4[] in_v2; +layout(location = 3) in vec4[] in_up; + +layout(location = 0) out vec4[] out_v0; +layout(location = 1) out vec4[] out_v1; +layout(location = 2) out vec4[] out_v2; +layout(location = 3) out vec4[] out_up; + +#define TESS_LVL_INNER 5 +#define TESS_LVL_OUTER 5 void main() { // Don't move the origin location of the patch - gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; - - // TODO: Write any shader outputs + out_v0[gl_InvocationID] = in_v0[gl_InvocationID]; + out_v1[gl_InvocationID] = in_v1[gl_InvocationID]; + out_v2[gl_InvocationID] = in_v2[gl_InvocationID]; + out_up[gl_InvocationID] = in_up[gl_InvocationID]; - // 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] = TESS_LVL_INNER; + gl_TessLevelInner[1] = TESS_LVL_INNER; + gl_TessLevelOuter[0] = TESS_LVL_OUTER; + gl_TessLevelOuter[1] = TESS_LVL_OUTER; + gl_TessLevelOuter[2] = TESS_LVL_OUTER; + gl_TessLevelOuter[3] = TESS_LVL_OUTER; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..f947cb1 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -8,11 +8,44 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 proj; } camera; -// TODO: Declare tessellation evaluation shader inputs and outputs +// https://www.khronos.org/opengl/wiki/Tessellation_Evaluation_Shader +// see grass.tesc for other links +layout(location = 0) in vec4[] in_v0; +layout(location = 1) in vec4[] in_v1; +layout(location = 2) in vec4[] in_v2; +layout(location = 3) in vec4[] in_up; + +layout(location = 0) out vec2 out_uv; void main() { + // following along w/ + // https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf + // de casteljau bezier + // Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade float u = gl_TessCoord.x; float v = gl_TessCoord.y; + + vec3 v0 = in_v0[0].xyz; + vec3 v1 = in_v1[0].xyz; + vec3 v2 = in_v2[0].xyz; + + float dir_angle = in_v0[0].w; + float width = in_v2[0].w; + + // interp along height of blade w/ v + vec3 a = mix(v0, v1, v); + vec3 b = mix(v1, v2, v); + vec3 c = mix(a, b, v); + + // from Blades.cpp convention, y(height) 0 + vec3 t0 = normalize(b - a); + vec3 t1 = normalize(vec3(-cos(dir_angle), 0, sin(dir_angle))); + + vec3 c0 = c - width * t1; + vec3 c1 = c + width * t1; + + float t = (u + 0.5f * v - u * v); - // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + out_uv = vec2(u, v); + gl_Position = camera.proj * camera.view * vec4(mix(c0, c1, t), 1.); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..9406962 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -6,12 +6,19 @@ layout(set = 1, binding = 0) uniform ModelBufferObject { mat4 model; }; -// TODO: Declare vertex shader inputs and outputs +layout(location = 0) in vec4 in_v0; +layout(location = 1) in vec4 in_v1; +layout(location = 2) in vec4 in_v2; +layout(location = 3) in vec4 in_up; -out gl_PerVertex { - vec4 gl_Position; -}; +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() { - // TODO: Write gl_Position and any other shader outputs + out_v0 = vec4((model * vec4(in_v0.xyz, 1.f)).xyz, in_v0.w); + out_v1 = vec4((model * vec4(in_v1.xyz, 1.f)).xyz, in_v1.w); + out_v2 = vec4((model * vec4(in_v2.xyz, 1.f)).xyz, in_v2.w); + out_up = vec4((model * vec4(in_up.xyz, 1.f)).xyz, in_up.w); }