├── data ├── csm_1.jpg ├── csm_2.jpg └── sponza.zip ├── .gitmodules ├── src ├── shader │ ├── empty_fs.glsl │ ├── fullscreen_vs.glsl │ ├── copy_fs.glsl │ ├── depth_prepass_vs.glsl │ ├── shadow_map_vs.glsl │ ├── depth_reduction_fs.glsl │ ├── scene_vs.glsl │ ├── scene_fs.glsl │ └── setup_cascades_cs.glsl ├── CMakeLists.txt ├── csm.h ├── csm.cpp └── main.cpp ├── .gitignore ├── CMakeLists.txt ├── LICENSE └── README.md /data/csm_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/cascaded-shadow-maps/HEAD/data/csm_1.jpg -------------------------------------------------------------------------------- /data/csm_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/cascaded-shadow-maps/HEAD/data/csm_2.jpg -------------------------------------------------------------------------------- /data/sponza.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/cascaded-shadow-maps/HEAD/data/sponza.zip -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/dwSampleFramework"] 2 | path = external/dwSampleFramework 3 | url = https://github.com/diharaw/dwSampleFramework.git 4 | -------------------------------------------------------------------------------- /src/shader/empty_fs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // MAIN ------------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | void main() 6 | { 7 | 8 | } 9 | 10 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 2 | 3 | if(APPLE) 4 | add_executable(CascadedShadowMaps MACOSX_BUNDLE "main.cpp" "csm.h" "csm.cpp") 5 | set(MACOSX_BUNDLE_BUNDLE_NAME "com.dihara.csm") 6 | else() 7 | add_executable(CascadedShadowMaps "main.cpp" "csm.h" "csm.cpp") 8 | endif() 9 | 10 | target_link_libraries(CascadedShadowMaps dwSampleFramework) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | external/ 35 | external/* 36 | bin/ 37 | bin/* 38 | build/ 39 | build/* 40 | lib/ 41 | lib/* 42 | 43 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 2 | 3 | project("CascadedShadowMaps") 4 | 5 | IF(APPLE) 6 | set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++14") 7 | set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") 8 | ENDIF() 9 | 10 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib") 11 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib") 12 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) 13 | 14 | add_subdirectory(external/dwSampleFramework) 15 | 16 | include_directories("${DW_SAMPLE_FRAMEWORK_INCLUDES}") 17 | 18 | add_subdirectory(src) -------------------------------------------------------------------------------- /src/shader/fullscreen_vs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // OUTPUTS ---------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | out vec2 PS_IN_TexCoord; 6 | 7 | // ------------------------------------------------------------------ 8 | // MAIN ------------------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | void main() 12 | { 13 | PS_IN_TexCoord = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2); 14 | gl_Position = vec4(PS_IN_TexCoord * 2.0f + -1.0f, 0.0f, 1.0f); 15 | } 16 | 17 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dihara Wijetunga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/shader/copy_fs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // OUTPUTS ---------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | out vec2 PS_OUT_Color; 6 | 7 | // ------------------------------------------------------------------ 8 | // INPUTS ----------------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | in vec2 PS_IN_TexCoord; 12 | 13 | // ------------------------------------------------------------------ 14 | // UNIFORMS --------------------------------------------------------- 15 | // ------------------------------------------------------------------ 16 | 17 | uniform sampler2D s_Texture; 18 | 19 | // ------------------------------------------------------------------ 20 | // MAIN ------------------------------------------------------------- 21 | // ------------------------------------------------------------------ 22 | 23 | void main() 24 | { 25 | PS_OUT_Color = texture(s_Texture, PS_IN_TexCoord).xx; 26 | } 27 | 28 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/depth_prepass_vs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // INPUTS ----------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | layout (location = 0) in vec3 VS_IN_Position; 6 | layout (location = 1) in vec2 VS_IN_TexCoord; 7 | layout (location = 2) in vec3 VS_IN_Normal; 8 | layout (location = 3) in vec3 VS_IN_Tangent; 9 | layout (location = 4) in vec3 VS_IN_Bitangent; 10 | 11 | // ------------------------------------------------------------------ 12 | // UNIFORMS --------------------------------------------------------- 13 | // ------------------------------------------------------------------ 14 | 15 | layout(std430, binding = 0) buffer GlobalUniforms 16 | { 17 | mat4 view; 18 | mat4 projection; 19 | mat4 crop[8]; 20 | }; 21 | 22 | layout (std140) uniform ObjectUniforms 23 | { 24 | mat4 model; 25 | }; 26 | 27 | // ------------------------------------------------------------------ 28 | // MAIN ------------------------------------------------------------- 29 | // ------------------------------------------------------------------ 30 | 31 | void main() 32 | { 33 | gl_Position = projection * view * model * vec4(VS_IN_Position, 1.0); 34 | } 35 | 36 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/shadow_map_vs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // INPUTS ----------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | layout (location = 0) in vec3 VS_IN_Position; 6 | layout (location = 1) in vec2 VS_IN_TexCoord; 7 | layout (location = 2) in vec3 VS_IN_Normal; 8 | layout (location = 3) in vec3 VS_IN_Tangent; 9 | layout (location = 4) in vec3 VS_IN_Bitangent; 10 | 11 | // ------------------------------------------------------------------ 12 | // UNIFORMS --------------------------------------------------------- 13 | // ------------------------------------------------------------------ 14 | 15 | layout(std430, binding = 0) buffer GlobalUniforms 16 | { 17 | mat4 view; 18 | mat4 projection; 19 | mat4 crop[8]; 20 | }; 21 | 22 | layout (std140) uniform ObjectUniforms 23 | { 24 | mat4 model; 25 | }; 26 | 27 | uniform int u_CascadeIndex; 28 | 29 | // ------------------------------------------------------------------ 30 | // MAIN ------------------------------------------------------------- 31 | // ------------------------------------------------------------------ 32 | 33 | void main() 34 | { 35 | gl_Position = crop[u_CascadeIndex] * model * vec4(VS_IN_Position, 1.0); 36 | } 37 | 38 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/shader/depth_reduction_fs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // OUTPUTS ---------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | out vec2 PS_OUT_Color; 6 | 7 | // ------------------------------------------------------------------ 8 | // INPUTS ----------------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | in vec2 PS_IN_TexCoord; 12 | 13 | // ------------------------------------------------------------------ 14 | // UNIFORMS --------------------------------------------------------- 15 | // ------------------------------------------------------------------ 16 | 17 | uniform sampler2D s_Texture; 18 | 19 | // ------------------------------------------------------------------ 20 | // MAIN ------------------------------------------------------------- 21 | // ------------------------------------------------------------------ 22 | 23 | void main() 24 | { 25 | vec4 depth_x = textureGather(s_Texture, PS_IN_TexCoord, 0); 26 | vec4 depth_y = textureGather(s_Texture, PS_IN_TexCoord, 1); 27 | 28 | PS_OUT_Color = vec2(min(min(depth_x.x, depth_x.y), min(depth_x.z, depth_x.w)), max(max(depth_y.x, depth_y.y), max(depth_y.z, depth_y.w))); 29 | } 30 | 31 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CascadedShadowMaps 2 | 3 | A sample implementation of Cascaded Shadow Maps with Parallel Split Shadow Mapping (PSSM), stable cascades and Percentage Closer Filtering (PCF). 4 | 5 | ## Screenshots 6 | ![Sample](data/csm_1.jpg) 7 | 8 | ![Sample](data/csm_2.jpg) 9 | 10 | ## Dependencies 11 | * [dwSampleFramework](https://github.com/diharaw/dwSampleFramework) 12 | 13 | ## License 14 | ``` 15 | Copyright (c) 2019 Dihara Wijetunga 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 18 | associated documentation files (the "Software"), to deal in the Software without restriction, 19 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 20 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 21 | subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all copies or substantial 24 | portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 27 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 29 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | ``` 32 | -------------------------------------------------------------------------------- /src/shader/scene_vs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // INPUTS ----------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | layout (location = 0) in vec3 VS_IN_Position; 6 | layout (location = 1) in vec2 VS_IN_TexCoord; 7 | layout (location = 2) in vec3 VS_IN_Normal; 8 | layout (location = 3) in vec3 VS_IN_Tangent; 9 | layout (location = 4) in vec3 VS_IN_Bitangent; 10 | 11 | // ------------------------------------------------------------------ 12 | // OUTPUTS ---------------------------------------------------------- 13 | // ------------------------------------------------------------------ 14 | 15 | out vec3 PS_IN_WorldFragPos; 16 | out vec4 PS_IN_NDCFragPos; 17 | out vec3 PS_IN_Normal; 18 | out vec2 PS_IN_TexCoord; 19 | 20 | // ------------------------------------------------------------------ 21 | // UNIFORMS --------------------------------------------------------- 22 | // ------------------------------------------------------------------ 23 | 24 | layout(std430, binding = 0) buffer GlobalUniforms 25 | { 26 | mat4 view; 27 | mat4 projection; 28 | mat4 crop[8]; 29 | }; 30 | 31 | layout (std140) uniform ObjectUniforms 32 | { 33 | mat4 model; 34 | }; 35 | 36 | // ------------------------------------------------------------------ 37 | // MAIN ------------------------------------------------------------- 38 | // ------------------------------------------------------------------ 39 | 40 | void main() 41 | { 42 | vec4 position = model * vec4(VS_IN_Position, 1.0); 43 | PS_IN_WorldFragPos = position.xyz; 44 | PS_IN_Normal = mat3(model) * VS_IN_Normal; 45 | PS_IN_TexCoord = VS_IN_TexCoord; 46 | PS_IN_NDCFragPos = projection * view * position; 47 | gl_Position = PS_IN_NDCFragPos; 48 | } 49 | 50 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/csm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define MAX_FRUSTUM_SPLITS 8 9 | 10 | struct FrustumSplit 11 | { 12 | float near_plane; 13 | float far_plane; 14 | float ratio; 15 | float fov; 16 | glm::vec3 center; 17 | glm::vec3 corners[8]; 18 | }; 19 | 20 | struct CSM 21 | { 22 | dw::gl::Texture2D* m_shadow_maps = nullptr; 23 | dw::gl::Framebuffer* m_shadow_fbos[MAX_FRUSTUM_SPLITS]; 24 | float m_lambda; 25 | float m_near_offset; 26 | int m_split_count; 27 | int m_shadow_map_size; 28 | FrustumSplit m_splits[MAX_FRUSTUM_SPLITS]; 29 | float m_far_bounds[MAX_FRUSTUM_SPLITS]; 30 | glm::vec3 m_light_direction; 31 | glm::mat4 m_bias; 32 | glm::mat4 m_light_view; 33 | glm::mat4 m_crop_matrices[MAX_FRUSTUM_SPLITS]; // crop * proj * view 34 | glm::mat4 m_proj_matrices[MAX_FRUSTUM_SPLITS]; // crop * proj * light_view * inv_view 35 | glm::mat4 m_texture_matrices[MAX_FRUSTUM_SPLITS]; 36 | bool m_stable_pssm = true; 37 | 38 | CSM(); 39 | ~CSM(); 40 | void initialize(float lambda, float near_offset, int split_count, int shadow_map_size, dw::Camera* camera, int _width, int _height, glm::vec3 dir); 41 | void shutdown(); 42 | void update(dw::Camera* camera, glm::vec3 dir); 43 | void update_splits(dw::Camera* camera); 44 | void update_frustum_corners(dw::Camera* camera); 45 | void update_crop_matrices(glm::mat4 t_modelview, dw::Camera* camera); 46 | void update_texture_matrices(dw::Camera* camera); 47 | void update_far_bounds(dw::Camera* camera); 48 | void bind_sdsm_uniforms(dw::gl::Program* program, dw::Camera* camera, glm::vec3 dir); 49 | 50 | inline FrustumSplit* frustum_splits() { return &m_splits[0]; } 51 | inline glm::mat4 split_view_proj(int i) { return m_crop_matrices[i]; } 52 | inline glm::mat4 texture_matrix(int i) { return m_texture_matrices[i]; } 53 | inline float far_bound(int i) { return m_far_bounds[i]; } 54 | inline dw::gl::Texture2D* shadow_map() { return m_shadow_maps; } 55 | inline dw::gl::Framebuffer** framebuffers() { return &m_shadow_fbos[0]; } 56 | inline uint32_t frustum_split_count() { return m_split_count; } 57 | inline uint32_t near_offset() { return m_near_offset; } 58 | inline uint32_t lambda() { return m_lambda; } 59 | inline uint32_t shadow_map_size() { return m_shadow_map_size; } 60 | }; 61 | -------------------------------------------------------------------------------- /src/shader/scene_fs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // OUTPUTS ---------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | out vec4 PS_OUT_Color; 6 | 7 | // ------------------------------------------------------------------ 8 | // INPUTS ----------------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | in vec3 PS_IN_WorldFragPos; 12 | in vec4 PS_IN_NDCFragPos; 13 | in vec3 PS_IN_Normal; 14 | in vec2 PS_IN_TexCoord; 15 | 16 | // ------------------------------------------------------------------ 17 | // UNIFORMS --------------------------------------------------------- 18 | // ------------------------------------------------------------------ 19 | 20 | layout(std140, binding = 2) buffer CSMUniforms 21 | { 22 | mat4 texture_matrices[8]; 23 | vec4 direction; 24 | vec4 options; 25 | int num_cascades; 26 | float far_bounds[8]; 27 | }; 28 | 29 | uniform sampler2D s_Diffuse; 30 | uniform sampler2DArray s_ShadowMap; 31 | 32 | // ------------------------------------------------------------------ 33 | 34 | float depth_compare(float a, float b, float bias) 35 | { 36 | return a - bias > b ? 1.0 : 0.0; 37 | } 38 | 39 | // ------------------------------------------------------------------ 40 | 41 | float shadow_occlussion(float frag_depth, vec3 n, vec3 l) 42 | { 43 | int index = 0; 44 | float blend = 0.0; 45 | 46 | // Find shadow cascade. 47 | for (int i = 0; i < num_cascades - 1; i++) 48 | { 49 | if (frag_depth > far_bounds[i]) 50 | index = i + 1; 51 | } 52 | 53 | blend = clamp( (frag_depth - far_bounds[index] * 0.995) * 200.0, 0.0, 1.0); 54 | 55 | // Apply blend options. 56 | blend *= options.z; 57 | 58 | // Transform frag position into Light-space. 59 | vec4 light_space_pos = texture_matrices[index] * vec4(PS_IN_WorldFragPos, 1.0f); 60 | 61 | float current_depth = light_space_pos.z; 62 | 63 | float bias = max(0.0005 * (1.0 - dot(n, l)), 0.0005); 64 | 65 | float shadow = 0.0; 66 | vec2 texelSize = 1.0 / textureSize(s_ShadowMap, 0).xy; 67 | for(int x = -1; x <= 1; ++x) 68 | { 69 | for(int y = -1; y <= 1; ++y) 70 | { 71 | float pcfDepth = texture(s_ShadowMap, vec3(light_space_pos.xy + vec2(x, y) * texelSize, float(index))).r; 72 | shadow += current_depth - bias > pcfDepth ? 1.0 : 0.0; 73 | } 74 | } 75 | shadow /= 9.0; 76 | 77 | if (options.x == 1.0) 78 | { 79 | //if (blend > 0.0 && index != num_cascades - 1) 80 | //{ 81 | // light_space_pos = texture_matrices[index + 1] * vec4(PS_IN_WorldFragPos, 1.0f); 82 | // shadow_map_depth = texture(s_ShadowMap, vec3(light_space_pos.xy, float(index + 1))).r; 83 | // current_depth = light_space_pos.z; 84 | // float next_shadow = depth_compare(current_depth, shadow_map_depth, bias); 85 | // 86 | // return (1.0 - blend) * shadow + blend * next_shadow; 87 | //} 88 | //else 89 | return shadow; 90 | } 91 | else 92 | return 0.0; 93 | } 94 | 95 | // ------------------------------------------------------------------ 96 | 97 | vec3 debug_color(float frag_depth) 98 | { 99 | int index = 0; 100 | 101 | // Find shadow cascade. 102 | for (int i = 0; i < num_cascades - 1; i++) 103 | { 104 | if (frag_depth > far_bounds[i]) 105 | index = i + 1; 106 | } 107 | 108 | if (index == 0) 109 | return vec3(1.0, 0.0, 0.0); 110 | else if (index == 1) 111 | return vec3(0.0, 1.0, 0.0); 112 | else if (index == 2) 113 | return vec3(0.0, 0.0, 1.0); 114 | else 115 | return vec3(1.0, 1.0, 0.0); 116 | } 117 | 118 | // ------------------------------------------------------------------ 119 | // MAIN ------------------------------------------------------------- 120 | // ------------------------------------------------------------------ 121 | 122 | void main() 123 | { 124 | vec3 n = normalize(PS_IN_Normal); 125 | vec3 l = -direction.xyz; 126 | 127 | float lambert = max(0.0f, dot(n, l)); 128 | 129 | vec3 diffuse = vec3(0.7);// texture(s_Diffuse, PS_IN_TexCoord * 50).xyz; 130 | vec3 ambient = diffuse * 0.3; 131 | 132 | float frag_depth = (PS_IN_NDCFragPos.z / PS_IN_NDCFragPos.w) * 0.5 + 0.5; 133 | float shadow = shadow_occlussion(frag_depth, n, l); 134 | 135 | vec3 cascade = options.y == 1.0 ? debug_color(frag_depth) : vec3(0.0); 136 | vec3 color = (1.0 - shadow) * diffuse * lambert + ambient + cascade * 0.5; 137 | 138 | PS_OUT_Color = vec4(color, 1.0); 139 | } 140 | 141 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/csm.cpp: -------------------------------------------------------------------------------- 1 | #include "csm.h" 2 | #include 3 | #include 4 | 5 | CSM::CSM() 6 | { 7 | m_shadow_maps = nullptr; 8 | 9 | for (int i = 0; i < 8; i++) 10 | { 11 | m_shadow_fbos[i] = nullptr; 12 | } 13 | } 14 | 15 | CSM::~CSM() 16 | { 17 | 18 | } 19 | 20 | void CSM::initialize(float lambda, float near_offset, int split_count, int shadow_map_size, dw::Camera* camera, int _width, int _height, glm::vec3 dir) 21 | { 22 | m_lambda = lambda; 23 | m_near_offset = near_offset; 24 | m_split_count = split_count; 25 | m_shadow_map_size = shadow_map_size; 26 | 27 | if (m_shadow_maps) 28 | { 29 | DW_SAFE_DELETE(m_shadow_maps); 30 | m_shadow_maps = nullptr; 31 | } 32 | 33 | for (int i = 0; i < 8; i++) 34 | { 35 | if (m_shadow_fbos[i]) 36 | { 37 | DW_SAFE_DELETE(m_shadow_fbos[i]); 38 | m_shadow_fbos[i] = nullptr; 39 | } 40 | } 41 | 42 | m_shadow_maps = new dw::gl::Texture2D(m_shadow_map_size, m_shadow_map_size, m_split_count, 1, 1, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT); 43 | m_shadow_maps->set_min_filter(GL_NEAREST); 44 | m_shadow_maps->set_mag_filter(GL_NEAREST); 45 | m_shadow_maps->set_wrapping(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); 46 | 47 | for (int i = 0; i < m_split_count; i++) 48 | { 49 | m_shadow_fbos[i] = new dw::gl::Framebuffer(); 50 | m_shadow_fbos[i]->attach_depth_stencil_target(m_shadow_maps, i, 0); 51 | } 52 | 53 | float camera_fov = camera->m_fov; 54 | float width = _width; 55 | float height = _height; 56 | float ratio = width / height; 57 | 58 | // note that fov is in radians here and in OpenGL it is in degrees. 59 | // the 0.2f factor is important because we might get artifacts at 60 | // the screen borders. 61 | for (int i = 0; i < m_split_count; i++) 62 | { 63 | m_splits[i].fov = camera_fov / 57.2957795 + 0.2f; 64 | m_splits[i].ratio = ratio; 65 | } 66 | 67 | m_bias = glm::mat4(0.5f, 0.0f, 0.0f, 0.0f, 68 | 0.0f, 0.5f, 0.0f, 0.0f, 69 | 0.0f, 0.0f, 0.5f, 0.0f, 70 | 0.5f, 0.5f, 0.5f, 1.0f); 71 | 72 | update(camera, dir); 73 | } 74 | 75 | void CSM::shutdown() 76 | { 77 | for (int i = 0; i < 8; i++) 78 | { 79 | if (m_shadow_fbos[i]) 80 | DW_SAFE_DELETE(m_shadow_fbos[i]); 81 | } 82 | 83 | DW_SAFE_DELETE(m_shadow_maps); 84 | } 85 | 86 | void CSM::update(dw::Camera* camera, glm::vec3 dir) 87 | { 88 | dir = glm::normalize(dir); 89 | m_light_direction = dir; 90 | 91 | glm::vec3 center = camera->m_position + camera->m_forward * 50.0f; 92 | glm::vec3 light_pos = center - dir * ((camera->m_far - camera->m_near) / 2.0f); 93 | glm::vec3 right = glm::cross(dir, glm::vec3(0.0f, 1.0f, 0.0f)); 94 | 95 | glm::vec3 up = m_stable_pssm ? camera->m_up : camera->m_right; 96 | 97 | glm::mat4 modelview = glm::lookAt(light_pos, center, up); 98 | 99 | m_light_view = modelview; 100 | 101 | update_splits(camera); 102 | update_frustum_corners(camera); 103 | update_crop_matrices(modelview, camera); 104 | update_texture_matrices(camera); 105 | update_far_bounds(camera); 106 | } 107 | 108 | void CSM::update_splits(dw::Camera* camera) 109 | { 110 | float nd = camera->m_near; 111 | float fd = camera->m_far; 112 | 113 | float lambda = m_lambda; 114 | float ratio = fd / nd; 115 | m_splits[0].near_plane = nd; 116 | 117 | for (int i = 1; i < m_split_count; i++) 118 | { 119 | float si = i / (float)m_split_count; 120 | 121 | // Practical Split Scheme: https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html 122 | float t_near = lambda * (nd * powf(ratio, si)) + (1 - lambda) * (nd + (fd - nd) * si); 123 | float t_far = t_near * 1.005f; 124 | m_splits[i].near_plane = t_near; 125 | m_splits[i - 1].far_plane = t_far; 126 | } 127 | 128 | m_splits[m_split_count - 1].far_plane = fd; 129 | } 130 | 131 | void CSM::update_frustum_corners(dw::Camera* camera) 132 | { 133 | glm::vec3 center = camera->m_position; 134 | glm::vec3 view_dir = camera->m_forward; 135 | 136 | glm::vec3 up(0.0f, 1.0f, 0.0f); 137 | glm::vec3 right = glm::cross(view_dir, up); 138 | 139 | for (int i = 0; i < m_split_count; i++) 140 | { 141 | FrustumSplit& t_frustum = m_splits[i]; 142 | 143 | glm::vec3 fc = center + view_dir * t_frustum.far_plane; 144 | glm::vec3 nc = center + view_dir * t_frustum.near_plane; 145 | 146 | right = glm::normalize(right); 147 | up = glm::normalize(glm::cross(right, view_dir)); 148 | 149 | // these heights and widths are half the heights and widths of 150 | // the near and far plane rectangles 151 | float near_height = tan(t_frustum.fov / 2.0f) * t_frustum.near_plane; 152 | float near_width = near_height * t_frustum.ratio; 153 | float far_height = tan(t_frustum.fov / 2.0f) * t_frustum.far_plane; 154 | float far_width = far_height * t_frustum.ratio; 155 | 156 | t_frustum.corners[0] = nc - up * near_height - right * near_width; // near-bottom-left 157 | t_frustum.corners[1] = nc + up * near_height - right * near_width; // near-top-left 158 | t_frustum.corners[2] = nc + up * near_height + right * near_width; // near-top-right 159 | t_frustum.corners[3] = nc - up * near_height + right * near_width; // near-bottom-right 160 | 161 | t_frustum.corners[4] = fc - up * far_height - right * far_width; // far-bottom-left 162 | t_frustum.corners[5] = fc + up * far_height - right * far_width; // far-top-left 163 | t_frustum.corners[6] = fc + up * far_height + right * far_width; // far-top-right 164 | t_frustum.corners[7] = fc - up * far_height + right * far_width; // far-bottom-right 165 | } 166 | } 167 | 168 | void CSM::update_texture_matrices(dw::Camera* camera) 169 | { 170 | for (int i = 0; i < m_split_count; i++) 171 | m_texture_matrices[i] = m_bias * m_crop_matrices[i]; 172 | } 173 | 174 | void CSM::update_far_bounds(dw::Camera* camera) 175 | { 176 | // for every active split 177 | for(int i = 0 ; i < m_split_count ; i++) 178 | { 179 | // f[i].fard is originally in eye space - tell's us how far we can see. 180 | // Here we compute it in camera homogeneous coordinates. Basically, we calculate 181 | // cam_proj * (0, 0, f[i].fard, 1)^t and then normalize to [0; 1] 182 | 183 | FrustumSplit& split = m_splits[i]; 184 | glm::vec4 pos = camera->m_projection * glm::vec4(0.0f, 0.0f, -split.far_plane, 1.0f); 185 | glm::vec4 ndc = pos / pos.w; 186 | 187 | m_far_bounds[i] = ndc.z * 0.5f + 0.5f; 188 | } 189 | } 190 | 191 | void CSM::bind_sdsm_uniforms(dw::gl::Program* program, dw::Camera* camera, glm::vec3 dir) 192 | { 193 | dir = glm::normalize(dir); 194 | m_light_direction = dir; 195 | 196 | glm::vec3 center = camera->m_position + camera->m_forward * 50.0f; 197 | glm::vec3 light_pos = center - dir * ((camera->m_far - camera->m_near) / 2.0f); 198 | glm::vec3 right = glm::cross(dir, glm::vec3(0.0f, 1.0f, 0.0f)); 199 | 200 | glm::vec3 up = m_stable_pssm ? camera->m_up : camera->m_right; 201 | 202 | glm::mat4 modelview = glm::lookAt(light_pos, center, up); 203 | 204 | m_light_view = modelview; 205 | 206 | program->set_uniform("u_Lambda", m_lambda); 207 | program->set_uniform("u_NearOffset", m_near_offset); 208 | program->set_uniform("u_Bias", m_bias); 209 | program->set_uniform("u_ModelView", m_light_view); 210 | program->set_uniform("u_FOV", m_splits[0].fov); 211 | program->set_uniform("u_Ratio", m_splits[0].ratio); 212 | program->set_uniform("u_ShadowMapSize", m_shadow_map_size); 213 | program->set_uniform("u_StablePSSM", (int)m_stable_pssm); 214 | } 215 | 216 | void CSM::update_crop_matrices(glm::mat4 t_modelview, dw::Camera* camera) 217 | { 218 | glm::mat4 t_projection; 219 | for (int i = 0; i < m_split_count; i++) 220 | { 221 | FrustumSplit& t_frustum = m_splits[i]; 222 | 223 | glm::vec3 tmax(-INFINITY, -INFINITY, -INFINITY); 224 | glm::vec3 tmin(INFINITY, INFINITY, INFINITY); 225 | 226 | // find the z-range of the current frustum as seen from the light 227 | // in order to increase precision 228 | 229 | // note that only the z-component is need and thus 230 | // the multiplication can be simplified 231 | // transf.z = shad_modelview[2] * f.point[0].x + shad_modelview[6] * f.point[0].y + shad_modelview[10] * f.point[0].z + shad_modelview[14]; 232 | glm::vec4 t_transf = t_modelview * glm::vec4(t_frustum.corners[0], 1.0f); 233 | 234 | tmin.z = t_transf.z; 235 | tmax.z = t_transf.z; 236 | for (int j = 1; j < 8; j++) 237 | { 238 | t_transf = t_modelview * glm::vec4(t_frustum.corners[j], 1.0f); 239 | if (t_transf.z > tmax.z) { tmax.z = t_transf.z; } 240 | if (t_transf.z < tmin.z) { tmin.z = t_transf.z; } 241 | } 242 | 243 | //tmax.z += 50; // TODO: This solves the dissapearing shadow problem. but how to fix? 244 | 245 | // Calculate frustum split center 246 | t_frustum.center = glm::vec3(0.0f, 0.0f, 0.0f); 247 | 248 | for (int j = 0; j < 8; j++) 249 | t_frustum.center += t_frustum.corners[j]; 250 | 251 | t_frustum.center /= 8.0f; 252 | 253 | if (m_stable_pssm) 254 | { 255 | // Calculate bounding sphere radius 256 | float radius = 0.0f; 257 | 258 | for (int j = 0; j < 8; j++) 259 | { 260 | float length = glm::length(t_frustum.corners[j] - t_frustum.center); 261 | radius = glm::max(radius, length); 262 | } 263 | 264 | radius = ceil(radius * 16.0f) / 16.0f; 265 | 266 | // Find bounding box that fits the sphere 267 | glm::vec3 radius3(radius, radius, radius); 268 | 269 | glm::vec3 max = radius3; 270 | glm::vec3 min = -radius3; 271 | 272 | glm::vec3 cascade_extents = max - min; 273 | 274 | // Push the light position back along the light direction by the near offset. 275 | glm::vec3 shadow_camera_pos = t_frustum.center - m_light_direction * m_near_offset; 276 | 277 | // Add the near offset to the Z value of the cascade extents to make sure the orthographic frustum captures the entire frustum split (else it will exhibit cut-off issues). 278 | glm::mat4 ortho = glm::ortho(min.x, max.x, min.y, max.y, -m_near_offset, m_near_offset + cascade_extents.z); 279 | glm::mat4 view = glm::lookAt(shadow_camera_pos, t_frustum.center, camera->m_up); 280 | 281 | m_proj_matrices[i] = ortho; 282 | m_crop_matrices[i] = ortho * view; 283 | 284 | glm::vec4 shadow_origin = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); 285 | shadow_origin = m_crop_matrices[i] * shadow_origin; 286 | shadow_origin = shadow_origin * (m_shadow_map_size / 2.0f); 287 | 288 | glm::vec4 rounded_origin = glm::round(shadow_origin); 289 | glm::vec4 round_offset = rounded_origin - shadow_origin; 290 | round_offset = round_offset * (2.0f / m_shadow_map_size); 291 | round_offset.z = 0.0f; 292 | round_offset.w = 0.0f; 293 | 294 | glm::mat4& shadow_proj = m_proj_matrices[i]; 295 | 296 | shadow_proj[3][0] += round_offset.x; 297 | shadow_proj[3][1] += round_offset.y; 298 | shadow_proj[3][2] += round_offset.z; 299 | shadow_proj[3][3] += round_offset.w; 300 | 301 | m_crop_matrices[i] = shadow_proj * view; 302 | } 303 | else 304 | { 305 | glm::mat4 t_ortho = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, -m_near_offset, -tmin.z); 306 | glm::mat4 t_shad_mvp = t_ortho * t_modelview; 307 | 308 | // find the extends of the frustum slice as projected in light's homogeneous coordinates 309 | for (int j = 0; j < 8; j++) 310 | { 311 | t_transf = t_shad_mvp * glm::vec4(t_frustum.corners[j], 1.0f); 312 | 313 | t_transf.x /= t_transf.w; 314 | t_transf.y /= t_transf.w; 315 | 316 | if (t_transf.x > tmax.x) { tmax.x = t_transf.x; } 317 | if (t_transf.x < tmin.x) { tmin.x = t_transf.x; } 318 | if (t_transf.y > tmax.y) { tmax.y = t_transf.y; } 319 | if (t_transf.y < tmin.y) { tmin.y = t_transf.y; } 320 | } 321 | 322 | glm::vec2 tscale(2.0f / (tmax.x - tmin.x), 2.0f / (tmax.y - tmin.y)); 323 | glm::vec2 toffset(-0.5f * (tmax.x + tmin.x) * tscale.x, -0.5f * (tmax.y + tmin.y) * tscale.y); 324 | 325 | glm::mat4 t_shad_crop = glm::mat4(1.0f); 326 | t_shad_crop[0][0] = tscale.x; 327 | t_shad_crop[1][1] = tscale.y; 328 | t_shad_crop[0][3] = toffset.x; 329 | t_shad_crop[1][3] = toffset.y; 330 | t_shad_crop = glm::transpose(t_shad_crop); 331 | 332 | t_projection = t_shad_crop * t_ortho; 333 | 334 | // Store the projection matrix 335 | m_proj_matrices[i] = t_projection; 336 | m_crop_matrices[i] = t_projection * t_modelview; 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/shader/setup_cascades_cs.glsl: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------ 2 | // INPUTS ----------------------------------------------------------- 3 | // ------------------------------------------------------------------ 4 | 5 | layout (local_size_x = 1, local_size_y = 1) in; 6 | 7 | // ------------------------------------------------------------------ 8 | // UNIFORMS --------------------------------------------------------- 9 | // ------------------------------------------------------------------ 10 | 11 | layout(std430, binding = 0) buffer GlobalUniforms 12 | { 13 | mat4 view; 14 | mat4 projection; 15 | mat4 crop[8]; 16 | }; 17 | 18 | layout(std140, binding = 1) buffer CSMUniforms 19 | { 20 | mat4 texture_matrices[8]; 21 | vec4 direction; 22 | vec4 options; 23 | int num_cascades; 24 | float far_bounds[8]; 25 | }; 26 | 27 | uniform float u_Near; 28 | uniform float u_Far; 29 | uniform vec3 u_CameraPos; 30 | uniform vec3 u_CameraDir; 31 | uniform vec3 u_CameraUp; 32 | uniform int u_MaxMip; 33 | uniform float u_Lambda; 34 | uniform float u_NearOffset; 35 | uniform float u_FOV; 36 | uniform float u_Ratio; 37 | uniform mat4 u_Bias; 38 | uniform mat4 u_ModelView; 39 | uniform int u_StablePSSM; 40 | uniform int u_ShadowMapSize; 41 | 42 | uniform sampler2D u_Depth; 43 | 44 | // ------------------------------------------------------------------ 45 | // STRUCTURES ------------------------------------------------------- 46 | // ------------------------------------------------------------------ 47 | 48 | struct FrustumSplit 49 | { 50 | float near_plane; 51 | float far_plane; 52 | float ratio; 53 | float fov; 54 | vec3 center; 55 | vec3 corners[8]; 56 | }; 57 | 58 | // ------------------------------------------------------------------ 59 | // GLOBALS ---------------------------------------------------------- 60 | // ------------------------------------------------------------------ 61 | 62 | #define MAX_FRUSTUM_SPLITS 8 63 | #define kINFINITY 999999999 64 | 65 | FrustumSplit splits[MAX_FRUSTUM_SPLITS]; 66 | mat4 proj_matrices[MAX_FRUSTUM_SPLITS]; 67 | 68 | // ------------------------------------------------------------------ 69 | // FUNCTIONS -------------------------------------------------------- 70 | // ------------------------------------------------------------------ 71 | 72 | mat4 look_at(vec3 _eye, vec3 _origin, vec3 _up) 73 | { 74 | vec3 front = normalize(_origin - _eye); 75 | vec3 right = normalize(cross(front, _up)); 76 | vec3 up = normalize(cross(right, front)); 77 | 78 | mat4 m = mat4(1.0); 79 | 80 | m[0][0] = right.x; 81 | m[1][0] = right.y; 82 | m[2][0] = right.z; 83 | 84 | m[0][1] = up.x; 85 | m[1][1] = up.y; 86 | m[2][1] = up.z; 87 | 88 | m[0][2] = -front.x; 89 | m[1][2] = -front.y; 90 | m[2][2] = -front.z; 91 | 92 | m[3][0] = -dot(right, _eye); 93 | m[3][1] = -dot(up, _eye); 94 | m[3][2] = dot(front, _eye); 95 | 96 | return m; 97 | } 98 | 99 | // ------------------------------------------------------------------ 100 | 101 | mat4 ortho(float _l, float _r, float _b, float _t, float _n, float _f) 102 | { 103 | mat4 m = mat4(1.0); 104 | 105 | m[0][0] = 2.0 / (_r - _l); 106 | m[1][1] = 2.0 / (_t - _b); 107 | m[2][2] = -2.0 / (_f - _n); 108 | 109 | m[3][0] = -(_r + _l) / (_r - _l); 110 | m[3][1] = -(_t + _b) / (_t - _b); 111 | m[3][2] = -(_f + _n) / (_f - _n); 112 | 113 | return m; 114 | } 115 | 116 | // ------------------------------------------------------------------ 117 | 118 | // Take exponential depth and convert into linear depth. 119 | 120 | float depth_exp_to_view(mat4 inverse_proj, float exp_depth) 121 | { 122 | exp_depth = exp_depth * 2.0 - 1.0; 123 | float w = inverse_proj[2][3] * exp_depth + inverse_proj[3][3]; 124 | return (1.0 / w); 125 | } 126 | 127 | // ------------------------------------------------------------------ 128 | 129 | float depth_exp_to_view(float near, float far, float exp_depth) 130 | { 131 | return (2.0 * near * far) / (far + near - exp_depth * (far - near)); 132 | } 133 | 134 | // ------------------------------------------------------------------ 135 | 136 | float depth_view_to_linear_01(float near, float far, float depth) 137 | { 138 | return (depth - near) / (far - near); 139 | } 140 | 141 | // ------------------------------------------------------------------ 142 | 143 | float depth_linear_01_to_view(float near, float far, float depth) 144 | { 145 | return near + depth * (far - near); 146 | } 147 | 148 | // ------------------------------------------------------------------ 149 | 150 | float depth_exp_to_linear_01(float near, float far, float depth) 151 | { 152 | float view_depth = depth_exp_to_view(near, far, depth); 153 | return depth_view_to_linear_01(near, far, view_depth); 154 | } 155 | 156 | // ------------------------------------------------------------------ 157 | 158 | void update_splits() 159 | { 160 | vec2 min_max = textureLod(u_Depth, vec2(0.0), u_MaxMip).xy; 161 | 162 | // float nd = depth_exp_to_view(u_Near, u_Far, min_max.x) - 0.1; 163 | float fd = depth_exp_to_view(u_Near, u_Far, min_max.y) + 1.0; 164 | 165 | float nd = u_Near; 166 | // float fd = 200.0; 167 | 168 | float lambda = u_Lambda; 169 | float ratio = fd / nd; 170 | splits[0].near_plane = nd; 171 | 172 | for (int i = 1; i < num_cascades; i++) 173 | { 174 | float si = i / float(num_cascades); 175 | 176 | // Practical Split Scheme: https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html 177 | float t_near = lambda * (nd * pow(ratio, si)) + (1 - lambda) * (nd + (fd - nd) * si); 178 | float t_far = t_near * 1.005; 179 | splits[i].near_plane = t_near; 180 | splits[i - 1].far_plane = t_far; 181 | } 182 | 183 | splits[num_cascades - 1].far_plane = fd; 184 | } 185 | 186 | // ------------------------------------------------------------------ 187 | 188 | void update_frustum_corners() 189 | { 190 | vec3 center = u_CameraPos; 191 | vec3 view_dir = u_CameraDir; 192 | 193 | vec3 up = vec3(0.0, 1.0, 0.0); 194 | vec3 right = cross(view_dir, up); 195 | 196 | for (int i = 0; i < num_cascades; i++) 197 | { 198 | vec3 fc = center + view_dir * splits[i].far_plane; 199 | vec3 nc = center + view_dir * splits[i].near_plane; 200 | 201 | right = normalize(right); 202 | up = normalize(cross(right, view_dir)); 203 | 204 | // these heights and widths are half the heights and widths of 205 | // the near and far plane rectangles 206 | float near_height = tan(splits[i].fov / 2.0f) * splits[i].near_plane; 207 | float near_width = near_height * splits[i].ratio; 208 | float far_height = tan(splits[i].fov / 2.0f) * splits[i].far_plane; 209 | float far_width = far_height * splits[i].ratio; 210 | 211 | splits[i].corners[0] = nc - up * near_height - right * near_width; // near-bottom-left 212 | splits[i].corners[1] = nc + up * near_height - right * near_width; // near-top-left 213 | splits[i].corners[2] = nc + up * near_height + right * near_width; // near-top-right 214 | splits[i].corners[3] = nc - up * near_height + right * near_width; // near-bottom-right 215 | 216 | splits[i].corners[4] = fc - up * far_height - right * far_width; // far-bottom-left 217 | splits[i].corners[5] = fc + up * far_height - right * far_width; // far-top-left 218 | splits[i].corners[6] = fc + up * far_height + right * far_width; // far-top-right 219 | splits[i].corners[7] = fc - up * far_height + right * far_width; // far-bottom-right 220 | } 221 | } 222 | 223 | void update_texture_matrices() 224 | { 225 | for (int i = 0; i < num_cascades; i++) 226 | texture_matrices[i] = u_Bias * crop[i]; 227 | } 228 | 229 | void update_far_bounds() 230 | { 231 | // for every active split 232 | for(int i = 0 ; i < num_cascades; i++) 233 | { 234 | // f[i].fard is originally in eye space - tell's us how far we can see. 235 | // Here we compute it in camera homogeneous coordinates. Basically, we calculate 236 | // cam_proj * (0, 0, f[i].fard, 1)^t and then normalize to [0; 1] 237 | 238 | vec4 pos = projection * vec4(0.0, 0.0, -splits[i].far_plane, 1.0); 239 | vec4 ndc = pos / pos.w; 240 | 241 | far_bounds[i] = ndc.z * 0.5 + 0.5; 242 | } 243 | } 244 | 245 | void update_crop_matrices() 246 | { 247 | mat4 t_projection; 248 | 249 | for (int i = 0; i < num_cascades; i++) 250 | { 251 | vec3 tmax = vec3(-kINFINITY, -kINFINITY, -kINFINITY); 252 | vec3 tmin = vec3(kINFINITY, kINFINITY, kINFINITY); 253 | 254 | // find the z-range of the current frustum as seen from the light 255 | // in order to increase precision 256 | 257 | // note that only the z-component is need and thus 258 | // the multiplication can be simplified 259 | // transf.z = shad_modelview[2] * f.point[0].x + shad_modelview[6] * f.point[0].y + shad_modelview[10] * f.point[0].z + shad_modelview[14]; 260 | vec4 t_transf = u_ModelView * vec4(splits[i].corners[0], 1.0); 261 | 262 | tmin.z = t_transf.z; 263 | tmax.z = t_transf.z; 264 | 265 | for (int j = 1; j < 8; j++) 266 | { 267 | t_transf = u_ModelView * vec4(splits[i].corners[j], 1.0); 268 | if (t_transf.z > tmax.z) 269 | { 270 | tmax.z = t_transf.z; 271 | } 272 | if (t_transf.z < tmin.z) 273 | { 274 | tmin.z = t_transf.z; 275 | } 276 | } 277 | 278 | //tmax.z += 50; // TODO: This solves the dissapearing shadow problem. but how to fix? 279 | 280 | // Calculate frustum split center 281 | splits[i].center = vec3(0.0, 0.0, 0.0); 282 | 283 | for (int j = 0; j < 8; j++) 284 | splits[i].center += splits[i].corners[j]; 285 | 286 | splits[i].center /= 8.0; 287 | 288 | if (u_StablePSSM == 1) 289 | { 290 | // Calculate bounding sphere radius 291 | float radius = 0.0; 292 | 293 | for (int j = 0; j < 8; j++) 294 | { 295 | float l = length(splits[i].corners[j] - splits[i].center); 296 | radius = max(radius, l); 297 | } 298 | 299 | radius = ceil(radius * 16.0) / 16.0; 300 | 301 | // Find bounding box that fits the sphere 302 | vec3 radius3 = vec3(radius, radius, radius); 303 | 304 | vec3 max = radius3; 305 | vec3 min = -radius3; 306 | 307 | vec3 cascade_extents = max - min; 308 | 309 | // Push the light position back along the light direction by the near offset. 310 | vec3 shadow_camera_pos = splits[i].center - direction.xyz * u_NearOffset; 311 | 312 | // Add the near offset to the Z value of the cascade extents to make sure the orthographic frustum captures the entire frustum split (else it will exhibit cut-off issues). 313 | mat4 ortho = ortho(min.x, max.x, min.y, max.y, -u_NearOffset, u_NearOffset + cascade_extents.z); 314 | mat4 view = look_at(shadow_camera_pos, splits[i].center, u_CameraUp); 315 | 316 | proj_matrices[i] = ortho; 317 | crop[i] = ortho * view; 318 | 319 | vec4 shadow_origin = vec4(0.0, 0.0, 0.0, 1.0); 320 | shadow_origin = crop[i] * shadow_origin; 321 | shadow_origin = shadow_origin * (u_ShadowMapSize / 2.0); 322 | 323 | vec4 rounded_origin = round(shadow_origin); 324 | vec4 round_offset = rounded_origin - shadow_origin; 325 | round_offset = round_offset * (2.0 / u_ShadowMapSize); 326 | round_offset.z = 0.0; 327 | round_offset.w = 0.0; 328 | 329 | mat4 shadow_proj = proj_matrices[i]; 330 | 331 | shadow_proj[3][0] += round_offset.x; 332 | shadow_proj[3][1] += round_offset.y; 333 | shadow_proj[3][2] += round_offset.z; 334 | shadow_proj[3][3] += round_offset.w; 335 | 336 | crop[i] = shadow_proj * view; 337 | } 338 | else 339 | { 340 | mat4 t_ortho = ortho(-1.0, 1.0, -1.0, 1.0, -u_NearOffset, -tmin.z); 341 | mat4 t_shad_mvp = t_ortho * u_ModelView; 342 | 343 | // find the extends of the frustum slice as projected in light's homogeneous coordinates 344 | for (int j = 0; j < 8; j++) 345 | { 346 | t_transf = t_shad_mvp * vec4(splits[i].corners[j], 1.0); 347 | 348 | t_transf.x /= t_transf.w; 349 | t_transf.y /= t_transf.w; 350 | 351 | if (t_transf.x > tmax.x) { tmax.x = t_transf.x; } 352 | if (t_transf.x < tmin.x) { tmin.x = t_transf.x; } 353 | if (t_transf.y > tmax.y) { tmax.y = t_transf.y; } 354 | if (t_transf.y < tmin.y) { tmin.y = t_transf.y; } 355 | } 356 | 357 | vec2 tscale = vec2(2.0 / (tmax.x - tmin.x), 2.0 / (tmax.y - tmin.y)); 358 | vec2 toffset = vec2(-0.5 * (tmax.x + tmin.x) * tscale.x, -0.5 * (tmax.y + tmin.y) * tscale.y); 359 | 360 | mat4 t_shad_crop = mat4(1.0); 361 | t_shad_crop[0][0] = tscale.x; 362 | t_shad_crop[1][1] = tscale.y; 363 | t_shad_crop[0][3] = toffset.x; 364 | t_shad_crop[1][3] = toffset.y; 365 | t_shad_crop = transpose(t_shad_crop); 366 | 367 | t_projection = t_shad_crop * t_ortho; 368 | 369 | // Store the projection matrix 370 | proj_matrices[i] = t_projection; 371 | crop[i] = t_projection * u_ModelView; 372 | } 373 | } 374 | } 375 | 376 | // ------------------------------------------------------------------ 377 | // MAIN ------------------------------------------------------------- 378 | // ------------------------------------------------------------------ 379 | 380 | void main() 381 | { 382 | for (int i = 0; i < num_cascades; i++) 383 | { 384 | splits[i].fov = u_FOV; 385 | splits[i].ratio = u_Ratio; 386 | } 387 | 388 | update_splits(); 389 | update_frustum_corners(); 390 | update_crop_matrices(); 391 | update_texture_matrices(); 392 | update_far_bounds(); 393 | } 394 | 395 | // ------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "csm.h" 9 | 10 | #undef min 11 | #undef max 12 | 13 | // Uniform buffer data structure. 14 | struct ObjectUniforms 15 | { 16 | DW_ALIGNED(16) glm::mat4 model; 17 | }; 18 | 19 | struct GlobalUniforms 20 | { 21 | DW_ALIGNED(16) glm::mat4 view; 22 | DW_ALIGNED(16) glm::mat4 projection; 23 | DW_ALIGNED(16) glm::mat4 crop[8]; 24 | }; 25 | 26 | // Structure containing frustum split far-bound. 27 | // 16-byte aligned to adhere to the GLSL std140 memory layout's array packing scheme. 28 | // https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout 29 | struct FarBound 30 | { 31 | DW_ALIGNED(16) float far_bound; 32 | }; 33 | 34 | struct CSMUniforms 35 | { 36 | DW_ALIGNED(16) glm::mat4 texture_matrices[8]; 37 | DW_ALIGNED(16) glm::vec4 direction; 38 | DW_ALIGNED(16) glm::vec4 options; // x: shadows enabled, y: show cascades, z: blend enabled 39 | DW_ALIGNED(16) int num_cascades; 40 | DW_ALIGNED(16) FarBound far_bounds[8]; 41 | }; 42 | 43 | #define CAMERA_FAR_PLANE 1000.0f 44 | #define CSM_MAX_CASCADES 32 45 | 46 | enum CSMSplitMethod 47 | { 48 | CSM_SPLIT_MANUAL, 49 | CSM_SPLIT_PSSM, 50 | CSM_SPLIT_LOGARITHMIC 51 | }; 52 | 53 | struct CSMDesc 54 | { 55 | // Clip plane distances of the main camera 56 | float near_clip; 57 | float far_clip; 58 | // Min-Max distances of the cascades [0.0 - 1.0] 59 | float min_distance; 60 | float max_distance; 61 | int num_cascades; 62 | int shadow_map_size; 63 | int filter_size; 64 | CSMSplitMethod split_method; 65 | float pssm_lambda; 66 | glm::vec3 light_dir; 67 | bool stablize_cascades; 68 | float split_ratios[CSM_MAX_CASCADES]; 69 | float cascade_splits[CSM_MAX_CASCADES]; 70 | glm::vec4 cascade_scales[CSM_MAX_CASCADES]; 71 | glm::vec4 cascade_offsets[CSM_MAX_CASCADES]; 72 | }; 73 | 74 | glm::mat4 make_shadow_matrix(glm::mat4 inverse_vp, glm::vec3 cam_up_dir, glm::vec3 light_dir, bool stablize_cascades) 75 | { 76 | // Get the 8 points of the view frustum in world space 77 | glm::vec4 frustum_corners[8] = 78 | { 79 | glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f), 80 | glm::vec4(1.0f, 1.0f, -1.0f, 1.0f), 81 | glm::vec4(1.0f, -1.0f, -1.0f, 1.0f), 82 | glm::vec4(-1.0f, -1.0f, -1.0f, 1.0f), 83 | glm::vec4(-1.0f, 1.0f, 1.0f, 1.0f), 84 | glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), 85 | glm::vec4(1.0f, -1.0f, 1.0f, 1.0f), 86 | glm::vec4(-1.0f, -1.0f, 1.0f, 1.0f), 87 | }; 88 | 89 | glm::vec3 frustum_center = glm::vec3(0.0f); 90 | 91 | for (int i = 0; i < 8; i++) 92 | { 93 | glm::vec4 c = inverse_vp * frustum_corners[i]; 94 | frustum_corners[i] = c / frustum_corners[i].w; 95 | 96 | frustum_center += frustum_corners[i]; 97 | } 98 | 99 | frustum_center /= 8.0f; 100 | 101 | glm::vec3 up_dir = cam_up_dir; 102 | 103 | if (stablize_cascades) 104 | up_dir = glm::vec3(0.0f, 1.0f, 1.0f); 105 | 106 | // Get position of the shadow camera 107 | glm::vec3 shadow_camera_pos = frustum_center + light_dir * -0.5f; 108 | 109 | // Create shadow matrix 110 | glm::mat4 shadow_view = glm::lookAt(shadow_camera_pos, frustum_center, up_dir); 111 | glm::mat4 shadow_proj = glm::ortho(-0.5f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f); 112 | 113 | // Create bias matrix 114 | glm::mat4 tex_scale_bias = glm::mat4(1.0f); 115 | tex_scale_bias = glm::scale(tex_scale_bias, glm::vec3(0.5f, 0.5f, 1.0f)); 116 | tex_scale_bias = glm::translate(tex_scale_bias, glm::vec3(0.5f, 0.5f, 0.0f)); 117 | 118 | return tex_scale_bias * shadow_proj * shadow_view; 119 | } 120 | 121 | void generate_cascade_data(dw::Camera* camera, CSMDesc& desc) 122 | { 123 | float min_distance = desc.min_distance; 124 | float max_distance = desc.max_distance; 125 | 126 | float shadow_map_size = static_cast(desc.shadow_map_size); 127 | 128 | float cascade_splits[CSM_MAX_CASCADES]; 129 | 130 | if (desc.split_method == CSM_SPLIT_MANUAL) 131 | { 132 | for (int i = 0; i < desc.num_cascades; i++) 133 | cascade_splits[i] = min_distance + desc.split_ratios[i] * max_distance; 134 | } 135 | else if (desc.split_method == CSM_SPLIT_PSSM || desc.split_method == CSM_SPLIT_LOGARITHMIC) 136 | { 137 | float lambda = 1.0f; 138 | 139 | if (desc.split_method == CSM_SPLIT_PSSM) 140 | lambda = desc.pssm_lambda; 141 | 142 | float clip_range = desc.near_clip - desc.far_clip; 143 | float min_z = desc.near_clip + min_distance * clip_range; 144 | float max_z = desc.near_clip + max_distance * clip_range; 145 | float range = max_z - min_z; 146 | float ratio = max_z / min_z; 147 | 148 | for (int i = 0; i < desc.num_cascades; i++) 149 | { 150 | float p = (i + 1) / static_cast(desc.num_cascades); 151 | float log = min_z * pow(ratio, p); 152 | float uniform = min_z + range * p; 153 | float d = lambda * (log - uniform) + uniform; 154 | cascade_splits[i] = (d - desc.near_clip) / clip_range; 155 | } 156 | 157 | glm::mat4 global_shadow_matrix = make_shadow_matrix(glm::inverse(camera->m_view_projection), camera->m_up, desc.light_dir, desc.stablize_cascades); 158 | glm::mat4 inv_view_proj = glm::inverse(camera->m_view_projection); 159 | 160 | for (int cascade_idx = 0; cascade_idx < desc.num_cascades; cascade_idx++) 161 | { 162 | glm::vec3 frustum_corners[8] = 163 | { 164 | glm::vec3(-1.0f, 1.0f, -1.0f), 165 | glm::vec3(1.0f, 1.0f, -1.0f), 166 | glm::vec3(1.0f, -1.0f, -1.0f), 167 | glm::vec3(-1.0f, -1.0f, -1.0f), 168 | glm::vec3(-1.0f, 1.0f, 1.0f), 169 | glm::vec3(1.0f, 1.0f, 1.0f), 170 | glm::vec3(1.0f, -1.0f, 1.0f), 171 | glm::vec3(-1.0f, -1.0f, 1.0f), 172 | }; 173 | 174 | float prev_split_dist = cascade_idx == 0 ? desc.min_distance : cascade_splits[cascade_idx - 1]; 175 | float split_dist = cascade_splits[cascade_idx]; 176 | 177 | for (uint32_t i = 0; i < 8; ++i) 178 | frustum_corners[i] = glm::vec3(inv_view_proj * glm::vec4(frustum_corners[i], 1.0f)); 179 | 180 | // Get the corners of the current cascade slice of the view frustum 181 | for (uint32_t i = 0; i < 4; ++i) 182 | { 183 | glm::vec3 corner_ray = frustum_corners[i + 4] - frustum_corners[i]; 184 | glm::vec3 near_corner_ray = corner_ray * prev_split_dist; 185 | glm::vec3 far_corner_ray = corner_ray * split_dist; 186 | frustum_corners[i + 4] = frustum_corners[i] + far_corner_ray; 187 | frustum_corners[i] = frustum_corners[i] + near_corner_ray; 188 | } 189 | 190 | // Calculate the centroid of the view frustum slice 191 | glm::vec3 frustum_center = glm::vec3(0.0f); 192 | 193 | for (uint32_t i = 0; i < 8; ++i) 194 | frustum_center = frustum_center + frustum_corners[i]; 195 | 196 | frustum_center *= 1.0f / 8.0f; 197 | 198 | // Pick the up vector to use for the light camera 199 | glm::vec3 up_dir = camera->m_right; 200 | 201 | glm::vec3 min_extents; 202 | glm::vec3 max_extents; 203 | 204 | if (desc.stablize_cascades) 205 | { 206 | // This needs to be constant for it to be stable 207 | up_dir = glm::vec3(0.0f, 1.0f, 0.0f); 208 | 209 | // Calculate the radius of a bounding sphere surrounding the frustum corners 210 | float sphere_radius = 0.0f; 211 | for (uint32_t i = 0; i < 8; ++i) 212 | { 213 | float dist = glm::length(glm::vec3(frustum_corners[i]) - frustum_center); 214 | sphere_radius = std::max(sphere_radius, dist); 215 | } 216 | 217 | sphere_radius = std::ceil(sphere_radius * 16.0f) / 16.0f; 218 | 219 | max_extents = glm::vec3(sphere_radius, sphere_radius, sphere_radius); 220 | min_extents = -max_extents; 221 | } 222 | else 223 | { 224 | // Create a temporary view matrix for the light 225 | glm::vec3 light_camera_pos = frustum_center; 226 | glm::vec3 look_at = frustum_center - desc.light_dir; 227 | glm::mat4 light_view = glm::lookAt(light_camera_pos, look_at, up_dir); 228 | 229 | // Calculate an AABB around the frustum corners 230 | glm::vec3 mins = glm::vec3(FLT_MAX); 231 | glm::vec3 maxes = glm::vec3(-FLT_MAX); 232 | 233 | for (uint32_t i = 0; i < 8; ++i) 234 | { 235 | glm::vec3 corner = glm::vec3(light_view * glm::vec4(frustum_corners[i], 1.0f)); 236 | mins = glm::min(mins, corner); 237 | maxes = glm::min(maxes, corner); 238 | } 239 | 240 | min_extents = mins; 241 | max_extents = maxes; 242 | 243 | // Adjust the min/max to accommodate the filtering size 244 | float scale = (desc.shadow_map_size + desc.filter_size) / static_cast(desc.shadow_map_size); 245 | min_extents.x *= scale; 246 | min_extents.y *= scale; 247 | max_extents.x *= scale; 248 | max_extents.y *= scale; 249 | } 250 | 251 | glm::vec3 cascade_extents = max_extents - min_extents; 252 | 253 | // Get position of the shadow camera 254 | glm::vec3 shadow_camera_pos = frustum_center + desc.light_dir * -min_extents.z; 255 | 256 | // Come up with a new orthographic camera for the shadow caster 257 | glm::mat4 shadow_proj = glm::ortho(min_extents.x, min_extents.y, max_extents.x, max_extents.y, 0.0f, cascade_extents.z); 258 | glm::mat4 shadow_view = glm::lookAt(shadow_camera_pos, frustum_center, up_dir); 259 | glm::mat4 shadow_matrix = shadow_proj * shadow_view; 260 | 261 | if (desc.stablize_cascades) 262 | { 263 | // Create the rounding matrix, by projecting the world-space origin and determining 264 | // the fractional offset in texel space 265 | glm::vec4 shadow_origin = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); 266 | shadow_origin = shadow_matrix * shadow_origin; 267 | shadow_origin = shadow_origin * (desc.shadow_map_size / 2.0f); 268 | 269 | glm::vec4 rounded_origin = glm::round(shadow_origin); 270 | glm::vec4 round_offset = rounded_origin - shadow_origin; 271 | round_offset = round_offset * (2.0f / desc.shadow_map_size); 272 | round_offset.z = 0.0f; 273 | round_offset.w = 0.0f; 274 | 275 | shadow_proj[3] = shadow_proj[3] + round_offset; 276 | } 277 | 278 | // Draw the mesh with depth only, using the new shadow camera 279 | // RenderDepthCPU(context, shadowCamera, world, characterWorld, true); 280 | 281 | // Apply the scale/offset matrix, which transforms from [-1,1] 282 | // post-projection space to [0,1] UV space 283 | glm::mat4 tex_scale_bias; 284 | tex_scale_bias[0] = glm::vec4(0.5f, 0.0f, 0.0f, 0.0f); 285 | tex_scale_bias[1] = glm::vec4(0.0f, -0.5f, 0.0f, 0.0f); 286 | tex_scale_bias[2] = glm::vec4(0.0f, 0.0f, 1.0f, 0.0f); 287 | tex_scale_bias[3] = glm::vec4(0.5f, 0.5f, 0.0f, 1.0f); 288 | 289 | shadow_matrix = tex_scale_bias * shadow_matrix; 290 | 291 | // Store the split distance in terms of view space depth 292 | const float clip_dist = desc.far_clip - desc.near_clip; 293 | 294 | desc.cascade_splits[cascade_idx] = desc.near_clip + split_dist * clip_dist; 295 | 296 | // Calculate the position of the lower corner of the cascade partition, in the UV space 297 | // of the first cascade partition 298 | glm::mat4 inv_cascade_mat = glm::inverse(shadow_matrix); 299 | glm::vec3 cascade_corner = glm::vec3(inv_cascade_mat * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); 300 | cascade_corner = global_shadow_matrix * glm::vec4(cascade_corner, 1.0f); 301 | 302 | // Do the same for the upper corner 303 | glm::vec3 other_corner = glm::vec3(inv_cascade_mat * glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); 304 | other_corner = glm::vec3(global_shadow_matrix * glm::vec4(other_corner, 1.0f)); 305 | 306 | // Calculate the scale and offset 307 | glm::vec3 cascade_scale = glm::vec3(1.0f, 1.0f, 1.0f) / (other_corner - cascade_corner); 308 | 309 | desc.cascade_offsets[cascade_idx] = glm::vec4(-cascade_corner, 0.0f); 310 | desc.cascade_scales[cascade_idx] = glm::vec4(cascade_scale, 1.0f); 311 | } 312 | } 313 | } 314 | 315 | 316 | class Sample : public dw::Application 317 | { 318 | protected: 319 | 320 | // ----------------------------------------------------------------------------------------------------------------------------------- 321 | 322 | bool init(int argc, const char* argv[]) override 323 | { 324 | // Create GPU resources. 325 | /*if (!create_shaders()) 326 | return false; 327 | 328 | if (!create_uniform_buffer()) 329 | return false; 330 | 331 | create_framebuffers();*/ 332 | 333 | // Load mesh. 334 | if (!load_mesh()) 335 | return false; 336 | 337 | // Create camera. 338 | create_camera(); 339 | 340 | // Initial CSM. 341 | //initialize_csm(); 342 | 343 | return true; 344 | } 345 | 346 | // ----------------------------------------------------------------------------------------------------------------------------------- 347 | 348 | void update(double delta) override 349 | { 350 | // Debug GUI 351 | //debug_gui(); 352 | 353 | // Update camera. 354 | update_camera(); 355 | 356 | // Update CSM. 357 | //if (!m_ssdm) 358 | // m_csm.update(m_main_camera.get(), m_csm_uniforms.direction); 359 | // 360 | //// Update transforms. 361 | // update_transforms(m_debug_mode ? m_debug_camera.get() : m_main_camera.get()); 362 | 363 | //// Update global uniforms. 364 | //update_global_uniforms(m_global_uniforms); 365 | 366 | //// Update CSM uniforms. 367 | //update_csm_uniforms(m_csm_uniforms); 368 | 369 | //if (m_ssdm) 370 | //{ 371 | // // Render depth prepass 372 | // render_depth_prepass(); 373 | 374 | // copy_depth(); 375 | 376 | // depth_reduction(); 377 | 378 | // setup_cascades_sdsm(); 379 | //} 380 | 381 | // // Render debug view. 382 | // render_debug_view(); 383 | // 384 | // // Render shadow map. 385 | // render_shadow_map(); 386 | // 387 | // // Render scene. 388 | // render_scene(); 389 | 390 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 391 | glViewport(0, 0, m_width, m_height); 392 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 393 | 394 | if (m_debug_mode) 395 | m_debug_draw.frustum(m_main_camera->m_view_projection, glm::vec3(0.0f, 1.0f, 0.0f)); 396 | 397 | // Render debug draw. 398 | m_debug_draw.render(nullptr, m_width, m_height, m_debug_mode ? m_debug_camera->m_view_projection : m_main_camera->m_view_projection); 399 | } 400 | 401 | // ----------------------------------------------------------------------------------------------------------------------------------- 402 | 403 | void shutdown() override 404 | { 405 | m_depth_prepass_fbo.reset(); 406 | m_depth_prepass_rt.reset(); 407 | 408 | // Cleanup CSM. 409 | m_csm.shutdown(); 410 | } 411 | 412 | // ----------------------------------------------------------------------------------------------------------------------------------- 413 | 414 | dw::AppSettings intial_app_settings() 415 | { 416 | dw::AppSettings settings; 417 | 418 | settings.width = 1920; 419 | settings.height = 1080; 420 | settings.title = "Cascaded Shadow Maps"; 421 | 422 | return settings; 423 | } 424 | 425 | // ----------------------------------------------------------------------------------------------------------------------------------- 426 | 427 | void create_framebuffers() 428 | { 429 | for (auto& fbo : m_depth_reduction_fbos) 430 | fbo.reset(); 431 | 432 | m_depth_reduction_fbos.clear(); 433 | 434 | m_depth_reduction_rt.reset(); 435 | 436 | m_depth_prepass_fbo.reset(); 437 | m_depth_prepass_rt.reset(); 438 | 439 | m_depth_prepass_rt = std::make_unique(m_width, m_height, 1, 1, 1, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT); 440 | m_depth_prepass_rt->set_min_filter(GL_LINEAR); 441 | m_depth_prepass_rt->set_wrapping(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); 442 | 443 | m_depth_prepass_fbo = std::make_unique(); 444 | 445 | m_depth_prepass_fbo->attach_depth_stencil_target(m_depth_prepass_rt.get(), 0, 0); 446 | 447 | int32_t w = m_width; 448 | int32_t h = m_height; 449 | int32_t count = 0; 450 | 451 | while (!(w == 1 && h == 1)) 452 | { 453 | count++; 454 | w /= 2; 455 | h /= 2; 456 | 457 | w = std::max(w, 1); 458 | h = std::max(h, 1); 459 | } 460 | 461 | m_depth_mips = count; 462 | m_depth_reduction_fbos.resize(count + 1); 463 | 464 | m_depth_reduction_rt = std::make_unique(m_width, m_height, 1, count, 1, GL_RG32F, GL_RG, GL_FLOAT); 465 | m_depth_reduction_rt->generate_mipmaps(); 466 | m_depth_reduction_rt->set_wrapping(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); 467 | 468 | for (int i = 0; i <= count; i++) 469 | { 470 | m_depth_reduction_fbos[i] = std::make_unique(); 471 | m_depth_reduction_fbos[i]->attach_render_target(0, m_depth_reduction_rt.get(), 0, i); 472 | } 473 | } 474 | 475 | // ----------------------------------------------------------------------------------------------------------------------------------- 476 | 477 | void window_resized(int width, int height) override 478 | { 479 | // Override window resized method to update camera projection. 480 | m_main_camera->update_projection(60.0f, 0.1f, CAMERA_FAR_PLANE, float(m_width) / float(m_height)); 481 | m_debug_camera->update_projection(60.0f, 0.1f, CAMERA_FAR_PLANE * 2.0f, float(m_width) / float(m_height)); 482 | 483 | // Re-initialize CSM to fit new frustum shape. 484 | initialize_csm(); 485 | 486 | create_framebuffers(); 487 | } 488 | 489 | // ----------------------------------------------------------------------------------------------------------------------------------- 490 | 491 | void key_pressed(int code) override 492 | { 493 | // Handle forward movement. 494 | if(code == GLFW_KEY_W) 495 | m_heading_speed = m_camera_speed; 496 | else if(code == GLFW_KEY_S) 497 | m_heading_speed = -m_camera_speed; 498 | 499 | // Handle sideways movement. 500 | if(code == GLFW_KEY_A) 501 | m_sideways_speed = -m_camera_speed; 502 | else if(code == GLFW_KEY_D) 503 | m_sideways_speed = m_camera_speed; 504 | 505 | if (code == GLFW_KEY_G) 506 | m_debug_mode = !m_debug_mode; 507 | } 508 | 509 | // ----------------------------------------------------------------------------------------------------------------------------------- 510 | 511 | void key_released(int code) override 512 | { 513 | // Handle forward movement. 514 | if(code == GLFW_KEY_W || code == GLFW_KEY_S) 515 | m_heading_speed = 0.0f; 516 | 517 | // Handle sideways movement. 518 | if(code == GLFW_KEY_A || code == GLFW_KEY_D) 519 | m_sideways_speed = 0.0f; 520 | } 521 | 522 | // ----------------------------------------------------------------------------------------------------------------------------------- 523 | 524 | void mouse_pressed(int code) override 525 | { 526 | // Enable mouse look. 527 | if (code == GLFW_MOUSE_BUTTON_LEFT) 528 | m_mouse_look = true; 529 | } 530 | 531 | // ----------------------------------------------------------------------------------------------------------------------------------- 532 | 533 | void mouse_released(int code) override 534 | { 535 | // Disable mouse look. 536 | if (code == GLFW_MOUSE_BUTTON_LEFT) 537 | m_mouse_look = false; 538 | } 539 | 540 | // ----------------------------------------------------------------------------------------------------------------------------------- 541 | 542 | private: 543 | 544 | // ----------------------------------------------------------------------------------------------------------------------------------- 545 | 546 | void initialize_csm() 547 | { 548 | m_csm_uniforms.direction = glm::vec4(glm::vec3(m_light_dir_x, -1.0f, m_light_dir_z), 0.0f); 549 | m_csm_uniforms.direction = glm::normalize(m_csm_uniforms.direction); 550 | m_csm_uniforms.options.x = 1; 551 | m_csm_uniforms.options.y = 0; 552 | m_csm_uniforms.options.z = 1; 553 | 554 | m_csm.initialize(m_pssm_lambda, m_near_offset, m_cascade_count, m_shadow_map_size, m_main_camera.get(), m_width, m_height, m_csm_uniforms.direction); 555 | } 556 | 557 | // ----------------------------------------------------------------------------------------------------------------------------------- 558 | 559 | bool create_shaders() 560 | { 561 | // Create general shaders 562 | m_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/scene_vs.glsl")); 563 | m_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/scene_fs.glsl")); 564 | 565 | if (!m_vs || !m_fs) 566 | { 567 | DW_LOG_FATAL("Failed to create Shaders"); 568 | return false; 569 | } 570 | 571 | // Create general shader program 572 | dw::gl::Shader* shaders[] = { m_vs.get(), m_fs.get() }; 573 | m_program = std::make_unique(2, shaders); 574 | 575 | if (!m_program) 576 | { 577 | DW_LOG_FATAL("Failed to create Shader Program"); 578 | return false; 579 | } 580 | 581 | m_program->uniform_block_binding("ObjectUniforms", 1); 582 | 583 | // Create CSM shaders 584 | m_csm_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/shadow_map_vs.glsl")); 585 | m_csm_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/empty_fs.glsl")); 586 | 587 | if (!m_csm_vs || !m_csm_fs) 588 | { 589 | DW_LOG_FATAL("Failed to create CSM Shaders"); 590 | return false; 591 | } 592 | 593 | // Create CSM shader program 594 | dw::gl::Shader* csm_shaders[] = { m_csm_vs.get(), m_csm_fs.get() }; 595 | m_csm_program = std::make_unique(2, csm_shaders); 596 | 597 | if (!m_csm_program) 598 | { 599 | DW_LOG_FATAL("Failed to create CSM Shader Program"); 600 | return false; 601 | } 602 | 603 | m_csm_program->uniform_block_binding("ObjectUniforms", 1); 604 | 605 | // Create depth prepass shaders 606 | m_depth_prepass_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/depth_prepass_vs.glsl")); 607 | m_depth_prepass_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/empty_fs.glsl")); 608 | 609 | if (!m_depth_prepass_vs || !m_depth_prepass_fs) 610 | { 611 | DW_LOG_FATAL("Failed to create Depth Prepass Shaders"); 612 | return false; 613 | } 614 | 615 | // Create Depth prepass shader program 616 | dw::gl::Shader* depth_prepass_shaders[] = { m_depth_prepass_vs.get(), m_depth_prepass_fs.get() }; 617 | m_depth_prepass_program = std::make_unique(2, depth_prepass_shaders); 618 | 619 | if (!m_depth_prepass_program) 620 | { 621 | DW_LOG_FATAL("Failed to create Depth Prepass Shader Program"); 622 | return false; 623 | } 624 | 625 | m_depth_prepass_program->uniform_block_binding("ObjectUniforms", 1); 626 | 627 | // Create depth copy shaders 628 | m_depth_copy_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/fullscreen_vs.glsl")); 629 | m_depth_copy_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/copy_fs.glsl")); 630 | 631 | if (!m_depth_copy_vs || !m_depth_copy_fs) 632 | { 633 | DW_LOG_FATAL("Failed to create Depth Copy Shaders"); 634 | return false; 635 | } 636 | 637 | // Create Depth copy shader program 638 | dw::gl::Shader* depth_copy_shaders[] = { m_depth_copy_vs.get(), m_depth_copy_fs.get() }; 639 | m_depth_copy_program = std::make_unique(2, depth_copy_shaders); 640 | 641 | if (!m_depth_copy_program) 642 | { 643 | DW_LOG_FATAL("Failed to create Depth Copy Shader Program"); 644 | return false; 645 | } 646 | 647 | // Create depth reduction shaders 648 | m_depth_reduction_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/fullscreen_vs.glsl")); 649 | m_depth_reduction_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/depth_reduction_fs.glsl")); 650 | 651 | if (!m_depth_reduction_vs || !m_depth_reduction_fs) 652 | { 653 | DW_LOG_FATAL("Failed to create Depth Reduction Shaders"); 654 | return false; 655 | } 656 | 657 | // Create Depth reduction shader program 658 | dw::gl::Shader* depth_reduction_shaders[] = { m_depth_reduction_vs.get(), m_depth_reduction_fs.get() }; 659 | m_depth_reduction_program = std::make_unique(2, depth_reduction_shaders); 660 | 661 | if (!m_depth_reduction_program) 662 | { 663 | DW_LOG_FATAL("Failed to create Depth Reduction Shader Program"); 664 | return false; 665 | } 666 | 667 | // Create setup cascades shader 668 | m_setup_shadows_cs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_COMPUTE_SHADER, "shader/setup_cascades_cs.glsl")); 669 | 670 | if (!m_setup_shadows_cs) 671 | { 672 | DW_LOG_FATAL("Failed to create Setup Cascades Shaders"); 673 | return false; 674 | } 675 | 676 | // Create Depth reduction shader program 677 | dw::gl::Shader* setup_shadows_shaders[] = { m_setup_shadows_cs.get() }; 678 | m_setup_shadows_program = std::make_unique(1, setup_shadows_shaders); 679 | 680 | if (!m_setup_shadows_program) 681 | { 682 | DW_LOG_FATAL("Failed to create Setup Cascades Shader Program"); 683 | return false; 684 | } 685 | 686 | return true; 687 | } 688 | 689 | // ----------------------------------------------------------------------------------------------------------------------------------- 690 | 691 | bool create_uniform_buffer() 692 | { 693 | // Create uniform buffer for object matrix data 694 | m_object_ubo = std::make_unique(GL_DYNAMIC_DRAW, sizeof(ObjectUniforms)); 695 | 696 | // Create uniform buffer for global data 697 | m_global_ubo = std::make_unique(GL_DYNAMIC_DRAW, sizeof(GlobalUniforms)); 698 | 699 | // Create uniform buffer for CSM data 700 | m_csm_ubo = std::make_unique(GL_DYNAMIC_DRAW, sizeof(CSMUniforms)); 701 | 702 | return true; 703 | } 704 | 705 | // ----------------------------------------------------------------------------------------------------------------------------------- 706 | 707 | bool load_mesh() 708 | { 709 | //m_plane = dw::Mesh::load("plane.obj", &m_device); 710 | m_suzanne = dw::Mesh::load("sponza.obj", false); 711 | return m_suzanne != nullptr; 712 | } 713 | 714 | // ----------------------------------------------------------------------------------------------------------------------------------- 715 | 716 | void create_camera() 717 | { 718 | m_main_camera = std::make_unique(60.0f, 0.1f, CAMERA_FAR_PLANE, float(m_width) / float(m_height), glm::vec3(0.0f, 5.0f, 20.0f), glm::vec3(0.0f, 0.0, -1.0f)); 719 | m_debug_camera = std::make_unique(60.0f, 0.1f, CAMERA_FAR_PLANE * 2.0f, float(m_width) / float(m_height), glm::vec3(0.0f, 5.0f, 20.0f), glm::vec3(0.0f, 0.0, -1.0f)); 720 | } 721 | 722 | // ----------------------------------------------------------------------------------------------------------------------------------- 723 | 724 | void render_mesh(dw::Mesh::Ptr mesh, const ObjectUniforms& transforms, bool use_textures = true) 725 | { 726 | // Copy new data into UBO. 727 | update_object_uniforms(transforms); 728 | 729 | // Bind uniform buffers. 730 | m_global_ubo->bind_base(0); 731 | m_object_ubo->bind_base(1); 732 | 733 | // Bind vertex array. 734 | mesh->mesh_vertex_array()->bind(); 735 | 736 | for (uint32_t i = 0; i < mesh->sub_mesh_count(); i++) 737 | { 738 | dw::SubMesh& submesh = mesh->sub_meshes()[i]; 739 | 740 | // Bind texture. 741 | if (use_textures) 742 | mesh->material(submesh.mat_idx)->texture(0)->bind(0); 743 | 744 | // Issue draw call. 745 | glDrawElementsBaseVertex(GL_TRIANGLES, submesh.index_count, GL_UNSIGNED_INT, (void*)(sizeof(unsigned int) * submesh.base_index), submesh.base_vertex); 746 | } 747 | } 748 | 749 | // ----------------------------------------------------------------------------------------------------------------------------------- 750 | 751 | void render_scene() 752 | { 753 | // Bind and set viewport. 754 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 755 | glViewport(0, 0, m_width, m_height); 756 | 757 | // Clear default framebuffer. 758 | glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 759 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 760 | 761 | // Bind states. 762 | glEnable(GL_DEPTH_TEST); 763 | glEnable(GL_CULL_FACE); 764 | glCullFace(GL_BACK); 765 | 766 | // Bind shader program. 767 | m_program->use(); 768 | 769 | // Bind shadow map. 770 | m_csm.shadow_map()->bind(1); 771 | 772 | m_program->set_uniform("s_ShadowMap", 1); 773 | 774 | // Bind uniform buffers. 775 | m_csm_ubo->bind_base(2); 776 | 777 | // Draw meshes. 778 | //render_mesh(m_plane, m_plane_transforms); 779 | render_mesh(m_suzanne, m_suzanne_transforms, false); 780 | } 781 | 782 | // ----------------------------------------------------------------------------------------------------------------------------------- 783 | 784 | void render_depth_prepass() 785 | { 786 | // Bind and set viewport. 787 | m_depth_prepass_fbo->bind(); 788 | glViewport(0, 0, m_width, m_height); 789 | 790 | // Clear default framebuffer. 791 | glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 792 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 793 | 794 | // Bind states. 795 | glEnable(GL_DEPTH_TEST); 796 | glEnable(GL_CULL_FACE); 797 | glCullFace(GL_BACK); 798 | 799 | // Bind shader program. 800 | m_depth_prepass_program->use(); 801 | 802 | // Draw meshes. 803 | //render_mesh(m_plane, m_plane_transforms); 804 | render_mesh(m_suzanne, m_suzanne_transforms, false); 805 | } 806 | 807 | // ----------------------------------------------------------------------------------------------------------------------------------- 808 | 809 | void copy_depth() 810 | { 811 | glDisable(GL_DEPTH_TEST); 812 | glDisable(GL_CULL_FACE); 813 | 814 | m_depth_copy_program->use(); 815 | 816 | m_depth_reduction_fbos[0]->bind(); 817 | glViewport(0, 0, m_width, m_height); 818 | 819 | glClearColor(1.0f, 0.0f, 0.0f, 1.0f); 820 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 821 | 822 | m_depth_copy_program->set_uniform("s_Texture", 0); 823 | m_depth_prepass_rt->bind(0); 824 | 825 | glDrawArrays(GL_TRIANGLES, 0, 3); 826 | } 827 | 828 | // ----------------------------------------------------------------------------------------------------------------------------------- 829 | 830 | void depth_reduction() 831 | { 832 | glDisable(GL_DEPTH_TEST); 833 | glDisable(GL_CULL_FACE); 834 | 835 | m_depth_reduction_program->use(); 836 | 837 | for (uint32_t i = 1; i < m_depth_reduction_fbos.size(); i++) 838 | { 839 | float scale = pow(2, i); 840 | int w = m_width / scale; 841 | int h = m_height / scale; 842 | 843 | m_depth_reduction_fbos[i]->bind(); 844 | glViewport(0, 0, w, h); 845 | 846 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 847 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 848 | 849 | m_depth_reduction_program->set_uniform("s_Texture", 0); 850 | m_depth_reduction_rt->bind(0); 851 | 852 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, i - 1); 853 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i - 1); 854 | 855 | glDrawArrays(GL_TRIANGLES, 0, 3); 856 | } 857 | 858 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); 859 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1000); 860 | } 861 | 862 | // ----------------------------------------------------------------------------------------------------------------------------------- 863 | 864 | void render_shadow_map() 865 | { 866 | // Bind states. 867 | glEnable(GL_DEPTH_TEST); 868 | glDisable(GL_CULL_FACE); 869 | glCullFace(GL_BACK); 870 | 871 | // Bind shader program. 872 | m_csm_program->use(); 873 | 874 | for (int i = 0; i < m_csm.frustum_split_count(); i++) 875 | { 876 | m_csm_program->set_uniform("u_CascadeIndex", i); 877 | 878 | // Bind and set viewport. 879 | m_csm.framebuffers()[i]->bind(); 880 | glViewport(0, 0, m_csm.shadow_map_size(), m_csm.shadow_map_size()); 881 | 882 | // Clear default framebuffer. 883 | glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 884 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 885 | 886 | // Draw meshes. Disable textures because we don't need them here. 887 | //m_device.bind_rasterizer_state(m_rs); 888 | // render_mesh(m_plane, m_plane_transforms, false); 889 | 890 | render_mesh(m_suzanne, m_suzanne_transforms, false); 891 | } 892 | } 893 | 894 | // ----------------------------------------------------------------------------------------------------------------------------------- 895 | 896 | void setup_cascades_sdsm() 897 | { 898 | m_setup_shadows_program->use(); 899 | 900 | m_csm.bind_sdsm_uniforms(m_setup_shadows_program.get(), m_main_camera.get(), m_csm_uniforms.direction); 901 | 902 | m_setup_shadows_program->set_uniform("u_Near", m_main_camera->m_near); 903 | m_setup_shadows_program->set_uniform("u_Far", m_main_camera->m_far); 904 | m_setup_shadows_program->set_uniform("u_CameraPos", m_main_camera->m_position); 905 | m_setup_shadows_program->set_uniform("u_CameraDir", m_main_camera->m_forward); 906 | m_setup_shadows_program->set_uniform("u_CameraUp", m_main_camera->m_up); 907 | m_setup_shadows_program->set_uniform("u_MaxMip", m_depth_mips - 1); 908 | 909 | if (m_setup_shadows_program->set_uniform("u_Depth", 0)) 910 | m_depth_reduction_rt->bind(0); 911 | 912 | m_global_ubo->bind_base(0); 913 | m_csm_ubo->bind_base(1); 914 | 915 | glDispatchCompute(1, 1, 1); 916 | 917 | glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); 918 | } 919 | 920 | // ----------------------------------------------------------------------------------------------------------------------------------- 921 | 922 | void update_object_uniforms(const ObjectUniforms& transform) 923 | { 924 | void* ptr = m_object_ubo->map(GL_WRITE_ONLY); 925 | memcpy(ptr, &transform, sizeof(ObjectUniforms)); 926 | m_object_ubo->unmap(); 927 | } 928 | 929 | // ----------------------------------------------------------------------------------------------------------------------------------- 930 | 931 | void update_csm_uniforms(const CSMUniforms& csm) 932 | { 933 | void* ptr = m_csm_ubo->map(GL_WRITE_ONLY); 934 | memcpy(ptr, &csm, sizeof(CSMUniforms)); 935 | m_csm_ubo->unmap(); 936 | } 937 | 938 | // ----------------------------------------------------------------------------------------------------------------------------------- 939 | 940 | void update_global_uniforms(const GlobalUniforms& global) 941 | { 942 | void* ptr = m_global_ubo->map(GL_WRITE_ONLY); 943 | memcpy(ptr, &global, sizeof(GlobalUniforms)); 944 | m_global_ubo->unmap(); 945 | } 946 | 947 | // ----------------------------------------------------------------------------------------------------------------------------------- 948 | 949 | void update_transforms(dw::Camera* camera) 950 | { 951 | // Update camera matrices. 952 | m_global_uniforms.view = camera->m_view; 953 | m_global_uniforms.projection = camera->m_projection; 954 | 955 | // Update CSM farbounds. 956 | m_csm_uniforms.num_cascades = m_csm.frustum_split_count(); 957 | 958 | for (int i = 0; i < m_csm.frustum_split_count(); i++) 959 | { 960 | m_csm_uniforms.far_bounds[i].far_bound = m_csm.far_bound(i); 961 | m_csm_uniforms.texture_matrices[i] = m_csm.texture_matrix(i); 962 | m_global_uniforms.crop[i] = m_csm.split_view_proj(i); 963 | } 964 | 965 | // Update plane transforms. 966 | m_plane_transforms.model = glm::mat4(1.0f); 967 | 968 | // Update suzanne transforms. 969 | m_suzanne_transforms.model = glm::mat4(1.0f); 970 | m_suzanne_transforms.model = glm::translate(m_suzanne_transforms.model, glm::vec3(0.0f, 3.0f, 0.0f)); 971 | // m_suzanne_transforms.model = glm::rotate(m_suzanne_transforms.model, (float)glfwGetTime(), glm::vec3(0.0f, 1.0f, 0.0f)); 972 | m_suzanne_transforms.model = glm::scale(m_suzanne_transforms.model, glm::vec3(0.1f)); 973 | } 974 | 975 | // ----------------------------------------------------------------------------------------------------------------------------------- 976 | 977 | void update_camera() 978 | { 979 | dw::Camera* current = m_main_camera.get(); 980 | 981 | if (m_debug_mode) 982 | current = m_debug_camera.get(); 983 | 984 | float forward_delta = m_heading_speed * m_delta; 985 | float right_delta = m_sideways_speed * m_delta; 986 | 987 | current->set_translation_delta(current->m_forward, forward_delta); 988 | current->set_translation_delta(current->m_right, right_delta); 989 | 990 | if (m_mouse_look) 991 | { 992 | // Activate Mouse Look 993 | current->set_rotatation_delta(glm::vec3((float)(m_mouse_delta_y * m_camera_sensitivity), 994 | (float)(m_mouse_delta_x * m_camera_sensitivity), 995 | (float)(0.0f))); 996 | } 997 | else 998 | { 999 | current->set_rotatation_delta(glm::vec3((float)(0), 1000 | (float)(0), 1001 | (float)(0))); 1002 | } 1003 | 1004 | current->update(); 1005 | } 1006 | 1007 | // ----------------------------------------------------------------------------------------------------------------------------------- 1008 | 1009 | void debug_gui() 1010 | { 1011 | if (ImGui::Begin("Cascaded Shadow Maps")) 1012 | { 1013 | bool shadows = m_csm_uniforms.options.x; 1014 | ImGui::Checkbox("Enable Shadows", &shadows); 1015 | m_csm_uniforms.options.x = shadows; 1016 | 1017 | bool debug = m_csm_uniforms.options.y; 1018 | ImGui::Checkbox("Show Debug Cascades", &debug); 1019 | m_csm_uniforms.options.y = debug; 1020 | 1021 | bool blend = m_csm_uniforms.options.z; 1022 | ImGui::Checkbox("Blending", &blend); 1023 | m_csm_uniforms.options.z = blend; 1024 | 1025 | ImGui::Checkbox("Sample Distribution Shadow Maps", &m_ssdm); 1026 | ImGui::Checkbox("Stable", &m_csm.m_stable_pssm); 1027 | ImGui::Checkbox("Debug Camera", &m_debug_mode); 1028 | ImGui::Checkbox("Show Frustum Splits", &m_show_frustum_splits); 1029 | ImGui::Checkbox("Show Cascade Frustum", &m_show_cascade_frustums); 1030 | 1031 | ImGui::SliderFloat("Lambda", &m_csm.m_lambda, 0, 1); 1032 | 1033 | int split_count = m_csm.m_split_count; 1034 | ImGui::SliderInt("Frustum Splits", &split_count, 1, 4); 1035 | 1036 | if (split_count != m_csm.m_split_count) 1037 | m_csm.initialize(m_csm.m_lambda, m_csm.m_near_offset, split_count, m_csm.m_shadow_map_size, m_main_camera.get(), m_width, m_height, glm::vec3(m_csm_uniforms.direction)); 1038 | 1039 | float near_offset = m_csm.m_near_offset; 1040 | ImGui::SliderFloat("Near Offset", &near_offset, 100.0f, 1000.0f); 1041 | 1042 | if (m_near_offset != near_offset) 1043 | { 1044 | m_near_offset = near_offset; 1045 | m_csm.initialize(m_csm.m_lambda, m_near_offset, split_count, m_csm.m_shadow_map_size, m_main_camera.get(), m_width, m_height, glm::vec3(m_csm_uniforms.direction)); 1046 | } 1047 | 1048 | ImGui::SliderFloat("Light Direction X", &m_light_dir_x, 0.0f, 1.0f); 1049 | ImGui::SliderFloat("Light Direction Z", &m_light_dir_z, 0.0f, 1.0f); 1050 | 1051 | m_csm_uniforms.direction = glm::vec4(glm::vec3(m_light_dir_x, -1.0f, m_light_dir_z), 0.0f); 1052 | m_csm_uniforms.direction = glm::normalize(m_csm_uniforms.direction); 1053 | 1054 | static const char* items[] = { "256", "512", "1024", "2048" }; 1055 | static const int shadow_map_sizes[] = { 256, 512, 1024, 2048 }; 1056 | static int item_current = 3; 1057 | ImGui::Combo("Shadow Map Size", &item_current, items, IM_ARRAYSIZE(items)); 1058 | 1059 | if (shadow_map_sizes[item_current] != m_csm.m_shadow_map_size) 1060 | m_csm.initialize(m_csm.m_lambda, m_csm.m_near_offset, m_csm.m_split_count, shadow_map_sizes[item_current], m_main_camera.get(), m_width, m_height, glm::vec3(m_csm_uniforms.direction)); 1061 | 1062 | static int current_view = 0; 1063 | ImGui::RadioButton("Scene", ¤t_view, 0); 1064 | 1065 | for (int i = 0; i < m_csm.m_split_count; i++) 1066 | { 1067 | std::string name = "Cascade " + std::to_string(i + 1); 1068 | ImGui::RadioButton(name.c_str(), ¤t_view, i + 1); 1069 | } 1070 | } 1071 | ImGui::End(); 1072 | } 1073 | 1074 | // ----------------------------------------------------------------------------------------------------------------------------------- 1075 | 1076 | void render_debug_view() 1077 | { 1078 | for (int i = 0; i < m_csm.m_split_count; i++) 1079 | { 1080 | FrustumSplit& split = m_csm.frustum_splits()[i]; 1081 | 1082 | // Render frustum splits. 1083 | if (m_show_frustum_splits) 1084 | { 1085 | m_debug_draw.line(split.corners[0], split.corners[3], glm::vec3(1.0f)); 1086 | m_debug_draw.line(split.corners[3], split.corners[2], glm::vec3(1.0f)); 1087 | m_debug_draw.line(split.corners[2], split.corners[1], glm::vec3(1.0f)); 1088 | m_debug_draw.line(split.corners[1], split.corners[0], glm::vec3(1.0f)); 1089 | 1090 | m_debug_draw.line(split.corners[4], split.corners[7], glm::vec3(1.0f)); 1091 | m_debug_draw.line(split.corners[7], split.corners[6], glm::vec3(1.0f)); 1092 | m_debug_draw.line(split.corners[6], split.corners[5], glm::vec3(1.0f)); 1093 | m_debug_draw.line(split.corners[5], split.corners[4], glm::vec3(1.0f)); 1094 | 1095 | m_debug_draw.line(split.corners[0], split.corners[4], glm::vec3(1.0f)); 1096 | m_debug_draw.line(split.corners[1], split.corners[5], glm::vec3(1.0f)); 1097 | m_debug_draw.line(split.corners[2], split.corners[6], glm::vec3(1.0f)); 1098 | m_debug_draw.line(split.corners[3], split.corners[7], glm::vec3(1.0f)); 1099 | } 1100 | 1101 | // Render shadow frustums. 1102 | if (m_show_cascade_frustums) 1103 | m_debug_draw.frustum(m_csm.split_view_proj(i), glm::vec3(1.0f, 0.0f, 0.0f)); 1104 | } 1105 | 1106 | if (m_debug_mode) 1107 | m_debug_draw.frustum(m_main_camera->m_projection, m_main_camera->m_view, glm::vec3(0.0f, 1.0f, 0.0f)); 1108 | } 1109 | 1110 | // ----------------------------------------------------------------------------------------------------------------------------------- 1111 | 1112 | private: 1113 | // Clear color. 1114 | float m_clear_color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; 1115 | 1116 | // General GPU resources. 1117 | std::unique_ptr m_vs; 1118 | std::unique_ptr m_fs; 1119 | std::unique_ptr m_program; 1120 | std::unique_ptr m_object_ubo; 1121 | std::unique_ptr m_csm_ubo; 1122 | std::unique_ptr m_global_ubo; 1123 | 1124 | // CSM shaders. 1125 | std::unique_ptr m_csm_vs; 1126 | std::unique_ptr m_csm_fs; 1127 | std::unique_ptr m_csm_program; 1128 | 1129 | // Camera. 1130 | std::unique_ptr m_main_camera; 1131 | std::unique_ptr m_debug_camera; 1132 | 1133 | // Depth Pre-pass 1134 | std::unique_ptr m_depth_prepass_vs; 1135 | std::unique_ptr m_depth_prepass_fs; 1136 | std::unique_ptr m_depth_prepass_program; 1137 | std::unique_ptr m_depth_prepass_rt; 1138 | std::unique_ptr m_depth_prepass_fbo; 1139 | 1140 | std::unique_ptr m_depth_copy_vs; 1141 | std::unique_ptr m_depth_copy_fs; 1142 | std::unique_ptr m_depth_copy_program; 1143 | 1144 | std::unique_ptr m_depth_reduction_vs; 1145 | std::unique_ptr m_depth_reduction_fs; 1146 | std::unique_ptr m_depth_reduction_program; 1147 | std::unique_ptr m_depth_reduction_rt; 1148 | std::vector> m_depth_reduction_fbos; 1149 | 1150 | std::unique_ptr m_setup_shadows_cs; 1151 | std::unique_ptr m_setup_shadows_program; 1152 | 1153 | // Assets. 1154 | dw::Mesh::Ptr m_plane; 1155 | dw::Mesh::Ptr m_suzanne; 1156 | 1157 | // Uniforms. 1158 | ObjectUniforms m_plane_transforms; 1159 | ObjectUniforms m_suzanne_transforms; 1160 | GlobalUniforms m_global_uniforms; 1161 | CSMUniforms m_csm_uniforms; 1162 | 1163 | // Cascaded Shadow Mapping. 1164 | CSM m_csm; 1165 | 1166 | // Camera controls. 1167 | bool m_mouse_look = false; 1168 | bool m_debug_mode = false; 1169 | float m_heading_speed = 0.0f; 1170 | float m_sideways_speed = 0.0f; 1171 | float m_camera_sensitivity = 0.05f; 1172 | float m_camera_speed = 0.1f; 1173 | 1174 | // Default shadow options. 1175 | int m_depth_mips = 0; 1176 | bool m_ssdm = false; 1177 | int m_shadow_map_size = 2048; 1178 | int m_cascade_count = 4; 1179 | float m_pssm_lambda = 0.3; 1180 | float m_near_offset = 250.0f; 1181 | float m_light_dir_x = -1.0f; 1182 | float m_light_dir_z = 0.0f; 1183 | 1184 | // Debug options. 1185 | bool m_show_frustum_splits = false; 1186 | bool m_show_cascade_frustums = false; 1187 | }; 1188 | 1189 | DW_DECLARE_MAIN(Sample) 1190 | --------------------------------------------------------------------------------