From ea5e1fef2a5f603c2ea80b3be5206bd3f87ddd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Tue, 14 May 2024 16:36:12 +0200 Subject: [PATCH] EEVEE-Next: Sunlight Extraction Sun extraction convert part of light comming from the world to a sun light which increases the quality of the rendering. The goal of this feature is to workaround the limitation of the storage techniques used for environment lighting inside EEVEE. This first implementation works by clamping the world lighting and summing the excess lighting and (and its incomming directions) to deduce the sun position. All the lighting is then transfered into this light power. The sun angle is computed based on the directionnality of the excess lighting, the more divergent the excess lighting is, the bigger the angle. This has a few benefits: - It's stable and behave well under animation. This is because we average a lot of data. - It's fast as it can be done inside the remap shader in one pass. - It requires only one parameter, the clamp threshold. However, it has some issue: - It modifies the lighting as we change the incomming direction for excess lighting away from the chosen sun direction. This could be fixed by masking only lighting around the chosen sun direction (requires 2 passes, slower). - Given that this only average the direction, it behaves poorly if there two opposite bright light sources (it puts the sun in the middle). This could be fixed by extracting more suns, but that becomes more complex and requires even more passes. - It looks bad if the summed lighting is not supposed to be a perfect disk in specular reflections or if the sources are too divergent as the disk is too big and the approximation fails short. This could be mitigated by adding an upper bound to the sun radius. For now we workaround this issue by exposing the sun angle parameter in the UI. A more precise algorithm can be implemented in the future to avoid having to deal with these limitations. A possibility is to use importance sampling to randomize sun position. But that would be only for final render. Pull Request: https://projects.blender.org/blender/blender/pulls/121455 --- scripts/startup/bl_ui/properties_render.py | 9 +- scripts/startup/bl_ui/properties_world.py | 55 +++- .../blender/blenkernel/BKE_blender_version.h | 2 +- .../blenloader/intern/versioning_400.cc | 11 +- source/blender/draw/CMakeLists.txt | 2 + .../draw/engines/eevee_next/eevee_instance.cc | 4 +- .../draw/engines/eevee_next/eevee_light.cc | 60 +++- .../draw/engines/eevee_next/eevee_light.hh | 11 +- .../draw/engines/eevee_next/eevee_lookdev.hh | 9 +- .../eevee_next/eevee_reflection_probes.cc | 14 + .../eevee_next/eevee_reflection_probes.hh | 6 + .../draw/engines/eevee_next/eevee_sampling.cc | 2 +- .../draw/engines/eevee_next/eevee_shader.cc | 4 + .../draw/engines/eevee_next/eevee_shader.hh | 4 +- .../engines/eevee_next/eevee_shader_shared.hh | 9 +- .../draw/engines/eevee_next/eevee_world.cc | 21 +- .../draw/engines/eevee_next/eevee_world.hh | 26 ++ .../eevee_light_culling_select_comp.glsl | 16 + .../eevee_light_shadow_setup_comp.glsl | 273 ++++++++++++++++++ ...evee_reflection_probe_irradiance_comp.glsl | 18 -- .../eevee_reflection_probe_remap_comp.glsl | 51 +++- .../eevee_reflection_probe_sunlight_comp.glsl | 90 ++++++ .../shaders/infos/eevee_light_culling_info.hh | 10 + .../infos/eevee_reflection_probe_info.hh | 11 + source/blender/makesdna/DNA_scene_defaults.h | 1 - source/blender/makesdna/DNA_scene_types.h | 2 - source/blender/makesdna/DNA_world_defaults.h | 5 + source/blender/makesdna/DNA_world_types.h | 11 + source/blender/makesrna/intern/rna_scene.cc | 16 - source/blender/makesrna/intern/rna_world.cc | 33 +++ 30 files changed, 705 insertions(+), 81 deletions(-) create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_light_shadow_setup_comp.glsl create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_sunlight_comp.glsl diff --git a/scripts/startup/bl_ui/properties_render.py b/scripts/startup/bl_ui/properties_render.py index b8fbc506ef9..1811f490147 100644 --- a/scripts/startup/bl_ui/properties_render.py +++ b/scripts/startup/bl_ui/properties_render.py @@ -702,14 +702,7 @@ class RENDER_PT_eevee_next_clamping(RenderButtonsPanel, Panel): return (context.engine in cls.COMPAT_ENGINES) def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False - scene = context.scene - props = scene.eevee - - col = layout.column() - col.prop(props, "clamp_world", text="World") + pass class RENDER_PT_eevee_next_clamping_surface(RenderButtonsPanel, Panel): diff --git a/scripts/startup/bl_ui/properties_world.py b/scripts/startup/bl_ui/properties_world.py index a9a1f3fb0aa..6878f89a41c 100644 --- a/scripts/startup/bl_ui/properties_world.py +++ b/scripts/startup/bl_ui/properties_world.py @@ -151,9 +151,8 @@ class EEVEE_WORLD_PT_volume(WorldButtonsPanel, Panel): layout.label(text="No output node") -class EEVEE_WORLD_PT_probe(WorldButtonsPanel, Panel): - bl_label = "Light Probe" - bl_translation_context = i18n_contexts.id_id +class EEVEE_WORLD_PT_settings(WorldButtonsPanel, Panel): + bl_label = "Settings" bl_options = {'DEFAULT_CLOSED'} COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} @@ -163,13 +162,56 @@ class EEVEE_WORLD_PT_probe(WorldButtonsPanel, Panel): world = context.world return world and (engine in cls.COMPAT_ENGINES) + def draw(self, context): + pass + + +class EEVEE_WORLD_PT_lightprobe(WorldButtonsPanel, Panel): + bl_label = "Light Probe" + bl_parent_id = "EEVEE_WORLD_PT_settings" + COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} + def draw(self, context): layout = self.layout world = context.world layout.use_property_split = True - layout.prop(world, "probe_resolution") + layout.prop(world, "probe_resolution", text="Resolution") + + +class EEVEE_WORLD_PT_sun(WorldButtonsPanel, Panel): + bl_label = "Sun" + bl_parent_id = "EEVEE_WORLD_PT_settings" + COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} + + def draw(self, context): + layout = self.layout + + world = context.world + + layout.use_property_split = True + layout.prop(world, "sun_threshold", text="Threshold") + layout.prop(world, "sun_angle", text="Angle") + + +class EEVEE_WORLD_PT_sun_shadow(WorldButtonsPanel, Panel): + bl_label = "Shadow" + bl_parent_id = "EEVEE_WORLD_PT_sun" + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} + + def draw_header(self, context): + world = context.world + self.layout.prop(world, "use_sun_shadow", text="") + + def draw(self, context): + layout = self.layout + + world = context.world + + layout.use_property_split = True + layout.prop(world, "sun_shadow_maximum_resolution", text="Resolution Limit") class WORLD_PT_viewport_display(WorldButtonsPanel, Panel): @@ -193,7 +235,10 @@ classes = ( EEVEE_WORLD_PT_surface, EEVEE_WORLD_PT_volume, EEVEE_WORLD_PT_mist, - EEVEE_WORLD_PT_probe, + EEVEE_WORLD_PT_settings, + EEVEE_WORLD_PT_lightprobe, + EEVEE_WORLD_PT_sun, + EEVEE_WORLD_PT_sun_shadow, WORLD_PT_viewport_display, WORLD_PT_custom_props, ) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index b7e5982a91e..856a3b9089b 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -29,7 +29,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 36 +#define BLENDER_FILE_SUBVERSION 37 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 3202dd3eeea..e2cc2dd618f 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -3383,7 +3383,6 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) SceneEEVEE default_scene_eevee = *DNA_struct_default_get(SceneEEVEE); LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { scene->eevee.shadow_resolution_scale = default_scene_eevee.shadow_resolution_scale; - scene->eevee.clamp_world = 10.0f; } } } @@ -3548,6 +3547,16 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 37)) { + const World *default_world = DNA_struct_default_get(World); + LISTBASE_FOREACH (World *, world, &bmain->worlds) { + world->sun_threshold = default_world->sun_threshold; + world->sun_angle = default_world->sun_angle; + world->sun_shadow_maximum_resolution = default_world->sun_shadow_maximum_resolution; + world->flag |= WO_USE_SUN_SHADOW; + } + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index b73cecf0d6b..7a05bf5f30c 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -521,6 +521,7 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl + engines/eevee_next/shaders/eevee_light_shadow_setup_comp.glsl engines/eevee_next/shaders/eevee_light_eval_lib.glsl engines/eevee_next/shaders/eevee_light_iter_lib.glsl engines/eevee_next/shaders/eevee_light_lib.glsl @@ -564,6 +565,7 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_reflection_probe_mapping_lib.glsl engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl + engines/eevee_next/shaders/eevee_reflection_probe_sunlight_comp.glsl engines/eevee_next/shaders/eevee_renderpass_lib.glsl engines/eevee_next/shaders/eevee_sampling_lib.glsl engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index c88ecb52a85..d94b91dfa41 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -175,6 +175,9 @@ void Instance::view_update() void Instance::begin_sync() { + /* Needs to be first for sun light parameters. */ + world.sync(); + materials.begin_sync(); velocity.begin_sync(); /* NOTE: Also syncs camera. */ lights.begin_sync(); @@ -192,7 +195,6 @@ void Instance::begin_sync() motion_blur.sync(); hiz_buffer.sync(); main_view.sync(); - world.sync(); film.sync(); render_buffers.sync(); ambient_occlusion.sync(); diff --git a/source/blender/draw/engines/eevee_next/eevee_light.cc b/source/blender/draw/engines/eevee_next/eevee_light.cc index 2e17b2a15ba..3c3739ad8e0 100644 --- a/source/blender/draw/engines/eevee_next/eevee_light.cc +++ b/source/blender/draw/engines/eevee_next/eevee_light.cc @@ -15,6 +15,7 @@ #include "eevee_light.hh" #include "BLI_math_rotation.h" +#include "DNA_defaults.h" namespace blender::eevee { @@ -45,12 +46,14 @@ static eLightType to_light_type(short blender_light_type, /** \name Light Object * \{ */ -void Light::sync(ShadowModule &shadows, const Object *ob, float threshold) +void Light::sync(ShadowModule &shadows, + float4x4 object_to_world, + char visibility_flag, + const ::Light *la, + float threshold) { using namespace blender::math; - const ::Light *la = (const ::Light *)ob->data; - eLightType new_type = to_light_type(la->type, la->area_shape, la->mode & LA_USE_SOFT_FALLOFF); if (assign_if_different(this->type, new_type)) { shadow_discard_safe(shadows); @@ -59,7 +62,6 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold) this->color = float3(&la->r) * la->energy; float3 scale; - float4x4 object_to_world = ob->object_to_world(); object_to_world.view<3, 3>() = normalize_and_get_size(object_to_world.view<3, 3>(), scale); /* Make sure we have consistent handedness (in case of negatively scaled Z axis). */ @@ -72,10 +74,10 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold) shape_parameters_set(la, scale, threshold); - const bool diffuse_visibility = (ob->visibility_flag & OB_HIDE_DIFFUSE) == 0; - const bool glossy_visibility = (ob->visibility_flag & OB_HIDE_GLOSSY) == 0; - const bool transmission_visibility = (ob->visibility_flag & OB_HIDE_TRANSMISSION) == 0; - const bool volume_visibility = (ob->visibility_flag & OB_HIDE_VOLUME_SCATTER) == 0; + const bool diffuse_visibility = (visibility_flag & OB_HIDE_DIFFUSE) == 0; + const bool glossy_visibility = (visibility_flag & OB_HIDE_GLOSSY) == 0; + const bool transmission_visibility = (visibility_flag & OB_HIDE_TRANSMISSION) == 0; + const bool volume_visibility = (visibility_flag & OB_HIDE_VOLUME_SCATTER) == 0; float shape_power = shape_radiance_get(); float point_power = point_radiance_get(); @@ -325,16 +327,37 @@ void LightModule::begin_sync() sun_lights_len_ = 0; local_lights_len_ = 0; + + if (use_sun_lights_ && inst_.world.sun_threshold() > 0.0) { + /* Create a placeholder light to be fed by the GPU after sunlight extraction. + * Sunlight is disabled if power is zero. */ + ::Light la = blender::dna::shallow_copy( + *(const ::Light *)DNA_default_table[SDNA_TYPE_FROM_STRUCT(Light)]); + la.type = LA_SUN; + /* Set on the GPU. */ + la.r = la.g = la.b = -1.0f; /* Tag as world sun light. */ + la.energy = 1.0f; + la.sun_angle = inst_.world.sun_angle(); + la.shadow_maximum_resolution = inst_.world.sun_shadow_max_resolution(); + SET_FLAG_FROM_TEST(la.mode, inst_.world.use_sun_shadow(), LA_SHADOW); + + Light &light = light_map_.lookup_or_add_default(world_sunlight_key); + light.used = true; + light.sync(inst_.shadows, float4x4::identity(), 0, &la, light_threshold_); + + sun_lights_len_ += 1; + } } void LightModule::sync_light(const Object *ob, ObjectHandle &handle) { + const ::Light *la = static_cast(ob->data); if (use_scene_lights_ == false) { return; } if (use_sun_lights_ == false) { - if (static_cast(ob->data)->type == LA_SUN) { + if (la->type == LA_SUN) { return; } } @@ -343,7 +366,7 @@ void LightModule::sync_light(const Object *ob, ObjectHandle &handle) light.used = true; if (handle.recalc != 0 || !light.initialized) { light.initialized = true; - light.sync(inst_.shadows, ob, light_threshold_); + light.sync(inst_.shadows, ob->object_to_world(), ob->visibility_flag, la, light_threshold_); } sun_lights_len_ += int(is_sun_light(light.type)); local_lights_len_ += int(!is_sun_light(light.type)); @@ -428,6 +451,7 @@ void LightModule::end_sync() culling_tile_buf_.resize(total_word_count_); culling_pass_sync(); + update_pass_sync(); debug_pass_sync(); } @@ -444,6 +468,7 @@ void LightModule::culling_pass_sync() { auto &sub = culling_ps_.sub("Select"); sub.shader_set(inst_.shaders.static_shader_get(LIGHT_CULLING_SELECT)); + sub.bind_ubo("sunlight_buf", &inst_.world.sunlight); sub.bind_ssbo("light_cull_buf", &culling_data_buf_); sub.bind_ssbo("in_light_buf", light_buf_); sub.bind_ssbo("out_light_buf", culling_light_buf_); @@ -483,6 +508,20 @@ void LightModule::culling_pass_sync() } } +void LightModule::update_pass_sync() +{ + auto &pass = update_ps_; + pass.init(); + pass.shader_set(inst_.shaders.static_shader_get(LIGHT_SHADOW_SETUP)); + pass.bind_ssbo("light_buf", &culling_light_buf_); + pass.bind_ssbo("light_cull_buf", &culling_data_buf_); + pass.bind_ssbo("tilemaps_buf", &inst_.shadows.tilemap_pool.tilemaps_data); + pass.bind_resources(inst_.uniform_data); + /* TODO(fclem): Dispatch for all light. */ + pass.dispatch(int3(1, 1, 1)); + pass.barrier(GPU_BARRIER_SHADER_STORAGE); +} + void LightModule::debug_pass_sync() { if (inst_.debug_mode == eDebugMode::DEBUG_LIGHT_CULLING) { @@ -512,6 +551,7 @@ void LightModule::set_view(View &view, const int2 extent) culling_data_buf_.push_update(); inst_.manager->submit(culling_ps_, view); + inst_.manager->submit(update_ps_); } void LightModule::debug_draw(View &view, GPUFrameBuffer *view_fb) diff --git a/source/blender/draw/engines/eevee_next/eevee_light.hh b/source/blender/draw/engines/eevee_next/eevee_light.hh index f1d961c4532..4366976c4be 100644 --- a/source/blender/draw/engines/eevee_next/eevee_light.hh +++ b/source/blender/draw/engines/eevee_next/eevee_light.hh @@ -76,7 +76,11 @@ struct Light : public LightData, NonCopyable { } #endif - void sync(ShadowModule &shadows, const Object *ob, float threshold); + void sync(ShadowModule &shadows, + float4x4 object_to_world, + char visibility_flag, + const ::Light *la, + float threshold); void shadow_ensure(ShadowModule &shadows); void shadow_discard_safe(ShadowModule &shadows); @@ -113,6 +117,7 @@ class LightModule { /** Map of light objects data. Converted to flat array each frame. */ Map light_map_; + ObjectKey world_sunlight_key; /** Flat array sent to GPU, populated from light_map_. Source buffer for light culling. */ LightDataBuf light_buf_ = {"Lights_no_cull"}; /** Luminous intensity to consider the light boundary at. Used for culling. */ @@ -148,6 +153,9 @@ class LightModule { /** Total number of words the tile buffer needs to contain for the render resolution. */ uint total_word_count_ = 0; + /** Update light on the GPU after culling. Ran for each sample. */ + PassSimple update_ps_ = {"LightUpdate"}; + /** Debug Culling visualization. */ PassSimple debug_draw_ps_ = {"LightCulling.Debug"}; @@ -176,6 +184,7 @@ class LightModule { private: void culling_pass_sync(); + void update_pass_sync(); void debug_pass_sync(); }; diff --git a/source/blender/draw/engines/eevee_next/eevee_lookdev.hh b/source/blender/draw/engines/eevee_next/eevee_lookdev.hh index 90a60b566ff..932cb6117a9 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lookdev.hh +++ b/source/blender/draw/engines/eevee_next/eevee_lookdev.hh @@ -68,15 +68,20 @@ class LookdevWorld { return &world; } - float background_opacity_get() + float background_opacity_get() const { return parameters_.background_opacity; } - float background_blur_get() + float background_blur_get() const { return parameters_.blur; } + + float intensity_get() const + { + return parameters_.intensity; + } }; /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc index f0d569ea124..f462287a315 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc @@ -39,11 +39,13 @@ void SphereProbeModule::begin_sync() PassSimple &pass = remap_ps_; pass.init(); pass.specialize_constant(shader, "extract_sh", &extract_sh_); + pass.specialize_constant(shader, "extract_sun", &extract_sh_); pass.shader_set(shader); pass.bind_texture("cubemap_tx", &cubemap_tx_); pass.bind_texture("atlas_tx", &probes_tx_); pass.bind_image("atlas_img", &probes_tx_); pass.bind_ssbo("out_sh", &tmp_spherical_harmonics_); + pass.bind_ssbo("out_sun", &tmp_sunlight_); pass.push_constant("probe_coord_packed", reinterpret_cast(&probe_sampling_coord_)); pass.push_constant("write_coord_packed", reinterpret_cast(&probe_write_coord_)); pass.push_constant("world_coord_packed", reinterpret_cast(&world_data.atlas_coord)); @@ -74,6 +76,17 @@ void SphereProbeModule::begin_sync() pass.barrier(GPU_BARRIER_SHADER_STORAGE); pass.dispatch(1); } + { + PassSimple &pass = sum_sun_ps_; + pass.init(); + pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SUNLIGHT)); + pass.push_constant("probe_remap_dispatch_size", &dispatch_probe_pack_); + pass.bind_ssbo("in_sun", &tmp_sunlight_); + pass.bind_ssbo("sunlight_buf", &instance_.world.sunlight); + pass.barrier(GPU_BARRIER_SHADER_STORAGE); + pass.dispatch(1); + pass.barrier(GPU_BARRIER_UNIFORM); + } { PassSimple &pass = select_ps_; pass.init(); @@ -227,6 +240,7 @@ void SphereProbeModule::remap_to_octahedral_projection(const SphereProbeAtlasCoo if (extract_spherical_harmonics) { instance_.manager->submit(sum_sh_ps_); + instance_.manager->submit(sum_sun_ps_); /* All volume probe that needs to composite the world probe need to be updated. */ instance_.volume_probes.update_world_irradiance(); } diff --git a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh index 6db664f63a7..a454ad3aca3 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh @@ -46,6 +46,8 @@ class SphereProbeModule { PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"}; /** Sum irradiance information optionally extracted during `remap_ps_`. */ PassSimple sum_sh_ps_ = {"Probe.SumSphericalHarmonics"}; + /** Sum sunlight information optionally extracted during `remap_ps_`. */ + PassSimple sum_sun_ps_ = {"Probe.SumSunlight"}; /** Copy volume probe irradiance for the center of sphere probes. */ PassSimple select_ps_ = {"Probe.Select"}; /** Convolve the octahedral map to fill the Mip-map levels. */ @@ -86,6 +88,10 @@ class SphereProbeModule { /** Final buffer containing the spherical harmonics for the world. */ StorageBuffer spherical_harmonics_ = {"spherical_harmonics_"}; + /** Intermediate buffer to store sun light. */ + StorageArrayBuffer tmp_sunlight_ = { + "tmp_sunlight_"}; + /** * True if the next redraw will trigger a light-probe sphere update. * As syncing the draw passes for rendering has a significant overhead, diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.cc b/source/blender/draw/engines/eevee_next/eevee_sampling.cc index 40ac5ceea9b..ed931019197 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sampling.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sampling.cc @@ -63,7 +63,7 @@ void Sampling::init(const Scene *scene) auto clamp_value_load = [](float value) { return (value > 0.0) ? value : 1e20; }; - clamp_data_.world = clamp_value_load(scene->eevee.clamp_world); + clamp_data_.sun_threshold = clamp_value_load(inst_.world.sun_threshold()); clamp_data_.surface_direct = clamp_value_load(scene->eevee.clamp_surface_direct); clamp_data_.surface_indirect = clamp_value_load(scene->eevee.clamp_surface_indirect); clamp_data_.volume_direct = clamp_value_load(scene->eevee.clamp_volume_direct); diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index d21bc791f75..c879ad88222 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -189,6 +189,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_light_culling_tile"; case LIGHT_CULLING_ZBIN: return "eevee_light_culling_zbin"; + case LIGHT_SHADOW_SETUP: + return "eevee_light_shadow_setup"; case RAY_DENOISE_SPATIAL: return "eevee_ray_denoise_spatial"; case RAY_DENOISE_TEMPORAL: @@ -225,6 +227,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_reflection_probe_irradiance"; case SPHERE_PROBE_SELECT: return "eevee_reflection_probe_select"; + case SPHERE_PROBE_SUNLIGHT: + return "eevee_reflection_probe_sunlight"; case SHADOW_CLIPMAP_CLEAR: return "eevee_shadow_clipmap_clear"; case SHADOW_DEBUG: diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.hh b/source/blender/draw/engines/eevee_next/eevee_shader.hh index 5b4c626fb17..384b921aa7a 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader.hh @@ -81,6 +81,7 @@ enum eShaderType { LIGHT_CULLING_SORT, LIGHT_CULLING_TILE, LIGHT_CULLING_ZBIN, + LIGHT_SHADOW_SETUP, LIGHTPROBE_IRRADIANCE_BOUNDS, LIGHTPROBE_IRRADIANCE_OFFSET, @@ -106,9 +107,10 @@ enum eShaderType { RAY_TRACE_SCREEN, SPHERE_PROBE_CONVOLVE, + SPHERE_PROBE_IRRADIANCE, SPHERE_PROBE_REMAP, SPHERE_PROBE_SELECT, - SPHERE_PROBE_IRRADIANCE, + SPHERE_PROBE_SUNLIGHT, SHADOW_CLIPMAP_CLEAR, SHADOW_DEBUG, diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index 5edd0f8449d..bba374babe7 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -1560,6 +1560,13 @@ struct SphereProbeHarmonic { }; BLI_STATIC_ASSERT_ALIGN(SphereProbeHarmonic, 16) +struct SphereProbeSunLight { + float4 direction; + packed_float3 radiance; + float _pad0; +}; +BLI_STATIC_ASSERT_ALIGN(SphereProbeSunLight, 16) + /** \} */ /* -------------------------------------------------------------------- */ @@ -1725,7 +1732,7 @@ BLI_STATIC_ASSERT_ALIGN(HiZData, 16) * \{ */ struct ClampData { - float world; + float sun_threshold; float surface_direct; float surface_indirect; float volume_direct; diff --git a/source/blender/draw/engines/eevee_next/eevee_world.cc b/source/blender/draw/engines/eevee_next/eevee_world.cc index 734dde9f9dd..aa88f34a344 100644 --- a/source/blender/draw/engines/eevee_next/eevee_world.cc +++ b/source/blender/draw/engines/eevee_next/eevee_world.cc @@ -9,6 +9,7 @@ #include "BKE_lib_id.hh" #include "BKE_node.hh" #include "BKE_world.h" +#include "BLI_math_rotation.h" #include "DEG_depsgraph_query.hh" #include "NOD_shader.h" @@ -76,6 +77,21 @@ World::~World() return default_world_; } +::World *World::scene_world_get() +{ + return (inst_.scene->world != nullptr) ? inst_.scene->world : default_world_get(); +} + +float World::sun_threshold() +{ + float sun_threshold = scene_world_get()->sun_threshold; + if (inst_.use_studio_light()) { + /* Do not call `lookdev_world_.intensity_get()` as it might not be initialized yet. */ + sun_threshold *= inst_.v3d->shading.studiolight_intensity; + } + return sun_threshold; +} + void World::sync() { bool has_update = false; @@ -100,11 +116,8 @@ void World::sync() else if (has_volume_absorption_) { bl_world = default_world_get(); } - else if (inst_.scene->world != nullptr) { - bl_world = inst_.scene->world; - } else { - bl_world = default_world_get(); + bl_world = scene_world_get(); } bNodeTree *ntree = (bl_world->nodetree && bl_world->use_nodes) ? diff --git a/source/blender/draw/engines/eevee_next/eevee_world.hh b/source/blender/draw/engines/eevee_next/eevee_world.hh index 5dc52a4a148..be7eb8b30a6 100644 --- a/source/blender/draw/engines/eevee_next/eevee_world.hh +++ b/source/blender/draw/engines/eevee_next/eevee_world.hh @@ -47,6 +47,12 @@ class DefaultWorldNodeTree { * \{ */ class World { + public: + /** + * Buffer containing the sun light for the world. + * Filled by #LightProbeModule and read by #LightModule. */ + UniformBuffer sunlight = {"sunlight"}; + private: Instance &inst_; @@ -88,12 +94,32 @@ class World { return has_volume_scatter_; } + float sun_threshold(); + + float sun_angle() + { + return scene_world_get()->sun_angle; + } + + float sun_shadow_max_resolution() + { + return scene_world_get()->sun_shadow_maximum_resolution; + } + + bool use_sun_shadow() + { + return scene_world_get()->flag & WO_USE_SUN_SHADOW; + } + private: void sync_volume(); /* Returns a dummy black world for when a valid world isn't present or when we want to suppress * any light coming from the world. */ ::World *default_world_get(); + + /* Returns either the scene world or the default world if scene has no world. */ + ::World *scene_world_get(); }; /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl index 18c62e19820..1ec61c9714b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl @@ -21,6 +21,22 @@ void main() /* Sun lights are packed at the end of the array. Perform early copy. */ if (is_sun_light(light.type)) { + /* First sun-light is reserved for world light. Perform copy from dedicated buffer. */ + bool is_world_sun_light = light.color.r < 0.0; + if (is_world_sun_light) { + light.color = sunlight_buf.color; + light.object_to_world = sunlight_buf.object_to_world; + /* NOTE: Use the radius from UI instead of auto sun size for now. */ + // light.power = sunlight_buf.power; +#if USE_LIGHT_UNION + // light.sun.radius = sunlight_buf.sun.radius; + // light.sun.shadow_angle = sunlight_buf.sun.shadow_angle; +#else + // light.do_not_access_directly.radius_squared = + // sunlight_buf.do_not_access_directly.radius_squared; + // light.do_not_access_directly._pad1 = sunlight_buf.do_not_access_directly._pad1; +#endif + } /* NOTE: We know the index because sun lights are packed at the start of the input buffer. */ out_light_buf[light_cull_buf.local_lights_len + l_idx] = light; return; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_shadow_setup_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_shadow_setup_comp.glsl new file mode 100644 index 00000000000..2924a7067f5 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_shadow_setup_comp.glsl @@ -0,0 +1,273 @@ +/* SPDX-FileCopyrightText: 2022-2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** + * Setup tilemap positionning for each shadow casting light. + * Dispatched one thread per light. + */ + +#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) + +int shadow_directional_coverage_get(int level) +{ + return 1 << level; +} + +void orthographic_sync(int tilemap_id, + Transform light_tx, + int2 origin_offset, + int clipmap_level, + eShadowProjectionType projection_type) +{ + if (all(equal(tilemaps_buf[tilemap_id].grid_shift, int2(0)))) { + /* Only replace shift if it is not already dirty. */ + tilemaps_buf[tilemap_id].grid_shift = tilemaps_buf[tilemap_id].grid_offset - origin_offset; + } + tilemaps_buf[tilemap_id].grid_offset = origin_offset; + + mat3x3 object_to_world_transposed = mat3x3(tilemaps_buf[tilemap_id].viewmat); + + if (!all(equal(object_to_world_transposed[0], light_tx.x.xyz)) || + !all(equal(object_to_world_transposed[1], light_tx.y.xyz)) || + !all(equal(object_to_world_transposed[2], light_tx.z.xyz))) + { + tilemaps_buf[tilemap_id].grid_shift = int2(SHADOW_TILEMAP_RES); + } + + float level_size = shadow_directional_coverage_get(clipmap_level); + float half_size = level_size / 2.0; + float tile_size = level_size / float(SHADOW_TILEMAP_RES); + vec2 center_offset = vec2(origin_offset) * tile_size; + + /* object_mat is a rotation matrix. Reduce imprecision by taking the transpose which is also the + * inverse in this particular case. */ + tilemaps_buf[tilemap_id].viewmat[0] = vec4(light_tx.x.xyz, 0.0); + tilemaps_buf[tilemap_id].viewmat[1] = vec4(light_tx.y.xyz, 0.0); + tilemaps_buf[tilemap_id].viewmat[2] = vec4(light_tx.z.xyz, 0.0); + tilemaps_buf[tilemap_id].viewmat[3] = vec4(0.0, 0.0, 0.0, 1.0); + + tilemaps_buf[tilemap_id].projection_type = projection_type; + tilemaps_buf[tilemap_id].half_size = half_size; + tilemaps_buf[tilemap_id].center_offset = center_offset; + tilemaps_buf[tilemap_id].winmat = projection_orthographic( + -half_size + center_offset.x, + half_size + center_offset.x, + -half_size + center_offset.y, + half_size + center_offset.y, + /* Near/far is computed on GPU using casters bounds. */ + -1.0, + 1.0); +} + +void cascade_sync(inout LightData light) +{ + int level_min = light_sun_data_get(light).clipmap_lod_min; + int level_max = light_sun_data_get(light).clipmap_lod_max; + int level_range = level_max - level_min; + int level_len = level_range + 1; + + vec3 ws_camera_position = uniform_buf.camera.viewinv[3].xyz; + vec3 ws_camera_forward = uniform_buf.camera.viewinv[2].xyz; + float camera_clip_near = uniform_buf.camera.clip_near; + float camera_clip_far = uniform_buf.camera.clip_far; + + /* All tile-maps use the first level size. */ + float level_size = shadow_directional_coverage_get(level_min); + float half_size = level_size / 2.0; + float tile_size = level_size / float(SHADOW_TILEMAP_RES); + + /* Ideally we should only take the intersection with the scene bounds. */ + vec3 ws_far_point = ws_camera_position - ws_camera_forward * camera_clip_far; + vec3 ws_near_point = ws_camera_position - ws_camera_forward * camera_clip_near; + + vec3 ls_far_point = transform_direction_transposed(light.object_to_world, ws_far_point); + vec3 ls_near_point = transform_direction_transposed(light.object_to_world, ws_near_point); + + float2 local_view_direction = normalize(ls_far_point.xy - ls_near_point.xy); + float2 farthest_tilemap_center = local_view_direction * half_size * level_range; + + /* Offset for smooth level transitions. */ + light.object_to_world.x.w = ls_near_point.x; + light.object_to_world.y.w = ls_near_point.y; + light.object_to_world.z.w = ls_near_point.z; + + /* Offset in tiles from the scene origin to the center of the first tile-maps. */ + int2 origin_offset = int2(round(ls_near_point.xy / tile_size)); + /* Offset in tiles between the first and the last tile-maps. */ + int2 offset_vector = int2(round(farthest_tilemap_center / tile_size)); + + int2 base_offset_pos = (offset_vector * (1 << 16)) / max(level_range, 1); + + /* \note cascade_level_range starts the range at the unique LOD to apply to all tile-maps. */ + for (int i = 0; i < level_len; i++) { + /* Equal spacing between cascades layers since we want uniform shadow density. */ + int2 level_offset = origin_offset + shadow_cascade_grid_offset(base_offset_pos, i); + + orthographic_sync(light.tilemap_index + i, + light.object_to_world, + level_offset, + level_min, + SHADOW_PROJECTION_CASCADE); + } + + vec2 clipmap_origin = vec2(origin_offset) * tile_size; + +#if USE_LIGHT_UNION + /* Used as origin for the clipmap_base_offset trick. */ + light.sun.clipmap_origin = clipmap_origin; + /* Number of levels is limited to 32 by `clipmap_level_range()` for this reason. */ + light.sun.clipmap_base_offset_pos = base_offset_pos; + light.sun.clipmap_base_offset_neg = ivec2(0); +#else + /* Used as origin for the clipmap_base_offset trick. */ + light.do_not_access_directly._pad3 = clipmap_origin; + /* Number of levels is limited to 32 by `clipmap_level_range()` for this reason. */ + light.do_not_access_directly._pad0_reserved = intBitsToFloat(base_offset_pos.x); + light.do_not_access_directly._pad1_reserved = intBitsToFloat(base_offset_pos.y); + light.do_not_access_directly._pad7 = intBitsToFloat(0); + light.do_not_access_directly.shadow_projection_shift = intBitsToFloat(0); +#endif +} + +void clipmap_sync(inout LightData light) +{ + vec3 ws_camera_position = uniform_buf.camera.viewinv[3].xyz; + vec3 ls_camera_position = transform_direction_transposed(light.object_to_world, + ws_camera_position); + + int level_min = light_sun_data_get(light).clipmap_lod_min; + int level_max = light_sun_data_get(light).clipmap_lod_max; + int level_len = level_max - level_min + 1; + + vec2 clipmap_origin; + for (int lod = 0; lod < level_len; lod++) { + int level = level_min + lod; + /* Compute full offset from world origin to the smallest clipmap tile centered around the + * camera position. The offset is computed in smallest tile unit. */ + float tile_size = float(1 << level) / float(SHADOW_TILEMAP_RES); + int2 level_offset = int2(round(ls_camera_position.xy / tile_size)); + + orthographic_sync(light.tilemap_index + lod, + light.object_to_world, + level_offset, + level, + SHADOW_PROJECTION_CLIPMAP); + + clipmap_origin = vec2(level_offset) * tile_size; + } + + int2 pos_offset = int2(0); + int2 neg_offset = int2(0); + for (int lod = 0; lod < level_len - 1; lod++) { + /* Since offset can only differ by one tile from the higher level, we can compress that as a + * single integer where one bit contains offset between 2 levels. Then a single bit shift in + * the shader gives the number of tile to offset in the given tile-map space. However we need + * also the sign of the offset for each level offset. To this end, we split the negative + * offsets to a separate int. */ + int2 lvl_offset_next = tilemaps_buf[light.tilemap_index + lod + 1].grid_offset; + int2 lvl_offset = tilemaps_buf[light.tilemap_index + lod].grid_offset; + int2 lvl_delta = lvl_offset - (lvl_offset_next << 1); + pos_offset |= max(lvl_delta, int2(0)) << lod; + neg_offset |= max(-lvl_delta, int2(0)) << lod; + } + + /* Used for selecting the clipmap level. */ + light.object_to_world.x.w = ls_camera_position.x; + light.object_to_world.y.w = ls_camera_position.y; + light.object_to_world.z.w = ls_camera_position.z; +#if USE_LIGHT_UNION + /* Used as origin for the clipmap_base_offset trick. */ + light.sun.clipmap_origin = clipmap_origin; + /* Number of levels is limited to 32 by `clipmap_level_range()` for this reason. */ + light.sun.clipmap_base_offset_pos = pos_offset; + light.sun.clipmap_base_offset_neg = neg_offset; +#else + /* Used as origin for the clipmap_base_offset trick. */ + light.do_not_access_directly._pad3 = clipmap_origin; + /* Number of levels is limited to 32 by `clipmap_level_range()` for this reason. */ + light.do_not_access_directly._pad0_reserved = intBitsToFloat(pos_offset.x); + light.do_not_access_directly._pad1_reserved = intBitsToFloat(pos_offset.y); + light.do_not_access_directly._pad7 = intBitsToFloat(neg_offset.x); + light.do_not_access_directly.shadow_projection_shift = intBitsToFloat(neg_offset.y); +#endif +} + +#if 0 +void cubeface_sync(int tilemap_id, vec3 jitter_offset) +{ + /* Update corners. */ + viewmat = shadow_face_mat[cubeface] * from_location(float3(0.0, 0.0, -shift)) * + invert(object_mat); + + /* Update corners. */ + corners[0] += jitter_offset; + corners[1] += jitter_offset; + corners[2] += jitter_offset; + corners[3] += jitter_offset; + + /* Set dirty. */ + grid_shift = int2(SHADOW_TILEMAP_RES); +} +#endif + +void main() +{ + uint l_idx = gl_GlobalInvocationID.x; + if (l_idx >= light_cull_buf.items_count) { + return; + } + + LightData light = light_buf[l_idx]; + + if (light.tilemap_index == LIGHT_NO_SHADOW) { + return; + } + + if (is_sun_light(light.type)) { + /* Distant lights. */ + +#if 0 /* Jittered shadows. */ + vec3 position_on_light = random_position_on_light(light); + vec3 light_direction = normalize(position_on_light); + float3x3 object_to_world_transposed = transpose(from_up_axis(light_direction)); + + light.object_to_world.x.xyz = object_to_world_transposed[0]; + light.object_to_world.y.xyz = object_to_world_transposed[1]; + light.object_to_world.z.xyz = object_to_world_transposed[2]; +#endif + + if (light.type == LIGHT_SUN_ORTHO) { + cascade_sync(light); + } + else { + clipmap_sync(light); + } + } +#if 0 /* Jittered shadows. */ + else { + /* Local lights. */ +# if 0 /* Jittered shadows. */ + vec3 position_on_light = random_position_on_light(light); + light_buf[l_idx].shadow_position = position_on_light; + + int tilemap_count = 0; + if (is_area_light(light.type)) { + tilemap_count = 5; + } + else if (is_spot_light(light.type)) { + tilemap_count = (spot_angle > M_PI * 0.25) ? 5 : 1; + } + else { + tilemap_count = 6; + } + + for (int i = 0; i < tilemap_count; i++) { + cubeface_sync(light.tilemap_id + i, position_on_light); + } +# endif + } +#endif + + light_buf[l_idx] = light; +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_irradiance_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_irradiance_comp.glsl index d28f5b750e3..97c0b13815b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_irradiance_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_irradiance_comp.glsl @@ -12,24 +12,6 @@ shared vec4 local_sh_coefs[gl_WorkGroupSize.x][4]; -void spherical_harmonic_lds_store(uint index, SphericalHarmonicL1 sh) -{ - local_sh_coefs[index][0] = sh.L0.M0; - local_sh_coefs[index][1] = sh.L1.Mn1; - local_sh_coefs[index][2] = sh.L1.M0; - local_sh_coefs[index][3] = sh.L1.Mp1; -} - -SphericalHarmonicL1 spherical_harmonic_lds_load(uint index) -{ - SphericalHarmonicL1 sh; - sh.L0.M0 = local_sh_coefs[index][0]; - sh.L1.Mn1 = local_sh_coefs[index][1]; - sh.L1.M0 = local_sh_coefs[index][2]; - sh.L1.Mp1 = local_sh_coefs[index][3]; - return sh; -} - void main() { SphericalHarmonicL1 sh; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl index e5764346fb9..d55b3282d88 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl @@ -84,6 +84,10 @@ float octahedral_texel_solid_angle(ivec2 local_texel, void main() { + uint work_group_index = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x; + const uint local_index = gl_LocalInvocationIndex; + const uint group_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y; + SphereProbeUvArea world_coord = reinterpret_as_atlas_coord(world_coord_packed); SphereProbeUvArea sample_coord = reinterpret_as_atlas_coord(probe_coord_packed); SphereProbePixelArea write_coord = reinterpret_as_write_coord(write_coord_packed); @@ -107,8 +111,10 @@ void main() radiance.rgb = mix(world_radiance.rgb, radiance.rgb, opacity); } - float clamp_world = uniform_buf.clamp.world; - radiance = colorspace_brightness_clamp_max(radiance, clamp_world); + float sun_threshold = uniform_buf.clamp.sun_threshold; + vec3 radiance_clamped = colorspace_brightness_clamp_max(radiance, sun_threshold); + vec3 radiance_sun = radiance - radiance_clamped; + radiance = radiance_clamped; if (!any(greaterThanEqual(local_texel, ivec2(write_coord.extent)))) { float clamp_indirect = uniform_buf.clamp.surface_indirect; @@ -118,12 +124,42 @@ void main() imageStore(atlas_img, texel, vec4(out_radiance, 1.0)); } + float sample_weight = octahedral_texel_solid_angle(local_texel, write_coord, sample_coord); + + if (extract_sun) { + /* Parallel sum. Result is stored inside local_radiance[0]. */ + local_radiance[local_index] = radiance_sun.xyzz * sample_weight; + for (uint stride = group_size / 2; stride > 0; stride /= 2) { + barrier(); + if (local_index < stride) { + local_radiance[local_index] += local_radiance[local_index + stride]; + } + } + barrier(); + + if (gl_LocalInvocationIndex == 0u) { + out_sun[work_group_index].radiance = local_radiance[0].xyz; + } + barrier(); + + /* Reusing local_radiance for directions. */ + local_radiance[local_index] = vec4(normalize(direction), 1.0) * sample_weight * + length(radiance_sun.xyz); + for (uint stride = group_size / 2; stride > 0; stride /= 2) { + barrier(); + if (local_index < stride) { + local_radiance[local_index] += local_radiance[local_index + stride]; + } + } + barrier(); + + if (gl_LocalInvocationIndex == 0u) { + out_sun[work_group_index].direction = local_radiance[0]; + } + barrier(); + } + if (extract_sh) { - float sample_weight = octahedral_texel_solid_angle(local_texel, write_coord, sample_coord); - - const uint local_index = gl_LocalInvocationIndex; - const uint group_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y; - /* Parallel sum. Result is stored inside local_radiance[0]. */ local_radiance[local_index] = radiance.xyzz * sample_weight; uint stride = group_size / 2; @@ -158,7 +194,6 @@ void main() * instead of adding to it? */ spherical_harmonics_encode_signal_sample(L, local_radiance[0], sh); /* Outputs one SH for each thread-group. */ - uint work_group_index = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x; out_sh[work_group_index].L0_M0 = sh.L0.M0; out_sh[work_group_index].L1_Mn1 = sh.L1.Mn1; out_sh[work_group_index].L1_M0 = sh.L1.M0; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_sunlight_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_sunlight_comp.glsl new file mode 100644 index 00000000000..9d8f6da3ff2 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_sunlight_comp.glsl @@ -0,0 +1,90 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Sum all Suns extracting during remapping to octahedral map. + * Dispatch only one thread-group that sums. */ + +#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_reflection_probe_mapping_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) + +shared vec3 local_radiance[gl_WorkGroupSize.x]; +shared vec4 local_direction[gl_WorkGroupSize.x]; + +void main() +{ + SphereProbeSunLight sun; + sun.radiance = vec3(0.0); + sun.direction = vec4(0.0); + + /* First sum onto the local memory. */ + uint valid_data_len = probe_remap_dispatch_size.x * probe_remap_dispatch_size.y; + const uint iter_count = uint(SPHERE_PROBE_MAX_HARMONIC) / gl_WorkGroupSize.x; + for (uint i = 0; i < iter_count; i++) { + uint index = gl_WorkGroupSize.x * i + gl_LocalInvocationIndex; + if (index >= valid_data_len) { + break; + } + sun.radiance += in_sun[index].radiance; + sun.direction += in_sun[index].direction; + } + + /* Then sum across invocations. */ + const uint local_index = gl_LocalInvocationIndex; + local_radiance[local_index] = sun.radiance; + local_direction[local_index] = sun.direction; + + /* Parallel sum. */ + const uint group_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y; + for (uint stride = group_size / 2; stride > 0; stride /= 2) { + barrier(); + if (local_index < stride) { + local_radiance[local_index] += local_radiance[local_index + stride]; + local_direction[local_index] += local_direction[local_index + stride]; + } + } + + barrier(); + if (gl_LocalInvocationIndex == 0u) { + sunlight_buf.color = local_radiance[0]; + + /* Normalize the sum to get the mean direction. The length of the vector gives us the size of + * the sun light. */ + float len; + vec3 direction = normalize_and_get_length(local_direction[0].xyz / local_direction[0].w, len); + + mat3x3 tx = transpose(from_up_axis(direction)); + /* Convert to transform. */ + sunlight_buf.object_to_world.x = vec4(tx[0], 0.0); + sunlight_buf.object_to_world.y = vec4(tx[1], 0.0); + sunlight_buf.object_to_world.z = vec4(tx[2], 0.0); + + /* Auto sun angle. */ + float sun_angle_cos = 2.0 * len - 1.0; + float sun_angle = acos(sun_angle_cos); + /* Compute tangent from cosine. */ + float sun_angle_tan = sqrt(-1.0 + 1.0 / square(sun_angle_cos)); + /* Clamp value to avoid float imprecision artifacts. */ + float sun_radius = clamp(sun_angle_tan, 0.001, 20.0); + + /* Convert irradiance to radiance. */ + float shape_power = M_1_PI * (1.0 + 1.0 / square(sun_radius)); + float point_power = 1.0; + + sunlight_buf.power[LIGHT_DIFFUSE] = shape_power; + sunlight_buf.power[LIGHT_SPECULAR] = shape_power; + sunlight_buf.power[LIGHT_TRANSMISSION] = shape_power; + sunlight_buf.power[LIGHT_VOLUME] = point_power; + +#if USE_LIGHT_UNION + sunlight_buf.sun.radius = sun_radius; + sunlight_buf.sun.shadow_angle = sun_angle; +#else + sunlight_buf.do_not_access_directly.radius_squared = sun_radius; + sunlight_buf.do_not_access_directly._pad1 = sun_angle; +#endif + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh index 10b752759ba..2e2e6b1cc97 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh @@ -30,6 +30,7 @@ GPU_SHADER_CREATE_INFO(eevee_light_culling_select) .storage_buf(2, Qualifier::WRITE, "LightData", "out_light_buf[]") .storage_buf(3, Qualifier::WRITE, "float", "out_zdist_buf[]") .storage_buf(4, Qualifier::WRITE, "uint", "out_key_buf[]") + .uniform_buf(0, "LightData", "sunlight_buf") .compute_source("eevee_light_culling_select_comp.glsl"); GPU_SHADER_CREATE_INFO(eevee_light_culling_sort) @@ -61,6 +62,15 @@ GPU_SHADER_CREATE_INFO(eevee_light_culling_tile) .storage_buf(2, Qualifier::WRITE, "uint", "out_light_tile_buf[]") .compute_source("eevee_light_culling_tile_comp.glsl"); +GPU_SHADER_CREATE_INFO(eevee_light_shadow_setup) + .do_static_compilation(true) + .additional_info("eevee_shared", "draw_view", "draw_view_culling", "eevee_global_ubo") + .local_group_size(CULLING_SELECT_GROUP_SIZE) + .storage_buf(0, Qualifier::READ, "LightCullingData", "light_cull_buf") + .storage_buf(1, Qualifier::READ_WRITE, "LightData", "light_buf[]") + .storage_buf(2, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]") + .compute_source("eevee_light_shadow_setup_comp.glsl"); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh index d2e932269fc..42dde156e9a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh @@ -20,12 +20,14 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data) GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap) .local_group_size(SPHERE_PROBE_REMAP_GROUP_SIZE, SPHERE_PROBE_REMAP_GROUP_SIZE) .specialization_constant(Type::BOOL, "extract_sh", true) + .specialization_constant(Type::BOOL, "extract_sun", true) .push_constant(Type::IVEC4, "probe_coord_packed") .push_constant(Type::IVEC4, "write_coord_packed") .push_constant(Type::IVEC4, "world_coord_packed") .sampler(0, ImageType::FLOAT_CUBE, "cubemap_tx") .sampler(1, ImageType::FLOAT_2D_ARRAY, "atlas_tx") .storage_buf(0, Qualifier::WRITE, "SphereProbeHarmonic", "out_sh[SPHERE_PROBE_MAX_HARMONIC]") + .storage_buf(1, Qualifier::WRITE, "SphereProbeSunLight", "out_sun[SPHERE_PROBE_MAX_HARMONIC]") .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "atlas_img") .compute_source("eevee_reflection_probe_remap_comp.glsl") .additional_info("eevee_shared", "eevee_global_ubo") @@ -40,6 +42,15 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_irradiance) .do_static_compilation(true) .compute_source("eevee_reflection_probe_irradiance_comp.glsl"); +GPU_SHADER_CREATE_INFO(eevee_reflection_probe_sunlight) + .local_group_size(SPHERE_PROBE_SH_GROUP_SIZE) + .push_constant(Type::IVEC3, "probe_remap_dispatch_size") + .storage_buf(0, Qualifier::READ, "SphereProbeSunLight", "in_sun[SPHERE_PROBE_MAX_HARMONIC]") + .storage_buf(1, Qualifier::WRITE, "LightData", "sunlight_buf") + .additional_info("eevee_shared") + .do_static_compilation(true) + .compute_source("eevee_reflection_probe_sunlight_comp.glsl"); + GPU_SHADER_CREATE_INFO(eevee_reflection_probe_select) .local_group_size(SPHERE_PROBE_SELECT_GROUP_SIZE) .storage_buf(0, diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index 328a3a196f0..7a9865a57cb 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -230,7 +230,6 @@ .motion_blur_max = 32, \ .motion_blur_steps = 1, \ \ - .clamp_world = 10.0f, \ .clamp_surface_indirect = 10.0f, \ \ .shadow_cube_size = 512, \ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 1c0a05116bb..cb00f8c8f20 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1891,12 +1891,10 @@ typedef struct SceneEEVEE { int shadow_step_count; float shadow_resolution_scale; - float clamp_world; float clamp_surface_direct; float clamp_surface_indirect; float clamp_volume_direct; float clamp_volume_indirect; - char _pad[4]; int ray_tracing_method; diff --git a/source/blender/makesdna/DNA_world_defaults.h b/source/blender/makesdna/DNA_world_defaults.h index f8e88fb3f8b..14617e089d1 100644 --- a/source/blender/makesdna/DNA_world_defaults.h +++ b/source/blender/makesdna/DNA_world_defaults.h @@ -16,6 +16,8 @@ #define _DNA_DEFAULT_World \ { \ + .flag = WO_USE_SUN_SHADOW, \ + \ .horr = 0.05f, \ .horg = 0.05f, \ .horb = 0.05f, \ @@ -28,6 +30,9 @@ .mistdist = 25.0f, \ \ .probe_resolution = LIGHT_PROBE_RESOLUTION_1024, \ + .sun_threshold = 10.0f, \ + .sun_angle = DEG2RADF(0.526f), \ + .sun_shadow_maximum_resolution = 0.001f, \ } /** \} */ diff --git a/source/blender/makesdna/DNA_world_types.h b/source/blender/makesdna/DNA_world_types.h index 8ff79624b4c..b63d4706976 100644 --- a/source/blender/makesdna/DNA_world_types.h +++ b/source/blender/makesdna/DNA_world_types.h @@ -67,6 +67,13 @@ typedef struct World { * Resolution of the world probe when baked to a texture. Contains `eLightProbeResolution`. */ int probe_resolution; + /** Threshold for sun extraction. */ + float sun_threshold; + /** Angle for sun extraction. */ + float sun_angle; + /** Maximum resolution for extracted sun shadow. */ + float sun_shadow_maximum_resolution; + char _pad4[4]; /** Old animation system, deprecated for 2.5. */ struct Ipo *ipo DNA_DEPRECATED; @@ -120,6 +127,10 @@ enum { * converted manually. (Ref: #119734). */ WO_USE_EEVEE_FINITE_VOLUME = 1 << 3, + /** + * Use shadowing from the extracted sun light. + */ + WO_USE_SUN_SHADOW = 1 << 4, }; /** #World::probe_resolution. */ diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index 2d1b915086c..19778e08611 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -1942,11 +1942,6 @@ static void rna_SceneEEVEE_gi_cubemap_resolution_update(Main * /*main*/, FOREACH_SCENE_OBJECT_END; } -static void rna_SceneEEVEE_clamp_world_update(Main * /*main*/, Scene *scene, PointerRNA * /*ptr*/) -{ - DEG_id_tag_update(&scene->world->id, ID_RECALC_SHADING); -} - static void rna_SceneEEVEE_clamp_surface_indirect_update(Main * /*main*/, Scene *scene, PointerRNA * /*ptr*/) @@ -8073,17 +8068,6 @@ static void rna_def_scene_eevee(BlenderRNA *brna) RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); /* Clamping */ - prop = RNA_def_property(srna, "clamp_world", PROP_FLOAT, PROP_NONE); - RNA_def_property_ui_text( - prop, - "Clamp World", - "If non-zero, the maximum value for world contribution to the scene lighting. " - "Higher values will be scaled down to avoid too " - "much light bleeding at the cost of accuracy"); - RNA_def_property_range(prop, 0.0f, FLT_MAX); - RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_SceneEEVEE_clamp_world_update"); - prop = RNA_def_property(srna, "clamp_surface_direct", PROP_FLOAT, PROP_NONE); RNA_def_property_ui_text(prop, "Clamp Surface Direct", diff --git a/source/blender/makesrna/intern/rna_world.cc b/source/blender/makesrna/intern/rna_world.cc index 5670c6532a4..4aa1f0332c1 100644 --- a/source/blender/makesrna/intern/rna_world.cc +++ b/source/blender/makesrna/intern/rna_world.cc @@ -11,6 +11,8 @@ #include "RNA_define.hh" +#include "BLI_math_rotation.h" + #include "rna_internal.hh" #include "DNA_lightprobe_types.h" @@ -282,6 +284,37 @@ void RNA_def_world(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Resolution", "Resolution when baked to a texture"); RNA_def_property_update(prop, 0, "rna_World_draw_update"); + prop = RNA_def_property(srna, "sun_threshold", PROP_FLOAT, PROP_NONE); + RNA_def_property_ui_text(prop, + "Sun Threshold", + "If non-zero, the maximum value for world contribution that will be " + "recorded inside the world light probe. The excess contribution is " + "converted to a sun light. This reduces the light bleeding caused by " + "very bright light sources"); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_update(prop, 0, "rna_World_draw_update"); + + prop = RNA_def_property(srna, "sun_angle", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_range(prop, DEG2RADF(0.0f), DEG2RADF(180.0f)); + RNA_def_property_ui_text( + prop, "Sun Angle", "Angular diameter of the Sun as seen from the Earth"); + RNA_def_property_update(prop, 0, "rna_World_draw_update"); + + prop = RNA_def_property(srna, "use_sun_shadow", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", WO_USE_SUN_SHADOW); + RNA_def_property_ui_text(prop, "Use Shadow", "Enable sun shadow casting"); + RNA_def_property_update(prop, 0, "rna_World_draw_update"); + + prop = RNA_def_property(srna, "sun_shadow_maximum_resolution", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0001f, 0.020f, 0.05f, 4); + RNA_def_property_ui_text(prop, + "Shadows Resolution Limit", + "Maximum size of a shadow map pixel. Higher values use less memory at " + "the cost of shadow quality"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_update(prop, 0, "rna_World_draw_update"); + rna_def_lighting(brna); rna_def_world_mist(brna); }