From c39aba1ee821164c3c65f49432b825cce351b096 Mon Sep 17 00:00:00 2001
From: Jens Koenen <koenen@vr.rwth-aachen.de>
Date: Sat, 12 Feb 2022 16:45:36 +0100
Subject: [PATCH] Shadows fully implemented.

---
 res/dpr/light_library.glsl         |  56 ++++++++++++-
 res/dpr/native_stereo_forward.frag |   1 +
 res/dpr/shadow.geom                |   2 +-
 src/geometry_buffer.hpp            |   8 +-
 src/naive_stereo_forward.cpp       |  14 ++++
 src/naive_stereo_forward.hpp       |   2 +
 src/scene.cpp                      | 127 ++++++++++++++++++++---------
 src/shadow_cache.cpp               |   4 +-
 src/shadow_cache.hpp               |  31 +++++++
 9 files changed, 200 insertions(+), 45 deletions(-)

diff --git a/res/dpr/light_library.glsl b/res/dpr/light_library.glsl
index 2bc5709e..a1c4a67a 100644
--- a/res/dpr/light_library.glsl
+++ b/res/dpr/light_library.glsl
@@ -4,9 +4,12 @@
   If the define is not set, an error is emitted.
   After including the light-library, the function apply_local_lighting(...) can be used to compute local lighting.
   The used lighting model is physically based and was taken from the GLTF-Specification.
+  When the light calculation should also include shadows, the define SHADOW_DESCRIPTOR_SET has to be set.
+  This defines specifies the descriptor set for the shadow cache.
   Example:
   
-    #define LIGHT_DESCRIPTOR_SET 42
+    #define LIGHT_DESCRIPTOR_SET 42 //Required
+    #define SHADOW_DESCRIPTOR_SET 43 //Optional
     #include "light_library.glsl"
 
     void main()
@@ -55,6 +58,45 @@ layout(set = SHADOW_DESCRIPTOR_SET, binding = 3) uniform ShadowParameterBuffer
 //-- Shadow Functions ------------------------------------------------------------------------------
 
 #ifdef SHADOW_DESCRIPTOR_SET
+uint get_shadow_frustum(uint light, vec3 surface_position)
+{
+    vec3 direction = surface_position - lights[light].position;
+    vec3 absolut = abs(direction);
+    uint frustum = 0;
+
+    if (absolut.x > absolut.y && absolut.x > absolut.z)
+    {
+        frustum = 0;
+
+        if(direction.x < 0.0)
+        {
+            frustum += 1;
+        }
+    }
+
+    else if (absolut.y > absolut.z)
+    {
+        frustum = 2;
+
+        if(direction.y < 0.0)
+        {
+            frustum += 1;
+        }
+    }
+
+    else
+    {
+        frustum = 4;
+
+        if(direction.z < 0.0)
+        {
+            frustum += 1;
+        }
+    }
+
+    return frustum;
+}
+
 float get_shadow(uint light, vec3 surface_position)
 {
     uint light_type = lights[light].type;
@@ -67,7 +109,7 @@ float get_shadow(uint light, vec3 surface_position)
         shadow_position /= shadow_position.w;
         shadow_position.xy = (shadow_position.xy + 1.0) / 2.0;
 
-        return texture(sampler_shadow_directional, vec4(shadow_position.xy, layer, shadow_position.z - 0.001)).x;
+        return texture(sampler_shadow_directional, vec4(shadow_position.xy, layer, shadow_position.z - 0.008)).x;
     }
 
     else if (light_type == LIGHT_TYPE_SPOT && shadow_parameter.use_spot != 0)
@@ -83,7 +125,15 @@ float get_shadow(uint light, vec3 surface_position)
     
     else if (light_type == LIGHT_TYPE_POINT && shadow_parameter.use_point != 0)
     {
-        
+        uint layer = lights[light].type_index;
+        uint frustum = get_shadow_frustum(light, surface_position);
+
+        vec4 shadow_position = lights[light].shadow_matrix[frustum] * vec4(surface_position, 1.0);
+        shadow_position /= shadow_position.w;
+
+        vec3 shadow_direction = surface_position - lights[light].position;
+
+        return texture(sampler_shadow_point, vec4(shadow_direction, layer), shadow_position.z - 0.001).x;
     }
 
     return 1.0;
diff --git a/res/dpr/native_stereo_forward.frag b/res/dpr/native_stereo_forward.frag
index c37b7920..b3434468 100644
--- a/res/dpr/native_stereo_forward.frag
+++ b/res/dpr/native_stereo_forward.frag
@@ -3,6 +3,7 @@
 
 #define MATERIAL_DESCRIPTOR_SET 1
 #define LIGHT_DESCRIPTOR_SET 3
+#define SHADOW_DESCRIPTOR_SET 4
 
 #include "math_library.glsl"
 #include "material_library.glsl"
diff --git a/res/dpr/shadow.geom b/res/dpr/shadow.geom
index cafa101f..c3b0abe6 100644
--- a/res/dpr/shadow.geom
+++ b/res/dpr/shadow.geom
@@ -1,6 +1,6 @@
 #version 450 core
 
-layout(triangles, invocations = 2) in;
+layout(triangles) in;
 layout(triangle_strip, max_vertices = 3) out;
 
 layout(push_constant) uniform Constants
diff --git a/src/geometry_buffer.hpp b/src/geometry_buffer.hpp
index c48fa14e..cf8f2ed6 100644
--- a/src/geometry_buffer.hpp
+++ b/src/geometry_buffer.hpp
@@ -5,11 +5,15 @@
   Before calling the function apply_local_lighting(...) and computing the local lighting, it is neccessary to ensure that every writing process effecting the geometry buffer has been completed.
   Such a synchronisation can be defined by adding a render pass dependency between the last and the extrenal subpass.
   After the completion of apply_local_lighting(...) the local lighting can be read from the output image in a fragment shader without any additional synchronisations.
+  In order to also include shadows when executing apply_local_lighting(...), a shadow cache object has to be added to the create pareameters.
   Example:
 
+    //Optional: Shadow Cache
+    shadow_cache::ptr shadow_cache = create_shadow_cache(app()->scene(), shadow_cache_settings());
+
     //Create render pass
     geometry_buffer geometry_buffer;
-    geometry_buffer.create(output_image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, per_frame_descriptor, scene);
+    geometry_buffer.create(output_image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, per_frame_descriptor, scene, Optional: shadow_cache);
 
     subpass_dependency::ptr subpass_dependency = make_subpass_dependency(0, VK_SUBPASS_EXTERNAL);
     subpass_dependency->set_stage_mask(..., VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
@@ -41,6 +45,8 @@
     render_pass->create({image_views}, ...);
 
     //During rendering
+    shadow_cache->compute_shadow(command_buffer, frame); //Optinal: Generate shadow maps
+
     render_pass->process(command_buffer, 0); //Write to geometry buffer
     geometry_buffer.apply_local_lighting(command_buffer, per_frame_descriptor_set); //Compute local lighting and write it to the output image
 */
diff --git a/src/naive_stereo_forward.cpp b/src/naive_stereo_forward.cpp
index 2f131c10..39a87532 100644
--- a/src/naive_stereo_forward.cpp
+++ b/src/naive_stereo_forward.cpp
@@ -10,6 +10,7 @@ naive_stereo_forward::naive_stereo_forward(vr_app* app) : stereo_strategy(app)
 
 naive_stereo_forward::~naive_stereo_forward()
 {
+    this->shadow_cache->destroy();
     this->pipeline_layout->destroy();
 
     for (native_stereo_forward_pass& eye_pass : this->eye_passes)
@@ -21,6 +22,13 @@ naive_stereo_forward::~naive_stereo_forward()
 
 bool naive_stereo_forward::create()
 {
+    this->shadow_cache = create_shadow_cache(app()->scene(), shadow_cache_settings());
+
+    if (this->shadow_cache == nullptr)
+    {
+        return false;
+    }
+
     if (!this->create_pipeline_layout())
     {
         return false;
@@ -51,6 +59,10 @@ bool naive_stereo_forward::create()
 
 void naive_stereo_forward::render(VkCommandBuffer command_buffer, index frame)
 {
+    pass_timer().begin_pass(command_buffer, "shadow");
+    this->shadow_cache->compute_shadow(command_buffer, frame);
+    pass_timer().end_pass(command_buffer);
+
     pass_timer().begin_pass(command_buffer, "left_eye");
     this->eye_passes[0].render_pass->process(command_buffer, 0);
     pass_timer().end_pass(command_buffer);
@@ -70,6 +82,7 @@ bool naive_stereo_forward::create_pipeline_layout()
     this->pipeline_layout->add(app()->scene()->material_descriptor);
     this->pipeline_layout->add(app()->scene()->mesh_descriptor);
     this->pipeline_layout->add(app()->scene()->light_descriptor);
+    this->pipeline_layout->add(this->shadow_cache->get_descriptor());
     
     if (!this->pipeline_layout->create(app()->device()))
     {
@@ -200,6 +213,7 @@ void naive_stereo_forward::pipeline_function(VkCommandBuffer command_buffer, con
 {
     this->pipeline_layout->bind(command_buffer, framebuffer->per_frame_descriptor_set[app()->frame_index()], 0);
     this->pipeline_layout->bind(command_buffer, app()->scene()->light_descriptor_set, 3);
+    this->pipeline_layout->bind(command_buffer, this->shadow_cache->get_descriptor_set(), 4);
 
     for (size_t i = 0; i < app()->scene()->meshes.size(); ++i)
     {
diff --git a/src/naive_stereo_forward.hpp b/src/naive_stereo_forward.hpp
index 511159b1..f25d873b 100644
--- a/src/naive_stereo_forward.hpp
+++ b/src/naive_stereo_forward.hpp
@@ -1,5 +1,6 @@
 #pragma once
 #include "stereo_strategy.hpp"
+#include "shadow_cache.hpp"
 
 struct native_stereo_forward_pass
 {
@@ -24,6 +25,7 @@ private:
     void pipeline_function(VkCommandBuffer command_buffer, const stereo_framebuffer::ptr& framebuffer);
 
 private:
+    shadow_cache::ptr shadow_cache;
     lava::pipeline_layout::ptr pipeline_layout;
     native_stereo_forward_pass eye_passes[2];
 };
\ No newline at end of file
diff --git a/src/scene.cpp b/src/scene.cpp
index a6c4f341..0cc1eda5 100644
--- a/src/scene.cpp
+++ b/src/scene.cpp
@@ -188,18 +188,18 @@ void scene::compute_mesh_box(scene_mesh& mesh, const glm::mat4& localToWorldSpac
         glm::vec3(local_max.x, local_min.y, local_min.z),
         glm::vec3(local_max.x, local_min.y, local_max.z),
         glm::vec3(local_max.x, local_max.y, local_min.z),
-        glm::vec3(local_max.x, local_max.y, local_max.z),
+        glm::vec3(local_max.x, local_max.y, local_max.z)
     };
 
     for (glm::vec3& corner_point : mesh_local_box)
     {
-        corner_point = localToWorldSpace * glm::vec4(corner_point, 1.0);
+        corner_point = glm::vec3(localToWorldSpace * glm::vec4(corner_point, 1.0));
     }
 
-    glm::vec3 global_min = mesh_local_box[0];
-    glm::vec3 global_max = mesh_local_box[0];
+    glm::vec3 global_min = mesh_local_box.front();
+    glm::vec3 global_max = mesh_local_box.front();
 
-    for (glm::vec3& corner_point : mesh_local_box)
+    for (const glm::vec3& corner_point : mesh_local_box)
     {
         global_min = glm::min(global_min, corner_point);
         global_max = glm::max(global_max, corner_point);
@@ -267,40 +267,89 @@ void build_shadow_matrix(glsl::LightData& light_data, const glm::vec3& box_min,
         light_data.shadow_matrix[index] = glm::mat4(1.0f);
     }
 
-    glm::vec3 box_size = box_max - box_min;
-    float box_diagonal = glm::length(box_size);
-
     if (light_data.type == LIGHT_TYPE_POINT)
     {
-        //TODO:
+        std::array<glm::vec3, 8> directions = 
+        {
+            glm::vec3(1.0f, 0.0f, 0.0f),
+            glm::vec3(-1.0f, 0.0f, 0.0f),
+            glm::vec3(0.0f, 1.0f, 0.0f),
+            glm::vec3(0.0f, -1.0f, 0.0f),
+            glm::vec3(0.0f, 0.0f, 1.0f),
+            glm::vec3(0.0f, 0.0f, -1.0f)
+        };
+
+        std::array<glm::vec3, 8> sides =
+        {
+            glm::vec3(0.0f, -1.0f, 0.0f),
+            glm::vec3(0.0f, -1.0f, 0.0f),
+            glm::vec3(0.0f, 0.0f, 1.0f),
+            glm::vec3(0.0f, 0.0f, -1.0f),
+            glm::vec3(0.0f, -1.0f, 0.0f),
+            glm::vec3(0.0f, -1.0f, 0.0f)
+        };
+
+        glm::vec3 box_size = box_max - box_min;
+        float box_diagonal = glm::length(box_size);
+
+        glm::mat4 projection_matrix = glm::perspective(glm::pi<float>() / 2.0f, 1.0f, 10.0f, box_diagonal);
+
+        for (uint32_t index = 0; index < directions.size(); index++)
+        {
+            glm::mat4 view_matrix = glm::lookAt(light_data.position, light_data.position + directions[index], sides[index]);
+
+            light_data.shadow_matrix[index] = projection_matrix * view_matrix;
+        }
     }
 
     else if(light_data.type == LIGHT_TYPE_DIRECTIONAL)
     {
-         glm::vec3 direction = glm::normalize(light_data.direction);
-         glm::vec3 side1 = glm::vec3(1.0f, 0.0f, 0.0f);
+        glm::vec3 direction = glm::normalize(light_data.direction);
+        glm::vec3 side = glm::vec3(1.0f, 0.0f, 0.0f);
+
+        if (glm::dot(direction, side) > 0.9)
+        {
+            side = glm::vec3(0.0f, 1.0f, 0.0f);
+        }
 
-        if (glm::dot(direction, side1) > 0.9)
+        glm::mat4 view_matrix = glm::lookAt(glm::vec3(0.0f), direction, side);
+
+        std::array<glm::vec3, 8> box_points =
+        {
+            glm::vec3(box_min.x, box_min.y, box_min.z),
+            glm::vec3(box_min.x, box_min.y, box_max.z),
+            glm::vec3(box_min.x, box_max.y, box_min.z),
+            glm::vec3(box_min.x, box_max.y, box_max.z),
+            glm::vec3(box_max.x, box_min.y, box_min.z),
+            glm::vec3(box_max.x, box_min.y, box_max.z),
+            glm::vec3(box_max.x, box_max.y, box_min.z),
+            glm::vec3(box_max.x, box_max.y, box_max.z)
+        };
+
+        for (glm::vec3& point : box_points)
         {
-            side1 = glm::vec3(1.0f, 0.0f, 0.0f);
+            point = glm::vec3(view_matrix * glm::vec4(point, 1.0));
         }
 
-        glm::vec3 side2 = glm::cross(direction, side1);
-        side1 = glm::cross(direction, side2);
+        glm::vec3 light_min = box_points.front();
+        glm::vec3 light_max = box_points.front();
 
-        glm::mat4 view_matrix = glm::mat4(1.0f);
-        view_matrix[0] = glm::vec4(side1, 0.0f);
-        view_matrix[1] = glm::vec4(side2, 0.0f);
-        view_matrix[2] = glm::vec4(direction, 0.0f);
-        view_matrix = glm::transpose(view_matrix);
+        for (const glm::vec3& point : box_points)
+        {
+            light_min = glm::min(light_min, point);
+            light_max = glm::max(light_max, point);
+        }
 
-        //glm::mat4 projection_matrix = glm::ortho();
+        glm::mat4 projection_matrix = glm::ortho(light_min.x, light_max.x, light_min.y, light_max.y, -light_max.z, -light_min.z);
 
-        //light_data.shadow_matrix[0] = projection_matrix * view_matrix;
+        light_data.shadow_matrix[0] = projection_matrix * view_matrix;
     }
 
     else if(light_data.type == LIGHT_TYPE_SPOT)
     {
+        glm::vec3 box_size = box_max - box_min;
+        float box_diagonal = glm::length(box_size);
+
         glm::vec3 position = light_data.position;
         glm::vec3 direction = glm::normalize(light_data.direction);
         float angle = light_data.outer_angle;
@@ -309,7 +358,7 @@ void build_shadow_matrix(glsl::LightData& light_data, const glm::vec3& box_min,
 
         if (glm::dot(direction, side) > 0.9)
         {
-            side = glm::vec3(1.0f, 0.0f, 0.0f);
+            side = glm::vec3(0.0f, 1.0f, 0.0f);
         }
 
         glm::mat4 view_matrix = glm::lookAt(position, position + direction, side);
@@ -671,9 +720,9 @@ scene::ptr load_scene(lava::device_ptr device, lava::name filename, uint32_t fra
         light_data.position = assimp_vector3_to_glm(light->mPosition);
         light_data.type = type;
         light_data.color = assimp_color3_to_glm(light->mColorDiffuse) / 20.0f; //This factor is used so that the Bistro Scene looks okay. This factor needs to be removed if all scenes are reexported
-        light_data.outer_angle = glm::cos(light->mAngleOuterCone);
+        light_data.outer_angle = light->mAngleOuterCone;
         light_data.direction = glm::normalize(assimp_vector3_to_glm(light->mDirection));
-        light_data.inner_angle = glm::cos(light->mAngleInnerCone);
+        light_data.inner_angle = light->mAngleInnerCone;
         light_data.padding = glm::vec3(0.0f);
         light_data.type_index = type_index;
 
@@ -682,27 +731,29 @@ scene::ptr load_scene(lava::device_ptr device, lava::name filename, uint32_t fra
         scene->light_data.push_back(light_data);
     }
 
-    //Default Light
-    glsl::LightData light_data;
-    light_data.position = glm::vec3(0.0f, 1000.0f, 0.0f);
-    light_data.type = LIGHT_TYPE_DIRECTIONAL;
-    light_data.color = glm::vec3(10.0f);
-    light_data.outer_angle = glm::pi<float>() / 4.0f;
-    light_data.direction = glm::normalize(glm::vec3(0.1, -1.0f, 0.0));
-    light_data.inner_angle = glm::pi<float>() / 8.0f;
-    light_data.padding = glm::vec3(0.0f);
-    light_data.type_index = directional_index; //Has to be the same as the type !
+    if (assimp_scene->mNumLights == 0) //Default Light
+    {
+        glsl::LightData light_data;
+        light_data.position = glm::vec3(0.0f, 0.0f, 0.0f);
+        light_data.type = LIGHT_TYPE_DIRECTIONAL;
+        light_data.color = glm::vec3(10.0f);
+        light_data.outer_angle = 0.0f;
+        light_data.direction = glm::normalize(glm::vec3(0.1, -1.0f, 0.25));
+        light_data.inner_angle = 0.0f;
+        light_data.padding = glm::vec3(0.0f);
+        light_data.type_index = 0;
 
-    build_shadow_matrix(light_data, box_min, box_max);
+        build_shadow_matrix(light_data, box_min, box_max);
 
-    scene->light_data.push_back(light_data);
+        scene->light_data.push_back(light_data);
+    }
 
     float unit_scale_factor = 100.0; //Assume that the scene is in centimeters, the function below does not work for the rexported scenes
     //This factor needs to be removed if all scenes are reexported
     //assimp_scene->mMetaData->Get("UnitScaleFactor", unit_scale_factor);
 
     glsl::LightParameter light_parameter;
-    light_parameter.ambient = glm::vec3(0.05f); //This factor is used so that the Bistro Scene looks okay.
+    light_parameter.ambient = glm::vec3(0.075f); //This factor is used so that the Bistro Scene looks okay.
     light_parameter.scene_scale = 1.0f / unit_scale_factor;
     light_parameter.count = scene->light_data.size();
 
diff --git a/src/shadow_cache.cpp b/src/shadow_cache.cpp
index a7b260c7..38ca2f22 100644
--- a/src/shadow_cache.cpp
+++ b/src/shadow_cache.cpp
@@ -521,7 +521,7 @@ bool shadow_cache::create_render_pass(lava::device_ptr device, const shadow_cach
     this->render_pass->add(subpass_begin_dependency);
     this->render_pass->add(subpass_end_dependency);
     this->render_pass->add(attachment); //location 0: image array
-    this->render_pass->set_layers(this->image_array->get_depth());
+    this->render_pass->set_layers(this->image_array->get_info().arrayLayers);
     this->render_pass->set_clear_values(
     { 
         clear_value
@@ -654,7 +654,7 @@ void shadow_cache::pipline_function(VkCommandBuffer command_buffer)
                 constants[1] = light_index;
                 constants[2] = frustum_index;
 
-                vkCmdPushConstants(command_buffer, this->pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(uint32_t) * constants.size(), constants.data());
+                vkCmdPushConstants(command_buffer, this->pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(uint32_t) * constants.size(), constants.data());
 
                 this->pipline_scene(command_buffer);
             }
diff --git a/src/shadow_cache.hpp b/src/shadow_cache.hpp
index a666eac2..bf2aac28 100644
--- a/src/shadow_cache.hpp
+++ b/src/shadow_cache.hpp
@@ -1,3 +1,34 @@
+/*
+  The shadow cache is responsible for the allocation and generation of the shadow maps.
+  During the creation for the shadow cache, it is possible to set the resolution of shadow maps and the types of light for which a shadow map should be generated with the help of a shadow_cache_settings struct.
+  Before the shadow map can be used, the function compute_shadow(...) has to be called during rendering.
+  After that, the shadow cache can be used by ether by adding it to a geometry buffer or by attaching the descriptor set for the shadow cache to a pipeline.
+  In order to use the shadow cache during forward shading, it is neccessary to bind the descriptor of the cache to the binding point that is specified in the fragment shader by the define SHADOW_DESCRIPTOR_SET.
+  Example (Deferred):
+
+    shadow_cache::ptr shadow_cache = create_shadow_cache(app()->scene(), shadow_cache_settings());
+
+    geometry_buffer geometry_buffer;
+    geometry_buffer.create(output_image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, per_frame_descriptor, scene, shadow_cache); //Add the shadow cache to geometry buffer
+
+    //During Rendering
+    shadow_cache->compute_shadow(command_buffer, frame); //Generate shadow maps
+
+    render_pass->process(command_buffer, 0); //Fill geometry buffer
+    geometry_buffer.apply_local_lighting(command_buffer, per_frame_descriptor_set); //Compute local lighting with shadows
+
+  Example (Forward):
+
+    shadow_cache::ptr shadow_cache = create_shadow_cache(app()->scene(), shadow_cache_settings());
+
+    pipeline_layout->add(shadow_cache->get_descriptor()); //Same binding point as SHADOW_DESCRIPTOR_SET
+
+    //During Rendering
+    shadow_cache->compute_shadow(command_buffer, frame); //Generate shadow maps
+
+    pipeline_layout->bind(command_buffer, shadow_cache->get_descriptor_set(), 42); //Same binding point as SHADOW_DESCRIPTOR_SET
+*/
+
 #pragma once
 #include <liblava/lava.hpp>
 #include <memory>
-- 
GitLab