diff --git a/README.md b/README.md index 20ee451..c28b6ea 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,64 @@ 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) +* Hanting Xu +* [GitHub](https://github.com/HantingXu), [LinkedIn](www.linkedin.com/in/hanting-xu-25615b28b). +* Tested on: (Personal Computer) Windows 11, i7-12700H @ 2.70GHz 32GB, GeForce RTX 3070 Ti Laptop GPU -### (TODO: Your README) +### Project Description +#### General Introduction -*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. +Based on this [essay](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf), this project simulate a field of grass using Vulkan. + +#### Core Features +* basic grass rendering using Bezier Curve. + + * ![](img/blade_model.jpg) + + +* grass rendering with simulated force, including gravity, wind force and recovery force. + + * ![](img/force.png) + + +* grass culling according to the orietation of the grass, view frustrum and distance. + + +### Show Case +* Basic Grass Rendering without Force and Culling Applied + + * ![Result1](img/original.png) + + +* With Gravity Force and Recovery Force Applied + + * ![Result1](img/gravity.png) + + +* With All Forces Applied + + * ![Result1](img/wind.gif) + + +* All Forces Applied with Orientation Culling + + * ![Result1](img/orientation.gif) + + +* All Forces Applied with View-Frustrum Culling + + * ![Result1](img/frustrum.gif) + + +* All Forces Applied with Distance Culling + + * ![Result1](img/distance.gif) + + +* All Features Applied + + * ![Result1](img/all.gif) + +### Performance Analysis +The graph below shows the different FPS change with respect to different culling methods. The x-axis in the graph represents the number of blades to be rendered, while y-axis means the FPS. As we may observe from the graph, the FPS of the grass simulation delines as the number of blades to be drawn grows higher. And culling does greatly improve the overall performance, especially for orientation and distance culling. However, in this setting the view-frustrum culling seems to be useless and have little effect on the performance. That is probably because of the setting of the offset parameter is so small that only very few blades are culled. +* ![Result1](img/result.png) diff --git a/img/all.gif b/img/all.gif new file mode 100644 index 0000000..2a1e7d2 Binary files /dev/null and b/img/all.gif differ diff --git a/img/distance.gif b/img/distance.gif new file mode 100644 index 0000000..f75a570 Binary files /dev/null and b/img/distance.gif differ diff --git a/img/force.png b/img/force.png new file mode 100644 index 0000000..f66c1a5 Binary files /dev/null and b/img/force.png differ diff --git a/img/frustrum.gif b/img/frustrum.gif new file mode 100644 index 0000000..0546953 Binary files /dev/null and b/img/frustrum.gif differ diff --git a/img/gravity.png b/img/gravity.png new file mode 100644 index 0000000..93ada5b Binary files /dev/null and b/img/gravity.png differ diff --git a/img/orientation.gif b/img/orientation.gif new file mode 100644 index 0000000..60191cb Binary files /dev/null and b/img/orientation.gif differ diff --git a/img/original.png b/img/original.png new file mode 100644 index 0000000..9e2d849 Binary files /dev/null and b/img/original.png differ diff --git a/img/result.png b/img/result.png new file mode 100644 index 0000000..62792e6 Binary files /dev/null and b/img/result.png differ diff --git a/img/wind.gif b/img/wind.gif new file mode 100644 index 0000000..cf6c994 Binary files /dev/null and b/img/wind.gif differ diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..8323a46 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,38 @@ 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 bladesLayoutBinding = {}; + bladesLayoutBinding.binding = 0; + bladesLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladesLayoutBinding.descriptorCount = 1; + bladesLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladesLayoutBinding.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 = { bladesLayoutBinding, 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 +248,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 +353,42 @@ 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"); + } + + 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() { @@ -360,6 +429,72 @@ 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 desciptor 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"); + } + + 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].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i].dstBinding = 0; + descriptorWrites[3 * i].dstArrayElement = 0; + descriptorWrites[3 * i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i].descriptorCount = 1; + descriptorWrites[3 * i].pBufferInfo = &bladesBufferInfo; + descriptorWrites[3 * i].pImageInfo = nullptr; + descriptorWrites[3 * i].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 +852,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 +1019,11 @@ 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); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1116,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 @@ -1056,6 +1197,7 @@ Renderer::~Renderer() { vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(logicalDevice, computeDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..044e899 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,11 +56,14 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + 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..5ab9708 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,6 +2,16 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 +#define STIFFNESS 8.0 +#define GRAVITY 9.8 +#define MAXDISTANCE 15.0 +#define WITHWIND 1 +#define WITHGRAVITY 1 +#define WITHRECOVERY 1 +#define ORIENTATIONCULLING 1 +#define FRUSTRUMCULLING 1 +#define DISTANCECULLING 1 + layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -36,6 +46,22 @@ struct Blade { // uint firstInstance; // = 0 // } numBlades; +layout(set = 2, binding = 0) buffer Blades { + Blade blades[]; +}; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade 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; + + bool inBounds(float value, float bounds) { return (value >= -bounds) && (value <= bounds); } @@ -43,14 +69,99 @@ 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 + Blade currentBlade = blades[gl_GlobalInvocationID.x]; + + + vec3 v0 = vec3(currentBlade.v0); + vec3 v1 = vec3(currentBlade.v1); + vec3 v2 = vec3(currentBlade.v2); + vec3 up = vec3(currentBlade.up); + float height = currentBlade.v1.w; + vec3 oriV2 = v0 + up * height; + + vec3 gravity = vec3(0.0f); + vec3 recoveryForce = vec3(0.0f); + vec3 windForce = vec3(0.0f); + +#if WITHGRAVITY + // gravity + vec3 gravityE = vec3(0.0f, -1.0f, 0.0f) * GRAVITY; + vec3 dir = vec3(-cos(currentBlade.v0.w), 0.0f, sin(currentBlade.v0.w)); + vec3 gravityF = 0.25f * GRAVITY * dir; + gravity = gravityE + gravityF; +#endif + +#if WITHRECOVERY + // recovery force + recoveryForce = (oriV2 - v2) * STIFFNESS; +#endif +#if WITHWIND + // wind force + vec3 windDir = vec3(5.0f, 5.0f, 5.0f) * sin(totalTime); + // vec3 windDir = vec3(cos(totalTime), 0, sin(totalTime)) * 10.0f; + float fd = 1.0f - abs(dot(windDir, v2 - v0) / length(windDir) / length(v2 - v0)); + float fr = dot(v2 - v0, up) / height; + windForce = fd * fr * windDir; +#endif + + // update v2 + vec3 deltaV2 = (gravity + recoveryForce + windForce) * deltaTime; + v2 = v2 + deltaV2; + + // state validation + v2 = v2 - up * min(up * (v2 - v0), 0.0f); + float lProjection = length(v2 - v0 - up * dot(v2 - v0, up)); + v1 = v0 + height * up * max(1.0f - lProjection / height, 0.05f * max(lProjection / height, 1.0f)); + float bladeLength = (2.0f * length(v0 - v2) + 1.0f * (length(v0 - v1) + length(v1 - v2))) / 3.0f; + float ratio = height / bladeLength; + vec3 v1Corr = v0 + ratio * (v1 - v0); + vec3 v2Corr = v1Corr + ratio * (v2 - v1); + + blades[gl_GlobalInvocationID.x].v1 = vec4(v1Corr, currentBlade.v1.w); + blades[gl_GlobalInvocationID.x].v2 = vec4(v2Corr, currentBlade.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 + +#if ORIENTATIONCULLING + // orietation culling + vec3 cameraDirection = vec3(inverse(camera.view)[2]); + if(abs(dot(cameraDirection, dir / length(dir))) > 0.6f) + return; +#endif + +#if FRUSTRUMCULLING + // view-frustrum culling + vec3 m = 0.03125f * v0 * v1 * v2; + vec4 projV0 = camera.proj * camera.view * vec4(v0, 1.0f); + vec4 projV2 = camera.proj * camera.view * vec4(v2, 1.0f); + vec4 projM = camera.proj * camera.view * vec4(m, 1.0f); + + bool v0InRange = inBounds(projV0.x, projV0.w + 0.003f) && inBounds(projV0.y, projV0.w + 0.003f) && inBounds(projV0.z, projV0.w + 0.003f); + bool v2InRange = inBounds(projV2.x, projV2.w + 0.003f) && inBounds(projV2.y, projV2.w + 0.003f) && inBounds(projV2.z, projV2.w + 0.003f); + bool mInRange = inBounds(projM.x, projM.w + 0.003f) && inBounds(projM.y, projM.w + 0.003f) && inBounds(projM.z, projM.w + 0.003f); + + if((!v0InRange) && (!v2InRange) && (!mInRange)) + return; +#endif + +#if DISTANCECULLING + // distance culling + vec3 cameraPosition = vec3(inverse(camera.view)[3]); + float projDistance = length(v0 - cameraPosition - up * dot(v0 - cameraPosition, up)); + int bucketNum = 8; + if(gl_GlobalInvocationID.x % bucketNum > bucketNum * (1.0f - projDistance / MAXDISTANCE)) + return; +#endif + + culledBlades[atomicAdd(numBlades.vertexCount, 1)] = blades[gl_GlobalInvocationID.x]; } diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..15ef4b0 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,13 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout (location = 0) in vec3 pos_in; -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 color_out; void main() { // TODO: Compute fragment color - - outColor = vec4(1.0); + vec3 tipColor = vec3(0.23f, 0.59f, 0.22f); + vec3 baseColor = vec3(0.13f, 0.44f, 0.12f); + color_out = vec4(tipColor * pos_in.y + baseColor * (1.0f - pos_in.y), 0.5f); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..5c89127 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -1,6 +1,9 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +#define INNERLEVEL 5 +#define OUTERLEVEL 5 + layout(vertices = 1) out; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -9,18 +12,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] = INNERLEVEL; + gl_TessLevelInner[1] = INNERLEVEL; + gl_TessLevelOuter[0] = OUTERLEVEL; + gl_TessLevelOuter[1] = OUTERLEVEL; + gl_TessLevelOuter[2] = OUTERLEVEL; + gl_TessLevelOuter[3] = OUTERLEVEL; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..b714e4b 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,30 @@ 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 pos_out; 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 + float t = u + 0.5f * v - u * v; + + vec3 dir = vec3(-cos(v0_in[0].w), 0.0f, sin(v0_in[0].w)); + float wid = v2_in[0].w; + + vec3 v0 = vec3(v0_in[0]); + vec3 v1 = vec3(v1_in[0]); + vec3 v2 = vec3(v2_in[0]); + + vec3 tmpCenter = v0 * (1.0f - v) * (1.0f - v)+ v1 * v * (1.0f - v) * 2.0f + v2 * v * v; + vec3 tmpPos = (1 - t) * (tmpCenter - wid * dir) + t * (tmpCenter + wid * dir); + + pos_out = tmpPos; + gl_Position = camera.proj * camera.view * vec4(tmpPos, 1.0f); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..4863d5d 100644 --- a/src/shaders/grass.vert +++ b/src/shaders/grass.vert @@ -7,11 +7,31 @@ 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 + // TODO: Write gl_Position and any other shader outputs + v0_out = model * vec4(vec3(v0_in), 1.0f); + v1_out = model * vec4(vec3(v1_in), 1.0f); + v2_out = model * vec4(vec3(v2_in), 1.0f); + up_out = model * vec4(vec3(up_in), 1.0f); + + v0_out.w = v0_in.w; + v1_out.w = v1_in.w; + v2_out.w = v2_in.w; + up_out.w = up_in.w; + + gl_Position = model * vec4(vec3(v0_in), 1.0f); }