Antkeeper  0.0.1
light-probe-stage.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Christopher J. Howard
3  *
4  * This file is part of Antkeeper source code.
5  *
6  * Antkeeper source code is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Antkeeper source code is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
24 #include <algorithm>
25 #include <execution>
26 #include <stdexcept>
27 
28 namespace render {
29 
31  m_pipeline(&pipeline)
32 {
33  // Generate restricted mip range samplers
34  m_downsample_samplers.resize(16);
35  m_filter_samplers.resize(16);
36  for (std::size_t i = 0; i < m_downsample_samplers.size(); ++i)
37  {
38  m_downsample_samplers[i] = std::make_shared<gl::sampler>
39  (
46  0.0f,
47  0.0f,
48  false,
50  static_cast<float>(i),
51  static_cast<float>(i)
52  );
53 
54  m_filter_samplers[i] = std::make_shared<gl::sampler>
55  (
62  0.0f,
63  0.0f,
64  false,
66  static_cast<float>(i),
67  1000.0f
68  );
69  }
70 
71  // Construct empty vertex array
72  m_vertex_array = std::make_unique<gl::vertex_array>();
73 
74  // Load cubemap to spherical harmonics shader template and build shader program
75  m_cubemap_to_sh_shader_template = resource_manager.load<gl::shader_template>("cubemap-to-sh.glsl");
76  rebuild_cubemap_to_sh_shader_program();
77 
78  // Load cubemap downsample shader template and build shader program
79  m_cubemap_downsample_shader_template = resource_manager.load<gl::shader_template>("cubemap-downsample.glsl");
80  rebuild_cubemap_downsample_shader_program();
81 
82  // Load cubemap filter LUT shader template and build shader program
83  m_cubemap_filter_lut_shader_template = resource_manager.load<gl::shader_template>("cubemap-filter-lut.glsl");
84  rebuild_cubemap_filter_lut_shader_program();
85 
86  // Allocate cubemap filter LUT texture
87  m_cubemap_filter_lut_texture = std::make_shared<gl::texture_2d>
88  (
89  std::make_shared<gl::image_view_2d>
90  (
91  std::make_shared<gl::image_2d>
92  (
94  static_cast<std::uint32_t>(m_cubemap_filter_sample_count),
95  static_cast<std::uint32_t>(m_cubemap_filter_mip_count - 1)
96  )
97  ),
98  std::make_shared<gl::sampler>
99  (
105  )
106  );
107 
108  // Allocate cubemap filter LUT framebuffer and attach LUT texture
109  const gl::framebuffer_attachment attachments[1] =
110  {{
112  m_cubemap_filter_lut_texture->get_image_view(),
113  0
114  }};
115  m_cubemap_filter_lut_framebuffer = std::make_unique<gl::framebuffer>(attachments, m_cubemap_filter_lut_texture->get_image_view()->get_image()->get_dimensions()[0], m_cubemap_filter_lut_texture->get_image_view()->get_image()->get_dimensions()[1]);
116 
117  // Build cubemap filter LUT texture
118  rebuild_cubemap_filter_lut_texture();
119 
120  // Load cubemap filter shader template and build shader program
121  m_cubemap_filter_shader_template = resource_manager.load<gl::shader_template>("cubemap-filter.glsl");
122  rebuild_cubemap_filter_shader_program();
123 }
124 
126 {
127  const auto& light_probes = ctx.collection->get_objects(scene::light_probe::object_type_id);
128  if (light_probes.empty())
129  {
130  return;
131  }
132 
133  update_light_probes_luminance(light_probes);
134  update_light_probes_illuminance(light_probes);
135 }
136 
137 void light_probe_stage::update_light_probes_luminance(const std::vector<scene::object_base*>& light_probes)
138 {
139  bool state_bound = false;
140 
141  // Downsample cubemaps
142  std::for_each
143  (
144  std::execution::seq,
145  std::begin(light_probes),
146  std::end(light_probes),
147  [&](scene::object_base* object)
148  {
149  scene::light_probe& light_probe = static_cast<scene::light_probe&>(*object);
150  if (!light_probe.is_luminance_outdated() && !m_refilter_cubemaps)
151  {
152  return;
153  }
154 
155  // Store light probe luminance sampler
156  auto light_probe_luminance_sampler = light_probe.get_luminance_texture()->get_sampler();
157 
158  // Bind state, if unbound
159  if (!state_bound)
160  {
162  m_pipeline->bind_vertex_array(m_vertex_array.get());
163  m_pipeline->set_color_blend_enabled(false);
164  state_bound = true;
165  }
166 
167  // Bind cubemap downsample shader program
168  m_pipeline->bind_shader_program(m_cubemap_downsample_shader_program.get());
169 
170  // Get resolution of cubemap face for base mip level
171  const auto base_mip_face_size = light_probe.get_luminance_texture()->get_image_view()->get_image()->get_dimensions()[0];
172 
173  // Downsample mip chain
174  for (std::size_t i = 1; i < light_probe.get_luminance_framebuffers().size(); ++i)
175  {
176  // Set viewport to resolution of cubemap face size for current mip level
177  const auto current_mip_face_size = base_mip_face_size >> i;
178  const gl::viewport viewport[1] =
179  {{
180  0,
181  0,
182  static_cast<float>(current_mip_face_size),
183  static_cast<float>(current_mip_face_size)
184  }};
185  m_pipeline->set_viewport(0, viewport);
186 
187  // Restrict cubemap mipmap range to parent mip level
188  light_probe.get_luminance_texture()->set_sampler(m_downsample_samplers[i - 1]);
189 
190  // Update cubemap shader variable with light probe luminance texture
191  m_cubemap_downsample_cubemap_var->update(*light_probe.get_luminance_texture());
192 
193  // Bind framebuffer of current cubemap mip level
194  m_pipeline->bind_framebuffer(light_probe.get_luminance_framebuffers()[i].get());
195 
196  // Downsample
197  m_pipeline->draw(1, 1, 0, 0);
198  }
199 
200  // Bind cubemap filter shader program
201  m_pipeline->bind_shader_program(m_cubemap_filter_shader_program.get());
202 
203  // Pass filter lut texture to cubemap filter shader program
204  m_cubemap_filter_filter_lut_var->update(*m_cubemap_filter_lut_texture);
205 
206  // Filter mip chain
207  for (int i = 1; i < static_cast<int>(light_probe.get_luminance_framebuffers().size()) - 2; ++i)
208  {
209  // Update mip level shader variable
210  m_cubemap_filter_mip_level_var->update(static_cast<int>(i));
211 
212  // Set viewport to resolution of cubemap face size for current mip level
213  const auto current_mip_face_size = base_mip_face_size >> i;
214  const gl::viewport viewport[1] =
215  {{
216  0,
217  0,
218  static_cast<float>(current_mip_face_size),
219  static_cast<float>(current_mip_face_size)
220  }};
221  m_pipeline->set_viewport(0, viewport);
222 
223  // Restrict cubemap mipmap range to descendent levels
224  light_probe.get_luminance_texture()->set_sampler(m_filter_samplers[i + 1]);
225 
226  // Update cubemap shader variable with light probe luminance texture
227  m_cubemap_filter_cubemap_var->update(*light_probe.get_luminance_texture());
228 
229  // Bind framebuffer of current cubemap mip level
230  m_pipeline->bind_framebuffer(light_probe.get_luminance_framebuffers()[i].get());
231 
232  // Filter
233  m_pipeline->draw(1, 1, 0, 0);
234  }
235 
236  // Restore light probe luminance sampler
237  light_probe.get_luminance_texture()->set_sampler(light_probe_luminance_sampler);
238 
239  // Mark light probe luminance as current
240  light_probe.set_luminance_outdated(false);
241  }
242  );
243 }
244 
245 void light_probe_stage::update_light_probes_illuminance(const std::vector<scene::object_base*>& light_probes)
246 {
247  bool state_bound = false;
248 
249  // For each light probe
250  std::for_each
251  (
252  std::execution::seq,
253  std::begin(light_probes),
254  std::end(light_probes),
255  [&](scene::object_base* object)
256  {
257  scene::light_probe& light_probe = static_cast<scene::light_probe&>(*object);
258  if (!light_probe.is_illuminance_outdated() && !m_reproject_sh)
259  {
260  return;
261  }
262 
263  // Setup viewport and bind cubemap to spherical harmonics shader program
264  if (!state_bound)
265  {
267  m_pipeline->bind_vertex_array(m_vertex_array.get());
268  m_pipeline->set_color_blend_enabled(false);
269 
270  const gl::viewport viewport[1] =
271  {{
272  0,
273  0,
274  12,
275  1
276  }};
277  m_pipeline->set_viewport(0, viewport);
278 
279  m_pipeline->bind_shader_program(m_cubemap_to_sh_shader_program.get());
280  state_bound = true;
281  }
282 
283  // Bind light probe illuminance framebuffer
284  m_pipeline->bind_framebuffer(light_probe.get_illuminance_framebuffer().get());
285 
286  // Update cubemap to spherical harmonics cubemap variable with light probe luminance texture
287  m_cubemap_to_sh_cubemap_var->update(*light_probe.get_luminance_texture());
288 
289  // Draw fullscreen triangle
290  m_pipeline->draw(3, 1, 0, 0);
291 
292  // Mark light probe illuminance as current
293  light_probe.set_illuminance_outdated(false);
294  }
295  );
296 
297  m_reproject_sh = false;
298 }
299 
300 void light_probe_stage::set_sh_sample_count(std::size_t count)
301 {
302  if (m_sh_sample_count != count)
303  {
304  m_sh_sample_count = count;
305  sh_parameters_changed();
306  }
307 }
308 
309 void light_probe_stage::set_cubemap_filter_sample_count(std::size_t count)
310 {
311  if (m_cubemap_filter_sample_count != count)
312  {
313  m_cubemap_filter_sample_count = count;
314  cubemap_filter_parameters_changed();
315  }
316 }
317 
318 void light_probe_stage::set_cubemap_filter_mip_bias(float bias)
319 {
320  if (m_cubemap_filter_mip_bias != bias)
321  {
322  m_cubemap_filter_mip_bias = bias;
323  cubemap_filter_parameters_changed();
324  }
325 }
326 
327 void light_probe_stage::rebuild_cubemap_to_sh_shader_program()
328 {
329  m_cubemap_to_sh_shader_program = m_cubemap_to_sh_shader_template->build({{"SAMPLE_COUNT", std::to_string(m_sh_sample_count)}});
330  if (!m_cubemap_to_sh_shader_program->linked())
331  {
332  debug::log_error("Failed to build cubemap to spherical harmonics shader program: {}", m_cubemap_to_sh_shader_program->info());
333  debug::log_warning("{}", m_cubemap_to_sh_shader_template->configure(gl::shader_stage::vertex));
334  m_cubemap_to_sh_cubemap_var = nullptr;
335 
336  throw std::runtime_error("Failed to build cubemap to spherical harmonics shader program.");
337  }
338  else
339  {
340  m_cubemap_to_sh_cubemap_var = m_cubemap_to_sh_shader_program->variable("cubemap");
341  if (!m_cubemap_to_sh_cubemap_var)
342  {
343  throw std::runtime_error("Cubemap to spherical harmonics shader program has no `cubemap` variable.");
344  }
345  }
346 }
347 
348 void light_probe_stage::rebuild_cubemap_downsample_shader_program()
349 {
350  m_cubemap_downsample_shader_program = m_cubemap_downsample_shader_template->build({});
351  if (!m_cubemap_downsample_shader_program->linked())
352  {
353  debug::log_error("Failed to build cubemap downsample shader program: {}", m_cubemap_downsample_shader_program->info());
354  debug::log_warning("{}", m_cubemap_downsample_shader_template->configure(gl::shader_stage::vertex));
355  m_cubemap_downsample_cubemap_var = nullptr;
356 
357  throw std::runtime_error("Failed to build cubemap downsample shader program.");
358  }
359  else
360  {
361  m_cubemap_downsample_cubemap_var = m_cubemap_downsample_shader_program->variable("cubemap");
362  if (!m_cubemap_downsample_cubemap_var)
363  {
364  throw std::runtime_error("Cubemap downsample shader program has no `cubemap` variable.");
365  }
366  }
367 }
368 
369 void light_probe_stage::rebuild_cubemap_filter_lut_shader_program()
370 {
371  m_cubemap_filter_lut_shader_program = m_cubemap_filter_lut_shader_template->build({});
372  if (!m_cubemap_filter_lut_shader_program->linked())
373  {
374  debug::log_error("Failed to build cubemap filter LUT shader program: {}", m_cubemap_filter_lut_shader_program->info());
375  debug::log_warning("{}", m_cubemap_filter_lut_shader_template->configure(gl::shader_stage::vertex));
376  m_cubemap_filter_lut_resolution_var = nullptr;
377  m_cubemap_filter_lut_face_size_var = nullptr;
378  m_cubemap_filter_lut_mip_bias_var = nullptr;
379 
380  throw std::runtime_error("Failed to build cubemap filter LUT shader program.");
381  }
382  else
383  {
384  m_cubemap_filter_lut_resolution_var = m_cubemap_filter_lut_shader_program->variable("resolution");
385  m_cubemap_filter_lut_face_size_var = m_cubemap_filter_lut_shader_program->variable("face_size");
386  m_cubemap_filter_lut_mip_bias_var = m_cubemap_filter_lut_shader_program->variable("mip_bias");
387  if (!m_cubemap_filter_lut_resolution_var || !m_cubemap_filter_lut_face_size_var || !m_cubemap_filter_lut_mip_bias_var)
388  {
389  throw std::runtime_error("Cubemap filter LUT shader program is missing one or more required shader variables.");
390  }
391  }
392 }
393 
394 void light_probe_stage::rebuild_cubemap_filter_lut_texture()
395 {
396  m_pipeline->set_color_blend_enabled(false);
397  m_pipeline->bind_framebuffer(m_cubemap_filter_lut_framebuffer.get());
398 
399  const auto& cubemap_filter_lut_dimensions = m_cubemap_filter_lut_texture->get_image_view()->get_image()->get_dimensions();
400  const gl::viewport viewport[1] =
401  {{
402  0,
403  0,
404  static_cast<float>(cubemap_filter_lut_dimensions[0]),
405  static_cast<float>(cubemap_filter_lut_dimensions[1])
406  }};
407  m_pipeline->set_viewport(0, viewport);
408 
409  m_pipeline->bind_shader_program(m_cubemap_filter_lut_shader_program.get());
410  m_cubemap_filter_lut_resolution_var->update(math::fvec2{static_cast<float>(cubemap_filter_lut_dimensions[0]), static_cast<float>(cubemap_filter_lut_dimensions[1])});
411  m_cubemap_filter_lut_face_size_var->update(128.0f);
412  m_cubemap_filter_lut_mip_bias_var->update(m_cubemap_filter_mip_bias);
413  m_cubemap_filter_lut_framebuffer->resize(cubemap_filter_lut_dimensions[0], cubemap_filter_lut_dimensions[1]);
414 
415  m_pipeline->bind_vertex_array(m_vertex_array.get());
416  m_pipeline->set_primitive_topology(gl::primitive_topology::triangle_list);
417  m_pipeline->draw(3, 1, 0, 0);
418 }
419 
420 void light_probe_stage::rebuild_cubemap_filter_shader_program()
421 {
422  m_cubemap_filter_shader_program = m_cubemap_filter_shader_template->build({{"SAMPLE_COUNT", std::to_string(m_cubemap_filter_sample_count)}});
423  if (!m_cubemap_filter_shader_program->linked())
424  {
425  debug::log_error("Failed to build cubemap filter shader program: {}", m_cubemap_filter_shader_program->info());
426  debug::log_warning("{}", m_cubemap_filter_shader_template->configure(gl::shader_stage::vertex));
427  m_cubemap_filter_cubemap_var = nullptr;
428  m_cubemap_filter_filter_lut_var = nullptr;
429  m_cubemap_filter_mip_level_var = nullptr;
430 
431  throw std::runtime_error("Failed to build cubemap filter shader program.");
432  }
433  else
434  {
435  m_cubemap_filter_cubemap_var = m_cubemap_filter_shader_program->variable("cubemap");
436  m_cubemap_filter_filter_lut_var = m_cubemap_filter_shader_program->variable("filter_lut");
437  m_cubemap_filter_mip_level_var = m_cubemap_filter_shader_program->variable("mip_level");
438 
439  if (!m_cubemap_filter_cubemap_var || !m_cubemap_filter_filter_lut_var || !m_cubemap_filter_mip_level_var)
440  {
441  throw std::runtime_error("Cubemap filter shader program is missing one or more required shader variables.");
442  }
443  }
444 }
445 
446 void light_probe_stage::sh_parameters_changed()
447 {
448  rebuild_cubemap_to_sh_shader_program();
449  m_reproject_sh = true;
450 }
451 
452 void light_probe_stage::cubemap_filter_parameters_changed()
453 {
454  m_refilter_cubemaps = true;
455 }
456 
457 } // namespace render
Graphics pipeline interface.
Definition: pipeline.hpp:48
void set_primitive_topology(primitive_topology topology)
Sets the primitive topology to use for drawing.
Definition: pipeline.cpp:356
void bind_shader_program(const gl::shader_program *shader_program)
Sets the vertex input.
Definition: pipeline.cpp:292
void bind_framebuffer(const gl::framebuffer *framebuffer)
Sets the vertex input.
Definition: pipeline.cpp:275
void set_color_blend_enabled(bool enabled)
Controls whether blending is enabled for the corresponding color attachment.
Definition: pipeline.cpp:953
void set_viewport(std::uint32_t first_viewport, std::span< const viewport > viewports)
Sets one or more viewports.
Definition: pipeline.cpp:381
void draw(std::uint32_t vertex_count, std::uint32_t instance_count, std::uint32_t first_vertex, std::uint32_t first_instance)
Draws primitives.
Definition: pipeline.cpp:1028
void bind_vertex_array(const vertex_array *array)
Binds a vertex array.
Definition: pipeline.cpp:309
Template used to for generating one or more shader variants from a single source.
virtual void update(bool value) const
Updates the value of the variable.
void execute(render::context &ctx) override
Executes the render stage.
light_probe_stage(gl::pipeline &pipeline, ::resource_manager &resource_manager)
Constructs a light probe stage.
Manages the loading, caching, and saving of resources.
std::shared_ptr< T > load(const std::filesystem::path &path)
Loads and caches a resource.
const std::vector< object_base * > & get_objects() const noexcept
Returns all objects in the collection.
Definition: collection.hpp:56
const std::shared_ptr< gl::framebuffer > & get_illuminance_framebuffer() const noexcept
Returns the light probe's illuminance framebuffer.
Definition: light-probe.hpp:96
bool is_luminance_outdated() const noexcept
Returns true if the light probe's luminance is outdated.
const std::shared_ptr< gl::texture_cube > & get_luminance_texture() const noexcept
Returns the light probe's luminance texture.
Definition: light-probe.hpp:74
void set_illuminance_outdated(bool outdated)
Marks the light probe's illuminance as either outdated or current.
bool is_illuminance_outdated() const noexcept
Returns true if the light probe's illuminance is outdated.
const std::vector< std::shared_ptr< gl::framebuffer > > & get_luminance_framebuffers() const noexcept
Returns the light probe's luminance framebuffers.
Definition: light-probe.hpp:80
void set_luminance_outdated(bool outdated)
Marks the light probe's luminance as either outdated or current.
Abstract base class for scene objects.
Definition: object.hpp:37
static const std::atomic< std::size_t > object_type_id
Unique type ID for this scene object type.
Definition: object.hpp:175
constexpr int count(T x) noexcept
Returns the number of set bits in a value, known as a population count or Hamming weight.
Definition: bit-math.hpp:211
log_message< log_message_severity::warning, Args... > log_warning
Formats and logs a warning message.
Definition: log.hpp:130
log_message< log_message_severity::error, Args... > log_error
Formats and logs an error message.
Definition: log.hpp:144
@ r32g32b32a32_sfloat
@ repeat
Repeat wrap mode.
@ clamp_to_edge
Clamp to edge wrap mode.
@ color_attachment_bit
Framebuffer color attachment.
@ vertex
Vertex shader stage.
@ linear
Linear filtering.
@ nearest
Nearest filtering.
@ linear
Linear filtering.
@ nearest
Nearest filtering.
@ point_list
Separate point primitives.
@ triangle_list
Separate triangle primitives.
@ less
Comparison evaluates reference < test.
High-level rendering.
Viewport position, dimensions, and depth range.
Definition: viewport.hpp:31
n-dimensional vector.
Definition: vector.hpp:44
Context of a renderer.
Definition: context.hpp:40
scene::collection * collection
Collection of scene objects being rendered.
Definition: context.hpp:45