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); }