Unravel Engine C++ Reference
Loading...
Searching...
No Matches
pipeline.cpp
Go to the documentation of this file.
1#include "pipeline.h"
2#include "glm/ext/scalar_integer.hpp"
17#include <engine/engine.h>
23
25#include <graphics/graphics.h>
27
28#include <algorithm>
30#include <graphics/texture.h>
32
33namespace unravel
34{
35namespace rendering
36{
37
38namespace
39{
40
41
42auto get_default_format() -> gfx::texture_format
43{
44 return gfx::texture_format::RGBA8;
45}
46
47auto get_default_hdr_format() -> gfx::texture_format
48{
49 return gfx::texture_format::RGBA16F;
50}
51
52auto get_default_depth_format() -> gfx::texture_format
53{
54 return gfx::texture_format::D32F;
55}
56
57// Cubemap face captures keep HDR buffer setup from create_run_params, but write the
58// linear lighting result directly to the cubemap face before post-processing.
59void strip_post_effects_for_reflection_probe_capture(pipeline::run_params& params)
60{
61 params.fill_assao_params = {};
62 params.fill_auto_exposure_params = {};
63 params.fill_bloom_params = {};
64 params.fill_taa_params = {};
65 params.apply_taa_params = {};
66 params.fill_ssr_params = {};
67 params.fill_ssil_params = {};
68 params.fill_hdr_params = {};
69}
70
71void clear_reflection_probe_face(const gfx::frame_buffer::ptr& fbo)
72{
73 if(!fbo)
74 {
75 return;
76 }
77
78 gfx::render_pass pass("Reflection Probe/Clear Face");
79 pass.bind(fbo.get());
80 pass.set_view_proj(nullptr, nullptr);
81 pass.clear(BGFX_CLEAR_COLOR, 0, 0.0f, 0);
82}
83
84// run_pipeline_impl takes const camera& for reads; jitter only touches projection jitter state.
85void apply_pipeline_taa_jitter_to_camera(const camera& view_camera,
86 const usize32_t& viewport_size,
87 const pipeline::run_params& params)
88{
89 camera& cam = const_cast<camera&>(view_camera);
90 if(params.apply_taa_params)
91 {
92 params.apply_taa_params(cam, viewport_size);
93 }
94 else
95 {
96 cam.set_aa_data(viewport_size, 0u, 1u);
97 }
98}
99
100auto create_or_resize_d_buffer(gfx::render_view& rview,
101 const usize32_t& viewport_size,
102 const pipeline::run_params& params) -> const gfx::texture::ptr&
103{
104 auto& depth = rview.tex_get_or_emplace("DEPTH");
105 if(gfx::needs_recreate(depth, viewport_size))
106 {
107 depth.reset();
108 depth = std::make_shared<gfx::texture>(viewport_size.width,
109 viewport_size.height,
110 false,
111 1,
112 gfx::texture_format::D32F,
113 BGFX_TEXTURE_RT);
114 }
115
116 return depth;
117}
118
119auto create_or_resize_hiz_buffer(gfx::render_view& rview, const usize32_t& viewport_size) -> const gfx::texture::ptr&
120{
121 auto& hiz = rview.tex_get_or_emplace("HIZBUFFER");
122 if(gfx::needs_recreate(hiz, viewport_size))
123 {
124 // Create Hi-Z texture with compute shader support
125 hiz.reset();
126 hiz = std::make_shared<gfx::texture>(viewport_size.width,
127 viewport_size.height,
128 true, // generate mips
129 1, // one layer
130 gfx::texture_format::R32F, // R32F for better precision
131 BGFX_TEXTURE_RT | // Render target
132 BGFX_TEXTURE_COMPUTE_WRITE | // Allow compute writes
133 BGFX_SAMPLER_MIN_POINT | // Point sampling for min filter
134 BGFX_SAMPLER_MAG_POINT | // Point sampling for mag filter
135 BGFX_SAMPLER_MIP_POINT | // Point sampling for mips
136 BGFX_SAMPLER_U_CLAMP | // Clamp UVs
137 BGFX_SAMPLER_V_CLAMP // Clamp UVs
138 );
139 }
140
141 return hiz;
142}
143
144auto create_or_resize_g_buffer(gfx::render_view& rview,
145 const usize32_t& viewport_size,
146 const pipeline::run_params& params) -> const gfx::frame_buffer::ptr&
147{
148 auto& depth = create_or_resize_d_buffer(rview, viewport_size, params);
149
150 auto& fbo = rview.fbo_get_or_emplace("GBUFFER");
151 if(gfx::needs_recreate(fbo, viewport_size))
152 {
153 auto format = params.fill_hdr_params ? get_default_hdr_format() : get_default_format();
154
155 auto tex0 = std::make_shared<gfx::texture>(viewport_size.width,
156 viewport_size.height,
157 false,
158 1,
159 get_default_format(),
160 BGFX_TEXTURE_COMPUTE_WRITE | BGFX_TEXTURE_RT);
161
162 auto tex1 = std::make_shared<gfx::texture>(viewport_size.width,
163 viewport_size.height,
164 false,
165 1,
166 format,
167 BGFX_TEXTURE_RT);
168
169 auto tex2 = std::make_shared<gfx::texture>(viewport_size.width,
170 viewport_size.height,
171 false,
172 1,
173 format,
174 BGFX_TEXTURE_RT);
175
176 auto tex3 = std::make_shared<gfx::texture>(viewport_size.width,
177 viewport_size.height,
178 false,
179 1,
180 get_default_format(),
181 BGFX_TEXTURE_RT);
182
183 fbo.reset();
184 fbo = std::make_shared<gfx::frame_buffer>();
185 fbo->populate({tex0, tex1, tex2, tex3, depth});
186 }
187
188 return fbo;
189}
190
191auto create_or_resize_l_buffer(gfx::render_view& rview,
192 const usize32_t& viewport_size,
193 const pipeline::run_params& params) -> const gfx::frame_buffer::ptr&
194{
195 auto& depth = create_or_resize_d_buffer(rview, viewport_size, params);
196
197 auto& fbo = rview.fbo_get_or_emplace("LBUFFER");
198 if(gfx::needs_recreate(fbo, viewport_size))
199 {
200 auto format = params.fill_hdr_params ? get_default_hdr_format() : get_default_format();
201
202 auto tex = std::make_shared<gfx::texture>(viewport_size.width,
203 viewport_size.height,
204 false,
205 1,
206 format,
207 BGFX_TEXTURE_RT);
208 fbo = std::make_shared<gfx::frame_buffer>();
209 fbo->populate({tex});
210
211 auto tex_unshadowed = std::make_shared<gfx::texture>(viewport_size.width,
212 viewport_size.height,
213 false,
214 1,
215 format,
216 BGFX_TEXTURE_RT);
217
218
219 auto& fbo_depth = rview.fbo_get_or_emplace("LBUFFER_DEPTH");
220 fbo_depth.reset();
221 fbo_depth = std::make_shared<gfx::frame_buffer>();
222 fbo_depth->populate({tex, depth});
223 }
224
225 return fbo;
226}
227
228auto create_or_resize_r_buffer(gfx::render_view& rview,
229 const usize32_t& viewport_size,
230 const pipeline::run_params& params) -> const gfx::frame_buffer::ptr&
231{
232 auto& fbo = rview.fbo_get_or_emplace("RBUFFER");
233 if(gfx::needs_recreate(fbo, viewport_size))
234 {
235 auto format = params.fill_hdr_params ? get_default_hdr_format() : get_default_format();
236
237 auto tex = std::make_shared<gfx::texture>(viewport_size.width,
238 viewport_size.height,
239 false,
240 1,
241 format,
242 BGFX_TEXTURE_RT | BGFX_TEXTURE_COMPUTE_WRITE);
243
244 fbo.reset();
245 fbo = std::make_shared<gfx::frame_buffer>();
246 fbo->populate({tex});
247 }
248
249 return fbo;
250}
251auto create_or_resize_o_buffer(gfx::render_view& rview,
252 const usize32_t& viewport_size,
253 const pipeline::run_params& params) -> const gfx::frame_buffer::ptr&
254{
255 auto& depth = create_or_resize_d_buffer(rview, viewport_size, params);
256
257 auto& tex = rview.tex_get_or_emplace("OBUFFER");
258 if(gfx::needs_recreate(tex, viewport_size))
259 {
260 tex.reset();
261 tex = std::make_shared<gfx::texture>(viewport_size.width,
262 viewport_size.height,
263 false,
264 1,
265 get_default_format(),
266 BGFX_TEXTURE_COMPUTE_WRITE | BGFX_TEXTURE_RT);
267
268 }
269 {
270 auto& fbo = rview.fbo_get_or_emplace("OBUFFER_DEPTH");
271 if(gfx::needs_recreate(fbo, viewport_size))
272 {
273 fbo.reset();
274 fbo = std::make_shared<gfx::frame_buffer>();
275 fbo->populate({tex, depth});
276 }
277 }
278
279 auto& fbo = rview.fbo_get_or_emplace("OBUFFER");
280 if(gfx::needs_recreate(fbo, viewport_size))
281 {
282 fbo.reset();
283 fbo = std::make_shared<gfx::frame_buffer>();
284 fbo->populate({tex});
285 }
286
287 return fbo;
288}
289
290auto create_or_get_irradiance_texture(gfx::render_view& rview) -> const gfx::texture::ptr&
291{
292 auto& tex = rview.tex_get_or_emplace("IRRADIANCE_SH");
293 if(gfx::needs_recreate(tex, {9, 1}))
294 {
295 // Match auto-exposure: RGBA32F + COMPUTE_WRITE uses glTexStorage2D on GL (immutable
296 // storage). Initial data must go through update_texture_2d (glTexSubImage2D), not
297 // the texture ctor _mem path. BGFX_TEXTURE_RT keeps the GL texture sampleable after
298 // compute image writes (same pattern as Hi-Z and other compute targets).
299 // Layout: 9x1, one texel per SH coefficient, rgb = channels R,G,B (a unused).
300 tex.reset();
301 tex = std::make_shared<gfx::texture>(9,
302 1,
303 false,
304 1,
305 gfx::texture_format::RGBA32F,
306 BGFX_TEXTURE_RT | BGFX_TEXTURE_COMPUTE_WRITE |
307 BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT |
308 BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP);
309
310 float initial_coeffs[9 * 4] = {};
311 const gfx::memory_view* initial_pixels = gfx::copy(initial_coeffs, sizeof(initial_coeffs));
312 gfx::update_texture_2d(tex->native_handle(), 0, 0, 0, 0, 9, 1, initial_pixels);
313 }
314 return tex;
315}
316
317auto should_rebuild_shadows(const shadow::shadow_map_models_t& visibility_set,
318 const light& light,
319 const math::bbox& light_bounds,
320 const math::transform& light_transform) -> bool
321{
322 APP_SCOPE_PERF("Rendering/Shadow Rebuild Check Per Light");
323
324 auto light_world_bounds = math::bbox::mul(light_bounds, light_transform);
325 for(const auto& element : visibility_set)
326 {
327 const auto& entity = element.entity;
328 const auto& lod_data = element.lod_data;
329 const auto& transform_comp_ref = entity.get<transform_component>();
330 const auto& model_comp_ref = entity.get<model_component>();
331 const auto& model_world_bounds = model_comp_ref.get_world_bounds();
332
333 bool result = light_world_bounds.intersect(model_world_bounds);
334
335 if(result)
336 return true;
337 }
338
339 return false;
340}
341
342auto reflection_screen_stack_enabled(const pipeline::run_params& params) -> bool
343{
346}
347} // namespace
348
349auto deferred::get_light_program(const light& l) const -> const color_lighting&
350{
351 return color_lighting_[uint8_t(l.type)][uint8_t(l.shadow_params.depth)][uint8_t(l.shadow_params.type)];
352}
353
354auto deferred::get_light_program_no_shadows(const light& l) const -> const color_lighting&
355{
356 return color_lighting_no_shadow_[uint8_t(l.type)];
357}
358
359void deferred::submit_pbr_material(geom_program& program, const pbr_material& mat)
360{
361 const auto& color_map = mat.get_color_map();
362 const auto& normal_map = mat.get_normal_map();
363 const auto& roughness_map = mat.get_roughness_map();
364 const auto& metalness_map = mat.get_metalness_map();
365 const auto& ao_map = mat.get_ao_map();
366 const auto& emissive_map = mat.get_emissive_map();
367
368 const auto& albedo = color_map ? color_map : mat.default_color_map();
369 const auto& normal = normal_map ? normal_map : mat.default_normal_map();
370 const auto& roughness = roughness_map ? roughness_map : mat.default_color_map();
371 const auto& metalness = metalness_map ? metalness_map : mat.default_color_map();
372 const auto& ao = ao_map ? ao_map : mat.default_color_map();
373 const auto& emissive = emissive_map ? emissive_map : mat.default_color_map();
374
375 // Resolve and pin every texture up front. asset_handle::get() returns a
376 // shared_ptr<texture>; holding our own copies for the duration of the
377 // submit keeps the texture objects alive even if the asset watcher
378 // thread invalidates/reloads these handles mid-frame (which happens
379 // intermittently right after a scene is opened). Without this, a texture
380 // resolved here could be released between the individual set_texture
381 // calls, leaving a dangling pointer for native_handle().
382 const auto albedo_tex = albedo.get();
383 const auto normal_tex = normal.get();
384 const auto roughness_tex = roughness.get();
385 const auto metalness_tex = metalness.get();
386 const auto ao_tex = ao.get();
387 const auto emissive_tex = emissive.get();
388
389 const auto& base_color = mat.get_base_color();
390 const auto& subsurface_color = mat.get_subsurface_color();
391 const auto& emissive_color = mat.get_emissive_color();
392 const float emissive_intensity = mat.get_emissive_intensity();
393 const auto& surface_data = mat.get_surface_data();
394 const auto& tiling = mat.get_tiling();
395 const auto& dither_threshold = mat.get_dither_threshold();
396 const auto surface_data2 = mat.get_surface_data2();
397
398 gfx::set_texture(program.s_tex_color, 0, albedo_tex);
399 gfx::set_texture(program.s_tex_normal, 1, normal_tex);
400 gfx::set_texture(program.s_tex_roughness, 2, roughness_tex);
401 gfx::set_texture(program.s_tex_metalness, 3, metalness_tex);
402 gfx::set_texture(program.s_tex_ao, 4, ao_tex);
403 gfx::set_texture(program.s_tex_emissive, 5, emissive_tex);
404
405 math::color premultiplied_emissive{
406 emissive_color.value.r * emissive_intensity,
407 emissive_color.value.g * emissive_intensity,
408 emissive_color.value.b * emissive_intensity,
409 emissive_color.value.a};
410
411 gfx::set_uniform(program.u_base_color, base_color);
412 gfx::set_uniform(program.u_subsurface_color, subsurface_color);
413 gfx::set_uniform(program.u_emissive_color, premultiplied_emissive);
414 gfx::set_uniform(program.u_surface_data, surface_data);
415 gfx::set_uniform(program.u_tiling, tiling);
416 gfx::set_uniform(program.u_dither_threshold, dither_threshold);
417 gfx::set_uniform(program.u_surface_data2, surface_data2);
418
419 auto state = mat.get_render_states(true, true, true);
420
421 gfx::set_state(state);
422}
423
425{
426 APP_SCOPE_PERF("Rendering/Reflection Generation Pass");
427
429 [&](auto e, auto&& transform_comp, auto&& reflection_probe_comp, auto&& active)
430 {
431 if(reflection_probe_comp.already_generated())
432 {
433 return;
434 }
435
436 // reflection_probe_comp.set_generation_frame(gfx::get_render_frame());
437
438 const auto& world_transform = transform_comp.get_transform_global();
439
440 const auto& bounds = reflection_probe_comp.get_bounds();
441 if(!camera.test_obb(bounds, world_transform))
442 {
443 return;
444 }
445
446 const auto& probe = reflection_probe_comp.get_probe();
447
448 auto handle = scn.create_handle(e);
449 {
450 gfx::render_pass::push_scope("Build Reflections");
451
452 if(reflection_probe_comp.is_bake_cycle_unstarted())
453 {
454 for(std::uint32_t face = 0; face < 6; ++face)
455 {
456 clear_reflection_probe_face(reflection_probe_comp.get_cubemap_fbo(face));
457 }
458 }
459
460 bool any_face_dirty = false;
461 // iterate trough each cube face
462 for(std::uint32_t face = 0; face < 6; ++face)
463 {
464 if(reflection_probe_comp.already_generated(face))
465 {
466 continue;
467 }
468
469 reflection_probe_comp.set_generation_frame(face, gfx::get_render_frame());
470
471 auto camera = camera::get_face_camera(face, world_transform);
472 camera.set_far_clip(probe.get_face_extents(face, world_transform));
473 auto& rview = reflection_probe_comp.get_render_view(face);
474 const auto& cubemap_fbo = reflection_probe_comp.get_cubemap_fbo(face);
475
476 camera.set_viewport_size(usize32_t(cubemap_fbo->get_size()));
477
478 bool not_environment = probe.method != reflect_method::environment;
479
480 pipeline_flags pflags = 0;
482
483 if(not_environment)
484 {
486 }
487
488 if(reflection_probe_comp.get_capture_sky())
489 {
491 }
492
493 if(reflection_probe_comp.get_capture_shadows())
494 {
497 }
498
499 auto params = create_run_params(handle, &scn, &camera);
501 params.vflags = vflags;
502 params.pflags = pflags;
503 strip_post_effects_for_reflection_probe_capture(params);
504
505 //if(!reflection_probe_comp.get_capture_sky())
506 {
507 clear_reflection_probe_face(cubemap_fbo);
508 }
509
510 run_pipeline_impl(cubemap_fbo, scn, camera, rview, dt, params);
511 any_face_dirty = true;
512 }
513
514 if(any_face_dirty && reflection_probe_comp.is_bake_complete())
515 {
516 auto env_cube = reflection_probe_comp.get_cubemap();
517 auto env_cube_prefiltered = reflection_probe_comp.get_cubemap_prefiltered();
518 prefilter_pass::run_params prefilter_params;
519
520 prefilter_params.apply_prefilter = reflection_probe_comp.get_apply_prefilter();
521
522 for(std::uint32_t face = 0; face < 6; ++face)
523 {
524 const auto& cubemap_fbo = reflection_probe_comp.get_cubemap_fbo(face);
525 prefilter_params.input_faces[face] = cubemap_fbo->get_texture();
526 }
527
528 prefilter_params.output_cube = env_cube;
529 prefilter_params.output_cube_prefiltered = env_cube_prefiltered;
530
531 prefilter_pass_.run(reflection_probe_comp.get_render_view(0), prefilter_params);
532 }
533
535 }
536 });
537}
538
540{
541 APP_SCOPE_PERF("Rendering/Shadow Generation Pass");
542
544
545 bool queried = false;
546 shadow::shadow_map_models_t dirty_models;
547
548 const auto& view = camera.get_view();
549 const auto& proj = camera.get_projection();
550 const auto& camera_pos = camera.get_position();
551
553 [&](auto e, auto&& transform_comp, auto&& light_comp)
554 {
555 const auto& light = light_comp.get_light();
556
557 bool is_directional = light.type == light_type::directional;
558 bool has_render_mask = render_mask.mask != layer_reserved::everything_layer;
559 bool camera_dependant = is_directional || has_render_mask;
560 bool is_active = scn.registry->all_of<active_component>(e);
561
562 auto& generator = light_comp.get_shadowmap_generator();
563 generator.enable_adaptive_shadows(true);
564 generator.set_altitude_scale_factor(0.4f);
565 if(!camera_dependant && generator.already_updated())
566 {
567 return;
568 }
569
570 APP_SCOPE_PERF("Rendering/Shadow Generation Pass Per Light");
571
572 auto world_transform = transform_comp.get_transform_global();
573 world_transform.reset_scale();
574 const auto& light_direction = world_transform.z_unit_axis();
575
576 generator.update(camera, light, world_transform, is_active);
577
578 if(!is_active)
579 {
580 return;
581 }
582
583 const auto& bounds = light_comp.get_bounds_precise(light_direction);
584 if(!camera.test_obb(bounds, world_transform))
585 {
586 return;
587 }
588
590 {
591 return;
592 }
593
594 if(!queried)
595 {
596 gather_visible_models(scn, nullptr, query, render_mask, dt, [&](entt::handle entity, const lod_data& lod_data)
597 {
598 dirty_models.emplace_back(shadow::shadow_visibility_data{entity, lod_data});
599 }, &camera);
600 queried = true;
601 }
602
603 bool should_rebuild = should_rebuild_shadows(dirty_models, light, bounds, world_transform);
604
605 // If shadows shouldn't be rebuilt - continue.
606 if(!should_rebuild)
607 return;
608
609 APP_SCOPE_PERF("Rendering/Shadow Generation Pass Per Light After Cull");
610
611 generator.generate_shadowmaps(dirty_models, camera, &stats_);
612 });
613}
614
616 const camera& camera,
617 gfx::render_view& rview,
618 delta_t dt,
619 const run_params& params,
620 layer_mask render_mask) -> gfx::frame_buffer::ptr
621{
622 const auto& viewport_size = camera.get_viewport_size();
623 const auto& obuffer = create_or_resize_o_buffer(rview, viewport_size, params);
624
625 run_pipeline_impl(obuffer, scn, camera, rview, dt, params, render_mask);
626
627 return obuffer;
628}
629
631 scene& scn,
632 const camera& camera,
633 gfx::render_view& rview,
634 delta_t dt,
635 const run_params& params,
636 layer_mask render_mask)
637{
638 auto obuffer = run_pipeline(scn, camera, rview, dt, params, render_mask);
639
640 blit_pass::run_params pass_params;
641 pass_params.input = obuffer;
642 pass_params.output = output;
643 blit_pass_.run(rview, pass_params);
644}
645
647{
648 debug_pass_ = pass;
649}
650
652 scene& scn,
653 const camera& camera,
654 gfx::render_view& rview,
655 delta_t dt,
656 const run_params& params,
657 layer_mask render_mask)
658{
659 APP_SCOPE_PERF("Rendering/Run Pipeline");
660
661 const pipeline_flags stages = params.pflags;
662 const bool is_camera_run = params.run_type == pipeline_run_type::camera;
663 const bool is_probe_capture = params.run_type == pipeline_run_type::reflection_probe_capture;
664
665 if(is_camera_run)
666 {
667 stats_ = {};
668 }
669
670 visibility_set_models_t visibility_set;
671 gfx::frame_buffer::ptr target = nullptr;
672
673 const bool build_shadowmaps = (stages & pipeline_steps::shadow_pass) != 0u;
674 const bool build_reflection_probes = (stages & pipeline_steps::reflection_probe) != 0u;
675
676 if(build_reflection_probes)
677 {
678 build_reflections(scn, camera, dt);
679 }
680
681 if(build_shadowmaps)
682 {
684 }
685
686 const auto& viewport_size = camera.get_viewport_size();
687 create_or_resize_d_buffer(rview, viewport_size, params);
688 create_or_resize_g_buffer(rview, viewport_size, params);
689 create_or_resize_l_buffer(rview, viewport_size, params);
690 create_or_resize_r_buffer(rview, viewport_size, params);
691
692 apply_pipeline_taa_jitter_to_camera(camera, viewport_size, params);
693
695 {
696 gather_visible_models(scn, &camera, params.vflags, render_mask, dt, [&](entt::handle entity, const lod_data& lod_data)
697 {
698 visibility_set.emplace_back(visibility_data{entity, lod_data});
699 });
700 }
701
702 run_g_buffer_pass(visibility_set, camera, rview, dt);
703
704 run_assao_pass(camera, rview, dt, params);
705
706 run_reflection_probe_pass(scn, camera, rview, build_reflection_probes, dt);
707
708 const bool hiz_active = run_hiz_pass(camera, rview, params, viewport_size, dt);
709
710 // SSR samples the previous visible output before this frame overwrites it, so traced
711 // reflections use the same resolved scene color that was presented last frame.
712 run_ssr_pass(camera, rview, output, params);
713
714 // Direct lighting starts the current frame LBUFFER after SSR has consumed its history source.
715 target = run_direct_lighting_pass(scn, camera, rview, build_shadowmaps, dt);
716
717 // SSIL pass
718 run_ssil_pass(camera, rview, params);
719
720 // Indirect lighting after SSIL so it can use the result.
721 target = run_indirect_lighting_pass(scn, camera, rview, build_reflection_probes, dt);
722
723 if(stages & pipeline_steps::atmospheric)
724 {
725 target = run_atmospherics_pass(target, scn, camera, rview, dt);
726 }
727
728 if(stages & pipeline_steps::particles_pass)
729 {
730 run_particle_pass(scn, camera, rview, target);
731 }
732
733 if(is_probe_capture)
734 {
735 blit_pass::run_params pass_params;
736 pass_params.input = target;
737 pass_params.output = output;
738 blit_pass_.run(rview, pass_params);
739 batch_collector_.clear();
740 return;
741 }
742
743 target = run_taa_pass(camera, rview, target, output, params);
744
745 run_auto_exposure_pass(rview, target, params, dt);
746
747 target = run_bloom_pass(rview, target, params);
748
749 target = run_tonemapping_pass(rview, target, output, params);
750
751 run_fxaa_pass(rview, target, output, params);
752
753 if(is_camera_run)
754 {
755 run_ui_pass(scn, camera, rview, output);
756
757 if(debug_pass_ >= 0)
758 {
759 run_debug_visualization_pass(camera, rview, output);
760 }
761 }
762
763 // After all passes that sample PREV_DEPTH (must follow Hi-Z / SSIL path). TAA also
764 // consumes PREV_DEPTH for its real temporal disocclusion test -- keep the snapshot
765 // alive whenever TAA is enabled even if Hi-Z isn't driving it this frame.
766 const bool taa_active = static_cast<bool>(params.fill_taa_params);
767 if(hiz_active || taa_active)
768 {
769 snapshot_prev_depth(rview, viewport_size);
770 }
771
772 // Clear batch collector for this frame
773 batch_collector_.clear();
774
775}
776
777void deferred::snapshot_prev_depth(gfx::render_view& rview, const usize32_t& viewport_size)
778{
779 auto depth_src = rview.fbo_get("GBUFFER")->get_texture(4);
780 auto& prev_depth = rview.tex_get_or_emplace("PREV_DEPTH");
781 if(gfx::needs_recreate(prev_depth, viewport_size))
782 {
783 prev_depth.reset();
784 prev_depth = std::make_shared<gfx::texture>(viewport_size.width,
785 viewport_size.height,
786 false,
787 1,
788 gfx::texture_format::D32F,
789 BGFX_TEXTURE_BLIT_DST);
790 }
791 gfx::render_pass blit_pass("History/Prev Depth Blit Pass");
792 gfx::blit(blit_pass.id,
793 prev_depth->native_handle(), 0, 0,
794 depth_src->native_handle(), 0, 0);
795}
796
797void deferred::run_g_buffer_pass(const visibility_set_models_t& visibility_set,
798 const camera& camera,
799 gfx::render_view& rview,
800 delta_t dt)
801{
802 APP_SCOPE_PERF("Rendering/G-Buffer Pass");
803
804 const auto& view = camera.get_view();
805 const auto& proj = camera.get_projection();
806 const auto& viewport_size = camera.get_viewport_size();
807
808 const auto& gbuffer = rview.fbo_get("GBUFFER");
809
810 gfx::render_pass pass("G-Buffer/Pass");
811 pass.clear();
812 pass.set_view_proj(view, proj);
813 pass.bind(gbuffer.get());
814
815 // Clear batch collector for this frame
816 batch_collector_.clear();
817
818 const auto& view_frustum = camera.get_frustum();
819
820 for(const auto& element : visibility_set)
821 {
822 const auto& entity = element.entity;
823 const auto& lod_data = element.lod_data;
824 const auto& transform_comp = entity.get<transform_component>();
825 auto& model_comp = entity.get<model_component>();
826
827 const auto& model = model_comp.get_model();
828 if(!model.is_valid())
829 {
830 continue;
831 }
832
833 const auto& world_transform = transform_comp.get_transform_global();
834 const auto clip_planes = math::vec2(camera.get_near_clip(), camera.get_far_clip());
835
836 const auto current_time = lod_data.current_time;
837 const auto current_lod_index = lod_data.current_lod_index;
838 const auto target_lod_index = lod_data.target_lod_index;
839
840 // Optimized single-component LOD transition parameters
841 // Positive: current LOD fading out (1.0 → 0.0)
842 // Negative: target LOD fading in (0.0 → -1.0)
843 const float transition_progress = lod_data.transition_time > 0.0f
844 ? current_time / lod_data.transition_time
845 : 1.0f;
846
847 const auto params = math::vec3{1.0f - transition_progress, 0.0f, 0.0f}; // Current LOD: positive, fading out
848 const auto params_inv = math::vec3{-transition_progress, 0.0f, 0.0f}; // Target LOD: negative, fading in
849
850 const auto& submesh_transforms = model_comp.get_submesh_transforms();
851 const auto& bone_transforms = model_comp.get_bone_transforms();
852 const auto& skinning_matrices = model_comp.get_skinning_transforms();
853
854 auto camera_pos = camera.get_position();
855
856
857 model::submit_callbacks callbacks;
858 callbacks.setup_begin = [&](const model::submit_callbacks::params& submit_params)
859 {
860 if(submit_params.skinned)
861 {
862 stats_.drawn_skinned_models++;
863 }
864 else
865 {
866 stats_.drawn_models++;
867 }
868 geom_program& prog = submit_params.skinned ? geom_program_skinned_ : geom_program_;
869 prog.program->begin();
870 gfx::set_uniform(prog.u_camera_wpos, camera_pos);
871 gfx::set_uniform(prog.u_camera_clip_planes, clip_planes);
872 };
873 callbacks.setup_params_per_instance = [&](const model::submit_callbacks::params& submit_params)
874 {
875 geom_program& prog = submit_params.skinned ? geom_program_skinned_ : geom_program_;
876
877 gfx::set_uniform(prog.u_lod_params, params);
878 };
879 callbacks.setup_params_per_submesh =
880 [&](const model::submit_callbacks::params& submit_params, const material& mat)
881 {
882 if(submit_params.skinned)
883 {
884 stats_.drawn_skinned_submeshes++;
885 }
886 else
887 {
888 stats_.drawn_static_submeshes++;
889 }
890 geom_program& prog = submit_params.skinned ? geom_program_skinned_ : geom_program_;
891
892 bool submitted = mat.submit(prog.program.get());
893 if(!submitted)
894 {
895 if(mat.is<pbr_material>())
896 {
897 const auto& pbr = static_cast<const pbr_material&>(mat);
898 submit_pbr_material(prog, pbr);
899 }
900 }
901
902 gfx::submit(pass.id, prog.program->native_handle(), 0, submit_params.preserve_state);
903 };
904 callbacks.setup_end = [&](const model::submit_callbacks::params& submit_params)
905 {
906 geom_program& prog = submit_params.skinned ? geom_program_skinned_ : geom_program_;
907
908 prog.program->end();
909 };
910
911 model_comp.set_last_render_frame(gfx::get_render_frame());
912
913 const auto extras = model_comp.get_submit_extras(false);
914
915 // Check if this model can be batched (static mesh, no skinning)
916 const bool is_skinned = !skinning_matrices.empty();
917 const bool can_batch = batch_collector::is_static_mesh_batching_enabled() && !is_skinned;
918
919 if (can_batch)
920 {
921 // Collect this model for batching with appropriate transforms
922 model.submit_for_batching(batch_collector_, world_transform, submesh_transforms, current_lod_index, params.x, &view_frustum, &camera, extras);
923 stats_.drawn_models++;
924 // Handle LOD transitions for batched models
925 if(math::epsilonNotEqual(current_time, 0.0f, math::epsilon<float>()))
926 {
927 model.submit_for_batching(batch_collector_, world_transform, submesh_transforms, target_lod_index, params_inv.x, &view_frustum, &camera, extras);
928 stats_.drawn_models++;
929 }
930 }
931 else
932 {
933 // Render individually (skinned meshes, complex transforms, etc.)
934 model.submit(world_transform,
935 submesh_transforms,
936 bone_transforms,
937 skinning_matrices,
938 current_lod_index,
939 callbacks,
940 &view_frustum,
941 &camera,
942 extras);
943 if(math::epsilonNotEqual(current_time, 0.0f, math::epsilon<float>()))
944 {
945 callbacks.setup_params_per_instance = [&](const model::submit_callbacks::params& submit_params)
946 {
947 geom_program& prog = submit_params.skinned ? geom_program_skinned_ : geom_program_;
948
949 gfx::set_uniform(prog.u_lod_params, params_inv);
950 };
951
952 model.submit(world_transform,
953 submesh_transforms,
954 bone_transforms,
955 skinning_matrices,
956 target_lod_index,
957 callbacks,
958 &view_frustum,
959 &camera,
960 extras);
961 }
962 }
963 }
964
965 // Submit all collected batches
967 {
968 submit_batched_geometry(pass, camera);
969 }
970 gfx::discard();
971}
972
973void deferred::submit_batched_geometry(gfx::render_pass& pass, const camera& camera)
974{
975 APP_SCOPE_PERF("Rendering/Submit Batched Geometry");
976
977 // Prepare batches for rendering
978 submit_context context;
979 context.view_id = pass.id;
981 context.enable_distance_sorting = false; // Opaque objects don't need distance sorting
982 context.max_instances_per_batch = 1024; // BGFX instance limit
983 context.enable_profiling = true;
984
985 batch_collector_.prepare_batches(context);
986
987 const auto& prepared_batches = batch_collector_.get_prepared_batches();
988 if (prepared_batches.empty())
989 {
990 return;
991 }
992
993 // Set up common uniforms
994 const auto camera_pos = camera.get_position();
995 const auto clip_planes = math::vec2(camera.get_near_clip(), camera.get_far_clip());
996
997 geom_program_instanced_.program->begin();
998 gfx::set_uniform(geom_program_instanced_.u_camera_wpos, camera_pos);
999 gfx::set_uniform(geom_program_instanced_.u_camera_clip_planes, clip_planes);
1000
1001 // Submit each batch
1002 for (const auto* batch : prepared_batches)
1003 {
1004 if (!batch->is_valid() || batch->instances.empty())
1005 {
1006 continue;
1007 }
1008
1009 const auto instance_count = static_cast<uint32_t>(batch->instances.size());
1010 stats_.drawn_static_submeshes += instance_count;
1011
1012 const auto mesh_ptr = batch->key.mesh_ptr;
1013 const auto material_ptr = batch->key.material_ptr;
1014 const auto lod_index = batch->key.lod_index;
1015 const auto submesh_index = batch->key.submesh_index;
1016
1017 if (!mesh_ptr || !material_ptr)
1018 {
1019 continue;
1020 }
1021
1022 const auto submesh = mesh_ptr->get_submesh(submesh_index, lod_index);
1023 if(!submesh)
1024 {
1025 continue;
1026 }
1027
1028 // Create instance buffer from batch instances
1029 const auto instance_data_size = static_cast<uint16_t>(instance_vertex_data::packed_size());
1030
1031 // Allocate instance buffer
1032 bgfx::InstanceDataBuffer instance_buffer;
1033 bgfx::allocInstanceDataBuffer(&instance_buffer, instance_count, instance_data_size);
1034 if (!instance_buffer.data)
1035 {
1036 continue; // Skip this batch if allocation failed
1037 }
1038
1039
1040 // Submit the mesh with instancing
1041 // Bind vertex and index buffers for the specific submesh
1042 mesh_ptr->bind_render_buffers_for_submesh(submesh, lod_index);
1043
1044 // Pack instance data into buffer
1045 auto* buffer_data = reinterpret_cast<instance_vertex_data*>(instance_buffer.data);
1046 for (size_t i = 0; i < batch->instances.size(); ++i)
1047 {
1048 buffer_data[i] = instance_vertex_data(batch->instances[i]);
1049 }
1050
1051 // Set instance data buffer
1052 bgfx::setInstanceDataBuffer(&instance_buffer);
1053
1054 // Submit material properties
1055 bool material_submitted = material_ptr->submit(geom_program_instanced_.program.get());
1056 if (!material_submitted)
1057 {
1058 if (material_ptr->is<pbr_material>())
1059 {
1060 const auto& pbr = static_cast<const pbr_material&>(*material_ptr);
1061 submit_pbr_material(geom_program_instanced_, pbr);
1062 }
1063 }
1064
1065 // Set LOD parameters (using global LOD settings for now)
1066 const auto lod_params = math::vec3{0.0f, -1.0f, 1.0f}; // Default LOD params
1067 gfx::set_uniform(geom_program_instanced_.u_lod_params, lod_params);
1068
1069 // Submit the instanced draw call
1070 gfx::submit(pass.id, geom_program_instanced_.program->native_handle(), 0, false);
1071 }
1072
1073 geom_program_instanced_.program->end();
1074
1075 // Update statistics
1076 const auto& batch_stats = batch_collector_.get_stats();
1077 stats_.add_batch_stats(batch_stats);
1078
1079 // Clear batches to invalidate all transform pointers and free memory
1080 batch_collector_.clear();
1081}
1082
1083void deferred::run_assao_pass(const camera& camera,
1084 gfx::render_view& rview,
1085 delta_t dt,
1086 const run_params& rparams)
1087{
1088 if(!reflection_screen_stack_enabled(rparams) || !rparams.fill_assao_params)
1089 {
1090 assao_pass_.release_resources(rview);
1091 return;
1092 }
1093 APP_SCOPE_PERF("Rendering/ASSAO Pass");
1094
1095 const auto& gbuffer = rview.fbo_get("GBUFFER");
1096
1097 auto color_ao = gbuffer->get_texture(0);
1098 auto normal = gbuffer->get_texture(1);
1099 auto depth = gbuffer->get_texture(4);
1100
1102 params.depth = depth.get();
1103 params.normal = normal.get();
1104 params.color_ao = color_ao.get();
1105
1106 rparams.fill_assao_params(params);
1107
1108 assao_pass_.run(camera, rview, params);
1109}
1110
1111auto deferred::run_irradiance_pass(scene& scn, gfx::render_view& rview) -> deferred::irradiance_pass_result
1112{
1113 APP_SCOPE_PERF("Rendering/Irradiance Pass");
1114
1115 irradiance_pass_result result;
1116
1117 if(irradiance_compute_program_.program && irradiance_compute_program_.program->is_valid())
1118 {
1119 const auto& irradiance_tex = create_or_get_irradiance_texture(rview);
1120
1121 struct skylight_params
1122 {
1123 float intensity = 0.0f;
1124 float sun_weight = 1.0f;
1125 float exposition = 0.1f;
1126 float sky_brightness = 1.0f;
1127 math::vec3 color = {1.0f, 1.0f, 1.0f};
1128 math::vec3 tint = {1.0f, 1.0f, 1.0f};
1129 math::vec3 light_dir;
1130 irradiance_perez_params perez;
1131 bool use_perez = false;
1132 bool is_skybox = false;
1133 bool use_sky = true;
1134 bool directional = true;
1136 };
1137 skylight_params dominant;
1138
1139 scn.registry->view<transform_component, skylight_component, active_component>().each(
1140 [&](auto e, auto&& transform_comp_ref, auto&& skylight_comp_ref, auto&& active)
1141 {
1142 const auto& skylight = skylight_comp_ref;
1143 float irradiance_intensity = skylight.get_irradiance_intensity();
1144 if(irradiance_intensity <= 0.0f)
1145 return;
1146
1147 const auto& world_transform = transform_comp_ref.get_transform_global();
1148 math::vec3 light_dir = world_transform.z_unit_axis();
1149 math::vec3 irradiance_color = {1.0f, 1.0f, 1.0f};
1150 bool use_perez = false;
1151 bool is_skybox = (skylight.get_mode() == skylight_component::sky_mode::skybox);
1152 // Two independent axes:
1153 // - wants_sky: does the sky/environment color contribute, or is the ambient a flat tint?
1154 // - directional: does the ambient vary with the surface normal (full SH), or is it flat (L0)?
1155 const bool wants_sky = skylight.get_irradiance_use_sky();
1156 const bool directional =
1157 (skylight.get_irradiance_quality() == skylight_component::irradiance_quality::directional);
1158 float sun_weight = 1.0f;
1159
1160 if(!is_skybox)
1161 {
1162 float sun_elevation = -light_dir.y;
1163 // sun_weight: 0 at horizon, 1 at zenith. Smooth ramp over ~20° to avoid near-1 at low angles.
1164 float x = math::clamp(sun_elevation / 0.35f, 0.0f, 1.0f);
1165 sun_weight = x * x * (3.0f - 2.0f * x);
1166 }
1167 float exposition = 0.1f;
1168
1169 if(!wants_sky)
1170 {
1171 // Sky ignored: flat artist ambient straight from the tint color. Kept independent
1172 // of sun elevation (sun_weight=1) and at unit exposition so the tint reads literally.
1173 sun_weight = 1.0f;
1174 exposition = 1.0f;
1175 }
1176 else if(!is_skybox && directional)
1177 {
1178 use_perez = true;
1179 compute_irradiance_perez_params(light_dir, skylight.get_turbidity(), dominant.perez);
1180 compute_perez_luminance(light_dir, dominant.perez.sky_luminance_rgb, dominant.perez.sun_luminance_rgb);
1181 irradiance_color = glm::mix(dominant.perez.sky_luminance_rgb, dominant.perez.sun_luminance_rgb, sun_weight);
1182 exposition = dominant.perez.exposition;
1183 }
1184 else if(!is_skybox)
1185 {
1186 // Flat ambient but the sky still contributes: collapse the Perez sky to one color.
1187 // Perez integral (sky + circumsolar + sun disc) yields ~4-5x zenith luminance.
1188 // mix(sky, sun, sun_weight * 0.25) empirically matches the directional result at same intensity.
1189 math::vec3 sky_luminance_rgb;
1190 math::vec3 sun_luminance_rgb;
1191 compute_perez_luminance(light_dir, sky_luminance_rgb, sun_luminance_rgb);
1192 irradiance_color = glm::mix(sky_luminance_rgb, sun_luminance_rgb, sun_weight * 0.25f);
1193 float sun_altitude = -light_dir.y;
1194 float altitude_factor = bx::lerp(0.6f, 1.0f, bx::clamp(bx::abs(sun_altitude), 0.0f, 1.0f));
1195 exposition = 0.1f * altitude_factor;
1196 }
1197 // skybox + wants_sky: irradiance_color stays white; the cubemap supplies the color in-shader.
1198
1199 float sky_brightness = skylight.get_sky_brightness();
1200 exposition *= sky_brightness;
1201
1202 const auto& tint = skylight.get_irradiance_tint();
1203 math::vec3 tint_vec = {tint.value.r, tint.value.g, tint.value.b};
1204 irradiance_color.x *= tint_vec.x;
1205 irradiance_color.y *= tint_vec.y;
1206 irradiance_color.z *= tint_vec.z;
1207
1208 if(irradiance_intensity > dominant.intensity)
1209 {
1210 dominant.intensity = irradiance_intensity;
1211 dominant.color = irradiance_color;
1212 dominant.tint = tint_vec;
1213 dominant.light_dir = light_dir;
1214 dominant.use_perez = use_perez;
1215 dominant.is_skybox = is_skybox;
1216 dominant.use_sky = wants_sky;
1217 dominant.directional = directional;
1218 dominant.sun_weight = sun_weight;
1219 dominant.exposition = exposition;
1220 dominant.sky_brightness = sky_brightness;
1221 dominant.cubemap = (is_skybox && wants_sky) ? skylight.get_cubemap() : asset_handle<gfx::texture>{};
1222 }
1223 });
1224
1225 gfx::render_pass irr_pass("Irradiance/Compute Pass");
1226 irradiance_compute_program_.program->begin();
1227 gfx::set_image(0, irradiance_tex->native_handle(), 0, bgfx::Access::Write);
1228
1229 int mode = 0;
1230 float ambient_vec[4];
1231 if(dominant.use_perez)
1232 {
1233 ambient_vec[0] = dominant.tint.x;
1234 ambient_vec[1] = dominant.tint.y;
1235 ambient_vec[2] = dominant.tint.z;
1236 ambient_vec[3] = dominant.intensity;
1237 }
1238 else
1239 {
1240 ambient_vec[0] = dominant.color.x;
1241 ambient_vec[1] = dominant.color.y;
1242 ambient_vec[2] = dominant.color.z;
1243 ambient_vec[3] = dominant.intensity;
1244 }
1245 auto cubemap_tex = dominant.cubemap.get();
1246 const bool use_cubemap = dominant.is_skybox && dominant.use_sky && cubemap_tex && cubemap_tex->info.cubeMap;
1247
1248 // Perez sky modes use physical luminance (exposition-scaled); cubemaps are typically
1249 // pre-baked in display range. Boost intensity for sky-derived non-cubemap modes so shadow
1250 // fill matches cubemap at the same user-facing intensity. The flat tint-only ambient is
1251 // already in display range, so it gets no boost.
1252 constexpr float ambient_intensity_boost = 2.0f;
1253 if(use_cubemap)
1254 ambient_vec[3] *= dominant.sky_brightness;
1255 else if(dominant.use_sky)
1256 ambient_vec[3] *= ambient_intensity_boost;
1257
1258 gfx::set_uniform(irradiance_compute_program_.u_irradiance_tint_intensity, ambient_vec);
1259
1260 // exposition: scale ambient to display range (matches atmospheric sky, ~0.1 at noon).
1261 float exp_val = dominant.exposition;
1262 float exp_vec[4] = {exp_val, 0.0f, 0.0f, 0.0f};
1263 gfx::set_uniform(irradiance_compute_program_.u_exposition, exp_vec);
1264
1265 if(dominant.intensity > 0.0f && dominant.use_perez)
1266 {
1267 mode = 1;
1268 gfx::set_uniform(irradiance_compute_program_.u_sun_direction, dominant.perez.sun_direction);
1269 gfx::set_uniform(irradiance_compute_program_.u_sun_luminance, dominant.perez.sun_luminance_rgb);
1270 gfx::set_uniform(irradiance_compute_program_.u_sky_luminance_xyz, dominant.perez.sky_luminance_xyz);
1271 gfx::set_uniform(irradiance_compute_program_.u_perez_coeff, &dominant.perez.perez_coeff[0][0], 5);
1272 }
1273 else if(use_cubemap)
1274 {
1275 // mode 2 = full directional SH, mode 3 = flat (cubemap averaged into L0 only).
1276 mode = dominant.directional ? 2 : 3;
1277 gfx::set_texture(irradiance_compute_program_.s_env, 1, cubemap_tex);
1278 }
1279 else if(!dominant.use_sky && dominant.directional)
1280 {
1281 // No sky contribution but directional requested: hemisphere gradient from the tint
1282 // (full tint up -> darkened tint down). Flat tint-only stays at mode 0.
1283 mode = 4;
1284 }
1285
1286 // x=mode, y=sun_weight (applied in shader for all modes)
1287 float mode_vec[4] = {float(mode), dominant.sun_weight, 0.0f, 0.0f};
1288 gfx::set_uniform(irradiance_compute_program_.u_mode, mode_vec);
1289
1290 bgfx::dispatch(irr_pass.id, irradiance_compute_program_.program->native_handle(), 1, 1, 1);
1291 irradiance_compute_program_.program->end();
1292
1293 result.irradiance_tex = irradiance_tex;
1294 result.global_color = dominant.color;
1295 result.global_intensity = dominant.intensity;
1296 }
1297 else
1298 {
1299 // Fallback when irradiance compute is unavailable: still create/bind texture (zeros)
1300 const auto& irradiance_tex = create_or_get_irradiance_texture(rview);
1301 result.irradiance_tex = irradiance_tex;
1302 scn.registry->view<transform_component, skylight_component, active_component>().each(
1303 [&](auto e, auto&& transform_comp_ref, auto&& skylight_comp_ref, auto&& active)
1304 {
1305 const auto& skylight = skylight_comp_ref;
1306 if(skylight.get_irradiance_quality() != skylight_component::irradiance_quality::flat)
1307 return;
1308 float irradiance_intensity = skylight.get_irradiance_intensity();
1309 if(irradiance_intensity <= 0.0f)
1310 return;
1311 const auto& world_transform = transform_comp_ref.get_transform_global();
1312 math::vec3 light_dir = world_transform.z_unit_axis();
1313 math::vec3 irradiance_color = {1.0f, 1.0f, 1.0f};
1314 // Only fold in the sky color when sky contribution is enabled; otherwise the
1315 // flat tint (applied below) is the whole ambient.
1316 if(skylight.get_irradiance_use_sky() && skylight.get_mode() != skylight_component::sky_mode::skybox)
1317 {
1318 math::vec3 sky_luminance_rgb;
1319 math::vec3 sun_luminance_rgb;
1320 compute_perez_luminance(light_dir, sky_luminance_rgb, sun_luminance_rgb);
1321 float sun_elevation = -light_dir.y;
1322 float x = math::clamp(sun_elevation / 0.35f, 0.0f, 1.0f);
1323 float sun_weight = x * x * (3.0f - 2.0f * x);
1324 irradiance_color = glm::mix(sky_luminance_rgb, sun_luminance_rgb, sun_weight);
1325 irradiance_intensity *= sun_weight;
1326 }
1327 const auto& tint = skylight.get_irradiance_tint();
1328 irradiance_color.x *= tint.value.r;
1329 irradiance_color.y *= tint.value.g;
1330 irradiance_color.z *= tint.value.b;
1331 if(irradiance_intensity > result.global_intensity)
1332 {
1333 result.global_intensity = irradiance_intensity;
1334 result.global_color = irradiance_color;
1335 }
1336 });
1337 }
1338
1339 return result;
1340}
1341
1342auto deferred::run_direct_lighting_pass(scene& scn,
1343 const camera& camera,
1344 gfx::render_view& rview,
1345 bool apply_shadows,
1347{
1348 APP_SCOPE_PERF("Rendering/Direct Lighting Pass");
1349
1350 const auto& view = camera.get_view();
1351 const auto& proj = camera.get_projection();
1352 const auto& camera_pos = camera.get_position();
1353
1354 const auto& gbuffer = rview.fbo_get("GBUFFER");
1355 const auto& lbuffer = rview.fbo_get("LBUFFER");
1356
1357 const auto buffer_size = lbuffer->get_size();
1358
1359 gfx::render_pass pass("Direct Lighting/Pass");
1360 pass.bind(lbuffer.get());
1361 pass.set_view_proj(view, proj);
1362 pass.clear(BGFX_CLEAR_COLOR, 0, 0.0f, 0);
1363
1364 scn.registry->view<transform_component, light_component, active_component>().each(
1365 [&](auto e, auto&& transform_comp_ref, auto&& light_comp_ref, auto&& active)
1366 {
1367 const auto& light = light_comp_ref.get_light();
1368 const auto& generator = light_comp_ref.get_shadowmap_generator();
1369 auto world_transform = transform_comp_ref.get_transform_global();
1370 world_transform.reset_scale();
1371 const auto& light_position = world_transform.get_position();
1372 const auto& light_direction = world_transform.z_unit_axis();
1373
1374 const auto& bounds = light_comp_ref.get_bounds_precise(light_direction);
1375 if(!camera.test_obb(bounds, world_transform))
1376 {
1377 return;
1378 }
1379
1380 irect32_t rect(0, 0, irect32_t::value_type(buffer_size.width), irect32_t::value_type(buffer_size.height));
1381 if(light_comp_ref
1382 .compute_projected_sphere_rect(rect, light_position, light_direction, camera_pos, view, proj) == 0)
1383 return;
1384
1385
1386 APP_SCOPE_PERF("Rendering/Direct Lighting Pass/Per Light");
1387
1388 bool has_shadows = light.casts_shadows && apply_shadows;
1389
1390 stats_.drawn_lights++;
1391 stats_.drawn_lights_casting_shadows += uint32_t(has_shadows);
1392
1393 const auto& lprogram = has_shadows ? get_light_program(light) : get_light_program_no_shadows(light);
1394
1395 lprogram.program->begin();
1396
1397 float contact_shadow_distance = light.contact_shadow.enabled
1399 : 0.0f;
1400
1401 float n_dot_l_low = light.contact_shadow.n_dot_l_fade_start;
1402 float n_dot_l_high = light.contact_shadow.n_dot_l_fade_end;
1403 if(n_dot_l_high < n_dot_l_low)
1404 {
1405 const float t = n_dot_l_low;
1406 n_dot_l_low = n_dot_l_high;
1407 n_dot_l_high = t;
1408 }
1409 const float contact_shadow_uniform[4] = {light.contact_shadow.thickness,
1410 n_dot_l_low,
1411 n_dot_l_high,
1413
1415 {
1416 float light_data[4] = {0.0f, 0.0f, 0.0f, contact_shadow_distance};
1417
1418 gfx::set_uniform(lprogram.u_light_direction, light_direction);
1419 gfx::set_uniform(lprogram.u_light_data, light_data);
1420 }
1422 {
1423 float light_data[4] = {light.point_data.range,
1425 0.0f,
1426 contact_shadow_distance};
1427
1428 gfx::set_uniform(lprogram.u_light_position, light_position);
1429 gfx::set_uniform(lprogram.u_light_data, light_data);
1430 }
1431
1433 {
1434 float light_data[4] = {light.spot_data.get_range(),
1435 math::cos(math::radians(light.spot_data.get_inner_angle() * 0.5f)),
1436 math::cos(math::radians(light.spot_data.get_outer_angle() * 0.5f)),
1437 contact_shadow_distance};
1438
1439 gfx::set_uniform(lprogram.u_light_direction, light_direction);
1440 gfx::set_uniform(lprogram.u_light_position, light_position);
1441 gfx::set_uniform(lprogram.u_light_data, light_data);
1442 }
1443
1444 gfx::set_uniform(lprogram.u_contact_shadow, contact_shadow_uniform);
1445
1446 float light_color_intensity[4] = {light.color.value.r,
1447 light.color.value.g,
1448 light.color.value.b,
1450
1451 gfx::set_uniform(lprogram.u_light_color_intensity, light_color_intensity);
1452
1453 gfx::set_uniform(lprogram.u_camera_position, camera_pos);
1454
1455 size_t i = 0;
1456 for(; i < gbuffer->get_attachment_count(); ++i)
1457 {
1458 gfx::set_texture(lprogram.s_tex[i], i, gbuffer->get_texture(i));
1459 }
1460 // Skip s_tex5 (RBUFFER) and s_tex6 (BRDF LUT) — not used by per-light direct shaders.
1461 // Shadow maps start at slot 7.
1462 i = 7;
1463
1464 if(has_shadows)
1465 {
1466 generator.submit_uniforms(i);
1467 }
1469 auto topology = gfx::clip_quad(1.0f);
1470 gfx::set_state(topology | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_BLEND_ADD);
1471 gfx::submit(pass.id, lprogram.program->native_handle());
1472 gfx::set_state(BGFX_STATE_DEFAULT);
1473
1474 lprogram.program->end();
1475 });
1476
1477 gfx::discard();
1478
1479 return lbuffer;
1480}
1481
1482auto deferred::run_indirect_lighting_pass(scene& scn,
1483 const camera& camera,
1484 gfx::render_view& rview,
1485 bool apply_reflection,
1487{
1488 APP_SCOPE_PERF("Rendering/Indirect Lighting Pass");
1489
1490 const auto& view = camera.get_view();
1491 const auto& proj = camera.get_projection();
1492 const auto& camera_pos = camera.get_position();
1493
1494 const auto& gbuffer = rview.fbo_get("GBUFFER");
1495 const auto& rbuffer = rview.fbo_safe_get("RBUFFER");
1496 const auto& lbuffer = rview.fbo_get("LBUFFER");
1497
1498 const auto irradiance_result = run_irradiance_pass(scn, rview);
1499
1500 gfx::render_pass pass("Indirect Lighting/Pass");
1501 pass.bind(lbuffer.get());
1502 pass.set_view_proj(view, proj);
1503
1504 const auto& iprogram = indirect_lighting_program_;
1505 iprogram.program->begin();
1506
1507 float light_data[4] = {irradiance_result.global_color.x, irradiance_result.global_color.y, irradiance_result.global_color.z, irradiance_result.global_intensity};
1508 gfx::set_uniform(iprogram.u_light_data, light_data);
1509 gfx::set_uniform(iprogram.u_camera_position, camera_pos);
1510
1511 size_t i = 0;
1512 for(; i < gbuffer->get_attachment_count(); ++i)
1513 {
1514 gfx::set_texture(iprogram.s_tex[i], i, gbuffer->get_texture(i));
1515 }
1516 gfx::set_texture(iprogram.s_tex[i], i, apply_reflection ? rbuffer->get_texture(0) : default_textures::get().black_texture());
1517 i++;
1518 gfx::set_texture(iprogram.s_tex[i], i, ibl_brdf_lut_.get());
1519 i++;
1520 gfx::set_texture(iprogram.s_irradiance, 7, irradiance_result.irradiance_tex ? irradiance_result.irradiance_tex : default_textures::get().black_texture());
1521
1522 const auto& ssil_tex = rview.tex_safe_get("SSIL");
1523 // Transparent (alpha 0) fallback when SSIL is disabled/absent so the shader's
1524 // mix(irradiance, ssil.rgb, ssil.a) collapses to the pure SH probe. The opaque-black
1525 // default (alpha 1) would instead force mix() to 0 and wipe out the ambient.
1526 gfx::set_texture(iprogram.s_ssil, 8, ssil_tex ? ssil_tex : default_textures::get().transparent_texture());
1527
1528
1529 auto topology = gfx::clip_quad(1.0f);
1530 gfx::set_state(topology | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_BLEND_ADD);
1531 gfx::submit(pass.id, iprogram.program->native_handle());
1532 gfx::set_state(BGFX_STATE_DEFAULT);
1533
1534 iprogram.program->end();
1535
1536 gfx::discard();
1537
1538 return lbuffer;
1539}
1540
1541void deferred::run_reflection_probe_pass(scene& scn, const camera& camera, gfx::render_view& rview, bool apply_probes, delta_t dt)
1542{
1543 if(!apply_probes)
1544 {
1545 return;
1546 }
1547
1548 APP_SCOPE_PERF("Rendering/Reflection Probe Pass");
1549
1550 const auto& view = camera.get_view();
1551 const auto& proj = camera.get_projection();
1552 const auto& camera_pos = camera.get_position();
1553
1554 const auto& viewport_size = camera.get_viewport_size();
1555 const auto& gbuffer = rview.fbo_get("GBUFFER");
1556 const auto& rbuffer = rview.fbo_get("RBUFFER");
1557
1558 const auto buffer_size = rbuffer->get_size();
1559
1560 gfx::render_pass pass("Reflections/Buffer Pass");
1561 pass.bind(rbuffer.get());
1562 pass.set_view_proj(view, proj);
1563 pass.clear(BGFX_CLEAR_COLOR, 0, 0.0f, 0);
1564
1565
1566 std::vector<entt::entity> sorted_probes;
1567
1568 // Collect all entities with the relevant components
1570 [&](auto e, auto&& transform_comp_ref, auto&& probe_comp_ref, auto&& active)
1571 {
1572 sorted_probes.emplace_back(e);
1573 });
1574
1575 // Sort the probes based on the method and max range
1576 std::sort(std::begin(sorted_probes),
1577 std::end(sorted_probes),
1578 [&](const auto& lhs, const auto& rhs)
1579 {
1580 const auto& lhs_comp = scn.registry->get<reflection_probe_component>(lhs);
1581 const auto& lhs_probe = lhs_comp.get_probe();
1582
1583 const auto& rhs_comp = scn.registry->get<reflection_probe_component>(rhs);
1584 const auto& rhs_probe = rhs_comp.get_probe();
1585
1586 // Environment probes should be last
1587 if(lhs_probe.method != rhs_probe.method)
1588 {
1589 return lhs_probe.method < rhs_probe.method; // Environment method is "greater"
1590 }
1591
1592 // If the reflection methods are the same, compare based on the maximum range
1593 return lhs_probe.get_max_range() > rhs_probe.get_max_range(); // Smaller ranges first
1594 });
1595
1596 // Render or process the sorted probes
1597 for(const auto& e : sorted_probes)
1598 {
1599 auto& transform_comp_ref = scn.registry->get<transform_component>(e);
1600 auto& probe_comp_ref = scn.registry->get<reflection_probe_component>(e);
1601
1602 const auto& probe = probe_comp_ref.get_probe();
1603 const auto& world_transform = transform_comp_ref.get_transform_global();
1604 const auto& probe_position = world_transform.get_position();
1605 const auto& probe_scale = world_transform.get_scale();
1606
1607 irect32_t rect(0, 0, irect32_t::value_type(buffer_size.width), irect32_t::value_type(buffer_size.height));
1608 if(probe_comp_ref.compute_projected_sphere_rect(rect, probe_position, probe_scale, camera_pos, view, proj) == 0)
1609 {
1610 continue;
1611 }
1612
1613 const auto& cubemap = probe_comp_ref.get_cubemap_prefiltered();
1614
1615 ref_probe_program* ref_probe_program = nullptr;
1616 float influence_radius = 0.0f;
1617 if(probe.type == probe_type::sphere && sphere_ref_probe_program_.program)
1618 {
1619 ref_probe_program = &sphere_ref_probe_program_;
1620 influence_radius =
1621 math::max(probe_scale.x, math::max(probe_scale.y, probe_scale.z)) * probe.sphere_data.range;
1622 }
1623
1624 if(probe.type == probe_type::box && box_ref_probe_program_.program)
1625 {
1626 math::transform t = world_transform;
1627 t.scale(probe.box_data.extents);
1628 auto u_inv_world = math::inverse(t).get_matrix();
1629 float data2[4] = {probe.box_data.extents.x,
1630 probe.box_data.extents.y,
1631 probe.box_data.extents.z,
1632 probe.box_data.transition_distance};
1633
1634 ref_probe_program = &box_ref_probe_program_;
1635
1636 gfx::set_uniform(box_ref_probe_program_.u_inv_world, u_inv_world);
1637 gfx::set_uniform(box_ref_probe_program_.u_data2, data2);
1638
1639 influence_radius = math::length(t.get_scale() + probe.box_data.transition_distance);
1640 }
1641
1642 if(ref_probe_program)
1643 {
1644 float mips = cubemap ? float(cubemap->info.numMips) : 1.0f;
1645 float data0[4] = {
1646 probe_position.x,
1647 probe_position.y,
1648 probe_position.z,
1649 influence_radius,
1650 };
1651
1652 const bool is_global_fallback = probe.method == reflect_method::environment;
1653 const float source_validity = 1.0f;
1654 float data1[4] = {mips, probe.intensity, is_global_fallback ? 1.0f : 0.0f, source_validity};
1655 float capture[4] = {probe_comp_ref.get_apply_prefilter() ? 1.0f : 0.0f, 0.0f, 0.0f, 0.0f};
1656
1657 gfx::set_uniform(ref_probe_program->u_data0, data0);
1658 gfx::set_uniform(ref_probe_program->u_data1, data1);
1659 gfx::set_uniform(ref_probe_program->u_capture, capture);
1660
1661 for(size_t i = 0; i < gbuffer->get_attachment_count(); ++i)
1662 {
1663 gfx::set_texture(ref_probe_program->s_tex[i], i, gbuffer->get_texture(i));
1664 }
1665
1666 gfx::set_texture(ref_probe_program->s_tex_cube, 5, cubemap);
1667
1669 auto topology = gfx::clip_quad(1.0f);
1670 gfx::set_state(topology | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_BLEND_ALPHA);
1671
1672 ref_probe_program->program->begin();
1673 gfx::submit(pass.id, ref_probe_program->program->native_handle());
1674 gfx::set_state(BGFX_STATE_DEFAULT);
1675 ref_probe_program->program->end();
1676 }
1677 }
1678
1679 gfx::discard();
1680}
1681
1682auto deferred::run_atmospherics_pass(gfx::frame_buffer::ptr input,
1683 scene& scn,
1684 const camera& camera,
1685 gfx::render_view& rview,
1687{
1688 APP_SCOPE_PERF("Rendering/Atmospheric Pass");
1689
1692
1693 bool found_sun = false;
1694
1696 scn.registry->view<transform_component, skylight_component, active_component>().each(
1697 [&](auto e, auto&& transform_comp_ref, auto&& light_comp_ref, auto&& active)
1698 {
1699 auto entity = scn.create_handle(e);
1700
1701 if(found_sun)
1702 {
1703 APPLOG_WARNING("[{}] More than one entity with this component. Others are ignored.", "Skylight");
1704 return;
1705 }
1706 const auto& cubemap = light_comp_ref.get_cubemap();
1707 auto cubemap_texture = cubemap.get();
1708 if(cubemap_texture)
1709 {
1710 if(cubemap_texture->info.cubeMap)
1711 {
1712 params_skybox.cubemap = cubemap;
1713 }
1714 }
1715
1716 mode = light_comp_ref.get_mode();
1717 found_sun = true;
1718 if(auto light_comp = entity.template try_get<light_component>())
1719 {
1720 const auto& light = light_comp->get_light();
1721
1723 {
1724 const auto& world_transform = transform_comp_ref.get_transform_global();
1725
1726 params_perez.light_direction = world_transform.z_unit_axis();
1727 params_perez.turbidity = light_comp_ref.get_turbidity();
1728 params_perez.cloud_mode = static_cast<int>(light_comp_ref.get_cloud_mode());
1729 params_perez.cloud_coverage = light_comp_ref.get_cloud_coverage();
1730 params_perez.cloud_base_altitude = light_comp_ref.get_cloud_base_altitude();
1731 params_perez.cloud_top_altitude = light_comp_ref.get_cloud_top_altitude();
1732 params_perez.cloud_density = light_comp_ref.get_cloud_density();
1733 params_perez.cloud_absorption = light_comp_ref.get_cloud_absorption();
1734 params_perez.cloud_light_absorption = light_comp_ref.get_cloud_light_absorption();
1735 params_perez.cloud_time = light_comp_ref.get_cloud_time();
1736 params_perez.sky_brightness = light_comp_ref.get_sky_brightness();
1737 params_perez.cloud_vol_uv_scale = light_comp_ref.get_cloud_vol_uv_scale();
1738 params_perez.cloud_vol_edge_width = light_comp_ref.get_cloud_vol_edge_width();
1739 params_perez.cloud_vol_shape_power = light_comp_ref.get_cloud_vol_shape_power();
1740 params_perez.cloud_vol_detail_erode = light_comp_ref.get_cloud_vol_detail_erode();
1741 params_perez.cloud_vol_macro_strength = light_comp_ref.get_cloud_vol_macro_strength();
1742 params_perez.cloud_vol_coarse_scale = light_comp_ref.get_cloud_vol_coarse_scale();
1743 params_perez.cloud_vol_base_mix = light_comp_ref.get_cloud_vol_base_mix();
1744 params_perez.cloud_vol_sun_intensity = light_comp_ref.get_cloud_vol_sun_intensity();
1745 }
1746 params_perez.irradiance_intensity = light_comp_ref.get_irradiance_intensity();
1747 }
1748 params_skybox.sky_brightness = light_comp_ref.get_sky_brightness();
1749 });
1750
1751 if(!found_sun)
1752 {
1753 return input;
1754 }
1755 const auto& viewport_size = camera.get_viewport_size();
1756
1757 auto c = camera;
1758 c.set_projection_mode(projection_mode::perspective);
1759
1760 auto lbuffer_depth = rview.fbo_get("LBUFFER_DEPTH");
1761
1762 switch(mode)
1763 {
1765 atmospheric_pass_skybox_.run(lbuffer_depth, c, rview, dt, params_skybox);
1766 break;
1767 default:
1768 atmospheric_pass_perez_.run(lbuffer_depth, c, rview, dt, params_perez);
1769 break;
1770 }
1771
1772 return input;
1773}
1774
1775void deferred::run_ssr_pass(const camera& camera,
1776 gfx::render_view& rview,
1777 const gfx::frame_buffer::ptr& previous_frame_source,
1778 const run_params& rparams)
1779{
1780 if(!reflection_screen_stack_enabled(rparams) || !rparams.fill_ssr_params)
1781 {
1782 ssr_pass_.release_resources(rview);
1783 return;
1784 }
1785
1786 ssr_pass::run_params ssr_params;
1787
1788 ssr_params.output = rview.fbo_get("RBUFFER");
1789 ssr_params.g_buffer = rview.fbo_get("GBUFFER");
1790
1791 ssr_params.previous_frame =
1792 previous_frame_source ? previous_frame_source->get_texture() : rview.fbo_get("LBUFFER")->get_texture();
1793
1794 ssr_params.cam = &camera;
1795
1796 if(rparams.fill_ssr_params)
1797 {
1798 rparams.fill_ssr_params(ssr_params);
1799 }
1800
1801 ssr_params.hiz_buffer = rview.tex_get("HIZBUFFER");
1802
1803 // BUG Cone tracing is not working properly, so we disable it for now.
1804 ssr_params.settings.fidelityfx.enable_cone_tracing = false;
1805
1806 ssr_pass_.run(rview, ssr_params);
1807}
1808
1809void deferred::run_ssil_pass(const camera& camera,
1810 gfx::render_view& rview,
1811 const run_params& rparams)
1812{
1813 if(!reflection_screen_stack_enabled(rparams) || !rparams.fill_ssil_params)
1814 {
1815 ssil_pass_.release_resources(rview);
1816 rview.tex_remove("SSIL");
1817 rview.tex_remove("PREV_SSIL");
1818 return;
1819 }
1820
1821 ssil_pass::run_params ssil_params;
1822 ssil_params.g_buffer = rview.fbo_get("GBUFFER");
1823 ssil_params.direct_lighting = rview.fbo_get("LBUFFER")->get_texture(0);
1824 ssil_params.prev_depth = rview.tex_safe_get("PREV_DEPTH");
1825 ssil_params.prev_ssil = rview.tex_safe_get("PREV_SSIL");
1826 // Last frame's environment SH (the pass that computes it runs later, in the indirect
1827 // lighting pass); used as the per-ray miss fallback so escaped rays integrate the
1828 // environment. Persists across frames in the render_view, so it is null only on frame 0.
1829 ssil_params.irradiance_sh = rview.tex_safe_get("IRRADIANCE_SH");
1830 ssil_params.cam = &camera;
1831
1832 rparams.fill_ssil_params(ssil_params);
1833
1834 ssil_params.hiz_buffer = rview.tex_get("HIZBUFFER");
1835
1836 auto result = ssil_pass_.run(rview, ssil_params);
1837 rview.tex_get_or_emplace("SSIL") = result;
1838
1839 if(ssil_params.settings.enable_multi_bounce && result)
1840 {
1841 // 1:1 blit of the SSIL output into PREV_SSIL. The output is full-res when the
1842 // trace runs reduced-res (the joint-bilateral upsample pass already reconstructed
1843 // it edge-aware), so feeding it back is safe -- the old failure mode was a NAIVE
1844 // full-viewport upscale that bled bright indirect across depth boundaries. Sizing
1845 // PREV_SSIL to the result keeps the blit a 1:1 copy regardless of trace resolution.
1846 const auto prev_sz = result->get_size();
1847 auto& prev_ssil = rview.tex_get_or_emplace("PREV_SSIL");
1848
1849 if(gfx::needs_recreate(prev_ssil, prev_sz))
1850 {
1851 prev_ssil.reset();
1852 prev_ssil = std::make_shared<gfx::texture>(static_cast<std::uint16_t>(prev_sz.width),
1853 static_cast<std::uint16_t>(prev_sz.height),
1854 false,
1855 1,
1856 gfx::texture_format::RGBA16F,
1857 BGFX_TEXTURE_BLIT_DST |
1858 BGFX_SAMPLER_U_CLAMP |
1859 BGFX_SAMPLER_V_CLAMP);
1860 }
1861 gfx::render_pass blit_pass("SSIL/Prev SSIL Blit Pass");
1863 prev_ssil->native_handle(), 0, 0,
1864 result->native_handle(), 0, 0);
1865 }
1866 else
1867 {
1868 rview.tex_remove("PREV_SSIL");
1869 }
1870
1871}
1872
1873auto deferred::run_taa_pass(const camera& camera,
1874 gfx::render_view& rview,
1876 const gfx::frame_buffer::ptr& output,
1877 const run_params& rparams) -> gfx::frame_buffer::ptr
1878{
1879 if(!rparams.fill_taa_params)
1880 {
1881 taa_pass_.release_resources(rview);
1882 return input;
1883 }
1884 const auto& gbuffer = rview.fbo_safe_get("GBUFFER");
1885 if(!input || !gbuffer)
1886 {
1887 return input;
1888 }
1890 p.input = input;
1891 p.output = nullptr;
1892 p.cam = &camera;
1893 p.g_buffer = gbuffer;
1894 rparams.fill_taa_params(p);
1895 return taa_pass_.run(rview, p);
1896}
1897
1898auto deferred::run_fxaa_pass(gfx::render_view& rview,
1900 const gfx::frame_buffer::ptr& output,
1901 const run_params& rparams) -> gfx::frame_buffer::ptr
1902{
1903 if(!rparams.fill_fxaa_params || rparams.fill_taa_params)
1904 {
1905 fxaa_pass_.release_resources(rview);
1906 return input;
1907 }
1908
1909 APP_SCOPE_PERF("Rendering/FXAA Pass");
1910
1911 fxaa_pass::run_params params;
1912 params.input = input;
1913 params.output = output;
1914
1915 rparams.fill_fxaa_params(params);
1916
1917 return fxaa_pass_.run(rview, params);
1918}
1919
1920void deferred::run_auto_exposure_pass(gfx::render_view& rview,
1922 const run_params& rparams,
1923 delta_t dt)
1924{
1925 if(!reflection_screen_stack_enabled(rparams) || !rparams.fill_auto_exposure_params)
1926 {
1927 auto_exposure_pass_.release_resources(rview);
1928 return;
1929 }
1931 params.input = input;
1932 params.delta_time = dt.count();
1933 rparams.fill_auto_exposure_params(params);
1934 auto_exposure_pass_.run(rview, params);
1935}
1936
1937auto deferred::run_bloom_pass(gfx::render_view& rview,
1939 const run_params& rparams) -> gfx::frame_buffer::ptr
1940{
1941 if(!reflection_screen_stack_enabled(rparams) || !rparams.fill_bloom_params || !rparams.fill_hdr_params)
1942 {
1943 bloom_pass_.release_resources(rview);
1944 return input;
1945 }
1947 params.input = input;
1948 rparams.fill_bloom_params(params);
1949
1950 if(rparams.fill_auto_exposure_params)
1951 {
1952 params.exposure_texture = auto_exposure_pass_.get_exposure_texture(rview);
1953 }
1954
1955 return bloom_pass_.run(rview, params);
1956}
1957
1958auto deferred::run_tonemapping_pass(gfx::render_view& rview,
1960 const gfx::frame_buffer::ptr& output,
1961 const run_params& rparams) -> gfx::frame_buffer::ptr
1962{
1963 if(!rparams.fill_hdr_params)
1964 {
1965 tonemapping_pass_.release_resources(rview);
1966 return input;
1967 }
1968 APP_SCOPE_PERF("Rendering/Tonemapping Pass");
1969
1971 params.input = input;
1972
1973 if(!rparams.fill_fxaa_params || rparams.fill_taa_params)
1974 {
1975 params.output = output;
1976 }
1977
1978 rparams.fill_hdr_params(params);
1979
1980 if(rparams.fill_auto_exposure_params)
1981 {
1982 params.exposure_texture = auto_exposure_pass_.get_exposure_texture(rview);
1983 }
1984
1985 return tonemapping_pass_.run(rview, params);
1986}
1987
1988void deferred::run_debug_visualization_pass(const camera& camera,
1989 gfx::render_view& rview,
1990 const gfx::frame_buffer::ptr& output)
1991{
1992 const auto& view = camera.get_view();
1993 const auto& proj = camera.get_projection();
1994 const auto& gbuffer = rview.fbo_get("GBUFFER");
1995 const auto& rbuffer = rview.fbo_safe_get("RBUFFER");
1996 const auto& irradiance_tex = create_or_get_irradiance_texture(rview);
1997
1998 gfx::render_pass pass("Debug/Visualization Pass");
1999 pass.bind(output.get());
2000 pass.set_view_proj(view, proj);
2001 // pass.clear(BGFX_CLEAR_COLOR, 0, 0.0f, 0);
2002
2003 const auto output_size = output->get_size();
2004
2005 debug_visualization_program_.program->begin();
2006
2007 float u_params[4] = {float(debug_pass_), 0.0f, 0.0f, 0.0f};
2008
2009 gfx::set_uniform(debug_visualization_program_.u_params, u_params);
2010
2011 size_t i = 0;
2012 for(; i < gbuffer->get_attachment_count(); ++i)
2013 {
2014 gfx::set_texture(debug_visualization_program_.s_tex[i], i, gbuffer->get_texture(i));
2015 }
2016 gfx::set_texture(debug_visualization_program_.s_tex[i], i, rbuffer);
2017 ++i;
2018 gfx::set_texture(debug_visualization_program_.s_tex[i], i, irradiance_tex);
2019 ++i;
2020 const auto& ssil_tex = rview.tex_safe_get("SSIL");
2021 if(ssil_tex)
2022 {
2023 gfx::set_texture(debug_visualization_program_.s_tex[i], i, ssil_tex);
2024 }
2025
2026 irect32_t rect(0, 0, irect32_t::value_type(output_size.width), irect32_t::value_type(output_size.height));
2028 auto topology = gfx::clip_quad(1.0f);
2029 gfx::set_state(topology | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A);
2030 gfx::submit(pass.id, debug_visualization_program_.program->native_handle());
2031 gfx::set_state(BGFX_STATE_DEFAULT);
2032 debug_visualization_program_.program->end();
2033
2034 gfx::discard();
2035}
2036
2037auto deferred::run_hiz_pass(const camera& camera,
2038 gfx::render_view& rview,
2039 const run_params& params,
2040 const usize32_t& viewport_size,
2041 delta_t dt) -> bool
2042{
2043 (void)dt;
2044 const bool want_hiz =
2045 reflection_screen_stack_enabled(params) && (params.fill_ssr_params || params.fill_ssil_params);
2046
2047 if(!want_hiz)
2048 {
2049 rview.tex_remove("HIZBUFFER");
2050 rview.tex_remove("PREV_DEPTH");
2051 return false;
2052 }
2053
2054 create_or_resize_hiz_buffer(rview, viewport_size);
2055
2056 APP_SCOPE_PERF("Rendering/SSR/Hi-Z Pass");
2057
2058 const auto& gbuffer = rview.fbo_get("GBUFFER");
2059 if(!gbuffer)
2060 {
2061 return false;
2062 }
2063
2065 hp.depth_buffer = gbuffer->get_texture(4);
2066 hp.output_hiz = rview.tex_get("HIZBUFFER");
2067 hp.cam = &camera;
2068
2069 hiz_pass_.run(rview, hp);
2070 return true;
2071}
2072
2073deferred::deferred()
2074{
2075 init(engine::context());
2076}
2077
2078deferred::~deferred()
2079{
2080 deinit(engine::context());
2081}
2082
2083auto deferred::init(rtti::context& ctx) -> bool
2084{
2085 auto& am = ctx.get_cached<asset_manager>();
2086
2087 auto load_program = [&](const std::string& vs, const std::string& fs)
2088 {
2089 auto vs_shader = am.get_asset<gfx::shader>("engine:/data/shaders/" + vs + ".sc");
2090 auto fs_shadfer = am.get_asset<gfx::shader>("engine:/data/shaders/" + fs + ".sc");
2091
2092 return std::make_unique<gpu_program>(vs_shader, fs_shadfer);
2093 };
2094
2095 geom_program_.program = load_program("deferred_geom/vs_deferred_geom", "deferred_geom/fs_deferred_geom");
2096 geom_program_.cache_uniforms();
2097
2098 geom_program_skinned_.program = load_program("deferred_geom/vs_deferred_geom_skinned", "deferred_geom/fs_deferred_geom");
2099 geom_program_skinned_.cache_uniforms();
2100
2101 geom_program_instanced_.program = load_program("deferred_geom/vs_deferred_geom_instanced", "deferred_geom/fs_deferred_geom");
2102 geom_program_instanced_.cache_uniforms();
2103
2104 sphere_ref_probe_program_.program = load_program("vs_clip_quad_ex", "reflection_probe/fs_sphere_reflection_probe");
2105 sphere_ref_probe_program_.cache_uniforms();
2106
2107 box_ref_probe_program_.program = load_program("vs_clip_quad_ex", "reflection_probe/fs_box_reflection_probe");
2108 box_ref_probe_program_.cache_uniforms();
2109
2110 indirect_lighting_program_.program = load_program("vs_clip_quad", "fs_deferred_indirect_light");
2111 indirect_lighting_program_.cache_uniforms();
2112
2113 auto cs_irradiance = am.get_asset<gfx::shader>("engine:/data/shaders/irradiance/cs_irradiance_sh.sc");
2114 if(cs_irradiance)
2115 {
2116 irradiance_compute_program_.program = std::make_unique<gpu_program>(cs_irradiance);
2117 irradiance_compute_program_.cache_uniforms();
2118 }
2119
2120 debug_visualization_program_.program = load_program("vs_clip_quad", "gbuffer/fs_gbuffer_visualize");
2121 debug_visualization_program_.cache_uniforms();
2122
2123 // Color lighting.
2124
2125 // clang-format off
2126 color_lighting_no_shadow_[uint8_t(light_type::spot)].program = load_program("vs_clip_quad", "fs_deferred_spot_light");
2127 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::hard)].program = load_program("vs_clip_quad", "fs_deferred_spot_light_hard");
2128 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::pcf) ].program = load_program("vs_clip_quad", "fs_deferred_spot_light_pcf");
2129 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::pcss) ].program = load_program("vs_clip_quad", "fs_deferred_spot_light_pcss");
2130 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::vsm) ].program = load_program("vs_clip_quad", "fs_deferred_spot_light_vsm");
2131 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::esm) ].program = load_program("vs_clip_quad", "fs_deferred_spot_light_esm");
2132
2133 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::hard)].program = load_program("vs_clip_quad", "fs_deferred_spot_light_hard_linear");
2134 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::pcf) ].program = load_program("vs_clip_quad", "fs_deferred_spot_light_pcf_linear");
2135 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::pcss) ].program = load_program("vs_clip_quad", "fs_deferred_spot_light_pcss_linear");
2136 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::vsm) ].program = load_program("vs_clip_quad", "fs_deferred_spot_light_vsm_linear");
2137 color_lighting_[uint8_t(light_type::spot)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::esm) ].program = load_program("vs_clip_quad", "fs_deferred_spot_light_esm_linear");
2138
2139 color_lighting_no_shadow_[uint8_t(light_type::point)].program = load_program("vs_clip_quad", "fs_deferred_point_light");
2140 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::hard)].program = load_program("vs_clip_quad", "fs_deferred_point_light_hard");
2141 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::pcf) ].program = load_program("vs_clip_quad", "fs_deferred_point_light_pcf");
2142 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::pcss) ].program = load_program("vs_clip_quad", "fs_deferred_point_light_pcss");
2143 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::vsm) ].program = load_program("vs_clip_quad", "fs_deferred_point_light_vsm");
2144 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::esm) ].program = load_program("vs_clip_quad", "fs_deferred_point_light_esm");
2145
2146 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::hard)].program = load_program("vs_clip_quad", "fs_deferred_point_light_hard_linear");
2147 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::pcf) ].program = load_program("vs_clip_quad", "fs_deferred_point_light_pcf_linear");
2148 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::pcss) ].program = load_program("vs_clip_quad", "fs_deferred_point_light_pcss_linear");
2149 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::vsm) ].program = load_program("vs_clip_quad", "fs_deferred_point_light_vsm_linear");
2150 color_lighting_[uint8_t(light_type::point)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::esm) ].program = load_program("vs_clip_quad", "fs_deferred_point_light_esm_linear");
2151
2152 color_lighting_no_shadow_[uint8_t(light_type::directional)].program = load_program("vs_clip_quad", "fs_deferred_directional_light");
2153 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::hard)].program = load_program("vs_clip_quad", "fs_deferred_directional_light_hard");
2154 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::pcf) ].program = load_program("vs_clip_quad", "fs_deferred_directional_light_pcf");
2155 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::pcss) ].program = load_program("vs_clip_quad", "fs_deferred_directional_light_pcss");
2156 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::vsm) ].program = load_program("vs_clip_quad", "fs_deferred_directional_light_vsm");
2157 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::invz)][uint8_t(sm_impl::esm) ].program = load_program("vs_clip_quad", "fs_deferred_directional_light_esm");
2158
2159 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::hard)].program = load_program("vs_clip_quad", "fs_deferred_directional_light_hard_linear");
2160 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::pcf) ].program = load_program("vs_clip_quad", "fs_deferred_directional_light_pcf_linear");
2161 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::pcss) ].program = load_program("vs_clip_quad", "fs_deferred_directional_light_pcss_linear");
2162 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::vsm) ].program = load_program("vs_clip_quad", "fs_deferred_directional_light_vsm_linear");
2163 color_lighting_[uint8_t(light_type::directional)][uint8_t(sm_depth::linear)][uint8_t(sm_impl::esm) ].program = load_program("vs_clip_quad", "fs_deferred_directional_light_esm_linear");
2164 // clang-format on
2165
2166 for(auto& byLightType : color_lighting_no_shadow_)
2167 {
2168 if(byLightType.program)
2169 {
2170 byLightType.cache_uniforms();
2171 }
2172 }
2173 for(auto& byLightType : color_lighting_)
2174 {
2175 for(auto& byDepthType : byLightType)
2176 {
2177 for(auto& bySmImpl : byDepthType)
2178 {
2179 if(bySmImpl.program)
2180 {
2181 bySmImpl.cache_uniforms();
2182 }
2183 }
2184 }
2185 }
2186
2187 ibl_brdf_lut_ = am.get_asset<gfx::texture>("engine:/data/textures/ibl_brdf_lut.png");
2188
2189 return pipeline::init(ctx);
2190}
2191
2192auto deferred::deinit(rtti::context& ctx) -> bool
2193{
2194 return true;
2195}
2196
2197
2198} // namespace rendering
2199} // namespace unravel
gfx::texture_format format
auto fbo_safe_get(const hpp::string_view &id) const -> const frame_buffer::ptr &
void tex_remove(const hpp::string_view &id)
auto fbo_get_or_emplace(const hpp::string_view &id) -> frame_buffer::ptr &
auto fbo_get(const hpp::string_view &id) const -> const frame_buffer::ptr &
auto tex_get(const hpp::string_view &id) const -> const texture::ptr &
auto tex_safe_get(const hpp::string_view &id) const -> const texture::ptr &
auto tex_get_or_emplace(const hpp::string_view &id) -> texture::ptr &
General purpose transformation class designed to maintain each component of the transformation separa...
Definition transform.hpp:27
Manages assets, including loading, unloading, and storage.
static auto is_static_mesh_batching_enabled() -> bool
auto run(gfx::render_view &rview, const run_params &params) -> gfx::frame_buffer::ptr
Executes the blit: copies params.input → params.output. Returns the actual output framebuffer.
Definition blit_pass.cpp:61
Class representing a camera. Contains functionality for manipulating and updating a camera....
Definition camera.h:62
auto get_far_clip() const -> float
Retrieves the distance from the camera to the far clip plane.
Definition camera.cpp:69
auto test_obb(const math::bbox &bounds, const math::transform &t) const -> bool
Tests if the specified OBB is within the frustum.
Definition camera.cpp:451
auto get_viewport_size() const -> const usize32_t &
Retrieves the size of the viewport.
Definition camera.cpp:37
auto get_projection() const -> const math::transform &
Retrieves the current projection matrix.
Definition camera.cpp:205
static auto get_face_camera(std::uint32_t face, const math::transform &transform) -> camera
Retrieves a camera for one of six cube faces.
Definition camera.cpp:872
auto get_view() const -> const math::transform &
Retrieves the current view matrix.
Definition camera.cpp:278
void set_far_clip(float distance)
Sets the far plane distance.
Definition camera.cpp:127
void set_aa_data(const usize32_t &viewport_size, std::uint32_t temporal_frame_index, std::uint32_t temporal_aa_samples, taa_jitter_mode jitter_mode=taa_jitter_mode::progressive_golden, float jitter_amplitude=1.0f, float jitter_temporal_phase_scale=1.0f)
Sets the current jitter value for temporal anti-aliasing.
Definition camera.cpp:767
void set_viewport_size(const usize32_t &viewport_size)
Sets the size of the viewport.
Definition camera.cpp:26
auto get_position() const -> const math::vec3 &
Retrieves the current position of the camera.
Definition camera.cpp:353
auto get_frustum() const -> const math::frustum &
Retrieves the current camera object frustum.
Definition camera.cpp:372
auto get_near_clip() const -> float
Retrieves the distance from the camera to the near clip plane.
Definition camera.cpp:64
static auto get() -> default_textures &
Class that contains core light data, used for rendering and other purposes.
Base class for materials used in rendering.
Definition material.h:44
Class that contains core data for meshes.
Structure describing a LOD group (set of meshes), LOD transitions, and their materials.
Definition model.h:275
auto is_valid() const -> bool
Checks if the model is valid.
Definition model.cpp:174
void submit(const math::mat4 &world_transform, const submesh_pose_mat4 &submesh_transforms, const pose_mat4 &bone_transforms, const std::vector< pose_mat4 > &skinning_transforms, unsigned int lod, const submit_callbacks &callbacks, const math::frustum *frustum=nullptr, const camera *view=nullptr, const model_submit_extras &extras={}) const
Submits the model for rendering.
Definition model.cpp:720
void submit_for_batching(batch_collector &collector, const math::mat4 &world_transform, const submesh_pose_mat4 &submesh_transforms, uint32_t lod_index, float lod_param=0.0f, const math::frustum *frustum=nullptr, const camera *view=nullptr, const model_submit_extras &extras={}) const
Collects this model into a batch collector for instanced rendering.
Definition model.cpp:1254
Class for physically-based rendering (PBR) materials.
Definition material.h:112
auto get_surface_data2() const -> math::vec4
Gets additional surface data for the material.
Definition material.h:287
auto get_dither_threshold() const -> const math::vec2 &
Gets the dither threshold of the material.
Definition material.h:327
auto get_emissive_map() const -> const asset_handle< gfx::texture > &
Gets the emissive map of the material.
Definition material.h:435
auto get_emissive_intensity() const -> float
Definition material.h:172
auto get_subsurface_color() const -> const math::color &
Gets the subsurface color of the material.
Definition material.h:140
auto get_color_map() const -> const asset_handle< gfx::texture > &
Gets the color map of the material.
Definition material.h:345
auto get_surface_data() const -> const math::vec4 &
Gets the surface data of the material.
Definition material.h:278
auto get_roughness_map() const -> const asset_handle< gfx::texture > &
Gets the roughness map of the material.
Definition material.h:381
auto get_metalness_map() const -> const asset_handle< gfx::texture > &
Gets the metalness map of the material.
Definition material.h:399
auto get_tiling() const -> const math::vec2 &
Gets the tiling factor of the material.
Definition material.h:309
auto get_normal_map() const -> const asset_handle< gfx::texture > &
Gets the normal map of the material.
Definition material.h:363
auto get_base_color() const -> const math::color &
Gets the base color of the material.
Definition material.h:122
auto get_emissive_color() const -> const math::color &
Gets the emissive color of the material.
Definition material.h:158
auto get_ao_map() const -> const asset_handle< gfx::texture > &
Gets the ambient occlusion map of the material.
Definition material.h:417
auto run(gfx::render_view &rview, const run_params &params) -> gfx::texture::ptr
Execute prefilter. Returns the filtered cubemap (output_cube or created internally).
Class that contains core reflection probe data, used for rendering and other purposes.
auto get_probe() const -> const reflection_probe &
Gets the reflection probe object.
void run_pipeline_impl(const gfx::frame_buffer::ptr &output, scene &scn, const camera &camera, gfx::render_view &rview, delta_t dt, const run_params &params, layer_mask render_mask=layer_mask{layer_reserved::everything_layer})
Definition pipeline.cpp:651
void set_debug_pass(int pass) override
Definition pipeline.cpp:646
void build_shadows(scene &scn, const camera &camera, delta_t dt, visibility_flags query=visibility_query::not_specified, layer_mask render_mask=layer_mask{layer_reserved::everything_layer})
Definition pipeline.cpp:539
void build_reflections(scene &scn, const camera &camera, delta_t dt)
Definition pipeline.cpp:424
auto run_pipeline(scene &scn, const camera &camera, gfx::render_view &rview, delta_t dt, const run_params &params, layer_mask render_mask=layer_mask{layer_reserved::everything_layer}) -> gfx::frame_buffer::ptr override
Renders the entire scene from the camera's perspective.
Definition pipeline.cpp:615
@ not_specified
No specific visibility query.
Definition pipeline.h:105
@ is_dirty
Query for dirty entities.
Definition pipeline.h:106
@ is_static
Query for static entities.
Definition pipeline.h:107
@ is_shadow_caster
Query for shadow casting entities.
Definition pipeline.h:108
prefilter_pass prefilter_pass_
Definition pipeline.h:239
@ reflection_probe_capture
Single cubemap face capture for reflection probe baking.
@ camera
Main camera / viewport rendering (post-processing, UI, probe build, etc.).
virtual void gather_visible_models(scene &scn, const camera *cam, visibility_flags query, const layer_mask &render_mask, delta_t dt, const std::function< void(entt::handle entity, const lod_data &lod_data)> &lod_data_callback, const camera *lod_reference_cam=nullptr)
Gathers visible models from the scene based on the given query.
Definition pipeline.cpp:110
uint32_t visibility_flags
Type alias for visibility flags.
Definition pipeline.h:111
virtual auto create_run_params(entt::handle camera_ent) const -> rendering::pipeline::run_params
Definition pipeline.cpp:231
Class that contains sky light data.
@ flat
Flat ambient: only the constant SH band (L0) is written, same color for every normal.
@ directional
Directional ambient: full L0-L2 spherical harmonics, varies with the surface normal.
sky_mode
Enumeration for sky modes.
Component that handles transformations (position, rotation, scale, etc.) in the ACE framework.
auto get_transform_global() const noexcept -> const math::transform &
Gets the global transform.
float x
size< std::uint32_t > usize32_t
std::chrono::duration< float > delta_t
math::vec3 normal
Definition defaults.cpp:53
uint16_t view
#define APPLOG_WARNING(...)
Definition logging.h:19
Definition cache.hpp:11
void submit(view_id _id, program_handle _handle, int32_t _depth, bool _preserveState)
uint16_t set_scissor(uint16_t _x, uint16_t _y, uint16_t _width, uint16_t _height)
Definition graphics.cpp:952
bgfx::TextureFormat::Enum texture_format
Definition format.h:10
void set_state(uint64_t _state, uint32_t _rgba)
Definition graphics.cpp:937
void update_texture_2d(texture_handle _handle, uint16_t _layer, uint8_t _mip, uint16_t _x, uint16_t _y, uint16_t _width, uint16_t _height, const memory_view *_mem, uint16_t _pitch)
Definition graphics.cpp:719
auto needs_recreate(const gfx::frame_buffer::ptr &fbo, const usize32_t &size) -> bool
void blit(view_id _id, texture_handle _dst, uint16_t _dstX, uint16_t _dstY, texture_handle _src, uint16_t _srcX, uint16_t _srcY, uint16_t _width, uint16_t _height)
void discard(uint8_t _flags)
const memory_view * copy(const void *_data, uint32_t _size)
Definition graphics.cpp:460
void set_image(uint8_t _stage, texture_handle _handle, uint8_t _mip, access _access, texture_format _format)
void set_uniform(uniform_handle _handle, const void *_value, uint16_t _num)
Definition graphics.cpp:977
bgfx::Memory memory_view
Definition graphics.h:23
uint32_t get_render_frame()
auto clip_quad(float depth, float width, float height) -> uint64_t
void set_texture(uint8_t _stage, uniform_handle _sampler, texture_handle _handle, uint32_t _flags)
auto inverse(transform_t< T, Q > const &t) noexcept -> transform_t< T, Q >
hpp::small_vector< visibility_data > visibility_set_models_t
Definition pipeline.h:52
hpp::small_vector< shadow_visibility_data > shadow_map_models_t
Definition shadow.h:624
@ everything_layer
Definition layer_mask.h:16
@ sphere
Sphere type reflection probe.
@ box
Box type reflection probe.
@ environment
Environment reflection method.
void compute_perez_luminance(const math::vec3 &light_direction, math::vec3 &out_sky_luminance_rgb, math::vec3 &out_sun_luminance_rgb)
Computes Perez sky and sun luminance from light direction (time-of-day). Uses the same tables as the ...
void compute_irradiance_perez_params(const math::vec3 &light_direction, float turbidity, irradiance_perez_params &out)
std::vector< math::color > color
#define APP_SCOPE_PERF(name_literal)
Create a scoped performance timer that records to the timeline profiler. Only accepts string literals...
Definition profiler.h:675
entt::handle entity
Thread-safe handle to an asset.
auto is() const -> bool
static void pop_scope()
void set_view_proj(const float *v, const float *p)
gfx::view_id id
void clear(uint16_t _flags, uint32_t _rgba=0x000000ff, float _depth=1.0f, uint8_t _stencil=0) const
static void push_scope(const char *name)
void bind(const frame_buffer *fb=nullptr) const
Storage for box vector values and wraps up common functionality.
Definition bbox.h:21
bbox & mul(const transform &t)
Transforms an axis aligned bounding box by the specified matrix.
Definition bbox.cpp:876
vec4 value
Definition color.h:80
T width() const
std::int32_t value_type
T height() const
T width
Definition basetypes.hpp:55
T height
Definition basetypes.hpp:56
float cloud_vol_uv_scale
Volumetric cloud u_cloudParams3: uv scale, edge width, shape power, detail erode.
float cloud_density
Cloud density/opacity multiplier.
float irradiance_intensity
Irradiance intensity. Scales cloud ambient (sky-scattered light into clouds).
float cloud_absorption
Beer-Lambert extinction coefficient [0.01-0.5].
float cloud_light_absorption
Light absorption / self-shadow strength [0.01-0.5].
float cloud_vol_macro_strength
Volumetric cloud u_cloudParams4: macro strength, coarse scale, base mix, sun intensity.
float cloud_top_altitude
Cloud top altitude in world units. Vol: slab top. Flat: ignored.
float cloud_coverage
Cloud coverage [0.0 = clear sky, 1.0 = overcast]. Controls the density threshold.
int cloud_mode
Cloud mode: 0=none, 1=flat, 2=volumetric.
float cloud_time
Accumulated elapsed time (seconds) for cloud animation.
float sky_brightness
Sky brightness multiplier (1.0 = neutral). Affects visible sky and irradiance.
float cloud_base_altitude
Cloud base altitude in world units. Vol: slab bottom. Flat: projection height.
float sky_brightness
Sky brightness multiplier (1.0 = neutral). Affects visible sky and irradiance.
gfx::frame_buffer::ptr input
Source framebuffer (must have a color texture).
Definition blit_pass.h:18
gfx::frame_buffer::ptr output
Optional destination framebuffer. If null, will be created to match input.
Definition blit_pass.h:19
gfx::frame_buffer::ptr input
Definition bloom_pass.h:54
gfx::texture::ptr exposure_texture
Definition bloom_pass.h:57
static auto context() -> rtti::context &
Definition engine.cpp:111
gfx::frame_buffer::ptr output
Definition fxaa_pass.h:18
gfx::frame_buffer::ptr input
Definition fxaa_pass.h:17
gfx::texture::ptr depth_buffer
Source depth buffer.
Definition hiz_pass.h:19
const camera * cam
Camera for near/far plane information.
Definition hiz_pass.h:21
gfx::texture::ptr output_hiz
Output Hi-Z texture (must be R32F or R16F format with mips)
Definition hiz_pass.h:20
static auto packed_size() -> size_t
float n_dot_l_fade_start
N·L smoothstep low edge: below this, contact shadow fades out (surface faces away from light).
Definition light.h:355
bool enabled
Whether contact shadows are enabled for this light.
Definition light.h:347
float n_dot_l_fade_end
N·L smoothstep high edge: at or above this, contact shadow has full strength.
Definition light.h:357
float range
The range of the point light.
Definition light.h:162
float exponent_falloff
The exponent falloff for the point light.
Definition light.h:164
float get_range() const
Gets the range of the spot light.
Definition light.h:106
float get_outer_angle() const
Gets the outer angle of the spot light.
Definition light.h:121
float get_inner_angle() const
Gets the inner angle of the spot light.
Definition light.h:136
Struct representing a light.
Definition light.h:87
bool casts_shadows
Whether the light casts shadows.
Definition light.h:212
float intensity
The intensity of the light.
Definition light.h:209
struct unravel::light::contact_shadow_params contact_shadow
light_type type
The type of the light.
Definition light.h:89
math::color color
The color of the light.
Definition light.h:207
point point_data
Data specific to point lights.
Definition light.h:203
spot spot_data
Data specific to spot lights.
Definition light.h:201
Contains level of detail (LOD) data for an entity per view. Uses distance-based hysteresis for stable...
Definition model.h:34
float current_time
Current time in the LOD transition.
Definition model.h:37
std::uint32_t target_lod_index
Target LOD index to transition to.
Definition model.h:36
float transition_time
Total time for the LOD transition.
Definition model.h:38
std::uint32_t current_lod_index
Current LOD index being rendered.
Definition model.h:35
Parameters for the submit callbacks.
Definition model.h:551
bool skinned
Indicates if the model is skinned.
Definition model.h:553
Callbacks for submitting the model for rendering.
Definition model.h:545
std::function< void(const params &info, const material &)> setup_params_per_submesh
Callback for setting up per submesh.
Definition model.h:562
std::function< void(const params &info)> setup_begin
Callback for setup begin.
Definition model.h:558
std::function< void(const params &info)> setup_params_per_instance
Callback for setting up per instance.
Definition model.h:560
std::function< void(const params &info)> setup_end
Callback for setup end.
Definition model.h:564
std::array< gfx::texture::ptr, 6 > input_faces
gfx::texture::ptr output_cube_prefiltered
Optional destination cubemap (will be created if null).
gfx::texture::ptr output_cube
Optional destination cubemap (will be created if null).
bool apply_prefilter
If false, copies mips from input to output.
std::function< void(assao_pass::run_params &params)> fill_assao_params
Definition pipeline.h:135
std::function< void(auto_exposure_pass::run_params &params)> fill_auto_exposure_params
Definition pipeline.h:136
std::function< void(taa_pass::run_params &params)> fill_taa_params
Definition pipeline.h:140
pipeline_flags pflags
Deferred pipeline only: bitmask of enabled passes (deferred::pipeline_steps).
Definition pipeline.h:133
std::function< void(ssr_pass::run_params &params)> fill_ssr_params
Definition pipeline.h:144
std::function< void(bloom_pass::run_params &params)> fill_bloom_params
Definition pipeline.h:137
std::function< void(tonemapping_pass::run_params &params)> fill_hdr_params
Definition pipeline.h:138
std::function< void(ssil_pass::run_params &params)> fill_ssil_params
Definition pipeline.h:145
std::function< void(camera &, const usize32_t &viewport_size)> apply_taa_params
Definition pipeline.h:143
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:70
std::unique_ptr< entt::registry > registry
The registry that manages all entities in the scene.
Definition scene.h:187
auto create_handle(entt::entity e) -> entt::handle
Creates an entity in the scene.
Definition scene.cpp:428
gfx::texture::ptr prev_depth
Definition ssil_pass.h:103
gfx::texture::ptr irradiance_sh
Definition ssil_pass.h:108
gfx::texture::ptr prev_ssil
Definition ssil_pass.h:104
gfx::frame_buffer::ptr g_buffer
Definition ssil_pass.h:100
gfx::texture::ptr hiz_buffer
Definition ssil_pass.h:101
gfx::texture::ptr direct_lighting
Definition ssil_pass.h:102
bool enable_cone_tracing
Enable cone tracing for glossy reflections.
Definition ssr_pass.h:54
gfx::frame_buffer::ptr output
Optional output buffer.
Definition ssr_pass.h:83
gfx::texture::ptr previous_frame
Previous frame color for reflection sampling.
Definition ssr_pass.h:86
gfx::frame_buffer::ptr g_buffer
G-buffer containing normals.
Definition ssr_pass.h:84
gfx::texture::ptr hiz_buffer
Hi-Z buffer texture.
Definition ssr_pass.h:85
fidelityfx_ssr_settings fidelityfx
FidelityFX SSR settings.
Definition ssr_pass.h:78
gfx::uniform_handle handle
Definition uniform.cpp:9