💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
13 KiB

/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include <engine/render/stages/light-probe-stage.hpp>
#include <engine/render/vertex-attribute.hpp>
#include <engine/scene/light-probe.hpp>
#include <engine/scene/collection.hpp>
#include <algorithm>
#include <execution>
#include <stdexcept>
#include <glad/glad.h>
namespace render {
light_probe_stage::light_probe_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager):
m_rasterizer(&rasterizer)
{
// Load cubemap to spherical harmonics shader template and build shader program
m_cubemap_to_sh_shader_template = resource_manager.load<gl::shader_template>("cubemap-to-sh.glsl");
rebuild_cubemap_to_sh_shader_program();
// Load cubemap downsample shader template and build shader program
m_cubemap_downsample_shader_template = resource_manager.load<gl::shader_template>("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<gl::shader_template>("cubemap-filter-lut.glsl");
rebuild_cubemap_filter_lut_shader_program();
// Allocate cubemap filter LUT texture
m_cubemap_filter_lut_texture = std::make_unique<gl::texture_2d>(static_cast<std::uint16_t>(m_cubemap_filter_sample_count), static_cast<std::uint16_t>(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<gl::framebuffer>();
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<gl::shader_template>("cubemap-filter.glsl");
rebuild_cubemap_filter_shader_program();
}
void light_probe_stage::execute(render::context& ctx)
{
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<scene::object_base*>& 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<scene::light_probe&>(*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<std::uint8_t>(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(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<int>(light_probe.get_luminance_framebuffers().size()) - 2; ++i)
{
// Update mip level shader variable
m_cubemap_filter_mip_level_var->update(static_cast<int>(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<std::uint8_t>(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(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<scene::object_base*>& light_probes)
{
bool state_bound = false;
// For each light probe
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<scene::light_probe&>(*object);
if (!light_probe.is_illuminance_outdated() && !m_reproject_sh)
{
return;
}
// Setup viewport and bind cubemap to spherical harmonics shader program
if (!state_bound)
{
glDisable(GL_BLEND);
m_rasterizer->set_viewport(0, 0, 12, 1);
m_rasterizer->use_program(*m_cubemap_to_sh_shader_program);
state_bound = true;
}
// Bind light probe illuminance framebuffer
m_rasterizer->use_framebuffer(*light_probe.get_illuminance_framebuffer());
// Update cubemap to spherical harmonics cubemap variable with light probe luminance texture
m_cubemap_to_sh_cubemap_var->update(*light_probe.get_luminance_texture());
// Draw quad
m_rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3);
// Mark light probe illuminance as current
light_probe.set_illuminance_outdated(false);
}
);
m_reproject_sh = false;
}
void light_probe_stage::set_sh_sample_count(std::size_t count)
{
if (m_sh_sample_count != count)
{
m_sh_sample_count = count;
sh_parameters_changed();
}
}
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)}});
if (!m_cubemap_to_sh_shader_program->linked())
{
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_to_sh_cubemap_var = nullptr;
throw std::runtime_error("Failed to build cubemap to spherical harmonics shader program.");
}
else
{
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::fvec2{static_cast<float>(m_cubemap_filter_lut_texture->get_width()), static_cast<float>(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(gl::drawing_mode::triangles, 0, 3);
}
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