diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..540b00747eee74a94be3c987d1ac4c8595738a09 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,20 @@ +Alexander Gauggel +Artur Wasmut +Josch Morgenstern +Katharina Krämer +Lars Hoerttrich +Leonie Franken +Mara Vogt +Mark O. Mints +Sebastian Gaida +Simeon Hermann +Susanne Dötsch +Tobias Frisch +Trevor Hollmann +Vanessa Karolek + + + + + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a0858b970dead0233261c4f3c126a0bf64732f7..5f82b94af2a98dfb6700fb098a6b2a84adf7b9a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,9 @@ endif() # configure everything to use the required dependencies include(${vkcv_config}/Libraries.cmake) +# set macro to enable vulkan debug labels +list(APPEND vkcv_definitions VULKAN_DEBUG_LABELS) + # set the compile definitions aka preprocessor variables add_compile_definitions(${vkcv_definitions}) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..c1f3c5685b29d78b26dcc8c9e454b74c33f73ba5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Universität Koblenz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp index 01d1455c20bc99d1956bd73a8a65eaef3350523c..028f8bcc10c3483c417462bc9382e45ae24e74d6 100644 --- a/include/vkcv/Core.hpp +++ b/include/vkcv/Core.hpp @@ -418,6 +418,16 @@ namespace vkcv void prepareSwapchainImageForPresent(const CommandStreamHandle& handle); void prepareImageForSampling(const CommandStreamHandle& cmdStream, const ImageHandle& image); void prepareImageForStorage(const CommandStreamHandle& cmdStream, const ImageHandle& image); + + // normally layout transitions for attachments are handled by the core + // however for manual vulkan use, e.g. ImGui integration, this function is exposed + // this is also why the command buffer is passed directly, instead of the command stream handle + void prepareImageForAttachmentManually(const vk::CommandBuffer& cmdBuffer, const ImageHandle& image); + + // if manual vulkan work, e.g. ImGui integration, changes an image layout this function must be used + // to update the internal image state + void updateImageLayoutManual(const vkcv::ImageHandle& image, const vk::ImageLayout layout); + void recordImageMemoryBarrier(const CommandStreamHandle& cmdStream, const ImageHandle& image); void recordBufferMemoryBarrier(const CommandStreamHandle& cmdStream, const BufferHandle& buffer); void resolveMSAAImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst); diff --git a/modules/gui/src/vkcv/gui/GUI.cpp b/modules/gui/src/vkcv/gui/GUI.cpp index 22c40d2937c69525c04ffd79f26107f829e42f4d..7ee335379603b3e21ab4d95f0738097bd954cf71 100644 --- a/modules/gui/src/vkcv/gui/GUI.cpp +++ b/modules/gui/src/vkcv/gui/GUI.cpp @@ -6,6 +6,9 @@ namespace vkcv::gui { + const static vk::ImageLayout initialImageLayout = vk::ImageLayout::eColorAttachmentOptimal; + const static vk::ImageLayout finalImageLayout = vk::ImageLayout::ePresentSrcKHR; + static void checkVulkanResult(VkResult resultCode) { if (resultCode == 0) return; @@ -95,8 +98,8 @@ namespace vkcv::gui { vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, - vk::ImageLayout::eUndefined, - vk::ImageLayout::ePresentSrcKHR + initialImageLayout, + finalImageLayout ); const vk::AttachmentReference attachmentReference ( @@ -199,7 +202,7 @@ namespace vkcv::gui { const Swapchain& swapchain = m_core.getSwapchain(m_windowHandle); const auto extent = swapchain.getExtent(); - + const vk::ImageView swapchainImageView = m_core.getSwapchainImageView(); const vk::FramebufferCreateInfo framebufferCreateInfo ( @@ -218,6 +221,10 @@ namespace vkcv::gui { submitInfo.queueType = QueueType::Graphics; m_core.recordAndSubmitCommandsImmediate(submitInfo, [&](const vk::CommandBuffer& commandBuffer) { + + assert(initialImageLayout == vk::ImageLayout::eColorAttachmentOptimal); + m_core.prepareImageForAttachmentManually(commandBuffer, vkcv::ImageHandle::createSwapchainImageHandle()); + const vk::Rect2D renderArea ( vk::Offset2D(0, 0), extent @@ -230,12 +237,17 @@ namespace vkcv::gui { 0, nullptr ); - + commandBuffer.beginRenderPass(beginInfo, vk::SubpassContents::eInline); ImGui_ImplVulkan_RenderDrawData(drawData, static_cast<VkCommandBuffer>(commandBuffer)); commandBuffer.endRenderPass(); + + // executing the renderpass changed the image layout without going through the image manager + // therefore the layout must be updated manually + m_core.updateImageLayoutManual(vkcv::ImageHandle::createSwapchainImageHandle(), finalImageLayout); + }, [&]() { m_context.getDevice().destroyFramebuffer(framebuffer); }); diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 07623300db36f655c9ed94136c12b604d7713ca0..52d4a4a21f001b9feaab917fcdac4d52c6bf7e63 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -5,6 +5,9 @@ add_subdirectory(first_mesh) add_subdirectory(first_scene) add_subdirectory(particle_simulation) add_subdirectory(rtx_ambient_occlusion) +add_subdirectory(sph) add_subdirectory(voxelization) add_subdirectory(mesh_shader) +add_subdirectory(saf_r) add_subdirectory(indirect_dispatch) +add_subdirectory(path_tracer) \ No newline at end of file diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp index 5ef277dfedbfa823a2b6fa55c5a6303117ddaa52..0871631827b87539bbe9b0050420088e199a39af 100644 --- a/projects/first_mesh/src/main.cpp +++ b/projects/first_mesh/src/main.cpp @@ -101,7 +101,7 @@ int main(int argc, const char** argv) { // since we only use one descriptor set (namely, desc set 0), directly address it // recreate copies of the bindings and the handles (to check whether they are properly reused instead of actually recreated) - std::unordered_map<uint32_t, vkcv::DescriptorBinding> set0Bindings = firstMeshProgram.getReflectedDescriptors().at(0); + const vkcv::DescriptorBindings& set0Bindings = firstMeshProgram.getReflectedDescriptors().at(0); auto set0BindingsExplicitCopy = set0Bindings; vkcv::DescriptorSetLayoutHandle setLayoutHandle = core.createDescriptorSetLayout(set0Bindings); diff --git a/projects/indirect_dispatch/assets/shaders/motionVectorMinMax.comp b/projects/indirect_dispatch/assets/shaders/motionVectorMinMax.comp index 4ad350b0d5300aa63a66d7aceb00ea0b642d07ee..06b1b98f37579ae33406691bf19999d42ab7eb83 100644 --- a/projects/indirect_dispatch/assets/shaders/motionVectorMinMax.comp +++ b/projects/indirect_dispatch/assets/shaders/motionVectorMinMax.comp @@ -12,6 +12,7 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; void main(){ ivec2 outImageRes = imageSize(outMotionMax); + ivec2 inImageRes = textureSize(sampler2D(inMotion, textureSampler), 0); ivec2 motionTileCoord = ivec2(gl_GlobalInvocationID.xy); if(any(greaterThanEqual(motionTileCoord, outImageRes))) @@ -28,6 +29,14 @@ void main(){ for(int x = 0; x < motionTileSize; x++){ for(int y = 0; y < motionTileSize; y++){ ivec2 sampleCoord = motionBufferBaseCoord + ivec2(x, y); + + bool sampleIsOutsideImage = false; + sampleIsOutsideImage = sampleIsOutsideImage || any(greaterThanEqual(sampleCoord, inImageRes)); + sampleIsOutsideImage = sampleIsOutsideImage || any(lessThan(sampleCoord, ivec2(0))); + + if(sampleIsOutsideImage) + continue; + vec2 motionSample = texelFetch(sampler2D(inMotion, textureSampler), sampleCoord, 0).rg; float velocitySample = length(motionSample); diff --git a/projects/indirect_dispatch/assets/shaders/motionVectorMinMaxNeighbourhood.comp b/projects/indirect_dispatch/assets/shaders/motionVectorMinMaxNeighbourhood.comp index 4d6e7c0af6115e816ba087570e5585ffde23b1e6..3f836341a97a683efe88f41416d541624be03a0e 100644 --- a/projects/indirect_dispatch/assets/shaders/motionVectorMinMaxNeighbourhood.comp +++ b/projects/indirect_dispatch/assets/shaders/motionVectorMinMaxNeighbourhood.comp @@ -12,6 +12,7 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; void main(){ ivec2 outImageRes = imageSize(outMotionMaxNeighbourhood); + ivec2 inImageRes = textureSize(sampler2D(inMotionMax, textureSampler), 0); ivec2 motionTileCoord = ivec2(gl_GlobalInvocationID.xy); if(any(greaterThanEqual(motionTileCoord, outImageRes))) @@ -27,6 +28,13 @@ void main(){ for(int y = -1; y <= 1; y++){ ivec2 sampleCoord = motionTileCoord + ivec2(x, y); + bool sampleIsOutsideImage = false; + sampleIsOutsideImage = sampleIsOutsideImage || any(greaterThanEqual(sampleCoord, inImageRes)); + sampleIsOutsideImage = sampleIsOutsideImage || any(lessThan(sampleCoord, ivec2(0))); + + if(sampleIsOutsideImage) + continue; + vec2 motionSampleMax = texelFetch(sampler2D(inMotionMax, textureSampler), sampleCoord, 0).rg; float velocitySampleMax = length(motionSampleMax); diff --git a/projects/indirect_dispatch/assets/shaders/motionVectorVisualisation.comp b/projects/indirect_dispatch/assets/shaders/motionVectorVisualisation.comp index 1cfb09c87e8288b8ea80c6ddfbe5f0d4918b7f2e..fdceb575feaf24e7114bbcf223585a28955f45b8 100644 --- a/projects/indirect_dispatch/assets/shaders/motionVectorVisualisation.comp +++ b/projects/indirect_dispatch/assets/shaders/motionVectorVisualisation.comp @@ -21,7 +21,10 @@ void main(){ if(any(greaterThanEqual(coord, outImageRes))) return; - vec2 motionVector = texelFetch(sampler2D(inMotion, textureSampler), coord / motionTileSize, 0).rg; + vec2 uv = (coord + 0.5) / vec2(outImageRes); + ivec2 inTextureRes = textureSize(sampler2D(inMotion, textureSampler), 0); + + vec2 motionVector = texelFetch(sampler2D(inMotion, textureSampler), ivec2(uv * inTextureRes), 0).rg; vec2 motionVectorNormalized = clamp(motionVector / range, -1, 1); vec2 color = motionVectorNormalized * 0.5 + 0.5; diff --git a/projects/indirect_dispatch/src/App.cpp b/projects/indirect_dispatch/src/App.cpp index d4afc61c7421bd45c773bbdbc3da796b868869d2..532cef11db5b2bb8b5d741f6505507ff23fa4163 100644 --- a/projects/indirect_dispatch/src/App.cpp +++ b/projects/indirect_dispatch/src/App.cpp @@ -28,7 +28,7 @@ App::App() : VK_MAKE_VERSION(0, 0, 1), { vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer }, { VK_KHR_SWAPCHAIN_EXTENSION_NAME })), - m_windowHandle(m_core.createWindow(m_applicationName, m_windowWidth, m_windowHeight, false)), + m_windowHandle(m_core.createWindow(m_applicationName, m_windowWidth, m_windowHeight, true)), m_cameraManager(m_core.getWindow(m_windowHandle)){} bool App::initialize() { diff --git a/projects/particle_simulation/src/main.cpp b/projects/particle_simulation/src/main.cpp index ddbe6195e66e78504d4bccb32b3b09ff680ab414..ae3c6795a66cdc81297986acb224a63055d02c44 100644 --- a/projects/particle_simulation/src/main.cpp +++ b/projects/particle_simulation/src/main.cpp @@ -22,7 +22,7 @@ int main(int argc, const char **argv) { {vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute}, { VK_KHR_SWAPCHAIN_EXTENSION_NAME } ); - vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true); + vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, false); vkcv::Window& window = core.getWindow(windowHandle); vkcv::camera::CameraManager cameraManager(window); @@ -61,7 +61,7 @@ int main(int argc, const char **argv) { shaderPathFragment = "shaders/shader_space.frag"; } else if (strcmp(argv[i], "--water") == 0) { - shaderPathCompute = "shaders/shader_water.comp"; + shaderPathCompute = "shaders/shader_water1.comp"; shaderPathFragment = "shaders/shader_water.frag"; } else if (strcmp(argv[i], "--gravity") == 0) { diff --git a/projects/path_tracer/.gitignore b/projects/path_tracer/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ff3dff30031efafa24269a9ac0ef93f64f63ded1 --- /dev/null +++ b/projects/path_tracer/.gitignore @@ -0,0 +1 @@ +saf_r \ No newline at end of file diff --git a/projects/path_tracer/CMakeLists.txt b/projects/path_tracer/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b8edc208c70a5e8e74c6c28221f783a68a3ec6c --- /dev/null +++ b/projects/path_tracer/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.16) +project(path_tracer) + +# setting c++ standard for the project +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# this should fix the execution path to load local files from the project +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +# adding source files to the project +add_executable(path_tracer + src/main.cpp) + +# this should fix the execution path to load local files from the project (for MSVC) +if(MSVC) + set_target_properties(path_tracer PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + set_target_properties(path_tracer PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + + # in addition to setting the output directory, the working directory has to be set + # by default visual studio sets the working directory to the build directory, when using the debugger + set_target_properties(path_tracer PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +endif() + +# including headers of dependencies and the VkCV framework +target_include_directories(path_tracer SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include}) + +# linking with libraries from all dependencies and the VkCV framework +target_link_libraries(path_tracer vkcv vkcv_testing vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler vkcv_gui) diff --git a/projects/path_tracer/shaders/clearImage.comp b/projects/path_tracer/shaders/clearImage.comp new file mode 100644 index 0000000000000000000000000000000000000000..97998e945112d166be7d00df98ee44ea8322a633 --- /dev/null +++ b/projects/path_tracer/shaders/clearImage.comp @@ -0,0 +1,17 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +layout(set=0, binding=0, rgba32f) uniform image2D outImage; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +void main(){ + + ivec2 outImageRes = imageSize(outImage); + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + + if(any(greaterThanEqual(coord, outImageRes))) + return; + + imageStore(outImage, coord, vec4(0)); +} \ No newline at end of file diff --git a/projects/path_tracer/shaders/combineImages.comp b/projects/path_tracer/shaders/combineImages.comp new file mode 100644 index 0000000000000000000000000000000000000000..d1a4e85caf175dfc3125afd847d7458ddec2fef1 --- /dev/null +++ b/projects/path_tracer/shaders/combineImages.comp @@ -0,0 +1,21 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +layout(set=0, binding=0, rgba32f) uniform image2D newImage; +layout(set=0, binding=1, rgba32f) uniform image2D meanImage; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +void main(){ + + ivec2 outImageRes = imageSize(meanImage); + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + + if(any(greaterThanEqual(coord, outImageRes))) + return; + + vec4 colorNew = imageLoad(newImage, coord); + vec4 colorMean = imageLoad(meanImage, coord); + + imageStore(meanImage, coord, colorNew + colorMean); +} \ No newline at end of file diff --git a/projects/path_tracer/shaders/path_tracer.comp b/projects/path_tracer/shaders/path_tracer.comp new file mode 100644 index 0000000000000000000000000000000000000000..f08bdfd123ede964befe5feed4ba9f438dc0a498 --- /dev/null +++ b/projects/path_tracer/shaders/path_tracer.comp @@ -0,0 +1,430 @@ +#version 450 core +#extension GL_ARB_separate_shader_objects : enable + +const float pi = 3.1415926535897932384626433832795; +const float hitBias = 0.0001; // used to offset hits to avoid self intersection +const float denomMin = 0.001; + +layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +struct Material { + vec3 emission; + float ks; // specular percentage + vec3 albedo; + float r; // roughness + vec3 f0; + float padding; +}; + +struct Sphere{ + vec3 center; + float radius; + int materialIndex; + float padding[3]; +}; + +struct Plane{ + vec3 center; + int materialIndex; + vec3 N; + float padding1; + vec2 extent; + vec2 padding2; +}; + +layout(std430, binding = 0) buffer spheres{ + Sphere inSpheres[]; +}; + +layout(std430, binding = 1) buffer planes{ + Plane inPlanes[]; +}; + +layout(std430, binding = 2) buffer materials{ + Material inMaterials[]; +}; + +layout(set=0, binding = 3, rgba32f) uniform image2D outImage; + +layout( push_constant ) uniform constants{ + mat4 viewToWorld; + vec3 skyColor; + int sphereCount; + int planeCount; + int frameIndex; +}; + +// ---- Intersection functions ---- + +struct Ray{ + vec3 origin; + vec3 direction; +}; + +struct Intersection{ + bool hit; + float distance; + vec3 pos; + vec3 N; + Material material; +}; + +// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection +Intersection raySphereIntersect(Ray ray, Sphere sphere){ + + Intersection intersection; + intersection.hit = false; + + vec3 L = sphere.center - ray.origin; + float tca = dot(L, ray.direction); + float d2 = dot(L, L) - tca * tca; + + if (d2 > sphere.radius * sphere.radius){ + return intersection; + } + float thc = float(sqrt(sphere.radius * sphere.radius - d2)); + float t0 = tca - thc; + float t1 = tca + thc; + + if (t0 < 0) + t0 = t1; + + if (t0 < 0) + return intersection; + + intersection.hit = true; + intersection.distance = t0; + intersection.pos = ray.origin + ray.direction * intersection.distance; + intersection.N = normalize(intersection.pos - sphere.center); + intersection.material = inMaterials[sphere.materialIndex]; + + return intersection; +} + +struct Basis{ + vec3 right; + vec3 up; + vec3 forward; +}; + +Basis buildBasisAroundNormal(vec3 N){ + Basis basis; + basis.up = N; + basis.right = abs(basis.up.x) < 0.99 ? vec3(1, 0, 0) : vec3(0, 0, 1); + basis.forward = normalize(cross(basis.up, basis.right)); + basis.right = cross(basis.up, basis.forward); + return basis; +} + +// see: https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-plane-and-ray-disk-intersection +Intersection rayPlaneIntersect(Ray ray, Plane plane){ + + Intersection intersection; + intersection.hit = false; + + vec3 toPlane = plane.center - ray.origin; + float denom = dot(ray.direction, plane.N); + if(abs(denom) < 0.001) + return intersection; + + intersection.distance = dot(toPlane, plane.N) / denom; + + if(intersection.distance < 0) + return intersection; + + intersection.pos = ray.origin + ray.direction * intersection.distance; + + vec3 centerToIntersection = intersection.pos - plane.center; + Basis planeBasis = buildBasisAroundNormal(plane.N); + float projectedRight = dot(centerToIntersection, planeBasis.right); + float projectedUp = dot(centerToIntersection, planeBasis.forward); + + intersection.hit = abs(projectedRight) <= plane.extent.x && abs(projectedUp) <= plane.extent.y; + intersection.N = plane.N; + intersection.material = inMaterials[plane.materialIndex]; + + return intersection; +} + +Intersection sceneIntersect(Ray ray) { + float minDistance = 100000; // lets start with something big + + Intersection intersection; + intersection.hit = false; + + for (int i = 0; i < sphereCount; i++) { + Intersection sphereIntersection = raySphereIntersect(ray, inSpheres[i]); + if (sphereIntersection.hit && sphereIntersection.distance < minDistance) { + intersection = sphereIntersection; + minDistance = intersection.distance; + } + } + for (int i = 0; i < planeCount; i++){ + Intersection planeIntersection = rayPlaneIntersect(ray, inPlanes[i]); + if (planeIntersection.hit && planeIntersection.distance < minDistance) { + intersection = planeIntersection; + minDistance = intersection.distance; + } + } + return intersection; +} + +vec3 biasHitPosition(vec3 hitPos, vec3 rayDirection, vec3 N){ + // return hitPos + N * hitBias; // works as long as no refraction/transmission is used and camera is outside sphere + return hitPos + sign(dot(rayDirection, N)) * N * hitBias; +} + +// ---- noise/hash functions for pseudorandom variables ---- + +// extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences +vec2 r2Sequence(uint n){ + n = n % 42000; + const float g = 1.32471795724474602596; + return fract(vec2( + n / g, + n / (g*g))); +} + +// random() and helpers from: https://www.shadertoy.com/view/XlycWh +float g_seed = 0; + +uint base_hash(uvec2 p) { + p = 1103515245U*((p >> 1U)^(p.yx)); + uint h32 = 1103515245U*((p.x)^(p.y>>3U)); + return h32^(h32 >> 16); +} + +vec2 hash2(inout float seed) { + uint n = base_hash(floatBitsToUint(vec2(seed+=.1,seed+=.1))); + uvec2 rz = uvec2(n, n*48271U); + return vec2(rz.xy & uvec2(0x7fffffffU))/float(0x7fffffff); +} + +void initRandom(ivec2 coord){ + g_seed = float(base_hash(coord)/float(0xffffffffU)+frameIndex); +} + +vec2 random(){ + return hash2(g_seed); +} + +// ---- shading ---- + +vec3 lambertBRDF(vec3 albedo){ + return albedo / pi; +} + +vec3 computeDiffuseBRDF(Material material){ + return lambertBRDF(material.albedo); +} + +float distributionGGX(float r, float NoH){ + float r2 = r*r; + float denom = pi * pow(NoH*NoH * (r2-1) + 1, 2); + return r2 / max(denom, denomMin); +} + +float geometryGGXSmith(float r, float NoL){ + float r2 = r*r; + float denom = NoL + sqrt(r2 + (1-r2) * NoL*NoL); + return 2 * NoL / max(denom, denomMin); +} + +float geometryGGX(float r, float NoV, float NoL){ + return geometryGGXSmith(r, NoV) * geometryGGXSmith(r, NoL); +} + +vec3 fresnelSchlick(vec3 f0, float NoH){ + return f0 + (1 - f0) * pow(1 - NoH, 5); +} + +vec3 computeSpecularBRDF(vec3 f0, float r, float NoV, float NoL, float NoH){ + float denom = 4 * NoV * NoL; + float D = distributionGGX(r, NoH); + float G = geometryGGX(r, NoV, NoL); + vec3 F = fresnelSchlick(f0, NoH); + return D * F * G / max(denom, denomMin); +} + +// ---- pathtracing and main ---- + +// distributions: https://link.springer.com/content/pdf/10.1007/978-1-4842-4427-2_16.pdf +float cosineDistributionPDF(float NoL){ + return NoL / pi; +} + +vec3 sampleCosineDistribution(vec2 xi){ + float phi = 2 * pi * xi.y; + return vec3( + sqrt(xi.x) * cos(phi), + sqrt(1 - xi.x), + sqrt(xi.x) * sin(phi)); +} + +float uniformDistributionPDF(){ + return 1.f / (2 * pi); +} + +vec3 sampleUniformDistribution(vec2 xi){ + float phi = 2 * pi * xi.y; + return vec3( + sqrt(xi.x) * cos(phi), + 1 - xi.x, + sqrt(xi.x) * sin(phi)); +} + +float ggxDistributionPDF(float r, float NoH){ + return distributionGGX(r, NoH) * NoH; +} + +float ggxDistributionPDFReflected(float r, float NoH, float NoV){ + float jacobian = 0.25 / max(NoV, denomMin); + return ggxDistributionPDF(r, NoH) * jacobian; +} + +vec3 sampleGGXDistribution(vec2 xi, float r){ + float phi = 2 * pi * xi.y; + float cosTheta = sqrt((1 - xi.x) / ((r*r - 1) * xi.x + 1)); + float sinTheta = sqrt(1 - cosTheta*cosTheta); + return vec3( + cos(phi) * sinTheta, + cosTheta, + sin(phi) * sinTheta); +} + +vec3 sampleTangentToWorldSpace(vec3 tangentSpaceSample, vec3 N){ + Basis tangentBasis = buildBasisAroundNormal(N); + return + tangentBasis.right * tangentSpaceSample.x + + tangentBasis.up * tangentSpaceSample.y + + tangentBasis.forward * tangentSpaceSample.z; +} + +vec3 castRay(Ray ray) { + + vec3 throughput = vec3(1); + vec3 color = vec3(0); + + const int maxDepth = 10; + for(int i = 0; i < maxDepth; i++){ + + Intersection intersection = sceneIntersect(ray); + + vec3 hitLighting = vec3(0); + vec3 brdf = vec3(1); + + // V is where the ray came from and will lead back to the camera (over multiple bounces) + vec3 V = -normalize(ray.direction); + vec3 R = reflect(-V, intersection.N); + float NoV = max(dot(intersection.N, V), 0); + + intersection.material.r *= intersection.material.r; // remapping for perceuptual linearity + intersection.material.r = max(intersection.material.r, 0.01); + + float kd = 1 - intersection.material.ks; + bool sampleDiffuse = random().x < kd; + + vec3 sampleTangentSpace; + float pdf; + if(sampleDiffuse){ + sampleTangentSpace = sampleCosineDistribution(random()); + ray.direction = sampleTangentToWorldSpace(sampleTangentSpace, intersection.N); + + float NoL = max(dot(intersection.N, ray.direction), 0); + pdf = cosineDistributionPDF(NoL); + } + else{ + #define IMPORTANCE + + #ifdef IMPORTANCE + sampleTangentSpace = sampleGGXDistribution(random(), intersection.material.r); + ray.direction = sampleTangentToWorldSpace(sampleTangentSpace, R); + vec3 L = normalize(ray.direction); + pdf = ggxDistributionPDFReflected(intersection.material.r, max(sampleTangentSpace.y, 0.01), max(dot(intersection.N, V), 0.01)); + #else + sampleTangentSpace = sampleUniformDistribution(random()); + ray.direction = sampleTangentToWorldSpace(sampleTangentSpace, intersection.N); + pdf = uniformDistributionPDF(); + #endif + } + + ray.origin = biasHitPosition(intersection.pos, ray.direction, intersection.N); + + // L is where the ray is going, as that is the direction where light will from + vec3 L = normalize(ray.direction); + vec3 H = normalize(L + V); + + float NoL = max(dot(intersection.N, L), 0); + float NoH = max(dot(intersection.N, H), 0); + + if(intersection.hit){ + vec3 diffuseBRDF = computeDiffuseBRDF(intersection.material); + + vec3 specularBRDF = computeSpecularBRDF(intersection.material.f0, intersection.material.r, NoV, NoL, NoH); + brdf = mix(diffuseBRDF, specularBRDF, intersection.material.ks); + + + hitLighting = intersection.material.emission * max(sign(NoV), 0); // objects only emit in direction of normal + } + else{ + hitLighting = skyColor; + } + + color += hitLighting * throughput; + throughput *= brdf * NoL / max(pdf, denomMin); + + if(!intersection.hit) + break; + } + + return color; +} + +// coord must be in pixel coordinates, but already shifted to pixel center +vec3 computeCameraRay(vec2 coord){ + + ivec2 outImageRes = imageSize(outImage); + float fovDegree = 45; + float fov = fovDegree * pi / 180; + + vec2 uv = coord / vec2(outImageRes); + vec2 ndc = 2 * uv - 1; + + float tanFovHalf = tan(fov / 2.f); + float aspectRatio = outImageRes.x / float(outImageRes.y); + float x = ndc.x * tanFovHalf * aspectRatio; + float y = -ndc.y * tanFovHalf; + + // view direction goes through pixel on image plane with z=1 + vec3 directionViewSpace = normalize(vec3(x, y, 1)); + vec3 directionWorldSpace = mat3(viewToWorld) * directionViewSpace; + return directionWorldSpace; +} + +void main(){ + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + vec2 pixelSize = 1.f / coord; + initRandom(coord); + + Ray cameraRay; + cameraRay.origin = viewToWorld[3].xyz; + vec2 coordCentered = coord + 0.5; + + vec3 color = vec3(0); + + const int samplesPerPixel = 1; + for(int i = 0; i < samplesPerPixel; i++){ + vec2 jitter = r2Sequence(i + frameIndex) - 0.5; + cameraRay.direction = computeCameraRay(coordCentered + jitter); + color += castRay(cameraRay); + } + color /= samplesPerPixel; + + vec4 final = vec4(color, 1); + + // occasional NaNs in reflection, should be fixed properly + if(any(isnan(color))) + final = vec4(0); + + imageStore(outImage, coord, final); +} \ No newline at end of file diff --git a/projects/path_tracer/shaders/presentImage.comp b/projects/path_tracer/shaders/presentImage.comp new file mode 100644 index 0000000000000000000000000000000000000000..a52159c0c6173779b091e5d4153b15b0a6361780 --- /dev/null +++ b/projects/path_tracer/shaders/presentImage.comp @@ -0,0 +1,23 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +layout(set=0, binding=0, rgba32f) uniform image2D inImage; +layout(set=0, binding=1, rgba8) uniform image2D outImage; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +void main(){ + + ivec2 outImageRes = imageSize(outImage); + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + + if(any(greaterThanEqual(coord, outImageRes))) + return; + + vec4 colorRaw = imageLoad(inImage, coord); + vec3 colorNormalized = colorRaw.rgb / colorRaw.a; + vec3 colorTonemapped = colorNormalized / (1 + dot(colorNormalized, vec3(0.71, 0.21, 0.08))); // reinhard tonemapping + vec3 colorGammaCorrected = pow(colorTonemapped, vec3(1.f / 2.2)); + + imageStore(outImage, coord, vec4(colorGammaCorrected, 0)); +} \ No newline at end of file diff --git a/projects/path_tracer/src/main.cpp b/projects/path_tracer/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c3b21f78cea479a463fb6224605a202d35d8e581 --- /dev/null +++ b/projects/path_tracer/src/main.cpp @@ -0,0 +1,451 @@ +#include <vkcv/Core.hpp> +#include <vkcv/camera/CameraManager.hpp> +#include <vkcv/asset/asset_loader.hpp> +#include <vkcv/shader/GLSLCompiler.hpp> +#include "vkcv/gui/GUI.hpp" +#include <chrono> +#include <vector> + +int main(int argc, const char** argv) { + + // structs must match shader version + struct Material { + Material(const glm::vec3& emission, const glm::vec3& albedo, float ks, float roughness, const glm::vec3& f0) + : emission(emission), albedo(albedo), ks(ks), roughness(roughness), f0(f0){} + + glm::vec3 emission; + float ks; + glm::vec3 albedo; + float roughness; + glm::vec3 f0; + float padding; + }; + + struct Sphere { + Sphere(const glm::vec3& c, const float& r, const int m) : center(c), radius(r), materialIndex(m) {} + + glm::vec3 center; + float radius; + uint32_t materialIndex; + float padding[3]; + }; + + struct Plane { + Plane(const glm::vec3& c, const glm::vec3& n, const glm::vec2 e, int m) + : center(c), normal(n), extent(e), materialIndex(m) {} + + glm::vec3 center; + uint32_t materialIndex; + glm::vec3 normal; + float padding1; + glm::vec2 extent; + glm::vec2 padding3; + }; + + const char* applicationName = "Path Tracer"; + + const int initialWidth = 1280; + const int initialHeight = 720; + vkcv::Window window = vkcv::Window::create( + applicationName, + initialWidth, + initialHeight, + true); + + vkcv::Core core = vkcv::Core::create( + window, + applicationName, + VK_MAKE_VERSION(0, 0, 1), + { vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute }, + { "VK_KHR_swapchain" } + ); + + // images + vkcv::ImageHandle outputImage = core.createImage( + vk::Format::eR32G32B32A32Sfloat, + initialWidth, + initialHeight, + 1, + false, + true).getHandle(); + + vkcv::ImageHandle meanImage = core.createImage( + vk::Format::eR32G32B32A32Sfloat, + initialWidth, + initialHeight, + 1, + false, + true).getHandle(); + + vkcv::shader::GLSLCompiler compiler; + + // path tracing shader + vkcv::ShaderProgram traceShaderProgram{}; + + compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/path_tracer.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + traceShaderProgram.addShader(shaderStage, path); + }); + + const vkcv::DescriptorBindings& traceDescriptorBindings = traceShaderProgram.getReflectedDescriptors().at(0); + vkcv::DescriptorSetLayoutHandle traceDescriptorSetLayout = core.createDescriptorSetLayout(traceDescriptorBindings); + vkcv::DescriptorSetHandle traceDescriptorSet = core.createDescriptorSet(traceDescriptorSetLayout); + + // image combine shader + vkcv::ShaderProgram imageCombineShaderProgram{}; + + compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/combineImages.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + imageCombineShaderProgram.addShader(shaderStage, path); + }); + + const vkcv::DescriptorBindings& imageCombineDescriptorBindings = imageCombineShaderProgram.getReflectedDescriptors().at(0); + vkcv::DescriptorSetLayoutHandle imageCombineDescriptorSetLayout = core.createDescriptorSetLayout(imageCombineDescriptorBindings); + vkcv::DescriptorSetHandle imageCombineDescriptorSet = core.createDescriptorSet(imageCombineDescriptorSetLayout); + vkcv::PipelineHandle imageCombinePipeline = core.createComputePipeline( + imageCombineShaderProgram, + { core.getDescriptorSetLayout(imageCombineDescriptorSetLayout).vulkanHandle }); + + vkcv::DescriptorWrites imageCombineDescriptorWrites; + imageCombineDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(0, outputImage), + vkcv::StorageImageDescriptorWrite(1, meanImage) + }; + core.writeDescriptorSet(imageCombineDescriptorSet, imageCombineDescriptorWrites); + + // image present shader + vkcv::ShaderProgram presentShaderProgram{}; + + compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/presentImage.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + presentShaderProgram.addShader(shaderStage, path); + }); + + const vkcv::DescriptorBindings& presentDescriptorBindings = presentShaderProgram.getReflectedDescriptors().at(0); + vkcv::DescriptorSetLayoutHandle presentDescriptorSetLayout = core.createDescriptorSetLayout(presentDescriptorBindings); + vkcv::DescriptorSetHandle presentDescriptorSet = core.createDescriptorSet(presentDescriptorSetLayout); + vkcv::PipelineHandle presentPipeline = core.createComputePipeline( + presentShaderProgram, + { core.getDescriptorSetLayout(presentDescriptorSetLayout).vulkanHandle }); + + // clear shader + vkcv::ShaderProgram clearShaderProgram{}; + + compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/clearImage.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + clearShaderProgram.addShader(shaderStage, path); + }); + + const vkcv::DescriptorBindings& imageClearDescriptorBindings = clearShaderProgram.getReflectedDescriptors().at(0); + vkcv::DescriptorSetLayoutHandle imageClearDescriptorSetLayout = core.createDescriptorSetLayout(imageClearDescriptorBindings); + vkcv::DescriptorSetHandle imageClearDescriptorSet = core.createDescriptorSet(imageClearDescriptorSetLayout); + vkcv::PipelineHandle imageClearPipeline = core.createComputePipeline( + clearShaderProgram, + { core.getDescriptorSetLayout(imageClearDescriptorSetLayout).vulkanHandle }); + + vkcv::DescriptorWrites imageClearDescriptorWrites; + imageClearDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(0, meanImage) + }; + core.writeDescriptorSet(imageClearDescriptorSet, imageClearDescriptorWrites); + + // buffers + typedef std::pair<std::string, Material> MaterialSetting; + + std::vector<MaterialSetting> materialSettings; + materialSettings.emplace_back(MaterialSetting("white", Material(glm::vec3(0), glm::vec3(0.65), 0, 0.25, glm::vec3(0.04)))); + materialSettings.emplace_back(MaterialSetting("red", Material(glm::vec3(0), glm::vec3(0.5, 0.0, 0.0), 0, 0.25, glm::vec3(0.04)))); + materialSettings.emplace_back(MaterialSetting("green", Material(glm::vec3(0), glm::vec3(0.0, 0.5, 0.0), 0, 0.25, glm::vec3(0.04)))); + materialSettings.emplace_back(MaterialSetting("light", Material(glm::vec3(20), glm::vec3(0), 0, 0.25, glm::vec3(0.04)))); + materialSettings.emplace_back(MaterialSetting("sphere", Material(glm::vec3(0), glm::vec3(0.65), 1, 0.25, glm::vec3(0.04)))); + materialSettings.emplace_back(MaterialSetting("ground", Material(glm::vec3(0), glm::vec3(0.65), 0, 0.25, glm::vec3(0.04)))); + + const uint32_t whiteMaterialIndex = 0; + const uint32_t redMaterialIndex = 1; + const uint32_t greenMaterialIndex = 2; + const uint32_t lightMaterialIndex = 3; + const uint32_t sphereMaterialIndex = 4; + const uint32_t groundMaterialIndex = 5; + + std::vector<Sphere> spheres; + spheres.emplace_back(Sphere(glm::vec3(0, -1.5, 0), 0.5, sphereMaterialIndex)); + + std::vector<Plane> planes; + planes.emplace_back(Plane(glm::vec3( 0, -2, 0), glm::vec3( 0, 1, 0), glm::vec2(2), groundMaterialIndex)); + planes.emplace_back(Plane(glm::vec3( 0, 2, 0), glm::vec3( 0, -1, 0), glm::vec2(2), whiteMaterialIndex)); + planes.emplace_back(Plane(glm::vec3( 2, 0, 0), glm::vec3(-1, 0, 0), glm::vec2(2), redMaterialIndex)); + planes.emplace_back(Plane(glm::vec3(-2, 0, 0), glm::vec3( 1, 0, 0), glm::vec2(2), greenMaterialIndex)); + planes.emplace_back(Plane(glm::vec3( 0, 0, 2), glm::vec3( 0, 0, -1), glm::vec2(2), whiteMaterialIndex)); + planes.emplace_back(Plane(glm::vec3( 0, 1.9, 0), glm::vec3( 0, -1, 0), glm::vec2(1), lightMaterialIndex)); + + vkcv::Buffer<Sphere> sphereBuffer = core.createBuffer<Sphere>( + vkcv::BufferType::STORAGE, + spheres.size()); + sphereBuffer.fill(spheres); + + vkcv::Buffer<Plane> planeBuffer = core.createBuffer<Plane>( + vkcv::BufferType::STORAGE, + planes.size()); + planeBuffer.fill(planes); + + vkcv::Buffer<Material> materialBuffer = core.createBuffer<Material>( + vkcv::BufferType::STORAGE, + materialSettings.size()); + + vkcv::DescriptorWrites traceDescriptorWrites; + traceDescriptorWrites.storageBufferWrites = { + vkcv::BufferDescriptorWrite(0, sphereBuffer.getHandle()), + vkcv::BufferDescriptorWrite(1, planeBuffer.getHandle()), + vkcv::BufferDescriptorWrite(2, materialBuffer.getHandle())}; + traceDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(3, outputImage)}; + core.writeDescriptorSet(traceDescriptorSet, traceDescriptorWrites); + + vkcv::PipelineHandle tracePipeline = core.createComputePipeline( + traceShaderProgram, + { core.getDescriptorSetLayout(traceDescriptorSetLayout).vulkanHandle }); + + if (!tracePipeline) + { + vkcv_log(vkcv::LogLevel::ERROR, "Could not create graphics pipeline. Exiting."); + return EXIT_FAILURE; + } + + vkcv::camera::CameraManager cameraManager(window); + uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); + + cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -2)); + + auto startTime = std::chrono::system_clock::now(); + float time = 0; + int frameIndex = 0; + bool clearMeanImage = true; + bool updateMaterials = true; + + float cameraPitchPrevious = 0; + float cameraYawPrevious = 0; + glm::vec3 cameraPositionPrevious = glm::vec3(0); + + uint32_t widthPrevious = initialWidth; + uint32_t heightPrevious = initialHeight; + + vkcv::gui::GUI gui(core, window); + + bool renderUI = true; + window.e_key.add([&renderUI](int key, int scancode, int action, int mods) { + if (key == GLFW_KEY_I && action == GLFW_PRESS) { + renderUI = !renderUI; + } + }); + + glm::vec3 skyColor = glm::vec3(0.2, 0.7, 0.8); + float skyColorMultiplier = 1; + + while (window.isWindowOpen()) + { + vkcv::Window::pollEvents(); + + uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem + if (!core.beginFrame(swapchainWidth, swapchainHeight)) { + continue; + } + + if (swapchainWidth != widthPrevious || swapchainHeight != heightPrevious) { + + // resize images + outputImage = core.createImage( + vk::Format::eR32G32B32A32Sfloat, + swapchainWidth, + swapchainHeight, + 1, + false, + true).getHandle(); + + meanImage = core.createImage( + vk::Format::eR32G32B32A32Sfloat, + swapchainWidth, + swapchainHeight, + 1, + false, + true).getHandle(); + + // update descriptor sets + traceDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(3, outputImage) }; + core.writeDescriptorSet(traceDescriptorSet, traceDescriptorWrites); + + vkcv::DescriptorWrites imageCombineDescriptorWrites; + imageCombineDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(0, outputImage), + vkcv::StorageImageDescriptorWrite(1, meanImage) + }; + core.writeDescriptorSet(imageCombineDescriptorSet, imageCombineDescriptorWrites); + + vkcv::DescriptorWrites imageClearDescriptorWrites; + imageClearDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(0, meanImage) + }; + core.writeDescriptorSet(imageClearDescriptorSet, imageClearDescriptorWrites); + + widthPrevious = swapchainWidth; + heightPrevious = swapchainHeight; + + clearMeanImage = true; + } + + auto end = std::chrono::system_clock::now(); + auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - startTime); + startTime = end; + + time += 0.000001f * static_cast<float>(deltatime.count()); + + cameraManager.update(0.000001 * static_cast<double>(deltatime.count())); + + const vkcv::CommandStreamHandle cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); + + uint32_t fullscreenDispatchCount[3] = { + static_cast<uint32_t> (std::ceil(swapchainWidth / 8.f)), + static_cast<uint32_t> (std::ceil(swapchainHeight / 8.f)), + 1 }; + + if (updateMaterials) { + std::vector<Material> materials; + for (const auto& settings : materialSettings) { + materials.push_back(settings.second); + } + materialBuffer.fill(materials); + updateMaterials = false; + clearMeanImage = true; + } + + float cameraPitch; + float cameraYaw; + cameraManager.getActiveCamera().getAngles(cameraPitch, cameraYaw); + + if (glm::abs(cameraPitch - cameraPitchPrevious) > 0.01 || glm::abs(cameraYaw - cameraYawPrevious) > 0.01) + clearMeanImage = true; // camera rotated + + cameraPitchPrevious = cameraPitch; + cameraYawPrevious = cameraYaw; + + glm::vec3 cameraPosition = cameraManager.getActiveCamera().getPosition(); + + if(glm::distance(cameraPosition, cameraPositionPrevious) > 0.0001) + clearMeanImage = true; // camera moved + + cameraPositionPrevious = cameraPosition; + + if (clearMeanImage) { + core.prepareImageForStorage(cmdStream, meanImage); + + core.recordComputeDispatchToCmdStream(cmdStream, + imageClearPipeline, + fullscreenDispatchCount, + { vkcv::DescriptorSetUsage(0, core.getDescriptorSet(imageClearDescriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); + + clearMeanImage = false; + } + + // path tracing + struct RaytracingPushConstantData { + glm::mat4 viewToWorld; + glm::vec3 skyColor; + int32_t sphereCount; + int32_t planeCount; + int32_t frameIndex; + }; + + RaytracingPushConstantData raytracingPushData; + raytracingPushData.viewToWorld = glm::inverse(cameraManager.getActiveCamera().getView()); + raytracingPushData.skyColor = skyColor * skyColorMultiplier; + raytracingPushData.sphereCount = spheres.size(); + raytracingPushData.planeCount = planes.size(); + raytracingPushData.frameIndex = frameIndex; + + vkcv::PushConstants pushConstantsCompute(sizeof(RaytracingPushConstantData)); + pushConstantsCompute.appendDrawcall(raytracingPushData); + + uint32_t traceDispatchCount[3] = { + static_cast<uint32_t> (std::ceil(swapchainWidth / 16.f)), + static_cast<uint32_t> (std::ceil(swapchainHeight / 16.f)), + 1 }; + + core.prepareImageForStorage(cmdStream, outputImage); + + core.recordComputeDispatchToCmdStream(cmdStream, + tracePipeline, + traceDispatchCount, + { vkcv::DescriptorSetUsage(0,core.getDescriptorSet(traceDescriptorSet).vulkanHandle) }, + pushConstantsCompute); + + core.prepareImageForStorage(cmdStream, meanImage); + core.recordImageMemoryBarrier(cmdStream, outputImage); + + // combine images + core.recordComputeDispatchToCmdStream(cmdStream, + imageCombinePipeline, + fullscreenDispatchCount, + { vkcv::DescriptorSetUsage(0,core.getDescriptorSet(imageCombineDescriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); + + core.recordImageMemoryBarrier(cmdStream, meanImage); + + // present image + const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); + + vkcv::DescriptorWrites presentDescriptorWrites; + presentDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(0, meanImage), + vkcv::StorageImageDescriptorWrite(1, swapchainInput) }; + core.writeDescriptorSet(presentDescriptorSet, presentDescriptorWrites); + + core.prepareImageForStorage(cmdStream, swapchainInput); + + core.recordComputeDispatchToCmdStream(cmdStream, + presentPipeline, + fullscreenDispatchCount, + { vkcv::DescriptorSetUsage(0,core.getDescriptorSet(presentDescriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); + + core.prepareSwapchainImageForPresent(cmdStream); + core.submitCommandStream(cmdStream); + + if (renderUI) { + gui.beginGUI(); + + ImGui::Begin("Settings"); + + clearMeanImage |= ImGui::ColorEdit3("Sky color", &skyColor.x); + clearMeanImage |= ImGui::InputFloat("Sky color multiplier", &skyColorMultiplier); + + if (ImGui::CollapsingHeader("Materials")) { + + for (auto& setting : materialSettings) { + if (ImGui::CollapsingHeader(setting.first.c_str())) { + + const glm::vec3 emission = setting.second.emission; + float emissionStrength = glm::max(glm::max(glm::max(emission.x, emission.y), emission.z), 1.f); + glm::vec3 emissionColor = emission / emissionStrength; + + updateMaterials |= ImGui::ColorEdit3((std::string("Emission color ") + setting.first).c_str(), &emissionColor.x); + updateMaterials |= ImGui::InputFloat((std::string("Emission strength ") + setting.first).c_str(), &emissionStrength); + + setting.second.emission = emissionStrength * emissionColor; + + updateMaterials |= ImGui::ColorEdit3((std::string("Albedo color ") + setting.first).c_str(), &setting.second.albedo.x); + updateMaterials |= ImGui::ColorEdit3((std::string("F0 ") + setting.first).c_str(), &setting.second.f0.x); + updateMaterials |= ImGui::DragFloat(( std::string("ks ") + setting.first).c_str(), &setting.second.ks, 0.01, 0, 1); + updateMaterials |= ImGui::DragFloat(( std::string("roughness ") + setting.first).c_str(), &setting.second.roughness, 0.01, 0, 1); + + } + } + } + + ImGui::End(); + + gui.endGUI(); + } + + core.endFrame(); + + frameIndex++; + } + return 0; +} diff --git a/projects/saf_r/.gitignore b/projects/saf_r/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ff3dff30031efafa24269a9ac0ef93f64f63ded1 --- /dev/null +++ b/projects/saf_r/.gitignore @@ -0,0 +1 @@ +saf_r \ No newline at end of file diff --git a/projects/saf_r/CMakeLists.txt b/projects/saf_r/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..61ede8ae5a5cedac78ff5781aec20973854a3df7 --- /dev/null +++ b/projects/saf_r/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.16) +project(saf_r) + +# setting c++ standard for the project +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# this should fix the execution path to load local files from the project +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +# adding source files to the project +add_executable(saf_r + src/main.cpp + "src/safrScene.hpp" + ) + +# this should fix the execution path to load local files from the project (for MSVC) +if(MSVC) + set_target_properties(saf_r PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + set_target_properties(saf_r PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + + # in addition to setting the output directory, the working directory has to be set + # by default visual studio sets the working directory to the build directory, when using the debugger + set_target_properties(saf_r PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +endif() + +# including headers of dependencies and the VkCV framework +target_include_directories(saf_r SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include}) + +# linking with libraries from all dependencies and the VkCV framework +target_link_libraries(saf_r vkcv vkcv_testing vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler) diff --git a/projects/saf_r/shaders/raytracing.comp b/projects/saf_r/shaders/raytracing.comp new file mode 100644 index 0000000000000000000000000000000000000000..a7c6b92a646e5c2f753946f74fe7ab78aea44fe6 --- /dev/null +++ b/projects/saf_r/shaders/raytracing.comp @@ -0,0 +1,292 @@ +#version 450 core +#extension GL_ARB_separate_shader_objects : enable + +// defines constants +const float pi = 3.1415926535897932384626433832795; +const float hitBias = 0.01; // used to offset hits to avoid self intersection + +layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +//structs of materials, lights, spheres and intersection for use in compute shader +struct Material { + vec3 albedo; + vec3 diffuseColor; + float specularExponent; + float refractiveIndex; +}; + +struct Light{ + vec3 position; + float intensity; +}; + +struct Sphere{ + vec3 center; + float radius; + Material material; +}; + +struct Intersection{ + bool hit; + vec3 pos; + vec3 N; + Material material; +}; + + +//incoming light data +layout(std430, binding = 0) coherent buffer lights{ + Light inLights[]; +}; + +// incoming sphere data +layout(std430, binding = 1) coherent buffer spheres{ + Sphere inSpheres[]; +}; + +// output store image as swapchain input +layout(set=0, binding = 2, rgba8) uniform image2D outImage; + +// incoming constants, because size of dynamic arrays cannot be computed on gpu +layout( push_constant ) uniform constants{ + mat4 viewToWorld; + int lightCount; + int sphereCount; +}; + +/* +* safrReflect computes the new reflected or refracted ray depending on the material +* @param vec3: raydirection vector +* @param vec3: normalvector on which should be reflected or refracted +* @param float: degree of refraction. In case of simple reflection it is 1.0 +* @return vec3: new ray that is the result of the reflection or refraction +*/ +vec3 safrReflect(vec3 V, vec3 N, float refractIndex){ + if(refractIndex != 1.0){ + // Snell's law + float cosi = - max(-1.f, min(1.f, dot(V,N))); + float etai = 1; + float etat = refractIndex; + vec3 n = N; + float swap; + if(cosi < 0){ + cosi = -cosi; + n = -N; + swap = etai; + etai = etat; + etat = swap; + } + float eta = etai / etat; + float k = 1 - eta * eta * (1 - cosi * cosi); + if(k < 0){ + return vec3(0,0,0); + } else { + return V * eta + n * (eta * cosi - sqrt(k)); + } + }else{ + return reflect(V, N); + } +} + +/* +* the rayIntersect function checks, if a ray from the raytracer passes through the sphere, hits the sphere or passes by the the sphere +* @param vec3: origin of ray +* @param vec3: direction of ray +* @param float: distance of the ray to the sphere (out because there are no references in shaders) +* @return bool: if ray interesects sphere or not (out because there are no references in shaders) +*/ + +bool rayIntersect(const vec3 origin, const vec3 dir, out float t0, const int id){ + vec3 L = inSpheres[id].center - origin; + float tca = dot(L, dir); + float d2 = dot(L, L) - tca * tca; + if (d2 > inSpheres[id].radius * inSpheres[id].radius){ + return false; + } + float thc = float(sqrt(inSpheres[id].radius * inSpheres[id].radius - d2)); + t0 = tca - thc; + float t1 = tca + thc; + if (t0 < 0) { + t0 = t1; + } + if (t0 < 0){ + return false; + } + return true; +} + +/* +* sceneIntersect iterates over whole scene (over every single object) to check for intersections +* @param vec3: Origin of the ray +* @param vec3: direction of the ray +* @return: Intersection struct with hit(bool) position, normal and material of sphere +*/ + +Intersection sceneIntersect(const vec3 rayOrigin, const vec3 rayDirection) { + //distance if spheres will be rendered + float min_d = 1.0 / 0.0; // lets start with something big + + Intersection intersection; + intersection.hit = false; + + //go over every sphere, check if sphere is hit by ray, save if hit is near enough into intersection struct + for (int i = 0; i < sphereCount; i++) { + float d; + if (rayIntersect(rayOrigin, rayDirection, d, i)) { + + intersection.hit = true; + + if(d < min_d){ + min_d = d; + intersection.pos = rayOrigin + rayDirection * d; + intersection.N = normalize(intersection.pos - inSpheres[i].center); + intersection.material = inSpheres[i].material; + } + } + } + + float checkerboard_dist = min_d; + if (abs(rayDirection.y)>1e-3) { + float d = -(rayOrigin.y + 4) / rayDirection.y; // the checkerboard plane has equation y = -4 + vec3 pt = rayOrigin + rayDirection * d; + if (d > 0 && abs(pt.x) < 10 && pt.z<-10 && pt.z>-30 && d < min_d) { + checkerboard_dist = d; + intersection.hit = true; + intersection.pos = pt; + intersection.N = vec3(0, 1, 0); + intersection.material = inSpheres[0].material; + } + } + return intersection; +} + +/* +* biasHitPosition computes the new hitposition with respect to the raydirection and a bias +* @param vec3: Hit Position +* @param vec3: direction of ray +* @param vec3: N(ormal) +* @return vec3: new Hit position depending on hitBias (used to offset hits to avoid self intersection) +*/ +vec3 biasHitPosition(vec3 hitPos, vec3 rayDirection, vec3 N){ + return hitPos + sign(dot(rayDirection, N)) * N * hitBias; +} + +/* +* computeHitLighting iterates over all lights to compute the color for every ray +* @param Intersection: struct with all the data of the intersection +* @param vec3: Raydirection +* @param float: material albedo of the intersection +* @return colour/shadows of sphere with illumination +*/ +vec3 computeHitLighting(Intersection intersection, vec3 V, out float outReflectionThroughput){ + + float lightIntensityDiffuse = 0; + float lightIntensitySpecular = 0; + + //iterate over every light source to compute sphere colours/shadows + for (int i = 0; i < lightCount; i++) { + + //compute normal + distance between light and intersection + vec3 L = normalize(inLights[i].position - intersection.pos); + float d = distance(inLights[i].position, intersection.pos); + + //compute shadows + vec3 shadowOrigin = biasHitPosition(intersection.pos, L, intersection.N); + Intersection shadowIntersection = sceneIntersect(shadowOrigin, L); + bool isShadowed = false; + if(shadowIntersection.hit){ + isShadowed = distance(shadowIntersection.pos, shadowOrigin) < d; + } + if(isShadowed){ + continue; + } + + lightIntensityDiffuse += inLights[i].intensity * max(0.f, dot(L, intersection.N)); + lightIntensitySpecular += pow(max(0.f, dot(safrReflect(V, intersection.N, intersection.material.refractiveIndex), L)), intersection.material.specularExponent) * inLights[i].intensity; + } + + outReflectionThroughput = intersection.material.albedo[2]; + return intersection.material.diffuseColor * lightIntensityDiffuse * intersection.material.albedo[0] + lightIntensitySpecular * intersection.material.albedo[1]; +} + +/* +* castRay throws a ray out of the initial origin with respect to the initial direction checks for intersection and refelction +* @param vec3: initial origin of ray +* @param vec3: initial direction of ray +* @param int: max depth o ray reflection +* @return s +*/ + +vec3 castRay(const vec3 initialOrigin, const vec3 initialDirection, int max_depth) { + + vec3 skyColor = vec3(0.2, 0.7, 0.8); + vec3 rayOrigin = initialOrigin; + vec3 rayDirection = initialDirection; + + float reflectionThroughput = 1; + vec3 color = vec3(0); + + //iterate to max depth of reflections + for(int i = 0; i < max_depth; i++){ + + Intersection intersection = sceneIntersect(rayOrigin, rayDirection); + + vec3 hitColor; + float hitReflectionThroughput; + + if(intersection.hit){ + hitColor = computeHitLighting(intersection, rayDirection, hitReflectionThroughput); + }else{ + hitColor = skyColor; + } + + color += hitColor * reflectionThroughput; + reflectionThroughput *= hitReflectionThroughput; + + //if there is no intersection of a ray with a sphere, break out of the loop + if(!intersection.hit){ + break; + } + + //compute new direction and origin of the reflected ray + rayDirection = normalize(safrReflect(rayDirection, intersection.N, intersection.material.refractiveIndex)); + rayOrigin = biasHitPosition(intersection.pos, rayDirection, intersection.N); + } + + return color; +} + +/* +* computeDirection transforms the pixel coords to worldspace coords +* @param ivec2: pixel coordinates +* @return vec3: world coordinates +*/ +vec3 computeDirection(ivec2 coord){ + + ivec2 outImageRes = imageSize(outImage); + float fovDegree = 45; + float fov = fovDegree * pi / 180; + + vec2 uv = coord / vec2(outImageRes); + vec2 ndc = 2 * uv - 1; + + float tanFovHalf = tan(fov / 2.f); + float aspectRatio = outImageRes.x / float(outImageRes.y); + float x = ndc.x * tanFovHalf * aspectRatio; + float y = -ndc.y * tanFovHalf; + + vec3 directionViewSpace = normalize(vec3(x, y, 1)); + vec3 directionWorldSpace = mat3(viewToWorld) * directionViewSpace; + return directionWorldSpace; +} + +// the main function +void main(){ + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + int max_depth = 4; + vec3 direction = computeDirection(coord); + vec3 cameraPos = viewToWorld[3].xyz; + vec3 color = castRay(cameraPos, direction, max_depth); + + imageStore(outImage, coord, vec4(color, 0.f)); +} \ No newline at end of file diff --git a/projects/saf_r/shaders/shader.frag b/projects/saf_r/shaders/shader.frag new file mode 100644 index 0000000000000000000000000000000000000000..64b45eb26b9831f6504e69882018e46120615615 --- /dev/null +++ b/projects/saf_r/shaders/shader.frag @@ -0,0 +1,13 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 texCoord; + +layout(location = 0) out vec3 outColor; + +layout(set=0, binding=1) uniform sampler textureSampler; + +void main() { + outColor = fragColor; +} \ No newline at end of file diff --git a/projects/saf_r/shaders/shader.vert b/projects/saf_r/shaders/shader.vert new file mode 100644 index 0000000000000000000000000000000000000000..b6419f5e348ddeca66d1c8b0eb0a4cf32e2e80c4 --- /dev/null +++ b/projects/saf_r/shaders/shader.vert @@ -0,0 +1,32 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 texCoord; + +layout( push_constant ) uniform constants{ + mat4 mvp; + mat4 proj; +}; + +void main() { + vec3 positions[3] = { + vec3(-1, -1, -1), + vec3( 3, -1, -1), + vec3(-1, 3, -1) + }; + + vec3 colors[3] = { + vec3(1, 0, 0), + vec3(0, 1, 0), + vec3(0, 0, 1) + }; + + vec4 position = mvp * vec4(positions[gl_VertexIndex], 1.0); + gl_Position = position; + + texCoord.x = ((proj * vec4(positions[gl_VertexIndex], 1.0)).x + 1.0) * 0.5; + texCoord.y = ((proj * vec4(positions[gl_VertexIndex], 1.0)).y + 1.0) * 0.5; + + fragColor = colors[gl_VertexIndex]; +} \ No newline at end of file diff --git a/projects/saf_r/src/main.cpp b/projects/saf_r/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3fef073a00f8263cc08ce17f033170d0f4031dc4 --- /dev/null +++ b/projects/saf_r/src/main.cpp @@ -0,0 +1,297 @@ +#include <iostream> +#include <vkcv/Core.hpp> +#include <GLFW/glfw3.h> +#include <vkcv/camera/CameraManager.hpp> +#include <vkcv/asset/asset_loader.hpp> +#include <vkcv/shader/GLSLCompiler.hpp> +#include <chrono> +#include <limits> +#include <cmath> +#include <vector> +#include <string.h> // memcpy(3) +#include "safrScene.hpp" + + +void createQuadraticLightCluster(std::vector<safrScene::Light>& lights, int countPerDimension, float dimension, float height, float intensity) { + float distance = dimension/countPerDimension; + + for(int x = 0; x <= countPerDimension; x++) { + for (int z = 0; z <= countPerDimension; z++) { + lights.push_back(safrScene::Light(glm::vec3(x * distance, height, z * distance), + float (intensity/countPerDimension) / 10.f) // Divide by 10, because intensity is busting O.o + ); + } + } + +} + +int main(int argc, const char** argv) { + const char* applicationName = "SAF_R"; + + //window creation + const int windowWidth = 800; + const int windowHeight = 600; + + vkcv::Core core = vkcv::Core::create( + applicationName, + VK_MAKE_VERSION(0, 0, 1), + { vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute }, + { "VK_KHR_swapchain" } + ); + + vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, false); + + //configuring the compute Shader + vkcv::PassConfig computePassDefinition({}); + vkcv::PassHandle computePass = core.createPass(computePassDefinition); + + if (!computePass) + { + std::cout << "Error. Could not create renderpass. Exiting." << std::endl; + return EXIT_FAILURE; + } + + std::string shaderPathCompute = "shaders/raytracing.comp"; + + //creating the shader programs + vkcv::ShaderProgram safrShaderProgram; + vkcv::shader::GLSLCompiler compiler; + vkcv::ShaderProgram computeShaderProgram{}; + + compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/shader.vert"), + [&safrShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + safrShaderProgram.addShader(shaderStage, path); + }); + + compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/shader.frag"), + [&safrShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + safrShaderProgram.addShader(shaderStage, path); + }); + + compiler.compile(vkcv::ShaderStage::COMPUTE, shaderPathCompute, [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + computeShaderProgram.addShader(shaderStage, path); + }); + + //create DescriptorSets (...) for every Shader + const vkcv::DescriptorBindings& descriptorBindings = safrShaderProgram.getReflectedDescriptors().at(0); + vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout(descriptorBindings); + vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout); + vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); + + const vkcv::DescriptorBindings& computeDescriptorBindings = computeShaderProgram.getReflectedDescriptors().at(0); + + vkcv::DescriptorSetLayoutHandle computeDescriptorSetLayout = core.createDescriptorSetLayout(computeDescriptorBindings); + vkcv::DescriptorSetHandle computeDescriptorSet = core.createDescriptorSet(computeDescriptorSetLayout); + + const std::vector<vkcv::VertexAttachment> computeVertexAttachments = computeShaderProgram.getVertexAttachments(); + + std::vector<vkcv::VertexBinding> computeBindings; + for (size_t i = 0; i < computeVertexAttachments.size(); i++) { + computeBindings.push_back(vkcv::VertexBinding(i, { computeVertexAttachments[i] })); + } + const vkcv::VertexLayout computeLayout(computeBindings); + + /* + * create the scene + */ + + //materials for the spheres + std::vector<safrScene::Material> materials; + safrScene::Material ivory(glm::vec4(0.6, 0.3, 0.1, 0.0), glm::vec3(0.4, 0.4, 0.3), 50., 1.0); + safrScene::Material red_rubber(glm::vec4(0.9, 0.1, 0.0, 0.0), glm::vec3(0.3, 0.1, 0.1), 10., 1.0); + safrScene::Material mirror( glm::vec4(0.0, 10.0, 0.8, 0.0), glm::vec3(1.0, 1.0, 1.0), 1425., 1.0); + safrScene::Material glass( glm::vec4(0.0, 10.0, 0.8, 0.0), glm::vec3(1.0, 1.0, 1.0), 1425., 1.5); + + materials.push_back(ivory); + materials.push_back(red_rubber); + materials.push_back(mirror); + + //spheres for the scene + std::vector<safrScene::Sphere> spheres; + spheres.push_back(safrScene::Sphere(glm::vec3(-3, 0, -16), 2, ivory)); + // spheres.push_back(safrScene::Sphere(glm::vec3(-1.0, -1.5, 12), 2, mirror)); + spheres.push_back(safrScene::Sphere(glm::vec3(-1.0, -1.5, -12), 2, glass)); + spheres.push_back(safrScene::Sphere(glm::vec3( 1.5, -0.5, -18), 3, red_rubber)); + spheres.push_back(safrScene::Sphere(glm::vec3( 7, 5, -18), 4, mirror)); + + //lights for the scene + std::vector<safrScene::Light> lights; + /* + lights.push_back(safrScene::Light(glm::vec3(-20, 20, 20), 1.5)); + lights.push_back(safrScene::Light(glm::vec3(30, 50, -25), 1.8)); + lights.push_back(safrScene::Light(glm::vec3(30, 20, 30), 1.7)); + */ + createQuadraticLightCluster(lights, 10, 2.5f, 20, 1.5f); + + + vkcv::SamplerHandle sampler = core.createSampler( + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::REPEAT + ); + + + //create Buffer for compute shader + vkcv::Buffer<safrScene::Light> lightsBuffer = core.createBuffer<safrScene::Light>( + vkcv::BufferType::STORAGE, + lights.size() + ); + lightsBuffer.fill(lights); + + vkcv::Buffer<safrScene::Sphere> sphereBuffer = core.createBuffer<safrScene::Sphere>( + vkcv::BufferType::STORAGE, + spheres.size() + ); + sphereBuffer.fill(spheres); + + vkcv::DescriptorWrites computeWrites; + computeWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0,lightsBuffer.getHandle()), + vkcv::BufferDescriptorWrite(1,sphereBuffer.getHandle())}; + core.writeDescriptorSet(computeDescriptorSet, computeWrites); + + + const auto& context = core.getContext(); + + auto safrIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, 3, vkcv::BufferMemoryType::DEVICE_LOCAL); + uint16_t indices[3] = { 0, 1, 2 }; + safrIndexBuffer.fill(&indices[0], sizeof(indices)); + + // an example attachment for passes that output to the window + const vkcv::AttachmentDescription present_color_attachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::CLEAR, + core.getSwapchain(windowHandle).getFormat()); + + vkcv::PassConfig safrPassDefinition({ present_color_attachment }); + vkcv::PassHandle safrPass = core.createPass(safrPassDefinition); + + if (!safrPass) + { + std::cout << "Error. Could not create renderpass. Exiting." << std::endl; + return EXIT_FAILURE; + } + + //create the render pipeline + compute pipeline + const vkcv::GraphicsPipelineConfig safrPipelineDefinition{ + safrShaderProgram, + (uint32_t)windowWidth, + (uint32_t)windowHeight, + safrPass, + {}, + { core.getDescriptorSetLayout(descriptorSetLayout).vulkanHandle }, + false + }; + + vkcv::GraphicsPipelineHandle safrPipeline = core.createGraphicsPipeline(safrPipelineDefinition); + + const vkcv::ComputePipelineConfig computePipelineConfig{ + computeShaderProgram, + {core.getDescriptorSetLayout(computeDescriptorSetLayout).vulkanHandle} + }; + + vkcv::ComputePipelineHandle computePipeline = core.createComputePipeline(computePipelineConfig); + + if (!safrPipeline || !computePipeline) + { + std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl; + return EXIT_FAILURE; + } + + auto start = std::chrono::system_clock::now(); + + const vkcv::Mesh renderMesh({}, safrIndexBuffer.getVulkanHandle(), 3); + vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle); + vkcv::DrawcallInfo drawcall(renderMesh, { descriptorUsage }, 1); + + //create the camera + vkcv::camera::CameraManager cameraManager(core.getWindow(windowHandle)); + uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); + uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL); + + cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, 2)); + cameraManager.getCamera(camIndex1).setPosition(glm::vec3(0.0f, 0.0f, 0.0f)); + cameraManager.getCamera(camIndex1).setCenter(glm::vec3(0.0f, 0.0f, -1.0f)); + + float time = 0; + + while (vkcv::Window::hasOpenWindow()) + { + vkcv::Window::pollEvents(); + + uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem + if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) { + continue; + } + + //configure timer + auto end = std::chrono::system_clock::now(); + auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start); + start = end; + + time += 0.000001f * static_cast<float>(deltatime.count()); + + //adjust light position + /* + 639a53157e7d3936caf7c3e40379159cbcf4c89e + lights[0].position.x += std::cos(time * 3.0f) * 2.5f; + lights[1].position.z += std::cos(time * 2.5f) * 3.0f; + lights[2].position.y += std::cos(time * 1.5f) * 4.0f; + lightsBuffer.fill(lights); + */ + + spheres[0].center.y += std::cos(time * 0.5f * 3.141f) * 0.25f; + spheres[1].center.x += std::cos(time * 2.f) * 0.25f; + spheres[1].center.z += std::cos(time * 2.f + 0.5f * 3.141f) * 0.25f; + sphereBuffer.fill(spheres); + + //update camera + cameraManager.update(0.000001 * static_cast<double>(deltatime.count())); + glm::mat4 mvp = cameraManager.getActiveCamera().getMVP(); + glm::mat4 proj = cameraManager.getActiveCamera().getProjection(); + + //create pushconstants for render + vkcv::PushConstants pushConstants(sizeof(glm::mat4) * 2); + pushConstants.appendDrawcall(std::array<glm::mat4, 2>{ mvp, proj }); + + auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); + + //configure the outImage for compute shader (render into the swapchain image) + computeWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, swapchainInput)}; + core.writeDescriptorSet(computeDescriptorSet, computeWrites); + core.prepareImageForStorage (cmdStream, swapchainInput); + + //fill pushconstants for compute shader + struct RaytracingPushConstantData { + glm::mat4 viewToWorld; + int32_t lightCount; + int32_t sphereCount; + }; + + RaytracingPushConstantData raytracingPushData; + raytracingPushData.lightCount = lights.size(); + raytracingPushData.sphereCount = spheres.size(); + raytracingPushData.viewToWorld = glm::inverse(cameraManager.getActiveCamera().getView()); + + vkcv::PushConstants pushConstantsCompute(sizeof(RaytracingPushConstantData)); + pushConstantsCompute.appendDrawcall(raytracingPushData); + + //dispatch compute shader + uint32_t computeDispatchCount[3] = {static_cast<uint32_t> (std::ceil( swapchainWidth/16.f)), + static_cast<uint32_t> (std::ceil(swapchainHeight/16.f)), + 1 }; // Anzahl workgroups + core.recordComputeDispatchToCmdStream(cmdStream, + computePipeline, + computeDispatchCount, + { vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet).vulkanHandle) }, + pushConstantsCompute); + + core.recordBufferMemoryBarrier(cmdStream, lightsBuffer.getHandle()); + + core.prepareSwapchainImageForPresent(cmdStream); + core.submitCommandStream(cmdStream); + + core.endFrame(windowHandle); + } + return 0; +} diff --git a/projects/saf_r/src/safrScene.hpp b/projects/saf_r/src/safrScene.hpp new file mode 100644 index 0000000000000000000000000000000000000000..33a298f82121971021d1912e6c1205e9c48a49f0 --- /dev/null +++ b/projects/saf_r/src/safrScene.hpp @@ -0,0 +1,51 @@ +#include <iostream> +#include <vkcv/Core.hpp> +#include <GLFW/glfw3.h> +#include <vkcv/camera/CameraManager.hpp> +#include <vkcv/asset/asset_loader.hpp> +#include <vkcv/shader/GLSLCompiler.hpp> +#include <chrono> +#include <limits> +#include <cmath> +#include <vector> +#include <string.h> // memcpy(3) + +class safrScene { + +public: + + /* + * Light struct with a position and intensity of the light source + */ + struct Light { + Light(const glm::vec3& p, const float& i) : position(p), intensity(i) {} + glm::vec3 position; + float intensity; + }; + + /* + * Material struct with defuse color, albedo and specular component + */ + struct Material { + Material(const glm::vec4& a, const glm::vec3& color, const float& spec, const float& r) : albedo(a), diffuse_color(color), specular_exponent(spec), refractive_index(r) {} + Material() : refractive_index(1), albedo(1, 0, 0, 0), diffuse_color(), specular_exponent() {} + glm::vec4 albedo; + alignas(16) glm::vec3 diffuse_color; + float specular_exponent; + float refractive_index; + }; + + /* + * the sphere is defined by it's center, the radius and the material + */ + struct Sphere { + glm::vec3 center; + float radius; + Material material; + + Sphere(const glm::vec3& c, const float& r, const Material& m) : center(c), radius(r), material(m) {} + + }; + + +}; \ No newline at end of file diff --git a/projects/sph/.gitignore b/projects/sph/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4964f89e973f38358aa57f564f56d3d4b0c328a9 --- /dev/null +++ b/projects/sph/.gitignore @@ -0,0 +1 @@ +particle_simulation \ No newline at end of file diff --git a/projects/sph/CMakeLists.txt b/projects/sph/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..592aa4409ae3e01f4054f430bab7c424d25219d0 --- /dev/null +++ b/projects/sph/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.16) +project(sph) + +# setting c++ standard for the project +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# this should fix the execution path to load local files from the project +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +# adding source files to the project +add_executable(sph + src/main.cpp + src/Particle.hpp + src/Particle.cpp + src/BloomAndFlares.hpp + src/BloomAndFlares.cpp + src/PipelineInit.hpp + src/PipelineInit.cpp) + +# this should fix the execution path to load local files from the project (for MSVC) +if(MSVC) + set_target_properties(sph PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + set_target_properties(sph PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + + # in addition to setting the output directory, the working directory has to be set + # by default visual studio sets the working directory to the build directory, when using the debugger + set_target_properties(sph PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +endif() + +# including headers of dependencies and the VkCV framework +target_include_directories(sph SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include}) + +# linking with libraries from all dependencies and the VkCV framework +target_link_libraries(sph vkcv vkcv_testing vkcv_camera vkcv_shader_compiler) diff --git a/projects/sph/shaders/bloom/composite.comp b/projects/sph/shaders/bloom/composite.comp new file mode 100644 index 0000000000000000000000000000000000000000..87b5ddb975106232d1cd3b6e5b8dc7e623dd0b59 --- /dev/null +++ b/projects/sph/shaders/bloom/composite.comp @@ -0,0 +1,38 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(set=0, binding=0) uniform texture2D blurImage; +layout(set=0, binding=1) uniform texture2D lensImage; +layout(set=0, binding=2) uniform sampler linearSampler; +layout(set=0, binding=3, r11f_g11f_b10f) uniform image2D colorBuffer; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + + +void main() +{ + if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(colorBuffer)))){ + return; + } + + ivec2 pixel_coord = ivec2(gl_GlobalInvocationID.xy); + vec2 pixel_size = vec2(1.0f) / textureSize(sampler2D(blurImage, linearSampler), 0); + vec2 UV = pixel_coord.xy * pixel_size; + + vec4 composite_color = vec4(0.0f); + + vec3 blur_color = texture(sampler2D(blurImage, linearSampler), UV).rgb; + vec3 lens_color = texture(sampler2D(lensImage, linearSampler), UV).rgb; + vec3 main_color = imageLoad(colorBuffer, pixel_coord).rgb; + + // composite blur and lens features + float bloom_weight = 0.01f; + float lens_weight = 0.f; + float main_weight = 1 - (bloom_weight + lens_weight); + + composite_color.rgb = blur_color * bloom_weight + + lens_color * lens_weight + + main_color * main_weight; + + imageStore(colorBuffer, pixel_coord, composite_color); +} \ No newline at end of file diff --git a/projects/sph/shaders/bloom/downsample.comp b/projects/sph/shaders/bloom/downsample.comp new file mode 100644 index 0000000000000000000000000000000000000000..2ab00c7c92798769153634f3479c5b7f3fb61d94 --- /dev/null +++ b/projects/sph/shaders/bloom/downsample.comp @@ -0,0 +1,76 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(set=0, binding=0) uniform texture2D inBlurImage; +layout(set=0, binding=1) uniform sampler inImageSampler; +layout(set=0, binding=2, r11f_g11f_b10f) uniform writeonly image2D outBlurImage; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + + +void main() +{ + if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outBlurImage)))){ + return; + } + + ivec2 pixel_coord = ivec2(gl_GlobalInvocationID.xy); + vec2 pixel_size = vec2(1.0f) / imageSize(outBlurImage); + vec2 UV = pixel_coord.xy * pixel_size; + vec2 UV_offset = UV + 0.5f * pixel_size; + + vec2 color_fetches[13] = { + // center neighbourhood (RED) + vec2(-1, 1), // LT + vec2(-1, -1), // LB + vec2( 1, -1), // RB + vec2( 1, 1), // RT + + vec2(-2, 2), // LT + vec2( 0, 2), // CT + vec2( 2, 2), // RT + + vec2(0 ,-2), // LC + vec2(0 , 0), // CC + vec2(2, 0), // CR + + vec2(-2, -2), // LB + vec2(0 , -2), // CB + vec2(2 , -2) // RB + }; + + float color_weights[13] = { + // 0.5f + 1.f/8.f, + 1.f/8.f, + 1.f/8.f, + 1.f/8.f, + + // 0.125f + 1.f/32.f, + 1.f/16.f, + 1.f/32.f, + + // 0.25f + 1.f/16.f, + 1.f/8.f, + 1.f/16.f, + + // 0.125f + 1.f/32.f, + 1.f/16.f, + 1.f/32.f + }; + + vec3 sampled_color = vec3(0.0f); + + for(uint i = 0; i < 13; i++) + { + vec2 color_fetch = UV_offset + color_fetches[i] * pixel_size; + vec3 color = texture(sampler2D(inBlurImage, inImageSampler), color_fetch).rgb; + color *= color_weights[i]; + sampled_color += color; + } + + imageStore(outBlurImage, pixel_coord, vec4(sampled_color, 1.f)); +} \ No newline at end of file diff --git a/projects/sph/shaders/bloom/lensFlares.comp b/projects/sph/shaders/bloom/lensFlares.comp new file mode 100644 index 0000000000000000000000000000000000000000..ce27d8850b709f61332d467914ddc944dc63109f --- /dev/null +++ b/projects/sph/shaders/bloom/lensFlares.comp @@ -0,0 +1,109 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(set=0, binding=0) uniform texture2D blurBuffer; +layout(set=0, binding=1) uniform sampler linearSampler; +layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D lensBuffer; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +vec3 sampleColorChromaticAberration(vec2 _uv) +{ + vec2 toCenter = (vec2(0.5) - _uv); + + vec3 colorScales = vec3(-1, 0, 1); + float aberrationScale = 0.1; + vec3 scaleFactors = colorScales * aberrationScale; + + float r = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.r).r; + float g = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.g).g; + float b = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.b).b; + return vec3(r, g, b); +} + +// _uv assumed to be flipped UV coordinates! +vec3 ghost_vectors(vec2 _uv) +{ + vec2 ghost_vec = (vec2(0.5f) - _uv); + + const uint c_ghost_count = 64; + const float c_ghost_spacing = length(ghost_vec) / c_ghost_count; + + ghost_vec *= c_ghost_spacing; + + vec3 ret_color = vec3(0.0f); + + for (uint i = 0; i < c_ghost_count; ++i) + { + // sample scene color + vec2 s_uv = fract(_uv + ghost_vec * vec2(i)); + vec3 s = sampleColorChromaticAberration(s_uv); + + // tint/weight + float d = distance(s_uv, vec2(0.5)); + float weight = 1.0f - smoothstep(0.0f, 0.75f, d); + s *= weight; + + ret_color += s; + } + + ret_color /= c_ghost_count; + return ret_color; +} + +vec3 halo(vec2 _uv) +{ + const float c_aspect_ratio = float(imageSize(lensBuffer).x) / float(imageSize(lensBuffer).y); + const float c_radius = 0.6f; + const float c_halo_thickness = 0.1f; + + vec2 halo_vec = vec2(0.5) - _uv; + //halo_vec.x /= c_aspect_ratio; + halo_vec = normalize(halo_vec); + //halo_vec.x *= c_aspect_ratio; + + + //vec2 w_uv = (_uv - vec2(0.5, 0.0)) * vec2(c_aspect_ratio, 1.0) + vec2(0.5, 0.0); + vec2 w_uv = _uv; + float d = distance(w_uv, vec2(0.5)); // distance to center + + float distance_to_halo = abs(d - c_radius); + + float halo_weight = 0.0f; + if(abs(d - c_radius) <= c_halo_thickness) + { + float distance_to_border = c_halo_thickness - distance_to_halo; + halo_weight = distance_to_border / c_halo_thickness; + + //halo_weight = clamp((halo_weight / 0.4f), 0.0f, 1.0f); + halo_weight = pow(halo_weight, 2.0f); + + + //halo_weight = 1.0f; + } + + return sampleColorChromaticAberration(_uv + halo_vec) * halo_weight; +} + + + +void main() +{ + if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(lensBuffer)))){ + return; + } + + ivec2 pixel_coord = ivec2(gl_GlobalInvocationID.xy); + vec2 pixel_size = vec2(1.0f) / imageSize(lensBuffer); + vec2 UV = pixel_coord.xy * pixel_size; + + vec2 flipped_UV = vec2(1.0f) - UV; + + vec3 color = vec3(0.0f); + + color += ghost_vectors(flipped_UV); + color += halo(UV); + color *= 0.5f; + + imageStore(lensBuffer, pixel_coord, vec4(color, 0.0f)); +} \ No newline at end of file diff --git a/projects/sph/shaders/bloom/upsample.comp b/projects/sph/shaders/bloom/upsample.comp new file mode 100644 index 0000000000000000000000000000000000000000..0ddeedb5b5af9e476dc19012fed6430544006c0e --- /dev/null +++ b/projects/sph/shaders/bloom/upsample.comp @@ -0,0 +1,45 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(set=0, binding=0) uniform texture2D inUpsampleImage; +layout(set=0, binding=1) uniform sampler inImageSampler; +layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D outUpsampleImage; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +void main() +{ + if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outUpsampleImage)))){ + return; + } + + + ivec2 pixel_coord = ivec2(gl_GlobalInvocationID.xy); + vec2 pixel_size = vec2(1.0f) / imageSize(outUpsampleImage); + vec2 UV = pixel_coord.xy * pixel_size; + + const float gauss_kernel[3] = {1.f, 2.f, 1.f}; + const float gauss_weight = 16.f; + + vec3 sampled_color = vec3(0.f); + + for(int i = -1; i <= 1; i++) + { + for(int j = -1; j <= 1; j++) + { + vec2 sample_location = UV + vec2(j, i) * pixel_size; + vec3 color = texture(sampler2D(inUpsampleImage, inImageSampler), sample_location).rgb; + color *= gauss_kernel[j+1]; + color *= gauss_kernel[i+1]; + color /= gauss_weight; + + sampled_color += color; + } + } + + //vec3 prev_color = imageLoad(outUpsampleImage, pixel_coord).rgb; + //float bloomRimStrength = 0.75f; // adjust this to change strength of bloom + //sampled_color = mix(prev_color, sampled_color, bloomRimStrength); + + imageStore(outUpsampleImage, pixel_coord, vec4(sampled_color, 1.f)); +} \ No newline at end of file diff --git a/projects/sph/shaders/flip.comp b/projects/sph/shaders/flip.comp new file mode 100644 index 0000000000000000000000000000000000000000..f5cd7bf3ea5ed8d04de970c816989230421c0b52 --- /dev/null +++ b/projects/sph/shaders/flip.comp @@ -0,0 +1,53 @@ +#version 450 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_GOOGLE_include_directive : enable + +layout(local_size_x = 256) in; + +struct Particle +{ + vec3 position; + float padding; + vec3 velocity; + float density; + vec3 force; + float pressure; +}; + +layout(std430, binding = 1) readonly buffer buffer_inParticle +{ + Particle inParticle[]; +}; + +layout(std430, binding = 0) writeonly buffer buffer_outParticle +{ + Particle outParticle[]; +}; + +layout( push_constant ) uniform constants{ + float h; + float mass; + float gasConstant; + float offset; + float gravity; + float viscosity; + float ABSORBTION; + float dt; + vec3 gravityDir; + float particleCount; +}; + +void main() { + uint id = gl_GlobalInvocationID.x; + + if(id >= int(particleCount)) + { + return; + } + + outParticle[id].force = inParticle[id].force; + outParticle[id].density = inParticle[id].density; + outParticle[id].pressure = inParticle[id].pressure; + outParticle[id].position = inParticle[id].position; + outParticle[id].velocity = inParticle[id].velocity; +} diff --git a/projects/sph/shaders/force.comp b/projects/sph/shaders/force.comp new file mode 100644 index 0000000000000000000000000000000000000000..ea9b378b48a23fd0208ab18d884dbccda5ab21f4 --- /dev/null +++ b/projects/sph/shaders/force.comp @@ -0,0 +1,95 @@ +#version 450 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_GOOGLE_include_directive : enable + +const float PI = 3.1415926535897932384626433832795; + +layout(local_size_x = 256) in; + +struct Particle +{ + vec3 position; + float padding; + vec3 velocity; + float density; + vec3 force; + float pressure; + +}; + +layout(std430, binding = 1) readonly buffer buffer_inParticle +{ + Particle inParticle[]; +}; + +layout(std430, binding = 0) writeonly buffer buffer_outParticle +{ + Particle outParticle[]; +}; + +layout( push_constant ) uniform constants{ + float h; + float mass; + float gasConstant; + float offset; + float gravity; + float viscosity; + float ABSORBTION; + float dt; + vec3 gravityDir; + float particleCount; +}; + +float spiky(float r) +{ + return (15.f / (PI * pow(h, 6)) * pow((h-r), 3)) * int(0<=r && r<=h); +} + +float grad_spiky(float r) +{ + return -45.f / (PI * pow(h, 6)) * pow((h-r), 2) * int(0<=r && r<=h); +} + + + +float laplacian(float r) +{ + return (45.f / (PI * pow(h,6)) * (h - r)) * int(0<=r && r<=h); +} + +vec3 pressureForce = vec3(0, 0, 0); +vec3 viscosityForce = vec3(0, 0, 0); +vec3 externalForce = vec3(0, 0, 0); + +void main() { + + uint id = gl_GlobalInvocationID.x; + + if(id >= int(particleCount)) + { + return; + } + + externalForce = inParticle[id].density * gravity * vec3(-gravityDir.x,gravityDir.y,gravityDir.z); + + for(uint i = 0; i < int(particleCount); i++) + { + if (id != i) + { + vec3 dir = inParticle[id].position - inParticle[i].position; + float dist = length(dir); + if(dist != 0) + { + pressureForce += mass * -(inParticle[id].pressure + inParticle[i].pressure)/(2.f * inParticle[i].density) * grad_spiky(dist) * normalize(dir); + viscosityForce += mass * (inParticle[i].velocity - inParticle[id].velocity)/inParticle[i].density * laplacian(dist); + } + } + } + viscosityForce *= viscosity; + + outParticle[id].force = externalForce + pressureForce + viscosityForce; + outParticle[id].density = inParticle[id].density; + outParticle[id].pressure = inParticle[id].pressure; + outParticle[id].position = inParticle[id].position; + outParticle[id].velocity = inParticle[id].velocity; +} diff --git a/projects/sph/shaders/particleShading.inc b/projects/sph/shaders/particleShading.inc new file mode 100644 index 0000000000000000000000000000000000000000..b2d1832b9ccd6ba05a585b59bdfdedd4729e80f8 --- /dev/null +++ b/projects/sph/shaders/particleShading.inc @@ -0,0 +1,6 @@ +float circleFactor(vec2 triangleCoordinates){ + // percentage of distance from center to circle edge + float p = clamp((0.4 - length(triangleCoordinates)) / 0.4, 0, 1); + // remapping for nice falloff + return sqrt(p); +} \ No newline at end of file diff --git a/projects/sph/shaders/particle_params.inc b/projects/sph/shaders/particle_params.inc new file mode 100644 index 0000000000000000000000000000000000000000..e35cce13be1bc84f045da276fe46666d0b852984 --- /dev/null +++ b/projects/sph/shaders/particle_params.inc @@ -0,0 +1,8 @@ +#define h 0.4 +#define mass 0.15 +#define gasConstant 3000 +#define offset 1800 +#define gravity -1000 +#define viscosity 1500 +#define ABSORBTION 0.9 +#define dt 0.0005 diff --git a/projects/sph/shaders/pressure.comp b/projects/sph/shaders/pressure.comp new file mode 100644 index 0000000000000000000000000000000000000000..05b3af3afb490b427cc1297f21a82a779d4c8ecb --- /dev/null +++ b/projects/sph/shaders/pressure.comp @@ -0,0 +1,72 @@ +#version 450 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_GOOGLE_include_directive : enable + +const float PI = 3.1415926535897932384626433832795; + +layout(local_size_x = 256) in; + +struct Particle +{ + vec3 position; + float padding; + vec3 velocity; + float density; + vec3 force; + float pressure; + +}; + +layout(std430, binding = 0) readonly buffer buffer_inParticle +{ + Particle inParticle[]; +}; + +layout(std430, binding = 1) writeonly buffer buffer_outParticle +{ + Particle outParticle[]; +}; + +layout( push_constant ) uniform constants{ + float h; + float mass; + float gasConstant; + float offset; + float gravity; + float viscosity; + float ABSORBTION; + float dt; + vec3 gravityDir; + float particleCount; +}; + +float poly6(float r) +{ + return (315.f * pow((pow(h,2)-pow(r,2)), 3)/(64.f*PI*pow(h, 9))) * int(0<=r && r<=h); +} + +float densitySum = 0.f; + +void main() { + + uint id = gl_GlobalInvocationID.x; + + if(id >= int(particleCount)) + { + return; + } + + for(uint i = 0; i < int(particleCount); i++) + { + if (id != i) + { + float dist = distance(inParticle[id].position, inParticle[i].position); + densitySum += mass * poly6(dist); + } + } + outParticle[id].density = max(densitySum,0.0000001f); + outParticle[id].pressure = max((densitySum - offset), 0.0000001f) * gasConstant; + outParticle[id].position = inParticle[id].position; + outParticle[id].velocity = inParticle[id].velocity; + outParticle[id].force = inParticle[id].force; +} diff --git a/projects/sph/shaders/shader.vert b/projects/sph/shaders/shader.vert new file mode 100644 index 0000000000000000000000000000000000000000..f5531ffa4f26d3652e8e35971c16af6dda2e3b45 --- /dev/null +++ b/projects/sph/shaders/shader.vert @@ -0,0 +1,49 @@ +#version 460 core +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 particle; + +struct Particle +{ + vec3 position; + float padding; + vec3 velocity; + float density; + vec3 force; + float pressure; +}; + +layout(std430, binding = 2) readonly buffer buffer_inParticle1 +{ + Particle inParticle1[]; +}; + +layout(std430, binding = 3) readonly buffer buffer_inParticle2 +{ + Particle inParticle2[]; +}; + +layout( push_constant ) uniform constants{ + mat4 view; + mat4 projection; +}; + +layout(location = 0) out vec2 passTriangleCoordinates; +layout(location = 1) out vec3 passVelocity; + +void main() +{ + int id = gl_InstanceIndex; + passVelocity = inParticle1[id].velocity; + + // particle position in view space + vec4 positionView = view * vec4(inParticle1[id].position, 1); + // by adding the triangle position in view space the mesh is always camera facing + positionView.xyz += particle; + // multiply with projection matrix for final position + gl_Position = projection * positionView; + + // 0.01 corresponds to vertex position size in main + float normalizationDivider = 0.012; + passTriangleCoordinates = particle.xy / normalizationDivider; +} \ No newline at end of file diff --git a/projects/sph/shaders/shader_water.frag b/projects/sph/shaders/shader_water.frag new file mode 100644 index 0000000000000000000000000000000000000000..2aee4ec692a2ada060a77389099b2c279e9c338c --- /dev/null +++ b/projects/sph/shaders/shader_water.frag @@ -0,0 +1,28 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_GOOGLE_include_directive : enable + +#include "particleShading.inc" + +layout(location = 0) in vec2 passTriangleCoordinates; +layout(location = 1) in vec3 passVelocity; + +layout(location = 0) out vec3 outColor; + +layout(set=0, binding=0) uniform uColor { + vec4 color; +} Color; + +layout(set=0,binding=1) uniform uPosition{ + vec2 position; +} Position; + +void main() +{ + float p = length(passVelocity)/100.f; + outColor = vec3(0.f+p/3.f, 0.05f+p/2.f, 0.4f+p); + + // make the triangle look like a circle + outColor *= circleFactor(passTriangleCoordinates); + +} diff --git a/projects/sph/shaders/tonemapping.comp b/projects/sph/shaders/tonemapping.comp new file mode 100644 index 0000000000000000000000000000000000000000..26f0232d66e3475afdd1266c0cc6288b47ed1c38 --- /dev/null +++ b/projects/sph/shaders/tonemapping.comp @@ -0,0 +1,19 @@ +#version 440 + +layout(set=0, binding=0, rgba16f) uniform image2D inImage; +layout(set=0, binding=1, rgba8) uniform image2D outImage; + + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +void main(){ + + if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(inImage)))){ + return; + } + ivec2 uv = ivec2(gl_GlobalInvocationID.xy); + vec3 linearColor = imageLoad(inImage, uv).rgb; + vec3 tonemapped = linearColor / (dot(linearColor, vec3(0.21, 0.71, 0.08)) + 1); // reinhard tonemapping + vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f)); + imageStore(outImage, uv, vec4(gammaCorrected, 0.f)); +} \ No newline at end of file diff --git a/projects/sph/shaders/updateData.comp b/projects/sph/shaders/updateData.comp new file mode 100644 index 0000000000000000000000000000000000000000..3c935b232aff11388cc3b371e5524fa30486b36f --- /dev/null +++ b/projects/sph/shaders/updateData.comp @@ -0,0 +1,109 @@ +#version 450 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_GOOGLE_include_directive : enable + +layout(local_size_x = 256) in; + +struct Particle +{ + vec3 position; + float padding; + vec3 velocity; + float density; + vec3 force; + float pressure; + +}; + +layout(std430, binding = 0) readonly buffer buffer_inParticle +{ + Particle inParticle[]; +}; + +layout(std430, binding = 1) writeonly buffer buffer_outParticle +{ + Particle outParticle[]; +}; + +layout( push_constant ) uniform constants{ + float h; + float mass; + float gasConstant; + float offset; + float gravity; + float viscosity; + float ABSORBTION; + float dt; + vec3 gravityDir; + float particleCount; +}; + +void main() { + + uint id = gl_GlobalInvocationID.x; + + if(id >= int(particleCount)) + { + return; + } + + vec3 accel = inParticle[id].force / inParticle[id].density; + vec3 vel_new = inParticle[id].velocity + (dt * accel); + + vec3 out_force = inParticle[id].force; + float out_density = inParticle[id].density; + float out_pressure = inParticle[id].pressure; + + if (length(vel_new) > 100.f) + { + vel_new = normalize(vel_new)*50; + out_density = 0.01f; + out_pressure = 0.01f; + out_force = gravity * vec3(-gravityDir.x,gravityDir.y,gravityDir.z); + } + + vec3 pos_new = inParticle[id].position + (dt * vel_new); + + // Überprüfe Randbedingungen x + if (inParticle[id].position.x < -1.0) + { + vel_new = reflect(vel_new.xyz, vec3(1.f,0.f,0.f)) * ABSORBTION; + pos_new.x = -1.0 + 0.01f; + } + else if (inParticle[id].position.x > 1.0) + { + vel_new = reflect(vel_new,vec3(1.f,0.f,0.f)) * ABSORBTION; + pos_new.x = 1.0 - 0.01f; + } + + // Überprüfe Randbedingungen y + if (inParticle[id].position.y < -1.0) + { + vel_new = reflect(vel_new,vec3(0.f,1.f,0.f)) * ABSORBTION; + pos_new.y = -1.0 + 0.01f; + + } + else if (inParticle[id].position.y > 1.0) + { + vel_new = reflect(vel_new,vec3(0.f,1.f,0.f)) * ABSORBTION; + pos_new.y = 1.0 - 0.01f; + } + + // Überprüfe Randbedingungen z + if (inParticle[id].position.z < -1.0 ) + { + vel_new = reflect(vel_new,vec3(0.f,0.f,1.f)) * ABSORBTION; + pos_new.z = -1.0 + 0.01f; + } + else if (inParticle[id].position.z > 1.0 ) + { + vel_new = reflect(vel_new,vec3(0.f,0.f,1.f)) * ABSORBTION; + pos_new.z = 1.0 - 0.01f; + } + + outParticle[id].force = out_force; + outParticle[id].density = out_density; + outParticle[id].pressure = out_pressure; + outParticle[id].position = pos_new; + outParticle[id].velocity = vel_new; +} diff --git a/projects/sph/src/BloomAndFlares.cpp b/projects/sph/src/BloomAndFlares.cpp new file mode 100644 index 0000000000000000000000000000000000000000..09534815afcd8ab238b79da5c6bbceb6672b043a --- /dev/null +++ b/projects/sph/src/BloomAndFlares.cpp @@ -0,0 +1,285 @@ +#include "BloomAndFlares.hpp" +#include <vkcv/shader/GLSLCompiler.hpp> + +BloomAndFlares::BloomAndFlares( + vkcv::Core *p_Core, + vk::Format colorBufferFormat, + uint32_t width, + uint32_t height) : + + p_Core(p_Core), + m_ColorBufferFormat(colorBufferFormat), + m_Width(width), + m_Height(height), + m_LinearSampler(p_Core->createSampler(vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::CLAMP_TO_EDGE)), + m_Blur(p_Core->createImage(colorBufferFormat, width, height, 1, true, true, false)), + m_LensFeatures(p_Core->createImage(colorBufferFormat, width, height, 1, false, true, false)) +{ + vkcv::shader::GLSLCompiler compiler; + + // DOWNSAMPLE + vkcv::ShaderProgram dsProg; + compiler.compile(vkcv::ShaderStage::COMPUTE, + "shaders/bloom/downsample.comp", + [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) + { + dsProg.addShader(shaderStage, path); + }); + for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++) + { + m_DownsampleDescSetLayouts.push_back( + p_Core->createDescriptorSetLayout(dsProg.getReflectedDescriptors().at(0))); + + m_DownsampleDescSets.push_back( + p_Core->createDescriptorSet(m_DownsampleDescSetLayouts.back())); + } + m_DownsamplePipe = p_Core->createComputePipeline({ + dsProg, { p_Core->getDescriptorSetLayout(m_DownsampleDescSetLayouts[0]).vulkanHandle } }); + + // UPSAMPLE + vkcv::ShaderProgram usProg; + compiler.compile(vkcv::ShaderStage::COMPUTE, + "shaders/bloom/upsample.comp", + [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) + { + usProg.addShader(shaderStage, path); + }); + for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++) + { + m_UpsampleDescSetLayouts.push_back( + p_Core->createDescriptorSetLayout(usProg.getReflectedDescriptors().at(0))); + m_UpsampleDescSets.push_back( + p_Core->createDescriptorSet(m_UpsampleDescSetLayouts.back())); + } + m_UpsamplePipe = p_Core->createComputePipeline({ + usProg, { p_Core->getDescriptorSetLayout(m_UpsampleDescSetLayouts[0]).vulkanHandle } }); + + // LENS FEATURES + vkcv::ShaderProgram lensProg; + compiler.compile(vkcv::ShaderStage::COMPUTE, + "shaders/bloom/lensFlares.comp", + [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) + { + lensProg.addShader(shaderStage, path); + }); + m_LensFlareDescSetLayout = p_Core->createDescriptorSetLayout(lensProg.getReflectedDescriptors().at(0)); + m_LensFlareDescSet = p_Core->createDescriptorSet(m_LensFlareDescSetLayout); + m_LensFlarePipe = p_Core->createComputePipeline({ + lensProg, { p_Core->getDescriptorSetLayout(m_LensFlareDescSetLayout).vulkanHandle } }); + + // COMPOSITE + vkcv::ShaderProgram compProg; + compiler.compile(vkcv::ShaderStage::COMPUTE, + "shaders/bloom/composite.comp", + [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) + { + compProg.addShader(shaderStage, path); + }); + m_CompositeDescSetLayout = p_Core->createDescriptorSetLayout(compProg.getReflectedDescriptors().at(0)); + m_CompositeDescSet = p_Core->createDescriptorSet(m_CompositeDescSetLayout); + m_CompositePipe = p_Core->createComputePipeline({ + compProg, { p_Core->getDescriptorSetLayout(m_CompositeDescSetLayout).vulkanHandle } }); +} + +void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream, + const vkcv::ImageHandle &colorAttachment) +{ + auto dispatchCountX = static_cast<float>(m_Width) / 8.0f; + auto dispatchCountY = static_cast<float>(m_Height) / 8.0f; + // blur dispatch + uint32_t initialDispatchCount[3] = { + static_cast<uint32_t>(glm::ceil(dispatchCountX)), + static_cast<uint32_t>(glm::ceil(dispatchCountY)), + 1 + }; + + // downsample dispatch of original color attachment + p_Core->prepareImageForSampling(cmdStream, colorAttachment); + p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle()); + + vkcv::DescriptorWrites initialDownsampleWrites; + initialDownsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, colorAttachment)}; + initialDownsampleWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)}; + initialDownsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), 0) }; + p_Core->writeDescriptorSet(m_DownsampleDescSets[0], initialDownsampleWrites); + + p_Core->recordComputeDispatchToCmdStream( + cmdStream, + m_DownsamplePipe, + initialDispatchCount, + {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[0]).vulkanHandle)}, + vkcv::PushConstants(0)); + + // downsample dispatches of blur buffer's mip maps + float mipDispatchCountX = dispatchCountX; + float mipDispatchCountY = dispatchCountY; + for(uint32_t mipLevel = 1; mipLevel < std::min((uint32_t)m_DownsampleDescSets.size(), m_Blur.getMipCount()); mipLevel++) + { + // mip descriptor writes + vkcv::DescriptorWrites mipDescriptorWrites; + mipDescriptorWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel - 1, true)}; + mipDescriptorWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)}; + mipDescriptorWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel) }; + p_Core->writeDescriptorSet(m_DownsampleDescSets[mipLevel], mipDescriptorWrites); + + // mip dispatch calculation + mipDispatchCountX /= 2.0f; + mipDispatchCountY /= 2.0f; + + uint32_t mipDispatchCount[3] = { + static_cast<uint32_t>(glm::ceil(mipDispatchCountX)), + static_cast<uint32_t>(glm::ceil(mipDispatchCountY)), + 1 + }; + + if(mipDispatchCount[0] == 0) + mipDispatchCount[0] = 1; + if(mipDispatchCount[1] == 0) + mipDispatchCount[1] = 1; + + // mip blur dispatch + p_Core->recordComputeDispatchToCmdStream( + cmdStream, + m_DownsamplePipe, + mipDispatchCount, + {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[mipLevel]).vulkanHandle)}, + vkcv::PushConstants(0)); + + // image barrier between mips + p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); + } +} + +void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream) +{ + // upsample dispatch + p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle()); + + const uint32_t upsampleMipLevels = std::min( + static_cast<uint32_t>(m_UpsampleDescSets.size() - 1), + static_cast<uint32_t>(3) + ); + + // upsample dispatch for each mip map + for(uint32_t mipLevel = upsampleMipLevels; mipLevel > 0; mipLevel--) + { + // mip descriptor writes + vkcv::DescriptorWrites mipUpsampleWrites; + mipUpsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel, true)}; + mipUpsampleWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)}; + mipUpsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel - 1) }; + p_Core->writeDescriptorSet(m_UpsampleDescSets[mipLevel], mipUpsampleWrites); + + auto mipDivisor = glm::pow(2.0f, static_cast<float>(mipLevel) - 1.0f); + + auto upsampleDispatchX = static_cast<float>(m_Width) / mipDivisor; + auto upsampleDispatchY = static_cast<float>(m_Height) / mipDivisor; + upsampleDispatchX /= 8.0f; + upsampleDispatchY /= 8.0f; + + const uint32_t upsampleDispatchCount[3] = { + static_cast<uint32_t>(glm::ceil(upsampleDispatchX)), + static_cast<uint32_t>(glm::ceil(upsampleDispatchY)), + 1 + }; + + p_Core->recordComputeDispatchToCmdStream( + cmdStream, + m_UpsamplePipe, + upsampleDispatchCount, + {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleDescSets[mipLevel]).vulkanHandle)}, + vkcv::PushConstants(0) + ); + // image barrier between mips + p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); + } +} + +void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream) +{ + // lens feature generation descriptor writes + p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle()); + p_Core->prepareImageForStorage(cmdStream, m_LensFeatures.getHandle()); + + vkcv::DescriptorWrites lensFeatureWrites; + lensFeatureWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), 0)}; + lensFeatureWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)}; + lensFeatureWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_LensFeatures.getHandle(), 0)}; + p_Core->writeDescriptorSet(m_LensFlareDescSet, lensFeatureWrites); + + auto dispatchCountX = static_cast<float>(m_Width) / 8.0f; + auto dispatchCountY = static_cast<float>(m_Height) / 8.0f; + // lens feature generation dispatch + uint32_t lensFeatureDispatchCount[3] = { + static_cast<uint32_t>(glm::ceil(dispatchCountX)), + static_cast<uint32_t>(glm::ceil(dispatchCountY)), + 1 + }; + p_Core->recordComputeDispatchToCmdStream( + cmdStream, + m_LensFlarePipe, + lensFeatureDispatchCount, + {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_LensFlareDescSet).vulkanHandle)}, + vkcv::PushConstants(0)); +} + +void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStream, + const vkcv::ImageHandle &colorAttachment) +{ + p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle()); + p_Core->prepareImageForSampling(cmdStream, m_LensFeatures.getHandle()); + p_Core->prepareImageForStorage(cmdStream, colorAttachment); + + // bloom composite descriptor write + vkcv::DescriptorWrites compositeWrites; + compositeWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle()), + vkcv::SampledImageDescriptorWrite(1, m_LensFeatures.getHandle())}; + compositeWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(2, m_LinearSampler)}; + compositeWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(3, colorAttachment)}; + p_Core->writeDescriptorSet(m_CompositeDescSet, compositeWrites); + + float dispatchCountX = static_cast<float>(m_Width) / 8.0f; + float dispatchCountY = static_cast<float>(m_Height) / 8.0f; + + uint32_t compositeDispatchCount[3] = { + static_cast<uint32_t>(glm::ceil(dispatchCountX)), + static_cast<uint32_t>(glm::ceil(dispatchCountY)), + 1 + }; + + // bloom composite dispatch + p_Core->recordComputeDispatchToCmdStream( + cmdStream, + m_CompositePipe, + compositeDispatchCount, + {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)}, + vkcv::PushConstants(0)); +} + +void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, + const vkcv::ImageHandle &colorAttachment) +{ + execDownsamplePipe(cmdStream, colorAttachment); + execUpsamplePipe(cmdStream); + execLensFeaturePipe(cmdStream); + execCompositePipe(cmdStream, colorAttachment); +} + +void BloomAndFlares::updateImageDimensions(uint32_t width, uint32_t height) +{ + if ((width == m_Width) && (height == m_Height)) { + return; + } + + m_Width = width; + m_Height = height; + + p_Core->getContext().getDevice().waitIdle(); + m_Blur = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, true, true, false); + m_LensFeatures = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, false, true, false); +} + + diff --git a/projects/sph/src/BloomAndFlares.hpp b/projects/sph/src/BloomAndFlares.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1644d38e9c98c7a0bf74d48b173f0e627214f1e1 --- /dev/null +++ b/projects/sph/src/BloomAndFlares.hpp @@ -0,0 +1,51 @@ +#pragma once +#include <vkcv/Core.hpp> +#include <glm/glm.hpp> + +class BloomAndFlares{ +public: + BloomAndFlares(vkcv::Core *p_Core, + vk::Format colorBufferFormat, + uint32_t width, + uint32_t height); + + void execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment); + + void updateImageDimensions(uint32_t width, uint32_t height); + +private: + vkcv::Core *p_Core; + + vk::Format m_ColorBufferFormat; + uint32_t m_Width; + uint32_t m_Height; + + vkcv::SamplerHandle m_LinearSampler; + vkcv::Image m_Blur; + vkcv::Image m_LensFeatures; + + + vkcv::ComputePipelineHandle m_DownsamplePipe; + std::vector<vkcv::DescriptorSetLayoutHandle> m_DownsampleDescSetLayouts; + std::vector<vkcv::DescriptorSetHandle> m_DownsampleDescSets; // per mip desc set + + vkcv::ComputePipelineHandle m_UpsamplePipe; + std::vector<vkcv::DescriptorSetLayoutHandle> m_UpsampleDescSetLayouts; + std::vector<vkcv::DescriptorSetHandle> m_UpsampleDescSets; // per mip desc set + + vkcv::ComputePipelineHandle m_LensFlarePipe; + vkcv::DescriptorSetLayoutHandle m_LensFlareDescSetLayout; + vkcv::DescriptorSetHandle m_LensFlareDescSet; + + vkcv::ComputePipelineHandle m_CompositePipe; + vkcv::DescriptorSetLayoutHandle m_CompositeDescSetLayout; + vkcv::DescriptorSetHandle m_CompositeDescSet; + + void execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment); + void execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream); + void execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream); + void execCompositePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment); +}; + + + diff --git a/projects/sph/src/Particle.cpp b/projects/sph/src/Particle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..329236989b7cab502e7a4e1bb5aa27869bed53cb --- /dev/null +++ b/projects/sph/src/Particle.cpp @@ -0,0 +1,39 @@ + +#include "Particle.hpp" + +Particle::Particle(glm::vec3 position, glm::vec3 velocity) +: m_position(position), + m_velocity(velocity) +{ + m_density = 0.f; + m_force = glm::vec3(0.f); + m_pressure = 0.f; +} + +const glm::vec3& Particle::getPosition()const{ + return m_position; +} + +void Particle::setPosition( const glm::vec3 pos ){ + m_position = pos; +} + +const glm::vec3& Particle::getVelocity()const{ + return m_velocity; +} + +void Particle::setVelocity( const glm::vec3 vel ){ + m_velocity = vel; +} + +const float& Particle::getDensity()const { + return m_density; +} + +const glm::vec3& Particle::getForce()const { + return m_force; +} + +const float& Particle::getPressure()const { + return m_pressure; +} \ No newline at end of file diff --git a/projects/sph/src/Particle.hpp b/projects/sph/src/Particle.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6c4ab50b74cc4544976318c23e36f4b91989ee66 --- /dev/null +++ b/projects/sph/src/Particle.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <glm/glm.hpp> + +class Particle { + +public: + Particle(glm::vec3 position, glm::vec3 velocity); + + const glm::vec3& getPosition()const; + + void setPosition( const glm::vec3 pos ); + + const glm::vec3& getVelocity()const; + + void setVelocity( const glm::vec3 vel ); + + const float& getDensity()const; + + const glm::vec3& getForce()const; + + const float& getPressure()const; + + + +private: + // all properties of the Particle + glm::vec3 m_position; + float m_padding1; + glm::vec3 m_velocity; + float m_density; + glm::vec3 m_force; + float m_pressure; +}; diff --git a/projects/sph/src/PipelineInit.cpp b/projects/sph/src/PipelineInit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6cf941fa0d8f8716b7d05daf9b6fb618b0fa7d85 --- /dev/null +++ b/projects/sph/src/PipelineInit.cpp @@ -0,0 +1,27 @@ +#include "PipelineInit.hpp" + +vkcv::DescriptorSetHandle PipelineInit::ComputePipelineInit(vkcv::Core *pCore, vkcv::ShaderStage shaderStage, std::filesystem::path includePath, + vkcv::ComputePipelineHandle &pipeline) { + vkcv::ShaderProgram shaderProgram; + vkcv::shader::GLSLCompiler compiler; + compiler.compile(shaderStage, includePath, + [&](vkcv::ShaderStage shaderStage1, const std::filesystem::path& path1) {shaderProgram.addShader(shaderStage1, path1); + }); + vkcv::DescriptorSetLayoutHandle descriptorSetLayout = pCore->createDescriptorSetLayout( + shaderProgram.getReflectedDescriptors().at(0)); + vkcv::DescriptorSetHandle descriptorSet = pCore->createDescriptorSet(descriptorSetLayout); + + const std::vector<vkcv::VertexAttachment> vertexAttachments = shaderProgram.getVertexAttachments(); + + std::vector<vkcv::VertexBinding> bindings; + for (size_t i = 0; i < vertexAttachments.size(); i++) { + bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] })); + } + const vkcv::VertexLayout layout(bindings); + + pipeline = pCore->createComputePipeline({ + shaderProgram, + { pCore->getDescriptorSetLayout(descriptorSetLayout).vulkanHandle } }); + + return descriptorSet; +} \ No newline at end of file diff --git a/projects/sph/src/PipelineInit.hpp b/projects/sph/src/PipelineInit.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e628af0eef9c0558719b405790246946d8720d47 --- /dev/null +++ b/projects/sph/src/PipelineInit.hpp @@ -0,0 +1,12 @@ +#pragma once +#include <vkcv/Core.hpp> +#include <vkcv/shader/GLSLCompiler.hpp> +#include <fstream> + +class PipelineInit{ +public: + static vkcv::DescriptorSetHandle ComputePipelineInit(vkcv::Core *pCore, + vkcv::ShaderStage shaderStage, + std::filesystem::path includePath, + vkcv::ComputePipelineHandle& pipeline); +}; diff --git a/projects/sph/src/main.cpp b/projects/sph/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..255fb0e73f068ff9e5e8ce892897a1325631b6e1 --- /dev/null +++ b/projects/sph/src/main.cpp @@ -0,0 +1,409 @@ +#include <iostream> +#include <vkcv/Core.hpp> +#include <GLFW/glfw3.h> +#include <vkcv/camera/CameraManager.hpp> +#include <chrono> +#include <random> +#include <glm/glm.hpp> +#include <glm/gtc/matrix_access.hpp> +#include <glm/gtc/matrix_transform.hpp> +#include <time.h> +#include <vkcv/shader/GLSLCompiler.hpp> +#include "BloomAndFlares.hpp" +#include "PipelineInit.hpp" +#include "Particle.hpp" + +int main(int argc, const char **argv) { + const char *applicationName = "SPH"; + + vkcv::Core core = vkcv::Core::create( + applicationName, + VK_MAKE_VERSION(0, 0, 1), + { vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute }, + { VK_KHR_SWAPCHAIN_EXTENSION_NAME } + ); + + vkcv::WindowHandle windowHandle = core.createWindow(applicationName, 1920, 1080, false); + vkcv::Window& window = core.getWindow(windowHandle); + + vkcv::camera::CameraManager cameraManager(window); + + auto particleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, 3, + vkcv::BufferMemoryType::DEVICE_LOCAL); + uint16_t indices[3] = {0, 1, 2}; + particleIndexBuffer.fill(&indices[0], sizeof(indices)); + + vk::Format colorFormat = vk::Format::eR16G16B16A16Sfloat; + // an example attachment for passes that output to the window + const vkcv::AttachmentDescription present_color_attachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::CLEAR, + colorFormat); + + + vkcv::PassConfig particlePassDefinition({present_color_attachment}); + vkcv::PassHandle particlePass = core.createPass(particlePassDefinition); + + vkcv::PassConfig computePassDefinition({}); + vkcv::PassHandle computePass = core.createPass(computePassDefinition); + + //rotation + float rotationx = 0; + float rotationy = 0; + + // params + float param_h = 0.20; + float param_mass = 0.03; + float param_gasConstant = 3500; + float param_offset = 200; + float param_gravity = -5000; + float param_viscosity = 10; + float param_ABSORBTION = 0.5; + float param_dt = 0.0005; + + if (!particlePass || !computePass) + { + std::cout << "Error. Could not create renderpass. Exiting." << std::endl; + return EXIT_FAILURE; + } + vkcv::shader::GLSLCompiler compiler; + +// comp shader 1 + vkcv::ComputePipelineHandle computePipeline1; + vkcv::DescriptorSetHandle computeDescriptorSet1 = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE, + "shaders/pressure.comp", computePipeline1); +// comp shader 2 + vkcv::ComputePipelineHandle computePipeline2; + vkcv::DescriptorSetHandle computeDescriptorSet2 = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE, + "shaders/force.comp", computePipeline2); + +//comp shader 3 + vkcv::ComputePipelineHandle computePipeline3; + vkcv::DescriptorSetHandle computeDescriptorSet3 = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE, + "shaders/updateData.comp", computePipeline3); + +//comp shader 4 + vkcv::ComputePipelineHandle computePipeline4; + vkcv::DescriptorSetHandle computeDescriptorSet4 = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE, + "shaders/flip.comp", computePipeline4); + +// shader + vkcv::ShaderProgram particleShaderProgram{}; + compiler.compile(vkcv::ShaderStage::VERTEX, "shaders/shader.vert", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + particleShaderProgram.addShader(shaderStage, path); + }); + compiler.compile(vkcv::ShaderStage::FRAGMENT, "shaders/shader_water.frag", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + particleShaderProgram.addShader(shaderStage, path); + }); + + vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout( + particleShaderProgram.getReflectedDescriptors().at(0)); + vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout); + + vkcv::Buffer<glm::vec3> vertexBuffer = core.createBuffer<glm::vec3>( + vkcv::BufferType::VERTEX, + 3 + ); + const std::vector<vkcv::VertexAttachment> vertexAttachments = particleShaderProgram.getVertexAttachments(); + + const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = { + vkcv::VertexBufferBinding(0, vertexBuffer.getVulkanHandle())}; + + std::vector<vkcv::VertexBinding> bindings; + for (size_t i = 0; i < vertexAttachments.size(); i++) { + bindings.push_back(vkcv::VertexBinding(i, {vertexAttachments[i]})); + } + + const vkcv::VertexLayout particleLayout(bindings); + + vkcv::GraphicsPipelineConfig particlePipelineDefinition{ + particleShaderProgram, + UINT32_MAX, + UINT32_MAX, + particlePass, + {particleLayout}, + {core.getDescriptorSetLayout(descriptorSetLayout).vulkanHandle}, + true}; + particlePipelineDefinition.m_blendMode = vkcv::BlendMode::Additive; + + const std::vector<glm::vec3> vertices = {glm::vec3(-0.012, 0.012, 0), + glm::vec3(0.012, 0.012, 0), + glm::vec3(0, -0.012, 0)}; + + vertexBuffer.fill(vertices); + + vkcv::GraphicsPipelineHandle particlePipeline = core.createGraphicsPipeline(particlePipelineDefinition); + + vkcv::Buffer<glm::vec4> color = core.createBuffer<glm::vec4>( + vkcv::BufferType::UNIFORM, + 1 + ); + + vkcv::Buffer<glm::vec2> position = core.createBuffer<glm::vec2>( + vkcv::BufferType::UNIFORM, + 1 + ); + + int numberParticles = 20000; + std::vector<Particle> particles; + for (int i = 0; i < numberParticles; i++) { + const float lo = 0.6; + const float hi = 0.9; + const float vlo = 0; + const float vhi = 70; + float x = lo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(hi-lo))); + float y = lo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(hi-lo))); + float z = lo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(hi-lo))); + float vx = vlo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(vhi-vlo))); + float vy = vlo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(vhi-vlo))); + float vz = vlo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(vhi-vlo))); + glm::vec3 pos = glm::vec3(x,y,z); + glm::vec3 vel = glm::vec3(vx,vy,vz); + //glm::vec3 vel = glm::vec3(0.0,0.0,0.0); + particles.push_back(Particle(pos, vel)); + } + + vkcv::Buffer<Particle> particleBuffer1 = core.createBuffer<Particle>( + vkcv::BufferType::STORAGE, + numberParticles * sizeof(glm::vec4) * 3 + + ); + + vkcv::Buffer<Particle> particleBuffer2 = core.createBuffer<Particle>( + vkcv::BufferType::STORAGE, + numberParticles * sizeof(glm::vec4) * 3 + ); + + particleBuffer1.fill(particles); + particleBuffer2.fill(particles); + + vkcv::DescriptorWrites setWrites; + setWrites.uniformBufferWrites = {vkcv::BufferDescriptorWrite(0,color.getHandle()), + vkcv::BufferDescriptorWrite(1,position.getHandle())}; + setWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(2,particleBuffer1.getHandle()), + vkcv::BufferDescriptorWrite(3,particleBuffer2.getHandle())}; + core.writeDescriptorSet(descriptorSet, setWrites); + + vkcv::DescriptorWrites computeWrites; + computeWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0,particleBuffer1.getHandle()), + vkcv::BufferDescriptorWrite(1,particleBuffer2.getHandle())}; + + core.writeDescriptorSet(computeDescriptorSet1, computeWrites); + core.writeDescriptorSet(computeDescriptorSet2, computeWrites); + core.writeDescriptorSet(computeDescriptorSet3, computeWrites); + core.writeDescriptorSet(computeDescriptorSet4, computeWrites); + + if (!particlePipeline || !computePipeline1 || !computePipeline2 || !computePipeline3 || !computePipeline4) + { + std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl; + return EXIT_FAILURE; + } + + const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); + + const vkcv::Mesh renderMesh({vertexBufferBindings}, particleIndexBuffer.getVulkanHandle(), + particleIndexBuffer.getCount()); + vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle); + + auto pos = glm::vec2(0.f); + auto spawnPosition = glm::vec3(0.f); + + std::vector<vkcv::DrawcallInfo> drawcalls; + drawcalls.push_back(vkcv::DrawcallInfo(renderMesh, {descriptorUsage}, numberParticles)); + + auto start = std::chrono::system_clock::now(); + + glm::vec4 colorData = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f); + uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); + uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL); + + cameraManager.getCamera(camIndex0).setNearFar(0.1, 30); + cameraManager.getCamera(camIndex1).setNearFar(0.1, 30); + + cameraManager.setActiveCamera(1); + + cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -2.5)); + cameraManager.getCamera(camIndex1).setPosition(glm::vec3(0.0f, 0.0f, -2.5f)); + cameraManager.getCamera(camIndex1).setCenter(glm::vec3(0.0f, 0.0f, 0.0f)); + + auto swapchainExtent = core.getSwapchain(window.getSwapchainHandle()).getExtent(); + + vkcv::ImageHandle colorBuffer = core.createImage( + colorFormat, + swapchainExtent.width, + swapchainExtent.height, + 1, false, true, true + ).getHandle(); + BloomAndFlares bloomAndFlares(&core, colorFormat, swapchainExtent.width, swapchainExtent.height); + + //tone mapping shader & pipeline + vkcv::ComputePipelineHandle tonemappingPipe; + vkcv::DescriptorSetHandle tonemappingDescriptor = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE, + "shaders/tonemapping.comp", tonemappingPipe); + + + while (vkcv::Window::hasOpenWindow()) { + vkcv::Window::pollEvents(); + + uint32_t swapchainWidth, swapchainHeight; + if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) { + continue; + } + + color.fill(&colorData); + position.fill(&pos); + + auto end = std::chrono::system_clock::now(); + float deltatime = 0.000001 * static_cast<float>( std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() ); + start = end; + + cameraManager.update(deltatime); + + // split view and projection to allow for easy billboarding in shader + struct { + glm::mat4 view; + glm::mat4 projection; + } renderingMatrices; + + glm::vec3 gravityDir = glm::rotate(glm::mat4(1.0), glm::radians(rotationx), glm::vec3(0.f,0.f,1.f)) * glm::vec4(0.f,1.f,0.f,0.f); + gravityDir = glm::rotate(glm::mat4(1.0), glm::radians(rotationy), glm::vec3(0.f,1.f,0.f)) * glm::vec4(gravityDir,0.f); + + renderingMatrices.view = cameraManager.getActiveCamera().getView(); + renderingMatrices.view = glm::rotate(renderingMatrices.view, glm::radians(rotationx), glm::vec3(0.f, 0.f, 1.f)); + renderingMatrices.view = glm::rotate(renderingMatrices.view, glm::radians(rotationy), glm::vec3(0.f, 1.f, 0.f)); + renderingMatrices.projection = cameraManager.getActiveCamera().getProjection(); + + // keybindings rotation + if (glfwGetKey(window.getWindow(), GLFW_KEY_LEFT) == GLFW_PRESS) + rotationx += deltatime * 50; + if (glfwGetKey(window.getWindow(), GLFW_KEY_RIGHT) == GLFW_PRESS) + rotationx -= deltatime * 50; + + if (glfwGetKey(window.getWindow(), GLFW_KEY_UP) == GLFW_PRESS) + rotationy += deltatime * 50; + if (glfwGetKey(window.getWindow(), GLFW_KEY_DOWN) == GLFW_PRESS) + rotationy -= deltatime * 50; + + // keybindings params + if (glfwGetKey(window.getWindow(), GLFW_KEY_T) == GLFW_PRESS) + param_h += deltatime * 0.2; + if (glfwGetKey(window.getWindow(), GLFW_KEY_G) == GLFW_PRESS) + param_h -= deltatime * 0.2; + + if (glfwGetKey(window.getWindow(), GLFW_KEY_Y) == GLFW_PRESS) + param_mass += deltatime * 0.2; + if (glfwGetKey(window.getWindow(), GLFW_KEY_H) == GLFW_PRESS) + param_mass -= deltatime * 0.2; + + if (glfwGetKey(window.getWindow(), GLFW_KEY_U) == GLFW_PRESS) + param_gasConstant += deltatime * 1500.0; + if (glfwGetKey(window.getWindow(), GLFW_KEY_J) == GLFW_PRESS) + param_gasConstant -= deltatime * 1500.0; + + if (glfwGetKey(window.getWindow(), GLFW_KEY_I) == GLFW_PRESS) + param_offset += deltatime * 400.0; + if (glfwGetKey(window.getWindow(), GLFW_KEY_K) == GLFW_PRESS) + param_offset -= deltatime * 400.0; + + if (glfwGetKey(window.getWindow(), GLFW_KEY_O) == GLFW_PRESS) + param_viscosity = 50; + if (glfwGetKey(window.getWindow(), GLFW_KEY_L) == GLFW_PRESS) + param_viscosity = 1200; + + + auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); + + glm::vec4 pushData[3] = { + glm::vec4(param_h,param_mass,param_gasConstant,param_offset), + glm::vec4(param_gravity,param_viscosity,param_ABSORBTION,param_dt), + glm::vec4(gravityDir.x,gravityDir.y,gravityDir.z,(float)numberParticles) + }; + + std::cout << "h: " << param_h << " | mass: " << param_mass << " | gasConstant: " << param_gasConstant << " | offset: " << param_offset << " | viscosity: " << param_viscosity << std::endl; + + vkcv::PushConstants pushConstantsCompute (sizeof(pushData)); + pushConstantsCompute.appendDrawcall(pushData); + + uint32_t computeDispatchCount[3] = {static_cast<uint32_t> (std::ceil(numberParticles/256.f)),1,1}; + + core.recordComputeDispatchToCmdStream(cmdStream, + computePipeline1, + computeDispatchCount, + {vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet1).vulkanHandle)}, + pushConstantsCompute); + + core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle()); + core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle()); + + core.recordComputeDispatchToCmdStream(cmdStream, + computePipeline2, + computeDispatchCount, + {vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet2).vulkanHandle)}, + pushConstantsCompute); + + core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle()); + core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle()); + + core.recordComputeDispatchToCmdStream(cmdStream, + computePipeline3, + computeDispatchCount, + { vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet3).vulkanHandle) }, + pushConstantsCompute); + + core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle()); + core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle()); + + core.recordComputeDispatchToCmdStream(cmdStream, + computePipeline4, + computeDispatchCount, + { vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet4).vulkanHandle) }, + pushConstantsCompute); + + core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle()); + core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle()); + + + // bloomAndFlares & tonemapping + vkcv::PushConstants pushConstantsDraw (sizeof(renderingMatrices)); + pushConstantsDraw.appendDrawcall(renderingMatrices); + + core.recordDrawcallsToCmdStream( + cmdStream, + particlePass, + particlePipeline, + pushConstantsDraw, + {drawcalls}, + { colorBuffer }, + windowHandle); + + bloomAndFlares.execWholePipeline(cmdStream, colorBuffer); + + core.prepareImageForStorage(cmdStream, colorBuffer); + core.prepareImageForStorage(cmdStream, swapchainInput); + + vkcv::DescriptorWrites tonemappingDescriptorWrites; + tonemappingDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(0, colorBuffer), + vkcv::StorageImageDescriptorWrite(1, swapchainInput) + }; + core.writeDescriptorSet(tonemappingDescriptor, tonemappingDescriptorWrites); + + uint32_t tonemappingDispatchCount[3]; + tonemappingDispatchCount[0] = std::ceil(swapchainExtent.width / 8.f); + tonemappingDispatchCount[1] = std::ceil(swapchainExtent.height / 8.f); + tonemappingDispatchCount[2] = 1; + + core.recordComputeDispatchToCmdStream( + cmdStream, + tonemappingPipe, + tonemappingDispatchCount, + {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(tonemappingDescriptor).vulkanHandle) }, + vkcv::PushConstants(0)); + + core.prepareSwapchainImageForPresent(cmdStream); + core.submitCommandStream(cmdStream); + core.endFrame(windowHandle); + } + + return 0; +} diff --git a/projects/voxelization/assets/shaders/depthToMoments.comp b/projects/voxelization/assets/shaders/depthToMoments.comp index 5a78d0cb9b748187d12057708fcd0de7658a61ed..79e47cdef02143ed97d53e533f47e822db8c0f6f 100644 --- a/projects/voxelization/assets/shaders/depthToMoments.comp +++ b/projects/voxelization/assets/shaders/depthToMoments.comp @@ -28,9 +28,11 @@ void main(){ z += texelFetch(sampler2DMS(srcTexture, depthSampler), uv, i).r; } z /= msaaCount; - + z = 2 * z - 1; // algorithm expects depth in range [-1:1] + float z2 = z*z; vec4 moments = vec4(z, z2, z2*z, z2*z2); vec4 momentsQuantized = quantizeMoments(moments); + imageStore(outImage, uv, momentsQuantized); } \ No newline at end of file diff --git a/projects/voxelization/assets/shaders/shadowBlur.inc b/projects/voxelization/assets/shaders/shadowBlur.inc index 06147415f118dca9badd15813b431a68682ce0b0..ed4994ed1ace34afdafff15920d18a2433a3c0a4 100644 --- a/projects/voxelization/assets/shaders/shadowBlur.inc +++ b/projects/voxelization/assets/shaders/shadowBlur.inc @@ -3,7 +3,7 @@ vec4 blurMomentShadowMap1D(ivec2 coord, ivec2 blurDirection, texture2D srcTexture, sampler depthSampler){ - int blurRadius = 9; + int blurRadius = 7; int minOffset = -(blurRadius-1) / 2; int maxOffset = -minOffset; diff --git a/projects/voxelization/assets/shaders/shadowBlurX.comp b/projects/voxelization/assets/shaders/shadowBlurX.comp index 45b91aad71673347dbf607fecef92463ef1c3c88..41d127fdf5ce46dec883d49af4f284b5787d5d38 100644 --- a/projects/voxelization/assets/shaders/shadowBlurX.comp +++ b/projects/voxelization/assets/shaders/shadowBlurX.comp @@ -18,6 +18,6 @@ void main(){ } ivec2 coord = ivec2(gl_GlobalInvocationID.xy); vec4 moments = blurMomentShadowMap1D(coord, ivec2(1, 0), srcTexture, depthSampler); - + // moments = texelFetch(sampler2D(srcTexture, depthSampler), coord, 0); imageStore(outImage, coord, moments); } \ No newline at end of file diff --git a/projects/voxelization/assets/shaders/shadowBlurY.comp b/projects/voxelization/assets/shaders/shadowBlurY.comp index 51d4df054b0d99e54149863a5967143518f61dd2..c1710d7d6c75ef0093fecfe708272f56f9541eaf 100644 --- a/projects/voxelization/assets/shaders/shadowBlurY.comp +++ b/projects/voxelization/assets/shaders/shadowBlurY.comp @@ -16,10 +16,8 @@ void main(){ if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){ return; } - ivec2 coord = ivec2(gl_GlobalInvocationID.xy); - vec2 pixelSize = vec2(1) / textureSize(sampler2D(srcTexture, depthSampler), 0); - + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); vec4 moments = blurMomentShadowMap1D(coord, ivec2(0, 1), srcTexture, depthSampler); - + // moments = texelFetch(sampler2D(srcTexture, depthSampler), coord, 0); imageStore(outImage, coord, moments); } \ No newline at end of file diff --git a/projects/voxelization/assets/shaders/shadowMapping.inc b/projects/voxelization/assets/shaders/shadowMapping.inc index c56ae8985c5c5fcef780b622d8b888f1081af74c..9124a05c310c2cc16e6b02802f5adb36bde42804 100644 --- a/projects/voxelization/assets/shaders/shadowMapping.inc +++ b/projects/voxelization/assets/shaders/shadowMapping.inc @@ -6,7 +6,8 @@ // nice math blob from the moment shadow mapping presentation float ComputeMSMShadowIntensity(vec4 _4Moments, float FragmentDepth, float DepthBias, float MomentBias) { - vec4 b=mix(_4Moments, vec4(0.5),MomentBias); + vec4 b=mix(_4Moments, vec4(0, 0.63, 0, 0.63),MomentBias); + vec3 z; z[0]=FragmentDepth-DepthBias; float L32D22=fma(-b[0], b[1], b[2]); @@ -39,24 +40,23 @@ float ComputeMSMShadowIntensity(vec4 _4Moments, float FragmentDepth, float Depth } vec4 quantizeMoments(vec4 moments){ - mat4 T = mat4( - -2.07224649, 13.7948857237, 0.105877704, 9.7924062118, - 32.23703778, -59.4683975703, -1.9077466311, -33.7652110555, - -68.571074599, 82.0359750338, 9.3496555107, 47.9456096605, - 39.3703274134, -35.364903257, -6.6543490743, -23.9728048165); - vec4 quantized = T * moments; - quantized[0] += 0.0359558848; - return quantized; + vec4 quantized; + quantized.r = 1.5 * moments.r - 2 * moments.b + 0.5; + quantized.g = 4 * moments.g - 4 * moments.a; + quantized.b = sqrt(3)/2 * moments.r - sqrt(12)/9 * moments.b + 0.5; + quantized.a = 0.5 * moments.g + 0.5 * moments.a; + + return quantized; } vec4 unquantizeMoments(vec4 moments){ - moments[0] -= 0.0359558848; - mat4 T = mat4( - 0.2227744146, 0.1549679261, 0.1451988946, 0.163127443, - 0.0771972861, 0.1394629426, 0.2120202157, 0.2591432266, - 0.7926986636, 0.7963415838, 0.7258694464, 0.6539092497, - 0.0319417555, -0.1722823173, -0.2758014811, -0.3376131734); - return T * moments; + moments -= vec4(0.5, 0, 0.5, 0); + vec4 unquantized; + unquantized.r = -1.f / 3 * moments.r + sqrt(3) * moments.b; + unquantized.g = 0.125 * moments.g + moments.a; + unquantized.b = -0.75 * moments.r + 0.75 * sqrt(3) * moments.b; + unquantized.a = -0.125 * moments.g + moments.a; + return unquantized / 0.98; // division reduces light bleeding } float rescaleRange(float a, float b, float v) @@ -78,18 +78,20 @@ float shadowTest(vec3 worldPos, LightInfo lightInfo, texture2D shadowMap, sample if(any(lessThan(lightPos.xy, vec2(0))) || any(greaterThan(lightPos.xy, vec2(1)))){ return 1; } - + lightPos.z = clamp(lightPos.z, 0, 1); + lightPos.z = 2 * lightPos.z - 1; // algorithm expects depth in range [-1:1] vec4 shadowMapSample = texture(sampler2D(shadowMap, shadowMapSampler), lightPos.xy); shadowMapSample = unquantizeMoments(shadowMapSample); float depthBias = 0.f; - float momentBias = 0.0003; + float momentBias = 0.0006; float shadow = ComputeMSMShadowIntensity(shadowMapSample, lightPos.z, depthBias, momentBias); - return reduceLightBleeding(shadow, 0.1f); + return clamp(shadow, 0, 1); + // return reduceLightBleeding(shadow, 0.1f); } #endif // #ifndef SHADOW_MAPPING_INC \ No newline at end of file diff --git a/projects/voxelization/src/BloomAndFlares.cpp b/projects/voxelization/src/BloomAndFlares.cpp index d47f61d0dc7fea4e38508b7b1d6c040595e2944a..2014d7a0219141ec6363b38a5311cb924b6b6a45 100644 --- a/projects/voxelization/src/BloomAndFlares.cpp +++ b/projects/voxelization/src/BloomAndFlares.cpp @@ -127,6 +127,8 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre 1 }; + p_Core->recordBeginDebugLabel(cmdStream, "Bloom downsample", { 1, 1, 1, 1 }); + // downsample dispatch of original color attachment p_Core->prepareImageForSampling(cmdStream, colorAttachment); p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle()); @@ -182,10 +184,14 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre // image barrier between mips p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); } + + p_Core->recordEndDebugLabel(cmdStream); } void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream) { + p_Core->recordBeginDebugLabel(cmdStream, "Bloom upsample", { 1, 1, 1, 1 }); + // upsample dispatch p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle()); @@ -227,10 +233,14 @@ void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream // image barrier between mips p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); } + + p_Core->recordEndDebugLabel(cmdStream); } void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream) { + p_Core->recordBeginDebugLabel(cmdStream, "Lense flare generation", { 1, 1, 1, 1 }); + // lens feature generation descriptor writes p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle()); p_Core->prepareImageForStorage(cmdStream, m_LensFeatures.getHandle()); @@ -295,11 +305,15 @@ void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStr // image barrier between mips p_Core->recordImageMemoryBarrier(cmdStream, m_LensFeatures.getHandle()); } + + p_Core->recordEndDebugLabel(cmdStream); } void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle& colorAttachment, const uint32_t attachmentWidth, const uint32_t attachmentHeight, const glm::vec3& cameraForward) { + p_Core->recordBeginDebugLabel(cmdStream, "Bloom/lense flare composition", { 1, 1, 1, 1 }); + p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle()); p_Core->prepareImageForSampling(cmdStream, m_LensFeatures.getHandle()); p_Core->prepareImageForStorage(cmdStream, colorAttachment); @@ -329,11 +343,13 @@ void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStrea // bloom composite dispatch p_Core->recordComputeDispatchToCmdStream( - cmdStream, - m_CompositePipe, - compositeDispatchCount, - {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)}, - pushConstants); + cmdStream, + m_CompositePipe, + compositeDispatchCount, + {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)}, + pushConstants); + + p_Core->recordEndDebugLabel(cmdStream); } void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment, diff --git a/projects/voxelization/src/ShadowMapping.cpp b/projects/voxelization/src/ShadowMapping.cpp index d8041c1e9935d148ded8fb1ca8f0e4d8b79fce71..ce4261ff2403139d10b9d677e7aa216a3e41178f 100644 --- a/projects/voxelization/src/ShadowMapping.cpp +++ b/projects/voxelization/src/ShadowMapping.cpp @@ -266,6 +266,7 @@ void ShadowMapping::recordShadowMapRendering( drawcalls.push_back(vkcv::DrawcallInfo(mesh, {})); } + m_corePtr->recordBeginDebugLabel(cmdStream, "Shadow map depth", {1, 1, 1, 1}); m_corePtr->recordDrawcallsToCmdStream( cmdStream, m_shadowMapPass, @@ -275,6 +276,7 @@ void ShadowMapping::recordShadowMapRendering( { m_shadowMapDepth.getHandle() }, windowHandle); m_corePtr->prepareImageForSampling(cmdStream, m_shadowMapDepth.getHandle()); + m_corePtr->recordEndDebugLabel(cmdStream); // depth to moments uint32_t dispatchCount[3]; @@ -287,6 +289,8 @@ void ShadowMapping::recordShadowMapRendering( vkcv::PushConstants msaaPushConstants (sizeof(msaaSampleCount)); msaaPushConstants.appendDrawcall(msaaSampleCount); + m_corePtr->recordBeginDebugLabel(cmdStream, "Depth to moments", { 1, 1, 1, 1 }); + m_corePtr->prepareImageForStorage(cmdStream, m_shadowMap.getHandle()); m_corePtr->recordComputeDispatchToCmdStream( cmdStream, @@ -295,6 +299,9 @@ void ShadowMapping::recordShadowMapRendering( { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_depthToMomentsDescriptorSet).vulkanHandle) }, msaaPushConstants); m_corePtr->prepareImageForSampling(cmdStream, m_shadowMap.getHandle()); + m_corePtr->recordEndDebugLabel(cmdStream); + + m_corePtr->recordBeginDebugLabel(cmdStream, "Moment shadow map blur", { 1, 1, 1, 1 }); // blur X m_corePtr->prepareImageForStorage(cmdStream, m_shadowMapIntermediate.getHandle()); @@ -316,6 +323,8 @@ void ShadowMapping::recordShadowMapRendering( vkcv::PushConstants(0)); m_shadowMap.recordMipChainGeneration(cmdStream); m_corePtr->prepareImageForSampling(cmdStream, m_shadowMap.getHandle()); + + m_corePtr->recordEndDebugLabel(cmdStream); } vkcv::ImageHandle ShadowMapping::getShadowMap() { diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp index 3cbff0df84757fb370a0372ddd45a9df401d4b60..c023af21c673651984e945ab03d16280c18f0768 100644 --- a/projects/voxelization/src/Voxelization.cpp +++ b/projects/voxelization/src/Voxelization.cpp @@ -251,6 +251,7 @@ void Voxelization::voxelizeMeshes( vkcv::PushConstants voxelCountPushConstants (sizeof(voxelCount)); voxelCountPushConstants.appendDrawcall(voxelCount); + m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel reset", { 1, 1, 1, 1 }); m_corePtr->recordComputeDispatchToCmdStream( cmdStream, m_voxelResetPipe, @@ -258,6 +259,7 @@ void Voxelization::voxelizeMeshes( { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).vulkanHandle) }, voxelCountPushConstants); m_corePtr->recordBufferMemoryBarrier(cmdStream, m_voxelBuffer.getHandle()); + m_corePtr->recordEndDebugLabel(cmdStream); // voxelization std::vector<vkcv::DrawcallInfo> drawcalls; @@ -270,6 +272,7 @@ void Voxelization::voxelizeMeshes( },1)); } + m_corePtr->recordBeginDebugLabel(cmdStream, "Voxelization", { 1, 1, 1, 1 }); m_corePtr->prepareImageForStorage(cmdStream, m_voxelImageIntermediate.getHandle()); m_corePtr->recordDrawcallsToCmdStream( cmdStream, @@ -279,6 +282,7 @@ void Voxelization::voxelizeMeshes( drawcalls, { m_dummyRenderTarget.getHandle() }, windowHandle); + m_corePtr->recordEndDebugLabel(cmdStream); // buffer to image const uint32_t bufferToImageGroupSize[3] = { 4, 4, 4 }; @@ -287,6 +291,7 @@ void Voxelization::voxelizeMeshes( bufferToImageDispatchCount[i] = glm::ceil(voxelResolution / float(bufferToImageGroupSize[i])); } + m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel buffer to image", { 1, 1, 1, 1 }); m_corePtr->recordComputeDispatchToCmdStream( cmdStream, m_bufferToImagePipe, @@ -295,14 +300,17 @@ void Voxelization::voxelizeMeshes( vkcv::PushConstants(0)); m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImageIntermediate.getHandle()); + m_corePtr->recordEndDebugLabel(cmdStream); // intermediate image mipchain + m_corePtr->recordBeginDebugLabel(cmdStream, "Intermediate Voxel mipmap generation", { 1, 1, 1, 1 }); m_voxelImageIntermediate.recordMipChainGeneration(cmdStream); m_corePtr->prepareImageForSampling(cmdStream, m_voxelImageIntermediate.getHandle()); + m_corePtr->recordEndDebugLabel(cmdStream); // secondary bounce + m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel secondary bounce", { 1, 1, 1, 1 }); m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle()); - m_corePtr->recordComputeDispatchToCmdStream( cmdStream, m_secondaryBouncePipe, @@ -310,12 +318,14 @@ void Voxelization::voxelizeMeshes( { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_secondaryBounceDescriptorSet).vulkanHandle) }, vkcv::PushConstants(0)); m_voxelImage.recordMipChainGeneration(cmdStream); - m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImage.getHandle()); + m_corePtr->recordEndDebugLabel(cmdStream); // final image mipchain + m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel mipmap generation", { 1, 1, 1, 1 }); m_voxelImage.recordMipChainGeneration(cmdStream); m_corePtr->prepareImageForSampling(cmdStream, m_voxelImage.getHandle()); + m_corePtr->recordEndDebugLabel(cmdStream); } void Voxelization::renderVoxelVisualisation( @@ -344,6 +354,7 @@ void Voxelization::renderVoxelVisualisation( vkcv::Mesh({}, nullptr, drawVoxelCount), { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).vulkanHandle) },1); + m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel visualisation", { 1, 1, 1, 1 }); m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle()); m_corePtr->recordDrawcallsToCmdStream( cmdStream, @@ -353,6 +364,7 @@ void Voxelization::renderVoxelVisualisation( { drawcall }, renderTargets, windowHandle); + m_corePtr->recordEndDebugLabel(cmdStream); } void Voxelization::updateVoxelOffset(const vkcv::camera::Camera& camera) { diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp index 763856cbe7b8b51c9d15e41a8170a2afe72c107d..a4ffb668e74d0a0829bb3c436ed5b992695b6ecf 100644 --- a/projects/voxelization/src/main.cpp +++ b/projects/voxelization/src/main.cpp @@ -776,6 +776,7 @@ int main(int argc, const char** argv) { const std::vector<vkcv::ImageHandle> prepassRenderTargets = { depthBuffer }; + core.recordBeginDebugLabel(cmdStream, "Depth prepass", { 1, 1, 1, 1 }); core.recordDrawcallsToCmdStream( cmdStream, prepassPass, @@ -786,6 +787,7 @@ int main(int argc, const char** argv) { windowHandle); core.recordImageMemoryBarrier(cmdStream, depthBuffer); + core.recordEndDebugLabel(cmdStream); vkcv::PushConstants pushConstants (2 * sizeof(glm::mat4)); @@ -802,6 +804,7 @@ int main(int argc, const char** argv) { const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer }; + core.recordBeginDebugLabel(cmdStream, "Forward rendering", { 1, 1, 1, 1 }); core.recordDrawcallsToCmdStream( cmdStream, forwardPass, @@ -810,6 +813,7 @@ int main(int argc, const char** argv) { drawcalls, renderTargets, windowHandle); + core.recordEndDebugLabel(cmdStream); if (renderVoxelVis) { voxelization.renderVoxelVisualisation(cmdStream, viewProjectionCamera, renderTargets, voxelVisualisationMip, windowHandle); @@ -819,6 +823,7 @@ int main(int argc, const char** argv) { skySettingsPushConstants.appendDrawcall(skySettings); // sky + core.recordBeginDebugLabel(cmdStream, "Sky", { 1, 1, 1, 1 }); core.recordDrawcallsToCmdStream( cmdStream, skyPass, @@ -827,6 +832,7 @@ int main(int argc, const char** argv) { { vkcv::DrawcallInfo(vkcv::Mesh({}, nullptr, 3), {}) }, renderTargets, windowHandle); + core.recordEndDebugLabel(cmdStream); const uint32_t fullscreenLocalGroupSize = 8; @@ -843,8 +849,8 @@ int main(int argc, const char** argv) { fulsscreenDispatchCount[2] = 1; if (usingMsaa) { + core.recordBeginDebugLabel(cmdStream, "MSAA resolve", { 1, 1, 1, 1 }); if (msaaCustomResolve) { - core.prepareImageForSampling(cmdStream, colorBuffer); core.prepareImageForStorage(cmdStream, resolvedColorBuffer); @@ -861,6 +867,7 @@ int main(int argc, const char** argv) { else { core.resolveMSAAImage(cmdStream, colorBuffer, resolvedColorBuffer); } + core.recordEndDebugLabel(cmdStream); } bloomFlares.execWholePipeline(cmdStream, resolvedColorBuffer, fsrWidth, fsrHeight, @@ -870,6 +877,7 @@ int main(int argc, const char** argv) { core.prepareImageForStorage(cmdStream, swapBuffer); core.prepareImageForSampling(cmdStream, resolvedColorBuffer); + core.recordBeginDebugLabel(cmdStream, "Tonemapping", { 1, 1, 1, 1 }); core.recordComputeDispatchToCmdStream( cmdStream, tonemappingPipeline, @@ -882,6 +890,7 @@ int main(int argc, const char** argv) { core.prepareImageForStorage(cmdStream, swapBuffer2); core.prepareImageForSampling(cmdStream, swapBuffer); + core.recordEndDebugLabel(cmdStream); if (bilinearUpscaling) { upscaling1.recordUpscaling(cmdStream, swapBuffer, swapBuffer2); @@ -906,6 +915,7 @@ int main(int argc, const char** argv) { glm::ceil(swapchainHeight / static_cast<float>(fullscreenLocalGroupSize)) ); + core.recordBeginDebugLabel(cmdStream, "Post Processing", { 1, 1, 1, 1 }); core.recordComputeDispatchToCmdStream( cmdStream, postEffectsPipeline, @@ -915,6 +925,7 @@ int main(int argc, const char** argv) { ).vulkanHandle) }, timePushConstants ); + core.recordEndDebugLabel(cmdStream); // present and end core.prepareSwapchainImageForPresent(cmdStream); diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp index f468bccc68c7b79391f1132160b7237a51f64081..9bb9e67166013a8d1c2f7eb22a2ea867ddc9da32 100644 --- a/src/vkcv/BufferManager.cpp +++ b/src/vkcv/BufferManager.cpp @@ -328,8 +328,8 @@ namespace vkcv { buffer.m_size); cmdBuffer.pipelineBarrier( - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eBottomOfPipe, + vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eAllCommands, {}, nullptr, memoryBarrier, diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp index de01b590c13e4941bd99619de05f2da144d6a2ec..efba11ec2a95d8b08c9c9ddc646ec471f4f7adce 100644 --- a/src/vkcv/Core.cpp +++ b/src/vkcv/Core.cpp @@ -52,7 +52,7 @@ namespace vkcv Core::Core(Context &&context, const CommandResources& commandResources, const SyncResources& syncResources) noexcept : m_Context(std::move(context)), m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)}, - m_PipelineManager{std::make_unique<GraphicsPipelineManager>(m_Context.m_Device)}, + m_PipelineManager{std::make_unique<GraphicsPipelineManager>(m_Context.m_Device, m_Context.m_PhysicalDevice)}, m_ComputePipelineManager{std::make_unique<ComputePipelineManager>(m_Context.m_Device)}, m_DescriptorManager(std::make_unique<DescriptorManager>(m_Context.m_Device)), m_BufferManager{std::unique_ptr<BufferManager>(new BufferManager())}, @@ -487,7 +487,7 @@ namespace vkcv void Core::recordBeginDebugLabel(const CommandStreamHandle &cmdStream, const std::string& label, const std::array<float, 4>& color) { -#ifndef NDEBUG + #ifdef VULKAN_DEBUG_LABELS static PFN_vkCmdBeginDebugUtilsLabelEXT beginDebugLabel = reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>( m_Context.getDevice().getProcAddr("vkCmdBeginDebugUtilsLabelEXT") ); @@ -506,11 +506,11 @@ namespace vkcv }; recordCommandsToStream(cmdStream, submitFunction, nullptr); -#endif + #endif } void Core::recordEndDebugLabel(const CommandStreamHandle &cmdStream) { -#ifndef NDEBUG + #ifdef VULKAN_DEBUG_LABELS static PFN_vkCmdEndDebugUtilsLabelEXT endDebugLabel = reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>( m_Context.getDevice().getProcAddr("vkCmdEndDebugUtilsLabelEXT") ); @@ -524,7 +524,7 @@ namespace vkcv }; recordCommandsToStream(cmdStream, submitFunction, nullptr); -#endif + #endif } void Core::recordComputeIndirectDispatchToCmdStream( @@ -781,6 +781,14 @@ namespace vkcv }, nullptr); } + void Core::prepareImageForAttachmentManually(const vk::CommandBuffer& cmdBuffer, const ImageHandle& image) { + transitionRendertargetsToAttachmentLayout({ image }, *m_ImageManager, cmdBuffer); + } + + void Core::updateImageLayoutManual(const vkcv::ImageHandle& image, const vk::ImageLayout layout) { + m_ImageManager->updateImageLayoutManual(image, layout); + } + void Core::recordImageMemoryBarrier(const CommandStreamHandle& cmdStream, const ImageHandle& image) { recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageMemoryBarrier(image, cmdBuffer); @@ -900,7 +908,7 @@ namespace vkcv static void setDebugObjectLabel(const vk::Device& device, const vk::ObjectType& type, uint64_t handle, const std::string& label) { -#ifndef NDEBUG +#ifndef VULKAN_DEBUG_LABELS static PFN_vkSetDebugUtilsObjectNameEXT setDebugLabel = reinterpret_cast<PFN_vkSetDebugUtilsObjectNameEXT>( device.getProcAddr("vkSetDebugUtilsObjectNameEXT") ); diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp index 69a35685098509afcbfd49210b2b09b454e9bbb9..7480289b1f2a0b1db194db3a06c52f7050888afe 100644 --- a/src/vkcv/DescriptorManager.cpp +++ b/src/vkcv/DescriptorManager.cpp @@ -80,7 +80,7 @@ namespace vkcv } //create the descriptor set's layout from the binding data gathered above - vk::DescriptorSetLayout vulkanHandle = nullptr; + vk::DescriptorSetLayout vulkanHandle; vk::DescriptorSetLayoutCreateInfo layoutInfo({}, bindingsVector); auto result = m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &vulkanHandle); if (result != vk::Result::eSuccess) { @@ -97,7 +97,7 @@ namespace vkcv { //create and allocate the set based on the layout provided DescriptorSetLayout setLayout = m_DescriptorSetLayouts[setLayoutHandle.getId()]; - vk::DescriptorSet vulkanHandle = nullptr; + vk::DescriptorSet vulkanHandle; vk::DescriptorSetAllocateInfo allocInfo(m_Pools.back(), 1, &setLayout.vulkanHandle); auto result = m_Device.allocateDescriptorSets(&allocInfo, &vulkanHandle); if(result != vk::Result::eSuccess) diff --git a/src/vkcv/GraphicsPipelineManager.cpp b/src/vkcv/GraphicsPipelineManager.cpp index cb7dd31dddd3a5c0742f95e8429175724aa26454..870220f45b52c022f7c2d445a40e99ab4a6a4a2f 100644 --- a/src/vkcv/GraphicsPipelineManager.cpp +++ b/src/vkcv/GraphicsPipelineManager.cpp @@ -5,8 +5,9 @@ namespace vkcv { - GraphicsPipelineManager::GraphicsPipelineManager(vk::Device device) noexcept : - m_Device{device}, + GraphicsPipelineManager::GraphicsPipelineManager(vk::Device device, vk::PhysicalDevice physicalDevice) noexcept : + m_Device(device), + m_physicalDevice(physicalDevice), m_Pipelines{} {} @@ -237,7 +238,9 @@ namespace vkcv * @param config sets Depth Clamping and Culling Mode * @return Pipeline Rasterization State Create Info Struct */ - vk::PipelineRasterizationStateCreateInfo createPipelineRasterizationStateCreateInfo(const GraphicsPipelineConfig &config) { + vk::PipelineRasterizationStateCreateInfo createPipelineRasterizationStateCreateInfo( + const GraphicsPipelineConfig &config, + const vk::PhysicalDeviceConservativeRasterizationPropertiesEXT& conservativeRasterProperties) { vk::CullModeFlags cullMode; switch (config.m_culling) { case CullMode::None: @@ -267,14 +270,14 @@ namespace vkcv 0.f, 1.f ); - + static vk::PipelineRasterizationConservativeStateCreateInfoEXT conservativeRasterization; if (config.m_UseConservativeRasterization) { conservativeRasterization = vk::PipelineRasterizationConservativeStateCreateInfoEXT( {}, vk::ConservativeRasterizationModeEXT::eOverestimate, - 0.f + std::max(1 - conservativeRasterProperties.primitiveOverestimationSize, 0.f) ); pipelineRasterizationStateCreateInfo.pNext = &conservativeRasterization; @@ -544,8 +547,13 @@ namespace vkcv createPipelineViewportStateCreateInfo(config); // rasterization state + vk::PhysicalDeviceConservativeRasterizationPropertiesEXT conservativeRasterProperties; + vk::PhysicalDeviceProperties deviceProperties; + vk::PhysicalDeviceProperties2 deviceProperties2(deviceProperties); + deviceProperties2.pNext = &conservativeRasterProperties; + m_physicalDevice.getProperties2(&deviceProperties2); vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo = - createPipelineRasterizationStateCreateInfo(config); + createPipelineRasterizationStateCreateInfo(config, conservativeRasterProperties); // multisample state vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo = diff --git a/src/vkcv/GraphicsPipelineManager.hpp b/src/vkcv/GraphicsPipelineManager.hpp index a08e64939dc967511095f22862c52de05148d7e9..782603ab0e1ffa9bde05fda96c5d2d259eff1953 100644 --- a/src/vkcv/GraphicsPipelineManager.hpp +++ b/src/vkcv/GraphicsPipelineManager.hpp @@ -20,7 +20,7 @@ namespace vkcv { public: GraphicsPipelineManager() = delete; // no default ctor - explicit GraphicsPipelineManager(vk::Device device) noexcept; // ctor + explicit GraphicsPipelineManager(vk::Device device, vk::PhysicalDevice physicalDevice) noexcept; // ctor ~GraphicsPipelineManager() noexcept; // dtor GraphicsPipelineManager(const GraphicsPipelineManager &other) = delete; // copy-ctor @@ -71,8 +71,9 @@ namespace vkcv GraphicsPipelineConfig m_config; }; - vk::Device m_Device; - std::vector<GraphicsPipeline> m_Pipelines; + vk::Device m_Device; + vk::PhysicalDevice m_physicalDevice; // needed to get infos to configure conservative rasterization + std::vector<GraphicsPipeline> m_Pipelines; void destroyPipelineById(uint64_t id); diff --git a/src/vkcv/ImageLayoutTransitions.cpp b/src/vkcv/ImageLayoutTransitions.cpp index 8d31c64ccbcbf33e259714f8c581c920738190b4..14b226847a15b5e9cadffc28555e76b88b61e6a3 100644 --- a/src/vkcv/ImageLayoutTransitions.cpp +++ b/src/vkcv/ImageLayoutTransitions.cpp @@ -58,8 +58,8 @@ namespace vkcv { void recordImageBarrier(vk::CommandBuffer cmdBuffer, vk::ImageMemoryBarrier barrier) { cmdBuffer.pipelineBarrier( - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eBottomOfPipe, + vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eAllCommands, {}, nullptr, nullptr, diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp index 4ddd7f8c44c6023a80831bc8b4b092692e84ec86..bde020498e19e3f9bf0667c7182ca13d11f9044f 100644 --- a/src/vkcv/ImageManager.cpp +++ b/src/vkcv/ImageManager.cpp @@ -685,4 +685,20 @@ namespace vkcv { } } + void ImageManager::updateImageLayoutManual(const vkcv::ImageHandle& handle, const vk::ImageLayout layout) { + const uint64_t id = handle.getId(); + + if (handle.isSwapchainImage()) { + m_swapchainImages[m_currentSwapchainInputImage].m_layout = layout; + } + else { + if (id >= m_images.size()) { + vkcv_log(LogLevel::ERROR, "Invalid handle"); + return; + } + m_swapchainImages[id].m_layout = layout; + } + + } + } \ No newline at end of file diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp index 4d99422118e8d464ea75d9f013b471f3dd40fd8c..124508d5e36711a13ad49ae289b9de98600e0d6e 100644 --- a/src/vkcv/ImageManager.hpp +++ b/src/vkcv/ImageManager.hpp @@ -120,5 +120,9 @@ namespace vkcv { void setSwapchainImages(const std::vector<vk::Image>& images, const std::vector<vk::ImageView>& views, uint32_t width, uint32_t height, vk::Format format); + // if manual vulkan work, e.g. ImGui integration, changes an image layout this function must be used + // to update the internal image state + void updateImageLayoutManual(const vkcv::ImageHandle& handle, const vk::ImageLayout layout); + }; } \ No newline at end of file