💿🐜 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

  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. #include <engine/render/stages/light-probe-stage.hpp>
  20. #include <engine/render/vertex-attribute.hpp>
  21. #include <engine/scene/light-probe.hpp>
  22. #include <engine/scene/collection.hpp>
  23. #include <algorithm>
  24. #include <execution>
  25. #include <stdexcept>
  26. #include <glad/glad.h>
  27. namespace render {
  28. light_probe_stage::light_probe_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager):
  29. m_rasterizer(&rasterizer)
  30. {
  31. // Load cubemap to spherical harmonics shader template and build shader program
  32. m_cubemap_to_sh_shader_template = resource_manager.load<gl::shader_template>("cubemap-to-sh.glsl");
  33. rebuild_cubemap_to_sh_shader_program();
  34. // Load cubemap downsample shader template and build shader program
  35. m_cubemap_downsample_shader_template = resource_manager.load<gl::shader_template>("cubemap-downsample.glsl");
  36. rebuild_cubemap_downsample_shader_program();
  37. // Load cubemap filter LUT shader template and build shader program
  38. m_cubemap_filter_lut_shader_template = resource_manager.load<gl::shader_template>("cubemap-filter-lut.glsl");
  39. rebuild_cubemap_filter_lut_shader_program();
  40. // Allocate cubemap filter LUT texture
  41. 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);
  42. // Allocate cubemap filter LUT framebuffer and attach LUT texture
  43. m_cubemap_filter_lut_framebuffer = std::make_unique<gl::framebuffer>();
  44. m_cubemap_filter_lut_framebuffer->attach(gl::framebuffer_attachment_type::color, m_cubemap_filter_lut_texture.get());
  45. // Build cubemap filter LUT texture
  46. rebuild_cubemap_filter_lut_texture();
  47. // Load cubemap filter shader template and build shader program
  48. m_cubemap_filter_shader_template = resource_manager.load<gl::shader_template>("cubemap-filter.glsl");
  49. rebuild_cubemap_filter_shader_program();
  50. }
  51. void light_probe_stage::execute(render::context& ctx)
  52. {
  53. const auto& light_probes = ctx.collection->get_objects(scene::light_probe::object_type_id);
  54. if (light_probes.empty())
  55. {
  56. return;
  57. }
  58. update_light_probes_luminance(light_probes);
  59. update_light_probes_illuminance(light_probes);
  60. }
  61. void light_probe_stage::update_light_probes_luminance(const std::vector<scene::object_base*>& light_probes)
  62. {
  63. bool state_bound = false;
  64. // Downsample cubemaps
  65. std::for_each
  66. (
  67. std::execution::seq,
  68. std::begin(light_probes),
  69. std::end(light_probes),
  70. [&](scene::object_base* object)
  71. {
  72. scene::light_probe& light_probe = static_cast<scene::light_probe&>(*object);
  73. if (!light_probe.is_luminance_outdated() && !m_refilter_cubemaps)
  74. {
  75. return;
  76. }
  77. // Bind state, if unbound
  78. if (!state_bound)
  79. {
  80. glDisable(GL_BLEND);
  81. state_bound = true;
  82. }
  83. // Bind cubemap downsample shader program
  84. m_rasterizer->use_program(*m_cubemap_downsample_shader_program);
  85. // Update cubemap shader variable with light probe luminance texture
  86. m_cubemap_downsample_cubemap_var->update(*light_probe.get_luminance_texture());
  87. // Get resolution of cubemap face for base mip level
  88. const std::uint16_t base_mip_face_size = light_probe.get_luminance_texture()->get_face_size();
  89. // Downsample mip chain
  90. for (std::size_t i = 1; i < light_probe.get_luminance_framebuffers().size(); ++i)
  91. {
  92. // Set viewport to resolution of cubemap face size for current mip level
  93. const std::uint16_t current_mip_face_size = base_mip_face_size >> i;
  94. m_rasterizer->set_viewport(0, 0, current_mip_face_size, current_mip_face_size);
  95. // Restrict cubemap mipmap range to parent mip level
  96. const std::uint8_t parent_mip_level = static_cast<std::uint8_t>(i - 1);
  97. light_probe.get_luminance_texture()->set_mip_range(parent_mip_level, parent_mip_level);
  98. // Bind framebuffer of current cubemap mip level
  99. m_rasterizer->use_framebuffer(*light_probe.get_luminance_framebuffers()[i]);
  100. // Downsample
  101. m_rasterizer->draw_arrays(gl::drawing_mode::points, 0, 1);
  102. }
  103. // Bind cubemap filter shader program
  104. m_rasterizer->use_program(*m_cubemap_filter_shader_program);
  105. // Pass cubemap and filter lut textures to cubemap filter shader program
  106. m_cubemap_filter_cubemap_var->update(*light_probe.get_luminance_texture());
  107. m_cubemap_filter_filter_lut_var->update(*m_cubemap_filter_lut_texture);
  108. // Filter mip chain
  109. for (int i = 1; i < static_cast<int>(light_probe.get_luminance_framebuffers().size()) - 2; ++i)
  110. {
  111. // Update mip level shader variable
  112. m_cubemap_filter_mip_level_var->update(static_cast<int>(i));
  113. // Set viewport to resolution of cubemap face size for current mip level
  114. const std::uint16_t current_mip_face_size = base_mip_face_size >> i;
  115. m_rasterizer->set_viewport(0, 0, current_mip_face_size, current_mip_face_size);
  116. // Restrict cubemap mipmap range to descendent levels
  117. light_probe.get_luminance_texture()->set_mip_range(static_cast<std::uint8_t>(i + 1), 255);
  118. // Bind framebuffer of current cubemap mip level
  119. m_rasterizer->use_framebuffer(*light_probe.get_luminance_framebuffers()[i]);
  120. // Filter
  121. m_rasterizer->draw_arrays(gl::drawing_mode::points, 0, 1);
  122. }
  123. // Restore cubemap mipmap range
  124. light_probe.get_luminance_texture()->set_mip_range(0, 255);
  125. // Mark light probe luminance as current
  126. light_probe.set_luminance_outdated(false);
  127. }
  128. );
  129. }
  130. void light_probe_stage::update_light_probes_illuminance(const std::vector<scene::object_base*>& light_probes)
  131. {
  132. bool state_bound = false;
  133. // For each light probe
  134. std::for_each
  135. (
  136. std::execution::seq,
  137. std::begin(light_probes),
  138. std::end(light_probes),
  139. [&](scene::object_base* object)
  140. {
  141. scene::light_probe& light_probe = static_cast<scene::light_probe&>(*object);
  142. if (!light_probe.is_illuminance_outdated() && !m_reproject_sh)
  143. {
  144. return;
  145. }
  146. // Setup viewport and bind cubemap to spherical harmonics shader program
  147. if (!state_bound)
  148. {
  149. glDisable(GL_BLEND);
  150. m_rasterizer->set_viewport(0, 0, 12, 1);
  151. m_rasterizer->use_program(*m_cubemap_to_sh_shader_program);
  152. state_bound = true;
  153. }
  154. // Bind light probe illuminance framebuffer
  155. m_rasterizer->use_framebuffer(*light_probe.get_illuminance_framebuffer());
  156. // Update cubemap to spherical harmonics cubemap variable with light probe luminance texture
  157. m_cubemap_to_sh_cubemap_var->update(*light_probe.get_luminance_texture());
  158. // Draw quad
  159. m_rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3);
  160. // Mark light probe illuminance as current
  161. light_probe.set_illuminance_outdated(false);
  162. }
  163. );
  164. m_reproject_sh = false;
  165. }
  166. void light_probe_stage::set_sh_sample_count(std::size_t count)
  167. {
  168. if (m_sh_sample_count != count)
  169. {
  170. m_sh_sample_count = count;
  171. sh_parameters_changed();
  172. }
  173. }
  174. void light_probe_stage::set_cubemap_filter_sample_count(std::size_t count)
  175. {
  176. if (m_cubemap_filter_sample_count != count)
  177. {
  178. m_cubemap_filter_sample_count = count;
  179. cubemap_filter_parameters_changed();
  180. }
  181. }
  182. void light_probe_stage::set_cubemap_filter_mip_bias(float bias)
  183. {
  184. if (m_cubemap_filter_mip_bias != bias)
  185. {
  186. m_cubemap_filter_mip_bias = bias;
  187. cubemap_filter_parameters_changed();
  188. }
  189. }
  190. void light_probe_stage::rebuild_cubemap_to_sh_shader_program()
  191. {
  192. m_cubemap_to_sh_shader_program = m_cubemap_to_sh_shader_template->build({{"SAMPLE_COUNT", std::to_string(m_sh_sample_count)}});
  193. if (!m_cubemap_to_sh_shader_program->linked())
  194. {
  195. debug::log::error("Failed to build cubemap to spherical harmonics shader program: {}", m_cubemap_to_sh_shader_program->info());
  196. debug::log::warning("{}", m_cubemap_to_sh_shader_template->configure(gl::shader_stage::vertex));
  197. m_cubemap_to_sh_cubemap_var = nullptr;
  198. throw std::runtime_error("Failed to build cubemap to spherical harmonics shader program.");
  199. }
  200. else
  201. {
  202. m_cubemap_to_sh_cubemap_var = m_cubemap_to_sh_shader_program->variable("cubemap");
  203. if (!m_cubemap_to_sh_cubemap_var)
  204. {
  205. throw std::runtime_error("Cubemap to spherical harmonics shader program has no `cubemap` variable.");
  206. }
  207. }
  208. }
  209. void light_probe_stage::rebuild_cubemap_downsample_shader_program()
  210. {
  211. m_cubemap_downsample_shader_program = m_cubemap_downsample_shader_template->build({});
  212. if (!m_cubemap_downsample_shader_program->linked())
  213. {
  214. debug::log::error("Failed to build cubemap downsample shader program: {}", m_cubemap_downsample_shader_program->info());
  215. debug::log::warning("{}", m_cubemap_downsample_shader_template->configure(gl::shader_stage::vertex));
  216. m_cubemap_downsample_cubemap_var = nullptr;
  217. throw std::runtime_error("Failed to build cubemap downsample shader program.");
  218. }
  219. else
  220. {
  221. m_cubemap_downsample_cubemap_var = m_cubemap_downsample_shader_program->variable("cubemap");
  222. if (!m_cubemap_downsample_cubemap_var)
  223. {
  224. throw std::runtime_error("Cubemap downsample shader program has no `cubemap` variable.");
  225. }
  226. }
  227. }
  228. void light_probe_stage::rebuild_cubemap_filter_lut_shader_program()
  229. {
  230. m_cubemap_filter_lut_shader_program = m_cubemap_filter_lut_shader_template->build({});
  231. if (!m_cubemap_filter_lut_shader_program->linked())
  232. {
  233. debug::log::error("Failed to build cubemap filter LUT shader program: {}", m_cubemap_filter_lut_shader_program->info());
  234. debug::log::warning("{}", m_cubemap_filter_lut_shader_template->configure(gl::shader_stage::vertex));
  235. m_cubemap_filter_lut_resolution_var = nullptr;
  236. m_cubemap_filter_lut_face_size_var = nullptr;
  237. m_cubemap_filter_lut_mip_bias_var = nullptr;
  238. throw std::runtime_error("Failed to build cubemap filter LUT shader program.");
  239. }
  240. else
  241. {
  242. m_cubemap_filter_lut_resolution_var = m_cubemap_filter_lut_shader_program->variable("resolution");
  243. m_cubemap_filter_lut_face_size_var = m_cubemap_filter_lut_shader_program->variable("face_size");
  244. m_cubemap_filter_lut_mip_bias_var = m_cubemap_filter_lut_shader_program->variable("mip_bias");
  245. if (!m_cubemap_filter_lut_resolution_var || !m_cubemap_filter_lut_face_size_var || !m_cubemap_filter_lut_mip_bias_var)
  246. {
  247. throw std::runtime_error("Cubemap filter LUT shader program is missing one or more required shader variables.");
  248. }
  249. }
  250. }
  251. void light_probe_stage::rebuild_cubemap_filter_lut_texture()
  252. {
  253. glDisable(GL_BLEND);
  254. m_rasterizer->use_framebuffer(*m_cubemap_filter_lut_framebuffer);
  255. m_rasterizer->set_viewport(0, 0, m_cubemap_filter_lut_texture->get_width(), m_cubemap_filter_lut_texture->get_height());
  256. m_rasterizer->use_program(*m_cubemap_filter_lut_shader_program);
  257. 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())});
  258. m_cubemap_filter_lut_face_size_var->update(128.0f);
  259. m_cubemap_filter_lut_mip_bias_var->update(m_cubemap_filter_mip_bias);
  260. m_rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3);
  261. }
  262. void light_probe_stage::rebuild_cubemap_filter_shader_program()
  263. {
  264. m_cubemap_filter_shader_program = m_cubemap_filter_shader_template->build({{"SAMPLE_COUNT", std::to_string(m_cubemap_filter_sample_count)}});
  265. if (!m_cubemap_filter_shader_program->linked())
  266. {
  267. debug::log::error("Failed to build cubemap filter shader program: {}", m_cubemap_filter_shader_program->info());
  268. debug::log::warning("{}", m_cubemap_filter_shader_template->configure(gl::shader_stage::vertex));
  269. m_cubemap_filter_cubemap_var = nullptr;
  270. m_cubemap_filter_filter_lut_var = nullptr;
  271. m_cubemap_filter_mip_level_var = nullptr;
  272. throw std::runtime_error("Failed to build cubemap filter shader program.");
  273. }
  274. else
  275. {
  276. m_cubemap_filter_cubemap_var = m_cubemap_filter_shader_program->variable("cubemap");
  277. m_cubemap_filter_filter_lut_var = m_cubemap_filter_shader_program->variable("filter_lut");
  278. m_cubemap_filter_mip_level_var = m_cubemap_filter_shader_program->variable("mip_level");
  279. if (!m_cubemap_filter_cubemap_var || !m_cubemap_filter_filter_lut_var || !m_cubemap_filter_mip_level_var)
  280. {
  281. throw std::runtime_error("Cubemap filter shader program is missing one or more required shader variables.");
  282. }
  283. }
  284. }
  285. void light_probe_stage::sh_parameters_changed()
  286. {
  287. rebuild_cubemap_to_sh_shader_program();
  288. m_reproject_sh = true;
  289. }
  290. void light_probe_stage::cubemap_filter_parameters_changed()
  291. {
  292. m_refilter_cubemaps = true;
  293. }
  294. } // namespace render