diff --git a/README.md b/README.md index 20ee451..8c234ca 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,73 @@ -Vulkan Grass Rendering -================================== +##
University of Pennsylvania, CIS 565: GPU Programming and Architecture
+#
Vulkan Grass Rendering
-**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5** +* Shutong Wu + * [LinkedIn](https://www.linkedin.com/in/shutong-wu-214043172/) + * [Email](shutong@seas.uepnn.edu) +* Tested on: Windows 10, i7-10700K CPU @ 3.80GHz, RTX3080, SM8.6, Personal Computer -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +## Overview +![](img/title.PNG) + +In this project I implemented the grass rendering techniques in 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) using Vulkan framework.
+The main components of this project includes: +- [Grass Simulation](#grass-simulation) +- [Grass Culling](#grass-culling) +- [Grass Rendering](#grass-rendering) + +## Grass Simulation +![](img/blade_model.jpg) +- In this Project, grass blades are represented as Bezier curves while performing physics calculations and culling operations. +- We will have three control points, v0, v1, v2, for each blade, and we will have the height/width/orientation/stiffness and the up vector to start with. + +### Gravity Simulation +Gravity is the essential force we add to the grass. It makes sure grass will have a simulated bending affect with extra forces. With only gravity force and no other force simulation grass will of course crumble to the ground, which will not be shown in this section. + +### Recovery Simulation +With the stiffness factor, recovery simulates grass's mass-spring system so it will not fall into the ground with extra forces like strong wind force. +![](img/bend.gif) + +### Wind Simulation +Wind force can generate more live images with grass. In this simulation I used a 2d noise algorithm to add more randomness to the wind. +![](img/wind.gif) +- We also have a wind force that can controls how strong the wind is. The gif above is using a wind force of 3, below is using a wind force of 5, and you can see the grasses bend more than the previous one. + +![](img/wind5.PNG) + +When we are done with all the simulation, we still need to do a validation to prevent behaviors like V2 pushed to the ground or V2 moving but V1 staying in the same position. +## Grass Culling +To cull blades that do not need to be rendered, we use three techniques to cull blades. +### Orientation Culling +Because grass does not have thickness in this project, we will cull the grass that has the same/exactly opposite direction as the view direction. +![](img/viewCulling.gif) + +### View-Frustum Culling +Cull blades outside of the view frustum is an essential in this project, and I use a small tolerance value to cull blades more conservatively. When there are a lot of grasses outside the view frustum, this can significantly improve the performance.
+This gif is taken when I choose a comparatively large tolerance value to cull the blades. +![](img/viewFrustumCulling.gif) + +### Distance Culling +- Distance culling technique will cull blades that are outside of a certain distance, based on camera and blade's positions. +- We will have a max distance, so that blade's distance larger than the max distance will be culled; +- We will also group grasses by levels based on their distance from the viewer. + +This is how distance level works on the grass: +Distance Level 7 | Distance Level 15 +:-------------------------:|:-------------------------: +![](img/cullinglevel7.PNG) | ![](img/cullinglevel15.PNG) + +## Grass Rendering +- Rendering is done using Vulkan's render pipeline. Each blade is passed as a vertex in the vertex shader and its color and lighting effect set in the fragment shader; +- In Vulkan's Tesselation Control Shader and Tessellation Evaluation shader, we set the tessellation level and implement the interpolation using de'castaljau algorithm. + +## Performance Analysis +In this performance analysis we mainly analyze how culling including the three different culling techniques improves performance in terms of runtime and total blades culled. +- Testing FPS is similar to test the total blades drawed in current frame but easier. +- We will use FPS to mesaure the performance, higher the FPS better the performance. +### Performance Test +![](img/Performance%20w_o%20culling%20Measured%20by%20FPS.png) +- From the graph above we can see that with more blades/triangles to draw, frame rate starts to decrease linearly +- With more culling techniques added, the performance will be better because there will be fewer blades to draw +- They all have their own best performance improvement scenario: View-Frustum when only few blades in the screen; distance culling when the camera is in a relatively long distance from the grasses; orientation culling when camera is facing towards the up vector of many grasses. They will all create performance improvement in this scenario generally. -### (TODO: Your README) -*DO NOT* leave the README to the last minute! It is a crucial part of the -project, and we will not be able to grade you without a good README. diff --git a/img/Performance w_o culling Measured by FPS.png b/img/Performance w_o culling Measured by FPS.png new file mode 100644 index 0000000..bd01ad7 Binary files /dev/null and b/img/Performance w_o culling Measured by FPS.png differ diff --git a/img/bend.gif b/img/bend.gif new file mode 100644 index 0000000..209a797 Binary files /dev/null and b/img/bend.gif differ diff --git a/img/cullinglevel15.PNG b/img/cullinglevel15.PNG new file mode 100644 index 0000000..fa3b1d0 Binary files /dev/null and b/img/cullinglevel15.PNG differ diff --git a/img/cullinglevel7.PNG b/img/cullinglevel7.PNG new file mode 100644 index 0000000..3f9e952 Binary files /dev/null and b/img/cullinglevel7.PNG differ diff --git a/img/distance.gif b/img/distance.gif new file mode 100644 index 0000000..91deac9 Binary files /dev/null and b/img/distance.gif differ diff --git a/img/distanceCulling.gif b/img/distanceCulling.gif new file mode 100644 index 0000000..dbe193f Binary files /dev/null and b/img/distanceCulling.gif differ diff --git a/img/title.PNG b/img/title.PNG new file mode 100644 index 0000000..7906a09 Binary files /dev/null and b/img/title.PNG differ diff --git a/img/viewCulling.gif b/img/viewCulling.gif new file mode 100644 index 0000000..3719d2d Binary files /dev/null and b/img/viewCulling.gif differ diff --git a/img/viewFrustumCulling.gif b/img/viewFrustumCulling.gif new file mode 100644 index 0000000..3becedf Binary files /dev/null and b/img/viewFrustumCulling.gif differ diff --git a/img/wind.gif b/img/wind.gif new file mode 100644 index 0000000..9f0f2b1 Binary files /dev/null and b/img/wind.gif differ diff --git a/img/wind5.PNG b/img/wind5.PNG new file mode 100644 index 0000000..3305c0b Binary files /dev/null and b/img/wind5.PNG differ diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b445d04..ee253ee 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -198,6 +198,40 @@ 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 + + //blades, culled blades, and bladesNum + VkDescriptorSetLayoutBinding bladeBinding = {}; + bladeBinding.binding = 0; + bladeBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bladeBinding.descriptorCount = 1; + bladeBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bladeBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding culledBladeBinding = {}; + culledBladeBinding.binding = 1; + culledBladeBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + culledBladeBinding.descriptorCount = 1; + culledBladeBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + culledBladeBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutBinding numBladeBinding = {}; + numBladeBinding.binding = 2; + numBladeBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + numBladeBinding.descriptorCount = 1; + numBladeBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + numBladeBinding.pImmutableSamplers = nullptr; + + std::vector bindings = {bladeBinding, culledBladeBinding, numBladeBinding}; + + // 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,10 @@ void Renderer::CreateDescriptorPool() { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, // TODO: Add any additional types and counts of descriptors you will need to allocate + + //Compute Descriptor + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(3 * scene->GetBlades().size()) } + }; VkDescriptorPoolCreateInfo poolInfo = {}; @@ -320,6 +358,39 @@ 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 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; + } + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } void Renderer::CreateTimeDescriptorSet() { @@ -360,6 +431,69 @@ 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 = sizeof(Blade) * NUM_BLADES; + + VkDescriptorBufferInfo culledBladesBufferInfo = {}; + culledBladesBufferInfo.buffer = scene->GetBlades()[i]->GetCulledBladesBuffer(); + culledBladesBufferInfo.offset = 0; + culledBladesBufferInfo.range = sizeof(Blade)*NUM_BLADES; + + VkDescriptorBufferInfo numBladesBufferInfo = {}; + numBladesBufferInfo.buffer = scene->GetBlades()[i]->GetNumBladesBuffer(); + numBladesBufferInfo.offset = 0; + numBladesBufferInfo.range = sizeof(BladeDrawIndirect); + + descriptorWrites[3 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 0].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 0].dstBinding = 0; + descriptorWrites[3 * i + 0].dstArrayElement = 0; + descriptorWrites[3 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 0].descriptorCount = 1; + descriptorWrites[3 * i + 0].pBufferInfo = &bladesBufferInfo; + descriptorWrites[3 * i + 0].pImageInfo = nullptr; + descriptorWrites[3 * i + 0].pTexelBufferView = nullptr; + + descriptorWrites[3 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 1].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 1].dstBinding = 1; + descriptorWrites[3 * i + 1].dstArrayElement = 0; + descriptorWrites[3 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 1].descriptorCount = 1; + descriptorWrites[3 * i + 1].pBufferInfo = &culledBladesBufferInfo; + descriptorWrites[3 * i + 1].pImageInfo = nullptr; + descriptorWrites[3 * i + 1].pTexelBufferView = nullptr; + + descriptorWrites[3 * i + 2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[3 * i + 2].dstSet = computeDescriptorSets[i]; + descriptorWrites[3 * i + 2].dstBinding = 2; + descriptorWrites[3 * i + 2].dstArrayElement = 0; + descriptorWrites[3 * i + 2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[3 * i + 2].descriptorCount = 1; + descriptorWrites[3 * i + 2].pBufferInfo = &numBladesBufferInfo; + descriptorWrites[3 * i + 2].pImageInfo = nullptr; + descriptorWrites[3 * i + 2].pTexelBufferView = nullptr; + } + // Update descriptor sets + vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } void Renderer::CreateGraphicsPipeline() { @@ -717,7 +851,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 +1018,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 (VkDescriptorSet const &descriptorSet : computeDescriptorSets) { + vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 2, 1, &descriptorSet, 0, nullptr); + vkCmdDispatch(computeCommandBuffer, NUM_BLADES/32, 1, 1); + } // ~ End recording ~ if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { @@ -976,13 +1114,16 @@ 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 + //Descriptor Sets + 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 diff --git a/src/Renderer.h b/src/Renderer.h index 95e025f..36caa9b 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -56,12 +56,15 @@ class Renderer { VkDescriptorSetLayout cameraDescriptorSetLayout; VkDescriptorSetLayout modelDescriptorSetLayout; VkDescriptorSetLayout timeDescriptorSetLayout; + VkDescriptorSetLayout computeDescriptorSetLayout; VkDescriptorPool descriptorPool; VkDescriptorSet cameraDescriptorSet; std::vector modelDescriptorSets; VkDescriptorSet timeDescriptorSet; + std::vector grassDescriptorSets; + std::vector computeDescriptorSets; VkPipelineLayout graphicsPipelineLayout; VkPipelineLayout grassPipelineLayout; diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp index 0fd0224..f88d349 100644 --- a/src/shaders/compute.comp +++ b/src/shaders/compute.comp @@ -2,8 +2,16 @@ #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 -layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; +#define CULLING true +#define ORIENTATION_CULL true +#define VIEW_FRUSTUM_CULL true +#define DISTANCE_CULL true +#define DISTANCEMAX 20 +#define DISTANCECULLINGLEVEL 15 +#define WINDSTR 1 +#define D vec4(0, -1.0, 0, 9.81) +layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) uniform CameraBufferObject { mat4 view; mat4 proj; @@ -12,7 +20,7 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { layout(set = 1, binding = 0) uniform Time { float deltaTime; float totalTime; -}; +} time; struct Blade { vec4 v0; @@ -21,36 +29,148 @@ struct Blade { vec4 up; }; -// TODO: Add bindings to: -// 1. Store the input blades -// 2. Write out the culled blades -// 3. Write the total number of blades remaining - -// The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call -// This is sort of an advanced feature so we've showed you what this buffer should look like -// -// layout(set = ???, binding = ???) buffer NumBlades { -// uint vertexCount; // Write the number of blades remaining here -// uint instanceCount; // = 1 -// uint firstVertex; // = 0 -// uint firstInstance; // = 0 -// } numBlades; +// GLSL Noise Algorithm from: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 +// using generic 2d noise because we only need two direction +float hash(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); } + +float noise(vec2 x) { + vec2 i = floor(x); + vec2 f = fract(x); + + // Four corners in 2D of a tile + float a = hash(i); + float b = hash(i + vec2(1.0, 0.0)); + float c = hash(i + vec2(0.0, 1.0)); + float d = hash(i + vec2(1.0, 1.0)); + vec2 u = f * f * (3.0 - 2.0 * f); + return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y; +} + + +vec3 windFunc(float t, vec3 v0){ + float n = noise(v0.xz); + return WINDSTR*vec3(sin(t)+n, 0, sin(t)+n); +} + +layout(set = 2, binding = 0) buffer Blades { + Blade data[]; +} blades; + +layout(set = 2, binding = 1) buffer CulledBlades { + Blade data[]; +} 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); } +bool inBounds(vec3 value, float bounds) { + return (value.x >= -bounds) && (value.x <= bounds) + && (value.y >= -bounds) && (value.y <= bounds) + && (value.z >= -bounds) && (value.z <= bounds); +} + +bool inFrustum(vec3 p) { + float t = 0.02f; + vec4 pNorm = camera.proj * camera.view * vec4(p, 1.0); + float h = pNorm.w + t; + return inBounds(pNorm.xyz, h); +} + 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.data[gl_GlobalInvocationID.x]; + //initialization + vec3 up = blade.up.xyz; + vec3 v0 = blade.v0.xyz; + vec3 v1 = blade.v1.xyz; + vec3 v2 = blade.v2.xyz; + float ori = blade.v0.w; + float height = blade.v1.w; + float width = blade.v2.w; + float stiffness = blade.up.w; + + //gravity + vec3 gE = normalize(D.xyz) * D.w; + vec3 f = normalize(cross(vec3(-cos(ori), 0, sin(ori)), up)); + vec3 gF = 0.25 * abs(gE) * f; + vec3 g = gE + gF; + + //recovery derived in the paper using Hooke's Law + vec3 iv2 = v0 + up * height; + vec3 r1 = (iv2 - v2) * stiffness; + + //wind + vec3 windDir = windFunc(time.totalTime, v0); + + float fd = 1 - abs(dot(normalize(windDir), normalize(v2 - v0))); + float fr = dot(v2 - v0, up) / height; + vec3 w = windDir * fd * fr; + + + //total force + vec3 tv2 = (g + r1 + w) * time.deltaTime; + v2 += tv2; + + //state validation + v2 -= up * min(dot(up, (v2-v0)), 0); + float lproj = length(v2 - v0 - up * dot((v2 - v0), up)); + v1 = v0 + height * up * max(1.f - lproj / height, 0.05f * max(lproj / height, 1.f)); + + float l0 = length(v2 - v0); + float l1 = length(v2 - v1) + length(v1 - v0); + float l = (2 * l0 + l1)/3; //n=3 + float r2 = height/l; + + v1 = v0 + r2 * (v1 - v0); + v2 = v1 + r2 * (v2 - v1); + blades.data[gl_GlobalInvocationID.x].v1.xyz = v1.xyz; + blades.data[gl_GlobalInvocationID.x].v2.xyz = v2.xyz; + // TODO: Cull blades that are too far away or not in the camera frustum and write them // to the culled blades buffer + if (CULLING) { + //orientation test + vec4 dirb = vec4(-cos(ori), 0.f, sin(ori), 0.f); + float result = abs(normalize(vec3(camera.view * dirb)).z); + if (ORIENTATION_CULL && result > 0.9) { + return; + } + + //view-frustum test + vec3 m = 0.25 * v0 + 0.5 * v1 + 0.25 * v2; + if (VIEW_FRUSTUM_CULL && !inFrustum(m) && !inFrustum(v0) && !inFrustum(v2)) { + return; + } + + //distance test + vec3 c = inverse(camera.view)[3].xyz; + float dproj = length(v0 - c - up * dot((v0-c), up)); + uint dmax = DISTANCECULLINGLEVEL; + if (DISTANCE_CULL && gl_GlobalInvocationID.x % DISTANCEMAX < int(floor(DISTANCEMAX * (1 - dproj / dmax)))) { + return; + } + } + //occlusion test + // 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 + uint idx = atomicAdd(numBlades.vertexCount, 1); + culledBlades.data[idx] = blade; } + + diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag index c7df157..95b19bb 100644 --- a/src/shaders/grass.frag +++ b/src/shaders/grass.frag @@ -7,11 +7,19 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare fragment shader inputs +layout(location = 0) in vec3 normal; +layout(location = 1) in vec3 pos; layout(location = 0) out vec4 outColor; void main() { // TODO: Compute fragment color - outColor = vec4(1.0); + vec3 lightPos = vec3(0.0, 100.0, 0.0); + vec3 lightDir = normalize(lightPos - pos); + + vec3 albedo = vec3(0, 1, 0); + vec3 ambient = vec3(0, 1, 0); + vec3 color = ambient * 0.3f + albedo * max(dot(lightDir, normal), 0.f); + outColor = vec4(color.xyz, 1.f); } diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc index f9ffd07..9a9276f 100644 --- a/src/shaders/grass.tesc +++ b/src/shaders/grass.tesc @@ -1,6 +1,6 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable - +#define TL 10 layout(vertices = 1) out; layout(set = 0, binding = 0) uniform CameraBufferObject { @@ -9,18 +9,31 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation control shader inputs and outputs +layout(location = 0) in vec4 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 vec4 out_v0[]; +layout(location = 1) out vec4 out_v1[]; +layout(location = 2) out vec4 out_v2[]; +layout(location = 3) out vec4 out_up[]; void main() { // Don't move the origin location of the patch gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; + // TODO: Write any shader outputs + 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: Write any shader outputs // 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] = TL; + gl_TessLevelInner[1] = TL; + gl_TessLevelOuter[0] = TL; + gl_TessLevelOuter[1] = TL; + gl_TessLevelOuter[2] = TL; + gl_TessLevelOuter[3] = TL; } diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese index 751fff6..73f7049 100644 --- a/src/shaders/grass.tese +++ b/src/shaders/grass.tese @@ -9,10 +9,40 @@ layout(set = 0, binding = 0) uniform CameraBufferObject { } camera; // TODO: Declare tessellation evaluation 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[]; + +layout(location = 0) out vec3 out_normal; +layout(location = 1) out vec3 out_pos; void main() { 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; + vec3 up = in_up[0].xyz; + float ori = in_v0[0].w; + float height = in_v1[0].w; + float width = in_v2[0].w; + float stiffness = in_up[0].w; // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade + //de casteljau algo + vec3 a = mix(v0, v1, v); + vec3 b = mix(v1, v2, v); + vec3 c = mix(a, b, v); + + vec3 t0 = normalize(b-a); + vec3 t1 = normalize(vec3(-cos(ori), 0, sin(ori))); + out_normal = normalize(cross(t0, t1)); + + float t = u + 0.5*v - u*v; + vec3 pos = mix(c - width * t1, c + width * t1, t); + + out_pos = pos; + + gl_Position = camera.proj * camera.view * vec4(pos, 1.f); } diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert index db9dfe9..b6b61ba 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 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 vec4 out_v0; +layout(location = 1) out vec4 out_v1; +layout(location = 2) out vec4 out_v2; +layout(location = 3) out vec4 out_up; out gl_PerVertex { vec4 gl_Position; }; +vec4 spaceConvert(vec4 in0) { + return vec4(vec3(model * vec4(in0.xyz, 1.f)), in0.w); +} + void main() { // TODO: Write gl_Position and any other shader outputs + gl_Position = spaceConvert(in_v0); + + //local space to world space + out_v0 = spaceConvert(in_v0); + out_v1 = spaceConvert(in_v1); + out_v2 = spaceConvert(in_v2); + out_up = spaceConvert(in_up); }