diff --git a/res/dpr/light_library.glsl b/res/dpr/light_library.glsl index 2bc5709e02357b2de35bdc6a1ef7159c80bec21b..a1c4a67a2dad88603a665f92c14f433fa1c46320 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 c37b7920bac5dee869ad511afb932b87ff405500..b3434468e7c446144fa28865eae318e154005e93 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 cafa101fed39e12545f15e0e8428c72194f7833b..c3b0abe6bff78750ea7bd808e4752505beaf7b5c 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 c48fa14e17040cebf4bec16f38cb28e7c1ae513c..cf8f2ed6a3aa6e0b5421418625ef707ecce10c6d 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 2f131c10c65c4076b2946a8d5c8aba0f5ed30c4b..39a87532a323422cfc87b9e410da36cb2918a99b 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 511159b1340e86adb7e357feacab7d490b5eb479..f25d873b6c90bedfad5bddbf99589b3035797bc9 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 a6c4f34170efdd515a8185e23881bec6f732e5f7..0cc1eda52f953699217e682b8a13c664d4cdb177 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 a7b260c7acc38f62ab29db05b2237751174cc334..38ca2f22e9677667b822cb563cd6bbde8824cb0b 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 a666eac2ee0cbd6924f4e3973a25f9a7b398ce98..bf2aac28b6e0604eba9e54d732f97c290bad7943 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>