diff --git a/src/engine/gl/texture-cube.cpp b/src/engine/gl/texture-cube.cpp index 33c249e..b090945 100644 --- a/src/engine/gl/texture-cube.cpp +++ b/src/engine/gl/texture-cube.cpp @@ -18,6 +18,7 @@ */ #include +#include namespace gl { @@ -103,6 +104,7 @@ void texture_cube::resized() const auto h = get_height(); const auto layout = infer_cube_map_layout(w, h); m_face_size = infer_cube_map_face_size(layout, w, h); + m_mip_count = 1 + static_cast(std::log2(m_face_size)); } } // namespace gl diff --git a/src/engine/gl/texture.cpp b/src/engine/gl/texture.cpp index 5fb620a..47291fa 100644 --- a/src/engine/gl/texture.cpp +++ b/src/engine/gl/texture.cpp @@ -202,7 +202,7 @@ void texture::set_max_level(std::uint8_t level) glTexParameteri(m_gl_texture_target, GL_TEXTURE_MAX_LEVEL, static_cast(m_max_level)); } -void texture::set_mipmap_range(std::uint8_t base_level, std::uint8_t max_level) +void texture::set_mip_range(std::uint8_t base_level, std::uint8_t max_level) { m_base_level = base_level; m_max_level = max_level; @@ -323,6 +323,7 @@ void texture::resize(std::uint16_t width, std::uint16_t height, std::uint16_t de break; } + m_mip_count = 1 + static_cast(std::log2(std::max(std::max(width, height), depth))); glGenerateMipmap(m_gl_texture_target); glTexParameteriv(m_gl_texture_target, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle_mask); diff --git a/src/engine/gl/texture.hpp b/src/engine/gl/texture.hpp index 039661b..bc902ff 100644 --- a/src/engine/gl/texture.hpp +++ b/src/engine/gl/texture.hpp @@ -84,26 +84,26 @@ public: void set_mag_filter(texture_mag_filter filter); /** - * Sets the index of lowest mipmap level. + * Sets the index of lowest accessible mip level. * - * @param level Index of the lowest mipmap level. + * @param level Index of the lowest accessible mip level. */ void set_base_level(std::uint8_t level); /** - * Sets the index of highest mipmap level. + * Sets the index of highest accessible mip level. * - * @param level Index of the highest mipmap level. + * @param level Index of the highest accessible mip level. */ void set_max_level(std::uint8_t level); /** - * Sets the range of mipmap levels. + * Sets the range of accessible mip levels. * - * @param base Index of the lowest mipmap level - * @param max Index of the highest mipmap level. + * @param base Index of the lowest accessible mip level. + * @param max Index of the highest accessible mip level. */ - void set_mipmap_range(std::uint8_t base_level, std::uint8_t max_level); + void set_mip_range(std::uint8_t base_level, std::uint8_t max_level); /** * Sets the maximum anisotropy. @@ -169,13 +169,19 @@ public: return m_filters; } - /// Returns the index of the lowest mipmap level. + /// Returns the number of available mip levels. + [[nodiscard]] inline std::uint16_t get_mip_count() const noexcept + { + return m_mip_count; + } + + /// Returns the index of the lowest accessible mip level. [[nodiscard]] inline std::uint8_t get_base_level() const noexcept { return m_base_level; } - /// Returns the index of the highest mipmap level. + /// Returns the index of the highest accessible mip level. [[nodiscard]] inline std::uint8_t get_max_level() const noexcept { return m_max_level; @@ -259,6 +265,9 @@ private: std::uint8_t m_base_level{}; std::uint8_t m_max_level{255}; float m_max_anisotropy{}; + +protected: + std::uint16_t m_mip_count{}; }; } // namespace gl diff --git a/src/engine/render/passes/material-pass.cpp b/src/engine/render/passes/material-pass.cpp index c558caf..7d33cc5 100644 --- a/src/engine/render/passes/material-pass.cpp +++ b/src/engine/render/passes/material-pass.cpp @@ -546,23 +546,7 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(clip_depth);}); } - // LTC variables - if (auto ltc_lut_1_var = shader_program.variable("ltc_lut_1")) - { - if (auto ltc_lut_2_var = shader_program.variable("ltc_lut_2")) - { - command_buffer.emplace_back - ( - [&, ltc_lut_1_var, ltc_lut_2_var]() - { - ltc_lut_1_var->update(*ltc_lut_1); - ltc_lut_2_var->update(*ltc_lut_2); - } - ); - } - } - - // IBL variables + // Update IBL variables if (auto brdf_lut_var = shader_program.variable("brdf_lut")) { command_buffer.emplace_back @@ -588,6 +572,17 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(std::max(static_cast(light_probe_luminance_texture->get_mip_count()) - 4.0f, 0.0f)); + } + ); + } + if (auto light_probe_illuminance_texture_var = shader_program.variable("light_probe_illuminance_texture")) { command_buffer.emplace_back @@ -600,6 +595,41 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(*ltc_lut_1); + ltc_lut_2_var->update(*ltc_lut_2); + } + ); + } + } + if (rectangle_light_count) + { + if (auto rectangle_light_colors_var = shader_program.variable("rectangle_light_colors")) + { + auto rectangle_light_corners_var = shader_program.variable("rectangle_light_corners"); + + if (rectangle_light_corners_var) + { + command_buffer.emplace_back + ( + [&, rectangle_light_colors_var, rectangle_light_corners_var]() + { + rectangle_light_colors_var->update(std::span{rectangle_light_colors.data(), rectangle_light_count}); + rectangle_light_corners_var->update(std::span{rectangle_light_corners.data(), rectangle_light_count * 4}); + } + ); + } + } + } + // Update directional light variables if (directional_light_count) { @@ -696,26 +726,6 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(std::span{rectangle_light_colors.data(), rectangle_light_count}); - rectangle_light_corners_var->update(std::span{rectangle_light_corners.data(), rectangle_light_count * 4}); - } - ); - } - } - } - // Update time variable if (auto time_var = shader_program.variable("time")) { diff --git a/src/engine/render/passes/sky-pass.cpp b/src/engine/render/passes/sky-pass.cpp index 4b3eed4..db7c3d0 100644 --- a/src/engine/render/passes/sky-pass.cpp +++ b/src/engine/render/passes/sky-pass.cpp @@ -183,17 +183,6 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe debug::log::warning("{}", m_sky_probe_shader_template->configure(gl::shader_stage::vertex)); } - // Load cubemap downsample shader template - m_cubemap_downsample_shader_template = resource_manager->load("cubemap-downsample.glsl"); - - // Build cubemap downsample shader program - m_cubemap_downsample_shader_program = m_cubemap_downsample_shader_template->build({}); - if (!m_cubemap_downsample_shader_program->linked()) - { - debug::log::error("Failed to build cubemap downsample shader program: {}", m_cubemap_downsample_shader_program->info()); - debug::log::warning("{}", m_cubemap_downsample_shader_template->configure(gl::shader_stage::vertex)); - } - // Load moon textures m_moon_albedo_map = resource_manager->load("moon-albedo.tex"); m_moon_normal_map = resource_manager->load("moon-normal.tex"); @@ -408,8 +397,6 @@ void sky_pass::render(render::context& ctx) rasterizer->use_program(*star_shader_program); if (star_model_view_projection_var) star_model_view_projection_var->update(model_view_projection); - if (star_distance_var) - star_distance_var->update(star_distance); if (star_exposure_var) star_exposure_var->update(camera_exposure); if (star_inv_resolution_var) @@ -645,7 +632,6 @@ void sky_pass::set_stars_model(std::shared_ptr model) if (star_shader_program->linked()) { star_model_view_projection_var = star_shader_program->variable("model_view_projection"); - star_distance_var = star_shader_program->variable("star_distance"); star_exposure_var = star_shader_program->variable("camera_exposure"); star_inv_resolution_var = star_shader_program->variable("inv_resolution"); } @@ -1145,6 +1131,10 @@ void sky_pass::rebuild_sky_probe_command_buffer() { m_sky_probe_command_buffer.emplace_back([&, light_direction_var](){light_direction_var->update(dominant_light_direction);}); } + if (auto light_illuminance_var = m_sky_probe_shader_program->variable("light_illuminance")) + { + m_sky_probe_command_buffer.emplace_back([&, light_illuminance_var](){light_illuminance_var->update(dominant_light_illuminance);}); + } if (auto observer_position_var = m_sky_probe_shader_program->variable("observer_position")) { m_sky_probe_command_buffer.emplace_back([&, observer_position_var](){observer_position_var->update(observer_position);}); @@ -1153,6 +1143,10 @@ void sky_pass::rebuild_sky_probe_command_buffer() { m_sky_probe_command_buffer.emplace_back([&, atmosphere_radii_var](){atmosphere_radii_var->update(atmosphere_radii);}); } + if (auto ground_albedo_var = m_sky_probe_shader_program->variable("ground_albedo")) + { + m_sky_probe_command_buffer.emplace_back([&, ground_albedo_var](){ground_albedo_var->update(m_ground_albedo);}); + } // Draw point m_sky_probe_command_buffer.emplace_back @@ -1164,73 +1158,6 @@ void sky_pass::rebuild_sky_probe_command_buffer() m_sky_probe->set_illuminance_outdated(true); } ); - - if (!m_cubemap_downsample_shader_program->linked()) - { - return; - } - - auto cubemap_var = m_cubemap_downsample_shader_program->variable("cubemap"); - if (!cubemap_var) - { - return; - } - - m_sky_probe_command_buffer.emplace_back - ( - [&, cubemap_var]() - { - // Bind downsample shader program - rasterizer->use_program(*m_cubemap_downsample_shader_program); - - // Change texture filter mode to linear to prevent undefined access when reading and writing to different mip levels of the same texture - // m_sky_probe->get_luminance_texture()->set_min_filter(gl::texture_min_filter::linear); - - // Update cubemap shader variable - cubemap_var->update(*m_sky_probe->get_luminance_texture()); - } - ); - - for (std::size_t i = 1; i < m_sky_probe_framebuffers.size(); ++i) - { - const auto resolution = m_sky_probe->get_luminance_texture()->get_face_size() >> i; - - // Bind sky probe mip framebuffer - m_sky_probe_command_buffer.emplace_back - ( - [&, resolution, i]() - { - rasterizer->set_viewport(0, 0, resolution, resolution); - - // Restrict mipmap range - const std::uint8_t base_mip_level = static_cast(i - 1); - m_sky_probe->get_luminance_texture()->set_mipmap_range(base_mip_level, base_mip_level); - - rasterizer->use_framebuffer(*m_sky_probe_framebuffers[i]); - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::points, 0, 1); - } - ); - } - - // Restore mipmap range - m_sky_probe_command_buffer.emplace_back - ( - [&]() - { - m_sky_probe->get_luminance_texture()->set_mipmap_range(0, 255); - } - ); - - - - // Restore texture filter mode - // m_sky_probe_command_buffer.emplace_back - // ( - // [&]() - // { - // m_sky_probe->get_luminance_texture()->set_min_filter(gl::texture_min_filter::linear_mipmap_linear); - // } - // ); } } // namespace render diff --git a/src/engine/render/passes/sky-pass.hpp b/src/engine/render/passes/sky-pass.hpp index 0f3b6cf..5f51eb8 100644 --- a/src/engine/render/passes/sky-pass.hpp +++ b/src/engine/render/passes/sky-pass.hpp @@ -262,8 +262,6 @@ private: std::vector> m_sky_probe_framebuffers; std::shared_ptr m_sky_probe_shader_template; std::unique_ptr m_sky_probe_shader_program; - std::shared_ptr m_cubemap_downsample_shader_template; - std::unique_ptr m_cubemap_downsample_shader_program; std::vector> m_sky_probe_command_buffer; float3 dominant_light_direction; @@ -326,7 +324,6 @@ private: std::unique_ptr star_shader_program; const gl::shader_variable* star_model_view_projection_var; const gl::shader_variable* star_exposure_var; - const gl::shader_variable* star_distance_var; const gl::shader_variable* star_inv_resolution_var; float2 mouse_position; diff --git a/src/engine/render/stages/light-probe-stage.cpp b/src/engine/render/stages/light-probe-stage.cpp index bebd39b..71cfb88 100644 --- a/src/engine/render/stages/light-probe-stage.cpp +++ b/src/engine/render/stages/light-probe-stage.cpp @@ -60,18 +60,135 @@ light_probe_stage::light_probe_stage(gl::rasterizer& rasterizer, ::resource_mana m_quad_vao->bind(render::vertex_attribute::position, position_attribute); } - // Load cubemap to spherical harmonics shader template + // Load cubemap to spherical harmonics shader template and build shader program m_cubemap_to_sh_shader_template = resource_manager.load("cubemap-to-sh.glsl"); - - // Build cubemap to spherical harmonics shader program rebuild_cubemap_to_sh_shader_program(); + + // Load cubemap downsample shader template and build shader program + m_cubemap_downsample_shader_template = resource_manager.load("cubemap-downsample.glsl"); + rebuild_cubemap_downsample_shader_program(); + + // Load cubemap filter LUT shader template and build shader program + m_cubemap_filter_lut_shader_template = resource_manager.load("cubemap-filter-lut.glsl"); + rebuild_cubemap_filter_lut_shader_program(); + + // Allocate cubemap filter LUT texture + m_cubemap_filter_lut_texture = std::make_unique(static_cast(m_cubemap_filter_sample_count), static_cast(m_cubemap_filter_mip_count - 1), gl::pixel_type::float_32, gl::pixel_format::rgba); + + // Allocate cubemap filter LUT framebuffer and attach LUT texture + m_cubemap_filter_lut_framebuffer = std::make_unique(); + m_cubemap_filter_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, m_cubemap_filter_lut_texture.get()); + + // Build cubemap filter LUT texture + rebuild_cubemap_filter_lut_texture(); + + // Load cubemap filter shader template and build shader program + m_cubemap_filter_shader_template = resource_manager.load("cubemap-filter.glsl"); + rebuild_cubemap_filter_shader_program(); } void light_probe_stage::execute(render::context& ctx) { - // Get all light probes in the collection const auto& light_probes = ctx.collection->get_objects(scene::light_probe::object_type_id); + if (light_probes.empty()) + { + return; + } + update_light_probes_luminance(light_probes); + update_light_probes_illuminance(light_probes); +} + +void light_probe_stage::update_light_probes_luminance(const std::vector& light_probes) +{ + bool state_bound = false; + + // Downsample cubemaps + std::for_each + ( + std::execution::seq, + std::begin(light_probes), + std::end(light_probes), + [&](scene::object_base* object) + { + scene::light_probe& light_probe = static_cast(*object); + if (!light_probe.is_luminance_outdated() && !m_refilter_cubemaps) + { + return; + } + + // Bind state, if unbound + if (!state_bound) + { + glDisable(GL_BLEND); + state_bound = true; + } + + // Bind cubemap downsample shader program + m_rasterizer->use_program(*m_cubemap_downsample_shader_program); + + // Update cubemap shader variable with light probe luminance texture + m_cubemap_downsample_cubemap_var->update(*light_probe.get_luminance_texture()); + + // Get resolution of cubemap face for base mip level + const std::uint16_t base_mip_face_size = light_probe.get_luminance_texture()->get_face_size(); + + // Downsample mip chain + for (std::size_t i = 1; i < light_probe.get_luminance_framebuffers().size(); ++i) + { + // Set viewport to resolution of cubemap face size for current mip level + const std::uint16_t current_mip_face_size = base_mip_face_size >> i; + m_rasterizer->set_viewport(0, 0, current_mip_face_size, current_mip_face_size); + + // Restrict cubemap mipmap range to parent mip level + const std::uint8_t parent_mip_level = static_cast(i - 1); + light_probe.get_luminance_texture()->set_mip_range(parent_mip_level, parent_mip_level); + + // Bind framebuffer of current cubemap mip level + m_rasterizer->use_framebuffer(*light_probe.get_luminance_framebuffers()[i]); + + // Downsample + m_rasterizer->draw_arrays(*m_quad_vao, gl::drawing_mode::points, 0, 1); + } + + // Bind cubemap filter shader program + m_rasterizer->use_program(*m_cubemap_filter_shader_program); + + // Pass cubemap and filter lut textures to cubemap filter shader program + m_cubemap_filter_cubemap_var->update(*light_probe.get_luminance_texture()); + m_cubemap_filter_filter_lut_var->update(*m_cubemap_filter_lut_texture); + + // Filter mip chain + for (int i = 1; i < static_cast(light_probe.get_luminance_framebuffers().size()) - 2; ++i) + { + // Update mip level shader variable + m_cubemap_filter_mip_level_var->update(static_cast(i)); + + // Set viewport to resolution of cubemap face size for current mip level + const std::uint16_t current_mip_face_size = base_mip_face_size >> i; + m_rasterizer->set_viewport(0, 0, current_mip_face_size, current_mip_face_size); + + // Restrict cubemap mipmap range to descendent levels + light_probe.get_luminance_texture()->set_mip_range(static_cast(i + 1), 255); + + // Bind framebuffer of current cubemap mip level + m_rasterizer->use_framebuffer(*light_probe.get_luminance_framebuffers()[i]); + + // Filter + m_rasterizer->draw_arrays(*m_quad_vao, gl::drawing_mode::points, 0, 1); + } + + // Restore cubemap mipmap range + light_probe.get_luminance_texture()->set_mip_range(0, 255); + + // Mark light probe luminance as current + light_probe.set_luminance_outdated(false); + } + ); +} + +void light_probe_stage::update_light_probes_illuminance(const std::vector& light_probes) +{ bool state_bound = false; // For each light probe @@ -101,7 +218,7 @@ void light_probe_stage::execute(render::context& ctx) m_rasterizer->use_framebuffer(*light_probe.get_illuminance_framebuffer()); // Update cubemap to spherical harmonics cubemap variable with light probe luminance texture - m_cubemap_var->update(*light_probe.get_luminance_texture()); + m_cubemap_to_sh_cubemap_var->update(*light_probe.get_luminance_texture()); // Draw quad m_rasterizer->draw_arrays(*m_quad_vao, gl::drawing_mode::triangle_strip, 0, 4); @@ -123,6 +240,24 @@ void light_probe_stage::set_sh_sample_count(std::size_t count) } } +void light_probe_stage::set_cubemap_filter_sample_count(std::size_t count) +{ + if (m_cubemap_filter_sample_count != count) + { + m_cubemap_filter_sample_count = count; + cubemap_filter_parameters_changed(); + } +} + +void light_probe_stage::set_cubemap_filter_mip_bias(float bias) +{ + if (m_cubemap_filter_mip_bias != bias) + { + m_cubemap_filter_mip_bias = bias; + cubemap_filter_parameters_changed(); + } +} + void light_probe_stage::rebuild_cubemap_to_sh_shader_program() { m_cubemap_to_sh_shader_program = m_cubemap_to_sh_shader_template->build({{"SAMPLE_COUNT", std::to_string(m_sh_sample_count)}}); @@ -130,24 +265,113 @@ void light_probe_stage::rebuild_cubemap_to_sh_shader_program() { debug::log::error("Failed to build cubemap to spherical harmonics shader program: {}", m_cubemap_to_sh_shader_program->info()); debug::log::warning("{}", m_cubemap_to_sh_shader_template->configure(gl::shader_stage::vertex)); - m_cubemap_var = nullptr; + m_cubemap_to_sh_cubemap_var = nullptr; throw std::runtime_error("Failed to build cubemap to spherical harmonics shader program."); } else { - m_cubemap_var = m_cubemap_to_sh_shader_program->variable("cubemap"); - if (!m_cubemap_var) + m_cubemap_to_sh_cubemap_var = m_cubemap_to_sh_shader_program->variable("cubemap"); + if (!m_cubemap_to_sh_cubemap_var) { throw std::runtime_error("Cubemap to spherical harmonics shader program has no `cubemap` variable."); } } } +void light_probe_stage::rebuild_cubemap_downsample_shader_program() +{ + m_cubemap_downsample_shader_program = m_cubemap_downsample_shader_template->build({}); + if (!m_cubemap_downsample_shader_program->linked()) + { + debug::log::error("Failed to build cubemap downsample shader program: {}", m_cubemap_downsample_shader_program->info()); + debug::log::warning("{}", m_cubemap_downsample_shader_template->configure(gl::shader_stage::vertex)); + m_cubemap_downsample_cubemap_var = nullptr; + + throw std::runtime_error("Failed to build cubemap downsample shader program."); + } + else + { + m_cubemap_downsample_cubemap_var = m_cubemap_downsample_shader_program->variable("cubemap"); + if (!m_cubemap_downsample_cubemap_var) + { + throw std::runtime_error("Cubemap downsample shader program has no `cubemap` variable."); + } + } +} + +void light_probe_stage::rebuild_cubemap_filter_lut_shader_program() +{ + m_cubemap_filter_lut_shader_program = m_cubemap_filter_lut_shader_template->build({}); + if (!m_cubemap_filter_lut_shader_program->linked()) + { + debug::log::error("Failed to build cubemap filter LUT shader program: {}", m_cubemap_filter_lut_shader_program->info()); + debug::log::warning("{}", m_cubemap_filter_lut_shader_template->configure(gl::shader_stage::vertex)); + m_cubemap_filter_lut_resolution_var = nullptr; + m_cubemap_filter_lut_face_size_var = nullptr; + m_cubemap_filter_lut_mip_bias_var = nullptr; + + throw std::runtime_error("Failed to build cubemap filter LUT shader program."); + } + else + { + m_cubemap_filter_lut_resolution_var = m_cubemap_filter_lut_shader_program->variable("resolution"); + m_cubemap_filter_lut_face_size_var = m_cubemap_filter_lut_shader_program->variable("face_size"); + m_cubemap_filter_lut_mip_bias_var = m_cubemap_filter_lut_shader_program->variable("mip_bias"); + if (!m_cubemap_filter_lut_resolution_var || !m_cubemap_filter_lut_face_size_var || !m_cubemap_filter_lut_mip_bias_var) + { + throw std::runtime_error("Cubemap filter LUT shader program is missing one or more required shader variables."); + } + } +} + +void light_probe_stage::rebuild_cubemap_filter_lut_texture() +{ + glDisable(GL_BLEND); + m_rasterizer->use_framebuffer(*m_cubemap_filter_lut_framebuffer); + m_rasterizer->set_viewport(0, 0, m_cubemap_filter_lut_texture->get_width(), m_cubemap_filter_lut_texture->get_height()); + m_rasterizer->use_program(*m_cubemap_filter_lut_shader_program); + m_cubemap_filter_lut_resolution_var->update(math::vector2{static_cast(m_cubemap_filter_lut_texture->get_width()), static_cast(m_cubemap_filter_lut_texture->get_height())}); + m_cubemap_filter_lut_face_size_var->update(128.0f); + m_cubemap_filter_lut_mip_bias_var->update(m_cubemap_filter_mip_bias); + m_rasterizer->draw_arrays(*m_quad_vao, gl::drawing_mode::triangle_strip, 0, 4); +} + +void light_probe_stage::rebuild_cubemap_filter_shader_program() +{ + m_cubemap_filter_shader_program = m_cubemap_filter_shader_template->build({{"SAMPLE_COUNT", std::to_string(m_cubemap_filter_sample_count)}}); + if (!m_cubemap_filter_shader_program->linked()) + { + debug::log::error("Failed to build cubemap filter shader program: {}", m_cubemap_filter_shader_program->info()); + debug::log::warning("{}", m_cubemap_filter_shader_template->configure(gl::shader_stage::vertex)); + m_cubemap_filter_cubemap_var = nullptr; + m_cubemap_filter_filter_lut_var = nullptr; + m_cubemap_filter_mip_level_var = nullptr; + + throw std::runtime_error("Failed to build cubemap filter shader program."); + } + else + { + m_cubemap_filter_cubemap_var = m_cubemap_filter_shader_program->variable("cubemap"); + m_cubemap_filter_filter_lut_var = m_cubemap_filter_shader_program->variable("filter_lut"); + m_cubemap_filter_mip_level_var = m_cubemap_filter_shader_program->variable("mip_level"); + + if (!m_cubemap_filter_cubemap_var || !m_cubemap_filter_filter_lut_var || !m_cubemap_filter_mip_level_var) + { + throw std::runtime_error("Cubemap filter shader program is missing one or more required shader variables."); + } + } +} + void light_probe_stage::sh_parameters_changed() { rebuild_cubemap_to_sh_shader_program(); m_reproject_sh = true; } +void light_probe_stage::cubemap_filter_parameters_changed() +{ + m_refilter_cubemaps = true; +} + } // namespace render diff --git a/src/engine/render/stages/light-probe-stage.hpp b/src/engine/render/stages/light-probe-stage.hpp index 7c523a2..9025da8 100644 --- a/src/engine/render/stages/light-probe-stage.hpp +++ b/src/engine/render/stages/light-probe-stage.hpp @@ -48,15 +48,19 @@ public: * * @exception std::runtime_error Failed to build cubemap to spherical harmonics shader program. * @exception std::runtime_error Cubemap to spherical harmonics shader program has no `cubemap` variable. + * @exception std::runtime_error Failed to build cubemap downsample shader program. + * @exception std::runtime_error Cubemap downsample shader program has no `cubemap` variable. + * @exception std::runtime_error Failed to build cubemap filter LUT shader program. + * @exception std::runtime_error Cubemap filter LUT shader program is missing one or more required shader variables. */ light_probe_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager); void execute(render::context& ctx) override; /** - * Sets the number of samples to use when projecting cubemaps into spherical harmonics. + * Sets the number of samples to use when projecting luminance cubemaps into spherical harmonics. * - * @param count Sample count. + * @param count Spherical harmonics sample count. * * @warning Triggers rebuilding of cubemap to spherical harmonics shader program. * @warning Triggers recalculation of the illuminance of all light probes on next call to `execute()`. @@ -66,24 +70,82 @@ public: */ void set_sh_sample_count(std::size_t count); - /// Returns the number of samples used when projecting cubemaps into spherical harmonics. + /** + * Sets the number of samples to use when filtering luminance cubemap mip chains. + * + * @param count Cubemap filter sample count. + */ + void set_cubemap_filter_sample_count(std::size_t count); + + /** + * Sets the mip bias to use when filtering luminance cubemap mip chains. + * + * @param bias Cubemap filter mip bias. + * + * @warning Triggers recalculation of the luminance of all light probes on next call to `execute()`. + * + * @exception std::runtime_error Failed to build cubemap filter LUT shader program. + * @exception std::runtime_error Cubemap filter LUT shader program is missing one or more required shader variables. + */ + void set_cubemap_filter_mip_bias(float bias); + + /// Returns the number of samples used when projecting luminance cubemaps into spherical harmonics. [[nodiscard]] inline std::size_t get_sh_sample_count() const noexcept { return m_sh_sample_count; } + /// Returns the number of samples used when filtering luminance cubemaps. + [[nodiscard]] inline std::size_t get_cubemap_filter_sample_count() const noexcept + { + return m_cubemap_filter_sample_count; + } + private: void rebuild_cubemap_to_sh_shader_program(); + void rebuild_cubemap_downsample_shader_program(); + void rebuild_cubemap_filter_lut_shader_program(); + void rebuild_cubemap_filter_lut_texture(); + void rebuild_cubemap_filter_shader_program(); void sh_parameters_changed(); + void cubemap_filter_parameters_changed(); + + void update_light_probes_luminance(const std::vector& light_probes); + void update_light_probes_illuminance(const std::vector& light_probes); gl::rasterizer* m_rasterizer; std::unique_ptr m_quad_vbo; std::unique_ptr m_quad_vao; std::shared_ptr m_cubemap_to_sh_shader_template; std::unique_ptr m_cubemap_to_sh_shader_program; - const gl::shader_variable* m_cubemap_var{}; + const gl::shader_variable* m_cubemap_to_sh_cubemap_var{}; std::size_t m_sh_sample_count{1024}; bool m_reproject_sh{true}; + + std::shared_ptr m_cubemap_downsample_shader_template; + std::unique_ptr m_cubemap_downsample_shader_program; + const gl::shader_variable* m_cubemap_downsample_cubemap_var{}; + std::vector> m_cubemap_downsample_framebuffers; + std::unique_ptr m_cubemap_downsample_texture; + + std::unique_ptr m_cubemap_filter_lut_texture; + std::unique_ptr m_cubemap_filter_lut_framebuffer; + std::shared_ptr m_cubemap_filter_lut_shader_template; + std::unique_ptr m_cubemap_filter_lut_shader_program; + const gl::shader_variable* m_cubemap_filter_lut_resolution_var{}; + const gl::shader_variable* m_cubemap_filter_lut_face_size_var{}; + const gl::shader_variable* m_cubemap_filter_lut_mip_bias_var{}; + + std::shared_ptr m_cubemap_filter_shader_template; + std::unique_ptr m_cubemap_filter_shader_program; + const gl::shader_variable* m_cubemap_filter_cubemap_var{}; + const gl::shader_variable* m_cubemap_filter_filter_lut_var{}; + const gl::shader_variable* m_cubemap_filter_mip_level_var{}; + + std::size_t m_cubemap_filter_sample_count{32}; + std::size_t m_cubemap_filter_mip_count{5}; + float m_cubemap_filter_mip_bias{1.0f}; + bool m_refilter_cubemaps{true}; }; } // namespace render diff --git a/src/engine/scene/light-probe.cpp b/src/engine/scene/light-probe.cpp index f4b48ac..2ab0f3d 100644 --- a/src/engine/scene/light-probe.cpp +++ b/src/engine/scene/light-probe.cpp @@ -47,6 +47,27 @@ void light_probe::set_luminance_texture(std::shared_ptr textur if (m_luminance_texture != texture) { m_luminance_texture = texture; + + // Update luminance framebuffers + if (m_luminance_texture) + { + const std::uint8_t mip_count = 1 + static_cast(std::log2(texture->get_face_size())); + for (std::uint8_t i = 0; i < mip_count; ++i) + { + if (i >= m_luminance_framebuffers.size()) + { + m_luminance_framebuffers.emplace_back(std::make_shared()); + } + + m_luminance_framebuffers[i]->attach(gl::framebuffer_attachment_type::color, m_luminance_texture.get(), i); + } + + if (m_luminance_framebuffers.size() > mip_count) + { + m_luminance_framebuffers.resize(mip_count); + } + } + set_luminance_outdated(true); set_illuminance_outdated(true); } diff --git a/src/engine/scene/light-probe.hpp b/src/engine/scene/light-probe.hpp index 0322cfa..e9d96a4 100644 --- a/src/engine/scene/light-probe.hpp +++ b/src/engine/scene/light-probe.hpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace scene { @@ -76,6 +77,12 @@ public: return m_luminance_texture; } + /// Returns the light probe's luminance framebuffers. + [[nodiscard]] inline const std::vector>& get_luminance_framebuffers() const noexcept + { + return m_luminance_framebuffers; + } + /** * Returns the light probe's illuminance texture. * @@ -127,6 +134,7 @@ private: void transformed() override; aabb_type m_bounds{}; std::shared_ptr m_luminance_texture; + std::vector> m_luminance_framebuffers; std::shared_ptr m_illuminance_texture; std::shared_ptr m_illuminance_framebuffer; math::matrix4 m_illuminance_matrices[3]; diff --git a/src/game/states/main-menu-state.cpp b/src/game/states/main-menu-state.cpp index afc693a..900d1e9 100644 --- a/src/game/states/main-menu-state.cpp +++ b/src/game/states/main-menu-state.cpp @@ -131,8 +131,8 @@ main_menu_state::main_menu_state(::game& ctx, bool fade_in): ctx.state_machine.pop(); // ctx.state_machine.emplace(std::make_unique(ctx)); // ctx.state_machine.emplace(std::make_unique(ctx)); - // ctx.state_machine.emplace(std::make_unique(ctx)); - ctx.state_machine.emplace(std::make_unique(ctx)); + ctx.state_machine.emplace(std::make_unique(ctx)); + // ctx.state_machine.emplace(std::make_unique(ctx)); } ); }; diff --git a/src/game/states/nest-selection-state.cpp b/src/game/states/nest-selection-state.cpp index 7b1e9ae..6f21249 100644 --- a/src/game/states/nest-selection-state.cpp +++ b/src/game/states/nest-selection-state.cpp @@ -303,7 +303,7 @@ nest_selection_state::nest_selection_state(::game& ctx): // Create sphere auto sphere_eid = ctx.entity_registry->create(); - auto sphere_static_mesh = std::make_shared(ctx.resource_manager->load("sphere.mdl")); + auto sphere_static_mesh = std::make_shared(ctx.resource_manager->load("diffuse-spheres.mdl")); ctx.entity_registry->emplace(sphere_eid, std::move(sphere_static_mesh), std::uint8_t{1}); ctx.entity_registry->patch (