diff --git a/.gitignore b/.gitignore
index 6c57396..e7ff8b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -560,3 +560,5 @@ xcuserdata
*.xccheckout
*.moved-aside
*.xcuserstate
+
+.vscode*
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 36d5dcb..8184669 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,10 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20")
+
project(cis565_project5_vulkan_grass_rendering)
OPTION(USE_D2D_WSI "Build the project using Direct to Display swapchain" OFF)
diff --git a/README.md b/README.md
index 20ee451..be740c9 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,55 @@ 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)
+* Nicholas Liu
+ * [Linkedin](https://www.linkedin.com/in/liunicholas6/)
+* Tested on: Linux Mint 22 Wilma, AMD Ryzen 7 5800X @ 2.512GHz, 32GB RAM, GeForce GTX 1660 Ti
-### (TODO: Your README)
+| |
+|:--:|
+| *I output the normals as colors for debug and it looked kinda good* |
+
+## Project Overview
+
+This Vulkan project implements grass rendering techniques outlined 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)
+
+Each blade of grass is represented as a cubic bezier: one at the base of the grass blade, that lies at some point above the grass blade, and one at the tip of the grass blade. The grass moves according to forces applied to the tip of the grass, namely gravity, a restorative force that tries to pull the grass to a vertical position, and a wind function. The grass control points are controlled using a compute shader.
+
+The control points of each grass blade are transformed into quads with the tesselation pipeline. These quads are then distorted into a triangle shape that are bent along the bezier, thus producing the grass shape!
+
+## Culling Methods
+
+For maximum performance, several different criteria for culling blades were implemented
+
+- Orientation culling: if a blade of grass is tangent to the camera's view vector, it has zero width and should not actually fill in any pixels, and can thus be discarded. Shown is an agressive cull that culls at `theta` where `cos(theta) < 10`
+
+- Frustum culling: Any blades that are not within the view frustum should not be rendered and are culled at the compute stage. Shown is culling with a frustum that is just slightly too tight, hence blades of grass disappearing at the sides.
+
+- Distance culling: In order to reduce the number of blades rendered, we don't render blades that are beyond a further distance. In order to lessen the effect of the aribtrary cutoff, we have each blade probabilistically have a higher change to disappear as they approach the maximum cutoff distance.
+
+
+| | | |
+|:--:|:--:|:--:|
+| *Orientation Culling* | *View Frustum Culling* | *Distance Culling* |
+
+## Performance
+
+I tested performance in 3 different camera positions: the default (looking at all the grass, at ground level), a far away scene, and a close up in the middle of the scene. For each of these I let the program run for 2^16 frames and timed how long it took to render them.
+
+| | | |
+|:--:|:--:|:--:|
+| *Default* | *Close* | *Far* |
+
+In general, turning on all method of culling performed best, as expected. Distance culling made the greatest impact when the camera was far, and orientation culling had a significant impact where the camera was more or less parallel to the ground plane (as said scenarios are where the most blades of grass get culled, respectively).
+
+Somewhat surprisingly, however, frustum culling seemed to have very little impact on performance generally -- its effect is explainable within the bounds of random variance; in fact the frustum-culling only run performed worse than no optimizations in the far camera setup. This is surprising, as I would expect frustum culling to have a very significant impact in the close up scene, where it should cull about 3/4 of the geometry. I hypothesize that the lack of performance benefit comes from the fact that the driver already performs frustum culling at the vertex stage with a computation that is about as cheap as what I perform manually in the compute stage.
+
+| Optimizations | Default Scene | Close Scene | Far Scene |
+|-------------------|---------------|-------------|-----------|
+| No Optimizations | 118.695 | 118.259 | 92.6448 |
+| Orientation Cull | 92.0582 | 91.6129 | 92.7531 |
+| Frustum Cull | 114.687 | 115.402 | 93.2135 |
+| Distance Cull | 115.637 | 114.76 | 35.1957 |
+| All Optimizations | 87.186 | 88.1485 | 35.5031 |
+
-*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/bin/Release/vulkan_grass_rendering.exe b/bin/Release/vulkan_grass_rendering.exe
deleted file mode 100644
index f68db3a..0000000
Binary files a/bin/Release/vulkan_grass_rendering.exe and /dev/null differ
diff --git a/img/chart.png b/img/chart.png
new file mode 100644
index 0000000..8677168
Binary files /dev/null and b/img/chart.png differ
diff --git a/img/closecam.png b/img/closecam.png
new file mode 100644
index 0000000..c3b503c
Binary files /dev/null and b/img/closecam.png differ
diff --git a/img/defaultcam.png b/img/defaultcam.png
new file mode 100644
index 0000000..ccacfe6
Binary files /dev/null and b/img/defaultcam.png differ
diff --git a/img/distancecull.gif b/img/distancecull.gif
new file mode 100644
index 0000000..36cb8e1
Binary files /dev/null and b/img/distancecull.gif differ
diff --git a/img/farcam.png b/img/farcam.png
new file mode 100644
index 0000000..5408d49
Binary files /dev/null and b/img/farcam.png differ
diff --git a/img/frustumcull.gif b/img/frustumcull.gif
new file mode 100644
index 0000000..528ec71
Binary files /dev/null and b/img/frustumcull.gif differ
diff --git a/img/normals.gif b/img/normals.gif
new file mode 100644
index 0000000..6919966
Binary files /dev/null and b/img/normals.gif differ
diff --git a/img/orientationcull.gif b/img/orientationcull.gif
new file mode 100644
index 0000000..6735b82
Binary files /dev/null and b/img/orientationcull.gif 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/BufferUtils.cpp b/src/BufferUtils.cpp
index acf617e..513b787 100644
--- a/src/BufferUtils.cpp
+++ b/src/BufferUtils.cpp
@@ -1,5 +1,6 @@
#include "BufferUtils.h"
#include "Instance.h"
+#include
void BufferUtils::CreateBuffer(Device* device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) {
// Create buffer
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index aea02fe..e285734 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,3 +1,7 @@
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20")
+
file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
file(GLOB IMAGES
@@ -30,21 +34,25 @@ else(WIN32)
target_link_libraries(vulkan_grass_rendering ${CMAKE_THREAD_LIBS_INIT})
endif(WIN32)
+if(WIN32)
+ set(GLSLANGVALIDATOR "/Bin/glslangValidator.exe")
+endif(WIN32)
+if(UNIX)
+ set(GLSLANGVALIDATOR "/bin/glslangValidator")
+endif(UNIX)
+
foreach(SHADER_SOURCE ${SHADER_SOURCES})
set(SHADER_DIR ${CMAKE_CURRENT_BINARY_DIR}/shaders)
- if(WIN32)
- get_filename_component(fname ${SHADER_SOURCE} NAME)
- add_custom_target(${fname}.spv
- COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR} &&
- $ENV{VK_SDK_PATH}/Bin/glslangValidator.exe -V ${SHADER_SOURCE} -o ${SHADER_DIR}/${fname}.spv -g
- SOURCES ${SHADER_SOURCE}
- )
- ExternalTarget("Shaders" ${fname}.spv)
- add_dependencies(vulkan_grass_rendering ${fname}.spv)
- endif(WIN32)
-
- # TODO: Build shaders on not windows
+ get_filename_component(fname ${SHADER_SOURCE} NAME)
+ add_custom_target(${fname}.spv
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR} &&
+ $ENV{VK_SDK_PATH}/${GLSLANGVALIDATOR} -V ${SHADER_SOURCE} -o ${SHADER_DIR}/${fname}.spv -g
+ SOURCES ${SHADER_SOURCE}
+ )
+ ExternalTarget("Shaders" ${fname}.spv)
+ add_dependencies(vulkan_grass_rendering ${fname}.spv)
+
endforeach()
target_link_libraries(vulkan_grass_rendering ${ASSIMP_LIBRARIES} Vulkan::Vulkan glfw)
diff --git a/src/Camera.cpp b/src/Camera.cpp
index 3afb5b8..2a6deba 100644
--- a/src/Camera.cpp
+++ b/src/Camera.cpp
@@ -8,10 +8,8 @@
#include "Camera.h"
#include "BufferUtils.h"
-Camera::Camera(Device* device, float aspectRatio) : device(device) {
- r = 10.0f;
- theta = 0.0f;
- phi = 0.0f;
+Camera::Camera(Device* device, float aspectRatio, float r, float theta, float phi)
+ : device(device), r(r), theta(theta), phi(phi) {
cameraBufferObject.viewMatrix = glm::lookAt(glm::vec3(0.0f, 1.0f, 10.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
cameraBufferObject.projectionMatrix = glm::perspective(glm::radians(45.0f), aspectRatio, 0.1f, 100.0f);
cameraBufferObject.projectionMatrix[1][1] *= -1; // y-coordinate is flipped
diff --git a/src/Camera.h b/src/Camera.h
index 6b10747..bec9abb 100644
--- a/src/Camera.h
+++ b/src/Camera.h
@@ -23,7 +23,7 @@ class Camera {
float r, theta, phi;
public:
- Camera(Device* device, float aspectRatio);
+ Camera(Device* device, float aspectRatio, float r = 10, float theta = 0, float phi = 0);
~Camera();
VkBuffer GetBuffer() const;
diff --git a/src/Device.h b/src/Device.h
index 163204b..4451eb7 100644
--- a/src/Device.h
+++ b/src/Device.h
@@ -6,6 +6,8 @@
#include "QueueFlags.h"
#include "SwapChain.h"
+class Instance;
+
class SwapChain;
class Device {
friend class Instance;
diff --git a/src/Renderer.cpp b/src/Renderer.cpp
index b445d04..0a3dae6 100644
--- a/src/Renderer.cpp
+++ b/src/Renderer.cpp
@@ -198,6 +198,30 @@ 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 binding {
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr
+ };
+
+ // Bindings for:
+ // Input, Output, NumBlades
+ std::vector bindings {binding, binding, binding};
+ for (uint32_t i = 0; i < bindings.size(); i++) {
+ bindings[i].binding = i;
+ }
+
+ VkDescriptorSetLayoutCreateInfo layoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .bindingCount = static_cast(bindings.size()),
+ .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 +240,9 @@ void Renderer::CreateDescriptorPool() {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 },
// TODO: Add any additional types and counts of descriptors you will need to allocate
+ // 3 different blade buffers (compute)
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3}
+
};
VkDescriptorPoolCreateInfo poolInfo = {};
@@ -320,6 +347,43 @@ 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,11 +424,79 @@ 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;
+ descriptorWrites.reserve(computeDescriptorSets.size() * 3);
+
+ VkWriteDescriptorSet descriptorWrite {
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .pImageInfo = nullptr,
+ .pTexelBufferView = nullptr
+ };
+
+ std::vector descriptorBufferInfos;
+ descriptorBufferInfos.reserve(scene->GetBlades().size() * 3);
+
+
+ for (uint32_t i = 0; i < scene->GetBlades().size(); ++i) {
+ auto& blades = scene->GetBlades()[i];
+ descriptorWrite.dstSet = computeDescriptorSets[i];
+
+ // inBlades
+ descriptorBufferInfos.push_back(VkDescriptorBufferInfo {
+ .buffer = blades->GetBladesBuffer(),
+ .offset = 0,
+ .range = VK_WHOLE_SIZE
+ });
+ descriptorWrite.dstBinding = 0;
+ descriptorWrite.pBufferInfo = &descriptorBufferInfos.back();
+ descriptorWrites.push_back(descriptorWrite);
+
+ // culledBlades
+ descriptorBufferInfos.push_back(VkDescriptorBufferInfo {
+ .buffer = blades->GetCulledBladesBuffer(),
+ .offset = 0,
+ .range = VK_WHOLE_SIZE
+ });
+ descriptorWrite.dstBinding = 1;
+ descriptorWrite.pBufferInfo = &descriptorBufferInfos.back();
+ descriptorWrites.push_back(descriptorWrite);
+
+ // numBlades
+ descriptorBufferInfos.push_back(VkDescriptorBufferInfo {
+ .buffer = blades->GetNumBladesBuffer(),
+ .offset = 0,
+ .range = VK_WHOLE_SIZE
+ });
+ descriptorWrite.dstBinding = 2;
+ descriptorWrite.pBufferInfo = &descriptorBufferInfos.back();
+ descriptorWrites.push_back(descriptorWrite);
+ }
+
+ // Update descriptor sets
+ vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
void Renderer::CreateGraphicsPipeline() {
- VkShaderModule vertShaderModule = ShaderModule::Create("shaders/graphics.vert.spv", logicalDevice);
- VkShaderModule fragShaderModule = ShaderModule::Create("shaders/graphics.frag.spv", logicalDevice);
+ VkShaderModule vertShaderModule = ShaderModule::Create("src/shaders/graphics.vert.spv", logicalDevice);
+ VkShaderModule fragShaderModule = ShaderModule::Create("src/shaders/graphics.frag.spv", logicalDevice);
// Assign each shader module to the appropriate stage in the pipeline
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {};
@@ -523,10 +655,10 @@ void Renderer::CreateGraphicsPipeline() {
void Renderer::CreateGrassPipeline() {
// --- Set up programmable shaders ---
- VkShaderModule vertShaderModule = ShaderModule::Create("shaders/grass.vert.spv", logicalDevice);
- VkShaderModule tescShaderModule = ShaderModule::Create("shaders/grass.tesc.spv", logicalDevice);
- VkShaderModule teseShaderModule = ShaderModule::Create("shaders/grass.tese.spv", logicalDevice);
- VkShaderModule fragShaderModule = ShaderModule::Create("shaders/grass.frag.spv", logicalDevice);
+ VkShaderModule vertShaderModule = ShaderModule::Create("src/shaders/grass.vert.spv", logicalDevice);
+ VkShaderModule tescShaderModule = ShaderModule::Create("src/shaders/grass.tesc.spv", logicalDevice);
+ VkShaderModule teseShaderModule = ShaderModule::Create("src/shaders/grass.tese.spv", logicalDevice);
+ VkShaderModule fragShaderModule = ShaderModule::Create("src/shaders/grass.frag.spv", logicalDevice);
// Assign each shader module to the appropriate stage in the pipeline
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {};
@@ -708,7 +840,7 @@ void Renderer::CreateGrassPipeline() {
void Renderer::CreateComputePipeline() {
// Set up programmable shaders
- VkShaderModule computeShaderModule = ShaderModule::Create("shaders/compute.comp.spv", logicalDevice);
+ VkShaderModule computeShaderModule = ShaderModule::Create("src/shaders/compute.comp.spv", logicalDevice);
VkPipelineShaderStageCreateInfo computeShaderStageInfo = {};
computeShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
@@ -716,8 +848,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 = {};
@@ -884,6 +1016,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 (size_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) / WORKGROUP_SIZE), 1, 1);
+ }
// ~ End recording ~
if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) {
@@ -976,13 +1112,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 +1194,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..728ab34 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 computeDescriptorSets;
+ std::vector grassDescriptorSets;
VkPipelineLayout graphicsPipelineLayout;
VkPipelineLayout grassPipelineLayout;
diff --git a/src/SwapChain.cpp b/src/SwapChain.cpp
index 711fec0..33c5e4b 100644
--- a/src/SwapChain.cpp
+++ b/src/SwapChain.cpp
@@ -3,89 +3,110 @@
#include "Instance.h"
#include "Device.h"
#include "Window.h"
+#include
+
+namespace
+{
+ // Specify the color channel format and color space type
+ VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats)
+ {
+ // VK_FORMAT_UNDEFINED indicates that the surface has no preferred format, so we can choose any
+ if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED)
+ {
+ return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
+ }
+
+ // Otherwise, choose a preferred combination
+ for (const auto &availableFormat : availableFormats)
+ {
+ // Ideal format and color space
+ if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
+ {
+ return availableFormat;
+ }
+ }
+
+ // Otherwise, return any format
+ return availableFormats[0];
+ }
+
+ // Specify the presentation mode of the swap chain
+ VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes)
+ {
+ // Second choice
+ VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR;
+
+ for (const auto &availablePresentMode : availablePresentModes)
+ {
+ if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR)
+ {
+ // First choice
+ return availablePresentMode;
+ }
+ else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR)
+ {
+ // Third choice
+ bestMode = availablePresentMode;
+ }
+ }
+
+ return bestMode;
+ }
+
+ // Specify the swap extent (resolution) of the swap chain
+ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities, GLFWwindow *window)
+ {
+ if (capabilities.currentExtent.width != std::numeric_limits::max())
+ {
+ return capabilities.currentExtent;
+ }
+ else
+ {
+ int width, height;
+ glfwGetWindowSize(window, &width, &height);
+ VkExtent2D actualExtent = {static_cast(width), static_cast(height)};
+
+ actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width));
+ actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height));
+
+ return actualExtent;
+ }
+ }
+}
+
+SwapChain::SwapChain(Device *device, VkSurfaceKHR vkSurface, unsigned int numBuffers)
+ : device(device), vkSurface(vkSurface), numBuffers(numBuffers)
+{
-namespace {
- // Specify the color channel format and color space type
- VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) {
- // VK_FORMAT_UNDEFINED indicates that the surface has no preferred format, so we can choose any
- if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
- return{ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
- }
-
- // Otherwise, choose a preferred combination
- for (const auto& availableFormat : availableFormats) {
- // Ideal format and color space
- if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
- return availableFormat;
- }
- }
-
- // Otherwise, return any format
- return availableFormats[0];
- }
-
- // Specify the presentation mode of the swap chain
- VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) {
- // Second choice
- VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR;
-
- for (const auto& availablePresentMode : availablePresentModes) {
- if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
- // First choice
- return availablePresentMode;
- }
- else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
- // Third choice
- bestMode = availablePresentMode;
- }
- }
-
- return bestMode;
- }
-
- // Specify the swap extent (resolution) of the swap chain
- VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GLFWwindow* window) {
- if (capabilities.currentExtent.width != std::numeric_limits::max()) {
- return capabilities.currentExtent;
- } else {
- int width, height;
- glfwGetWindowSize(window, &width, &height);
- VkExtent2D actualExtent = { static_cast(width), static_cast(height) };
-
- actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width));
- actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height));
-
- return actualExtent;
- }
- }
-}
-
-SwapChain::SwapChain(Device* device, VkSurfaceKHR vkSurface, unsigned int numBuffers)
- : device(device), vkSurface(vkSurface), numBuffers(numBuffers) {
-
Create();
VkSemaphoreCreateInfo semaphoreInfo = {};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
if (vkCreateSemaphore(device->GetVkDevice(), &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS ||
- vkCreateSemaphore(device->GetVkDevice(), &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) {
+ vkCreateSemaphore(device->GetVkDevice(), &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS)
+ {
throw std::runtime_error("Failed to create semaphores");
}
}
-void SwapChain::Create() {
- auto* instance = device->GetInstance();
+void SwapChain::Create(int w, int h)
+{
+ auto *instance = device->GetInstance();
- const auto& surfaceCapabilities = instance->GetSurfaceCapabilities();
+ const auto &surfaceCapabilities = instance->GetSurfaceCapabilities();
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(instance->GetSurfaceFormats());
VkPresentModeKHR presentMode = chooseSwapPresentMode(instance->GetPresentModes());
- VkExtent2D extent = chooseSwapExtent(surfaceCapabilities, GetGLFWWindow());
+ VkExtent2D extent {static_cast(w), static_cast(h)};
+ if (w == 0 || h == 0) {
+ extent = chooseSwapExtent(surfaceCapabilities, GetGLFWWindow());
+ }
uint32_t imageCount = surfaceCapabilities.minImageCount + 1;
imageCount = numBuffers > imageCount ? numBuffers : imageCount;
- if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount) {
+ if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount)
+ {
imageCount = surfaceCapabilities.maxImageCount;
}
@@ -104,18 +125,19 @@ void SwapChain::Create() {
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
- const auto& queueFamilyIndices = instance->GetQueueFamilyIndices();
- if (queueFamilyIndices[QueueFlags::Graphics] != queueFamilyIndices[QueueFlags::Present]) {
+ const auto &queueFamilyIndices = instance->GetQueueFamilyIndices();
+ if (queueFamilyIndices[QueueFlags::Graphics] != queueFamilyIndices[QueueFlags::Present])
+ {
// Images can be used across multiple queue families without explicit ownership transfers
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
unsigned int indices[] = {
static_cast(queueFamilyIndices[QueueFlags::Graphics]),
- static_cast(queueFamilyIndices[QueueFlags::Present])
- };
+ static_cast(queueFamilyIndices[QueueFlags::Present])};
createInfo.pQueueFamilyIndices = indices;
}
- else {
+ else
+ {
// An image is owned by one queue family at a time and ownership must be explicitly transfered between uses
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0;
@@ -138,7 +160,8 @@ void SwapChain::Create() {
createInfo.oldSwapchain = VK_NULL_HANDLE;
// Create swap chain
- if (vkCreateSwapchainKHR(device->GetVkDevice(), &createInfo, nullptr, &vkSwapChain) != VK_SUCCESS) {
+ if (vkCreateSwapchainKHR(device->GetVkDevice(), &createInfo, nullptr, &vkSwapChain) != VK_SUCCESS)
+ {
throw std::runtime_error("Failed to create swap chain");
}
@@ -151,59 +174,72 @@ void SwapChain::Create() {
vkSwapChainExtent = extent;
}
-void SwapChain::Destroy() {
+void SwapChain::Destroy()
+{
vkDestroySwapchainKHR(device->GetVkDevice(), vkSwapChain, nullptr);
}
-VkSwapchainKHR SwapChain::GetVkSwapChain() const {
+VkSwapchainKHR SwapChain::GetVkSwapChain() const
+{
return vkSwapChain;
}
-VkFormat SwapChain::GetVkImageFormat() const {
+VkFormat SwapChain::GetVkImageFormat() const
+{
return vkSwapChainImageFormat;
}
-VkExtent2D SwapChain::GetVkExtent() const {
+VkExtent2D SwapChain::GetVkExtent() const
+{
return vkSwapChainExtent;
}
-uint32_t SwapChain::GetIndex() const {
+uint32_t SwapChain::GetIndex() const
+{
return imageIndex;
}
-uint32_t SwapChain::GetCount() const {
+uint32_t SwapChain::GetCount() const
+{
return static_cast(vkSwapChainImages.size());
}
-VkImage SwapChain::GetVkImage(uint32_t index) const {
+VkImage SwapChain::GetVkImage(uint32_t index) const
+{
return vkSwapChainImages[index];
}
-VkSemaphore SwapChain::GetImageAvailableVkSemaphore() const {
+VkSemaphore SwapChain::GetImageAvailableVkSemaphore() const
+{
return imageAvailableSemaphore;
-
}
-VkSemaphore SwapChain::GetRenderFinishedVkSemaphore() const {
+VkSemaphore SwapChain::GetRenderFinishedVkSemaphore() const
+{
return renderFinishedSemaphore;
}
-void SwapChain::Recreate() {
+void SwapChain::Recreate(int w, int h)
+{
Destroy();
- Create();
+ Create(w, h);
}
-bool SwapChain::Acquire() {
- if (ENABLE_VALIDATION) {
+bool SwapChain::Acquire()
+{
+ if (ENABLE_VALIDATION)
+ {
// the validation layer implementation expects the application to explicitly synchronize with the GPU
vkQueueWaitIdle(device->GetQueue(QueueFlags::Present));
}
VkResult result = vkAcquireNextImageKHR(device->GetVkDevice(), vkSwapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
- if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
+ if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
+ {
throw std::runtime_error("Failed to acquire swap chain image");
}
- if (result == VK_ERROR_OUT_OF_DATE_KHR) {
+ if (result == VK_ERROR_OUT_OF_DATE_KHR)
+ {
Recreate();
return false;
}
@@ -211,8 +247,9 @@ bool SwapChain::Acquire() {
return true;
}
-bool SwapChain::Present() {
- VkSemaphore signalSemaphores[] = { renderFinishedSemaphore };
+bool SwapChain::Present()
+{
+ VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
// Submit result back to swap chain for presentation
VkPresentInfoKHR presentInfo = {};
@@ -220,7 +257,7 @@ bool SwapChain::Present() {
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
- VkSwapchainKHR swapChains[] = { vkSwapChain };
+ VkSwapchainKHR swapChains[] = {vkSwapChain};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
@@ -228,11 +265,13 @@ bool SwapChain::Present() {
VkResult result = vkQueuePresentKHR(device->GetQueue(QueueFlags::Present), &presentInfo);
- if (result != VK_SUCCESS) {
+ if (result != VK_SUCCESS)
+ {
throw std::runtime_error("Failed to present swap chain image");
}
- if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
+ if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR)
+ {
Recreate();
return false;
}
@@ -240,7 +279,8 @@ bool SwapChain::Present() {
return true;
}
-SwapChain::~SwapChain() {
+SwapChain::~SwapChain()
+{
vkDestroySemaphore(device->GetVkDevice(), imageAvailableSemaphore, nullptr);
vkDestroySemaphore(device->GetVkDevice(), renderFinishedSemaphore, nullptr);
Destroy();
diff --git a/src/SwapChain.h b/src/SwapChain.h
index dbafcf0..318b41b 100644
--- a/src/SwapChain.h
+++ b/src/SwapChain.h
@@ -17,14 +17,14 @@ class SwapChain {
VkSemaphore GetImageAvailableVkSemaphore() const;
VkSemaphore GetRenderFinishedVkSemaphore() const;
- void Recreate();
+ void Recreate(int w = 0, int h = 0);
bool Acquire();
bool Present();
~SwapChain();
private:
SwapChain(Device* device, VkSurfaceKHR vkSurface, unsigned int numBuffers);
- void Create();
+ void Create(int w = 0, int h = 0);
void Destroy();
Device* device;
diff --git a/src/Window.cpp b/src/Window.cpp
index a365dc9..fc6b466 100644
--- a/src/Window.cpp
+++ b/src/Window.cpp
@@ -1,5 +1,6 @@
#include
#include "Window.h"
+#include
namespace {
GLFWwindow* window = nullptr;
diff --git a/src/main.cpp b/src/main.cpp
index 8bf822b..f9b9818 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,6 +5,9 @@
#include "Camera.h"
#include "Scene.h"
#include "Image.h"
+#include
+
+#define BENCH_FRAMES 1 << 16
Device* device;
SwapChain* swapChain;
@@ -16,7 +19,7 @@ namespace {
if (width == 0 || height == 0) return;
vkDeviceWaitIdle(device->GetVkDevice());
- swapChain->Recreate();
+ swapChain->Recreate(width, height);
renderer->RecreateFrameResources();
}
@@ -66,8 +69,8 @@ namespace {
}
int main() {
- static constexpr char* applicationName = "Vulkan Grass Rendering";
- InitializeWindow(640, 480, applicationName);
+ static constexpr char applicationName[] = "Vulkan Grass Rendering";
+ InitializeWindow(1280, 960, applicationName);
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
@@ -90,8 +93,15 @@ int main() {
swapChain = device->CreateSwapChain(surface, 5);
+ // Default Camera
camera = new Camera(device, 640.f / 480.f);
+ // Close Camera
+ // camera = new Camera(device, 640.f / 480.f, 0, 0, 0);
+
+ // Far Camera
+ // camera = new Camera(device, 640.f / 480.f, 50, 45, 60);
+
VkCommandPoolCreateInfo transferPoolInfo = {};
transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
transferPoolInfo.queueFamilyIndex = device->GetInstance()->GetQueueFamilyIndices()[QueueFlags::Transfer];
@@ -106,7 +116,7 @@ int main() {
VkDeviceMemory grassImageMemory;
Image::FromFile(device,
transferCommandPool,
- "images/grass.jpg",
+ "src/images/grass.jpg",
VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_SAMPLED_BIT,
@@ -143,11 +153,25 @@ int main() {
glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback);
glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback);
+ #ifdef BENCH_FRAMES
+ double start = glfwGetTime();
+ for (size_t i = 0; i < BENCH_FRAMES; i++) {
+ if (ShouldQuit()) {
+ exit(1);
+ }
+ glfwPollEvents();
+ scene->UpdateTime();
+ renderer->Frame();
+ }
+ double end = glfwGetTime();
+ std::cout << end - start << std::endl;
+ #else
while (!ShouldQuit()) {
glfwPollEvents();
scene->UpdateTime();
renderer->Frame();
}
+ #endif
vkDeviceWaitIdle(device->GetVkDevice());
diff --git a/src/shaders/compute.comp b/src/shaders/compute.comp
index 0fd0224..f859e5d 100644
--- a/src/shaders/compute.comp
+++ b/src/shaders/compute.comp
@@ -21,11 +21,27 @@ struct Blade {
vec4 up;
};
+#define ORIENTATION_CULL
+// #define FRUSTUM_CULL
+// #define DISTANCE_CULL
+
+#ifdef DISTANCE_CULL
+#define MAX_DIST 50
+#endif
+
// TODO: Add bindings to:
// 1. Store the input blades
// 2. Write out the culled blades
// 3. Write the total number of blades remaining
+layout(set = 2, binding = 0) buffer InputBlades {
+ Blade inputBlades[];
+};
+
+layout(set = 2, binding = 1) buffer CulledBlades {
+ Blade culledBlades[];
+};
+
// 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
//
@@ -36,21 +52,113 @@ struct Blade {
// 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;
+
+const vec4 D = vec4(0, -1, 0, 1);
+
bool inBounds(float value, float bounds) {
return (value >= -bounds) && (value <= bounds);
}
+const float TOLERANCE = 0.1;
+bool inFrustum(vec3 point) {
+ vec4 p = camera.proj * camera.view * vec4(point, 1);
+ return inBounds(p.x, p.w + TOLERANCE) && inBounds(p.x, p.w + TOLERANCE) && p.z > 0 && p.z < p.w;
+}
+
+float rand(vec3 co){
+ return fract(sin(dot(co, vec3(12.9898, 78.233, 92.7345))) * 43758.5453);
+}
+
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 = inputBlades[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 = vec3(cos(orientation), 0, sin(orientation)); // tangent
+ vec3 f = normalize(cross(up, t1)); // front direction
+
+ vec3 gE = D.xyz * D.w; // environmental gravity
+ vec3 gF = 0.25 * D.w * f; // front gravity
+ vec3 gravity = gE + gF; // total gravity force
+
+ vec3 iv2 = v0 + up * height; // initial v2
+ vec3 recovery = (iv2 - v2) * stiffness; // recovery force
+
+ vec3 wi = 5 * sin(length(v0) + totalTime) * normalize(v0); // wind strength: TODO: use more interesting value than constant
+
+ float fd = 1 - abs(dot(normalize(wi), normalize(v2 - v0))); // directional alignment
+ float fr = dot(v2 - v0, up) / height; // height ratio
+ vec3 wind = fd * fr * wi; // wind force
+
+ vec3 translation = gravity + recovery + wind;
+ v2 += translation * deltaTime; // initial displacement
+ v2 -= up * min(dot(up, v2 - v0), 0); // state validation
+
+ // Initial placement of v1
+ vec3 v20 = v2 - v0;
+ float l_proj = length(v20 - up * dot(v20, up));
+ v1 = v0 + height * up * max(1 - l_proj / height, 0.05 * max(l_proj / height, 1));
+
+ // Length correction
+ vec3 v10 = v1 - v0;
+ vec3 v21 = v2 - v1;
+ float L0 = length(v20);
+ float L1 = 0.5 * length(v10) * length(v21);
+ float L = 0.5 * (L0 + L1);
+ float r = height / L;
+ v1 = v0 + r * v10;
+ v2 = v1 + r * v21;
+
+ inputBlades[gl_GlobalInvocationID.x].v1.xyz = v1;
+ inputBlades[gl_GlobalInvocationID.x].v2.xyz = v2;
+
+ #ifdef ORIENTATION_CULL
+ vec3 grassDir = normalize(vec3(camera.view * vec4(t1, 0)));
+ vec3 camDir = normalize(vec3(camera.view * vec4(v0, 1)));
+ if (abs(dot(grassDir, camDir)) > 0.90) {
+ return;
+ }
+ #endif
+
+ #ifdef FRUSTUM_CULL
+ if (!(inFrustum(v0) || inFrustum(0.25 * v0 + 0.5 * v1 + 0.25 * v2) || inFrustum(v2))) {
+ return;
+ }
+ #endif
+
+ #ifdef DISTANCE_CULL
+ float distance = -(camera.view * vec4(v0, 1)).z;
+ float dist_ratio = distance / MAX_DIST;
+ float cullProb = dist_ratio * dist_ratio;
+ if (rand(v0) < cullProb) {
+ return;
+ }
+ #endif
+
// 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
+
+ culledBlades[atomicAdd(numBlades.vertexCount, 1)] = inputBlades[gl_GlobalInvocationID.x];
}
diff --git a/src/shaders/grass.frag b/src/shaders/grass.frag
index c7df157..c04dc23 100644
--- a/src/shaders/grass.frag
+++ b/src/shaders/grass.frag
@@ -8,10 +8,15 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
// TODO: Declare fragment shader inputs
+layout(location = 0) in vec3 frag_normal;
+
layout(location = 0) out vec4 outColor;
+const vec3 lightDir = vec3(1, -1, 0);
+
void main() {
+ float lambert = abs(dot(lightDir, frag_normal));
// TODO: Compute fragment color
-
- outColor = vec4(1.0);
+ // outColor = vec4((frag_normal + 1) / 2, 1) - vec4(0, 0.5, 0, 0);
+ outColor = vec4(0, lambert, 0, 1);
}
diff --git a/src/shaders/grass.tesc b/src/shaders/grass.tesc
index f9ffd07..90bf041 100644
--- a/src/shaders/grass.tesc
+++ b/src/shaders/grass.tesc
@@ -9,18 +9,33 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} camera;
// TODO: Declare tessellation control shader inputs and outputs
+layout(location = 0) in vec4 tesc_v0[];
+layout(location = 1) in vec4 tesc_v1[];
+layout(location = 2) in vec4 tesc_v2[];
+layout(location = 3) in vec4 tesc_up[];
+
+layout(location = 0) out vec4 tese_v0[];
+layout(location = 1) out vec4 tese_v1[];
+layout(location = 2) out vec4 tese_v2[];
+layout(location = 3) out vec4 tese_up[];
+
+int tes_level = 16;
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
+ tese_v0[gl_InvocationID] = tesc_v0[gl_InvocationID];
+ tese_v1[gl_InvocationID] = tesc_v1[gl_InvocationID];
+ tese_v2[gl_InvocationID] = tesc_v2[gl_InvocationID];
+ tese_up[gl_InvocationID] = tesc_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] = tes_level;
+ gl_TessLevelInner[1] = tes_level;
+ gl_TessLevelOuter[0] = tes_level;
+ gl_TessLevelOuter[1] = tes_level;
+ gl_TessLevelOuter[2] = tes_level;
+ gl_TessLevelOuter[3] = tes_level;
}
diff --git a/src/shaders/grass.tese b/src/shaders/grass.tese
index 751fff6..8dd3eb7 100644
--- a/src/shaders/grass.tese
+++ b/src/shaders/grass.tese
@@ -9,10 +9,42 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
} camera;
// TODO: Declare tessellation evaluation shader inputs and outputs
+layout(location = 0) in vec4 tese_v0[];
+layout(location = 1) in vec4 tese_v1[];
+layout(location = 2) in vec4 tese_v2[];
+layout(location = 3) in vec4 tese_up[];
+
+layout(location = 0) out vec3 frag_normal;
void main() {
+ // TODO: 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;
- // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade
+ vec3 v0 = tese_v0[0].xyz;
+ vec3 v1 = tese_v1[0].xyz;
+ vec3 v2 = tese_v2[0].xyz;
+ vec3 up = tese_up[0].xyz;
+
+ float orientation = tese_v0[0].w;
+ vec3 t1 = vec3(cos(orientation), 0, sin(orientation));
+
+ float h = tese_v1[0].w;
+ float w = tese_v2[0].w;
+
+ // De Casteljeau's bezier
+ vec3 a = mix(v0, v1, v);
+ vec3 b = mix(v1, v2, v);
+ vec3 t0 = b - a;
+
+ frag_normal = normalize(cross(t0, t1));
+
+ vec3 c = mix(a, b, v);
+
+ vec3 c0 = c - w * t1;
+ vec3 c1 = c + w * t1;
+
+ vec3 pos = mix(c0, c1, u - u * v * v);
+
+ gl_Position = camera.proj * camera.view * vec4(pos, 1);
}
diff --git a/src/shaders/grass.vert b/src/shaders/grass.vert
index db9dfe9..7743fd6 100644
--- a/src/shaders/grass.vert
+++ b/src/shaders/grass.vert
@@ -7,11 +7,28 @@ layout(set = 1, binding = 0) uniform ModelBufferObject {
};
// TODO: Declare vertex shader inputs and outputs
+layout(location = 0) in vec4 vert_v0;
+layout(location = 1) in vec4 vert_v1;
+layout(location = 2) in vec4 vert_v2;
+layout(location = 3) in vec4 vert_up;
-out gl_PerVertex {
- vec4 gl_Position;
-};
+layout(location = 0) out vec4 tesc_v0;
+layout(location = 1) out vec4 tesc_v1;
+layout(location = 2) out vec4 tesc_v2;
+layout(location = 3) out vec4 tesc_up;
+
+vec4 apply_model(vec4 v) {
+ vec4 res = model * vec4(v.xyz, 1);
+ res.w = v.w;
+ return res;
+}
void main() {
// TODO: Write gl_Position and any other shader outputs
+ tesc_v0 = apply_model(vert_v0);
+ tesc_v1 = apply_model(vert_v1);
+ tesc_v2 = apply_model(vert_v2);
+ tesc_up = apply_model(vert_up);
+
+ gl_Position = tesc_v0;
}