From 9cacdf6c4266314decdbf036898fba25c24c44f7 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 19 Jul 2023 13:48:31 +0200 Subject: [PATCH] EEVEE-Next: Extract Spherical Harmonics from World This PR adds diffuse light from environment to the scene without having an irradiance volume in the scene. It does this by extracting the sperical harmonics from the world probe and store it in the irradiance brick that is reserved for world diffuse light in the irradiance cache. This also fixes that selecting an LookDev HDRI didn't update the diffuse light. **Known Issues** - When sampling probes with lower resolution strokes are visible, leading to flickering and instability in the spherical harmonics. This is an issue that should be solved in a separate patch as it is already visible in main - When selecting a lookdev HDRI all irradiance volumes needs to be disabled manually for the expected results (but this also disabled GI). In the future this will be done automatically or we will add a solution to separate the world diffuse light from the irradiance cache so we can update it on the fly. Pull Request: https://projects.blender.org/blender/blender/pulls/110110 --- source/blender/draw/CMakeLists.txt | 1 + .../draw/engines/eevee_next/eevee_defines.hh | 2 + .../draw/engines/eevee_next/eevee_instance.cc | 6 +- .../eevee_next/eevee_irradiance_cache.cc | 2 + .../eevee_next/eevee_irradiance_cache.hh | 3 + .../eevee_next/eevee_reflection_probes.cc | 36 ++++++++- .../eevee_next/eevee_reflection_probes.hh | 7 ++ .../draw/engines/eevee_next/eevee_shader.cc | 2 + .../draw/engines/eevee_next/eevee_shader.hh | 1 + .../draw/engines/eevee_next/eevee_view.cc | 37 +++++---- .../shaders/eevee_reflection_probe_lib.glsl | 2 +- ...flection_probe_update_irradiance_comp.glsl | 76 +++++++++++++++++++ .../shaders/eevee_sampling_lib.glsl | 8 ++ .../infos/eevee_reflection_probe_info.hh | 10 +++ 14 files changed, 173 insertions(+), 20 deletions(-) create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index b161e774d11..8f66a6a911e 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -523,6 +523,7 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl + engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl engines/eevee_next/shaders/eevee_sampling_lib.glsl engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl engines/eevee_next/shaders/eevee_shadow_lib.glsl diff --git a/source/blender/draw/engines/eevee_next/eevee_defines.hh b/source/blender/draw/engines/eevee_next/eevee_defines.hh index 41311c921ab..0c0d1c6aa66 100644 --- a/source/blender/draw/engines/eevee_next/eevee_defines.hh +++ b/source/blender/draw/engines/eevee_next/eevee_defines.hh @@ -33,6 +33,8 @@ * Border size requires depends on the max number of mipmap levels. */ #define REFLECTION_PROBE_MIPMAP_LEVELS 5 #define REFLECTION_PROBE_BORDER_SIZE float(1 << (REFLECTION_PROBE_MIPMAP_LEVELS - 1)) +#define REFLECTION_PROBE_SH_GROUP_SIZE 512 +#define REFLECTION_PROBE_SH_SAMPLES_PER_GROUP 64 /** * IMPORTANT: Some data packing are tweaked for these values. diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index 2000931e6c0..24e26d84561 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -73,8 +73,9 @@ void Instance::init(const int2 &output_res, shadows.init(); motion_blur.init(); main_view.init(); - irradiance_cache.init(); + /* Irradiance Cache needs reflection probes to be initialized. */ reflection_probes.init(); + irradiance_cache.init(); } void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager) @@ -103,8 +104,9 @@ void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager) depth_of_field.init(); shadows.init(); main_view.init(); - irradiance_cache.init(); + /* Irradiance Cache needs reflection probes to be initialized. */ reflection_probes.init(); + irradiance_cache.init(); } void Instance::set_time(float time) diff --git a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc index 6088479cae9..699a8f002da 100644 --- a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc +++ b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc @@ -69,6 +69,8 @@ void IrradianceCache::init() /* Clear the pool to avoid any interpolation to undefined values. */ irradiance_atlas_tx_.clear(float4(0.0f)); } + + inst_.reflection_probes.do_world_update_irradiance_set(true); } if (irradiance_atlas_tx_.is_valid() == false) { diff --git a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh index 857b4e03690..3f43e9f54c4 100644 --- a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh +++ b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh @@ -21,6 +21,7 @@ class Instance; class CapturePipeline; class ShadowModule; class Camera; +class ReflectionProbeModule; /** * Baking related pass and data. Not used at runtime. @@ -181,6 +182,8 @@ class IrradianceCache { private: void debug_pass_draw(View &view, GPUFrameBuffer *view_fb); void display_pass_draw(View &view, GPUFrameBuffer *view_fb); + + friend class ReflectionProbeModule; }; } // namespace blender::eevee 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 430d4b7b784..22a8bff2f78 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc @@ -55,6 +55,16 @@ void ReflectionProbeModule::init() pass.push_constant("reflection_probe_index", &reflection_probe_index_); pass.dispatch(&dispatch_probe_pack_); } + + { + PassSimple &pass = update_irradiance_ps_; + pass.init(); + pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_UPDATE_IRRADIANCE)); + pass.push_constant("reflection_probe_index", &reflection_probe_index_); + pass.bind_image("irradiance_atlas_img", &instance_.irradiance_cache.irradiance_atlas_tx_); + bind_resources(&pass); + pass.dispatch(int2(1, 1)); + } } void ReflectionProbeModule::begin_sync() { @@ -384,6 +394,13 @@ void ReflectionProbeModule::do_world_update_set(bool value) { ReflectionProbe &world_probe = probes_.lookup(world_object_key_); world_probe.do_render = value; + world_probe.do_world_irradiance_update = value; +} + +void ReflectionProbeModule::do_world_update_irradiance_set(bool value) +{ + ReflectionProbe &world_probe = probes_.lookup(world_object_key_); + world_probe.do_world_irradiance_update = value; } bool ReflectionProbeModule::has_only_world_probe() const @@ -453,7 +470,7 @@ std::optional ReflectionProbeModule::update_info_pop( const bool do_probe_sync = instance_.do_probe_sync(); const int max_shift = int(log2(max_resolution_)); for (const Map::Item &item : probes_.items()) { - if (!item.value.do_render) { + if (!item.value.do_render && !item.value.do_world_irradiance_update) { continue; } if (probe_type == ReflectionProbe::Type::World && item.value.type != probe_type) { @@ -466,7 +483,6 @@ std::optional ReflectionProbeModule::update_info_pop( if (item.value.type == ReflectionProbe::Type::Probe && !do_probe_sync) { continue; } - probes_.lookup(item.key).do_render = false; ReflectionProbeData &probe_data = data_buf_[item.value.index]; ReflectionProbeUpdateInfo info = {}; @@ -475,6 +491,12 @@ std::optional ReflectionProbeModule::update_info_pop( info.resolution = 1 << (max_shift - probe_data.layer_subdivision - 1); info.clipping_distances = item.value.clipping_distances; info.probe_pos = float3(probe_data.pos); + info.do_render = item.value.do_render; + info.do_world_irradiance_update = item.value.do_world_irradiance_update; + + ReflectionProbe &probe = probes_.lookup(item.key); + probe.do_render = false; + probe.do_world_irradiance_update = false; if (cubemap_tx_.ensure_cube(GPU_RGBA16F, info.resolution, @@ -510,6 +532,16 @@ void ReflectionProbeModule::remap_to_octahedral_projection(uint64_t object_key) instance_.manager->submit(remap_ps_); } +void ReflectionProbeModule::update_world_irradiance() +{ + const ReflectionProbe &probe = probes_.lookup(world_object_key_); + + /* Update shader parameters that change per dispatch. */ + reflection_probe_index_ = probe.index; + + instance_.manager->submit(update_irradiance_ps_); +} + void ReflectionProbeModule::update_probes_texture_mipmaps() { GPU_texture_update_mipmap_chain(probes_tx_); 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 bf27918f9b8..dc996b744b8 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh @@ -38,6 +38,7 @@ struct ReflectionProbe { bool do_update_data = false; /* Should the area in the probes_tx_ be updated? */ bool do_render = false; + bool do_world_irradiance_update = false; /** * Probes that aren't used during a draw can be cleared. @@ -102,6 +103,7 @@ class ReflectionProbeModule { Texture probes_tx_ = {"Probes"}; PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"}; + PassSimple update_irradiance_ps_ = {"Probe.UpdateIrradiance"}; int3 dispatch_probe_pack_ = int3(0); @@ -133,6 +135,7 @@ class ReflectionProbeModule { bool do_world_update_get() const; void do_world_update_set(bool value); + void do_world_update_irradiance_set(bool value); void debug_print() const; @@ -167,6 +170,7 @@ class ReflectionProbeModule { std::optional update_info_pop(ReflectionProbe::Type probe_type); void remap_to_octahedral_projection(uint64_t object_key); void update_probes_texture_mipmaps(); + void update_world_irradiance(); bool has_only_world_probe() const; @@ -196,6 +200,9 @@ struct ReflectionProbeUpdateInfo { float2 clipping_distances; uint64_t object_key; + + bool do_render; + bool do_world_irradiance_update; }; /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index c019e9681ca..ddc0d300ed4 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -160,6 +160,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_lightprobe_irradiance_load"; case REFLECTION_PROBE_REMAP: return "eevee_reflection_probe_remap"; + case REFLECTION_PROBE_UPDATE_IRRADIANCE: + return "eevee_reflection_probe_update_irradiance"; 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 840192ba07f..ddf1e479c63 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader.hh @@ -76,6 +76,7 @@ enum eShaderType { MOTION_BLUR_TILE_FLATTEN_VIEWPORT, REFLECTION_PROBE_REMAP, + REFLECTION_PROBE_UPDATE_IRRADIANCE, SHADOW_CLIPMAP_CLEAR, SHADOW_DEBUG, diff --git a/source/blender/draw/engines/eevee_next/eevee_view.cc b/source/blender/draw/engines/eevee_next/eevee_view.cc index 25a265ff43a..96684287f71 100644 --- a/source/blender/draw/engines/eevee_next/eevee_view.cc +++ b/source/blender/draw/engines/eevee_next/eevee_view.cc @@ -206,24 +206,31 @@ void CaptureView::render_world() View view = {"Capture.View"}; GPU_debug_group_begin("World.Capture"); - for (int face : IndexRange(6)) { - float4x4 view_m4 = cubeface_mat(face); - float4x4 win_m4 = math::projection::perspective(-update_info->clipping_distances.x, - update_info->clipping_distances.x, - -update_info->clipping_distances.x, - update_info->clipping_distances.x, - update_info->clipping_distances.x, - update_info->clipping_distances.y); - view.sync(view_m4, win_m4); + if (update_info->do_render) { + for (int face : IndexRange(6)) { + float4x4 view_m4 = cubeface_mat(face); + float4x4 win_m4 = math::projection::perspective(-update_info->clipping_distances.x, + update_info->clipping_distances.x, + -update_info->clipping_distances.x, + update_info->clipping_distances.x, + update_info->clipping_distances.x, + update_info->clipping_distances.y); + view.sync(view_m4, win_m4); - capture_fb_.ensure(GPU_ATTACHMENT_NONE, - GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face)); - GPU_framebuffer_bind(capture_fb_); - inst_.pipelines.world.render(view); + capture_fb_.ensure( + GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemap_tx_, face)); + GPU_framebuffer_bind(capture_fb_); + inst_.pipelines.world.render(view); + } + + inst_.reflection_probes.remap_to_octahedral_projection(update_info->object_key); + inst_.reflection_probes.update_probes_texture_mipmaps(); } - inst_.reflection_probes.remap_to_octahedral_projection(update_info->object_key); - inst_.reflection_probes.update_probes_texture_mipmaps(); + if (update_info->do_world_irradiance_update) { + inst_.reflection_probes.update_world_irradiance(); + } GPU_debug_group_end(); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl index bd32470f221..5d59b80ad5b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl @@ -15,4 +15,4 @@ vec3 reflection_probes_world_sample(vec3 L, float lod) { ReflectionProbeData probe_data = reflection_probe_buf[0]; return reflection_probes_sample(L, lod, probe_data).rgb; -} \ No newline at end of file +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl new file mode 100644 index 00000000000..4d7db0d25fa --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl @@ -0,0 +1,76 @@ + +/* Shader to extract spherical harmonics cooefs from octahedral mapped reflection probe. */ + +#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) + +void atlas_store(vec4 sh_coefficient, ivec2 atlas_coord, int layer) +{ + for (int x = 0; x < IRRADIANCE_GRID_BRICK_SIZE; x++) { + for (int y = 0; y < IRRADIANCE_GRID_BRICK_SIZE; y++) { + for (int z = 0; z < IRRADIANCE_GRID_BRICK_SIZE; z++) { + ivec3 brick_coord = ivec3(x, y, z); + imageStore(irradiance_atlas_img, + ivec3(atlas_coord, layer * IRRADIANCE_GRID_BRICK_SIZE) + brick_coord, + sh_coefficient); + } + } + } +} + +shared vec4 cooefs[gl_WorkGroupSize.x][4]; + +void main() +{ + SphericalHarmonicL1 cooef; + cooef.L0.M0 = vec4(0.0); + cooef.L1.Mn1 = vec4(0.0); + cooef.L1.M0 = vec4(0.0); + cooef.L1.Mp1 = vec4(0.0); + + ReflectionProbeData probe_data = reflection_probe_buf[reflection_probe_index]; + const int subdivision_64 = 5; + float layer_mipmap = clamp( + subdivision_64 - probe_data.layer_subdivision, 0, REFLECTION_PROBE_MIPMAP_LEVELS); + + /* Perform multiple sample. */ + uint store_index = gl_LocalInvocationID.x; + float total_samples = float(gl_WorkGroupSize.x * REFLECTION_PROBE_SH_SAMPLES_PER_GROUP); + float sample_weight = 4.0 * M_PI / total_samples; + float sample_offset = float(gl_LocalInvocationID.x * REFLECTION_PROBE_SH_SAMPLES_PER_GROUP); + for (int sample_index = 0; sample_index < REFLECTION_PROBE_SH_SAMPLES_PER_GROUP; sample_index++) + { + vec2 rand = fract(hammersley_2d(sample_index + sample_offset, total_samples)); + vec3 direction = sample_sphere(rand); + vec4 light = reflection_probes_sample(direction, layer_mipmap, probe_data); + spherical_harmonics_encode_signal_sample(direction, light * sample_weight, cooef); + } + cooefs[store_index][0] = cooef.L0.M0; + cooefs[store_index][1] = cooef.L1.Mn1; + cooefs[store_index][2] = cooef.L1.M0; + cooefs[store_index][3] = cooef.L1.Mp1; + + barrier(); + if (gl_LocalInvocationID.x == 0) { + /* Join results */ + vec4 result[4]; + result[0] = vec4(0.0); + result[1] = vec4(0.0); + result[2] = vec4(0.0); + result[3] = vec4(0.0); + + for (uint i = 0; i < gl_WorkGroupSize.x; i++) { + result[0] += cooefs[i][0]; + result[1] += cooefs[i][1]; + result[2] += cooefs[i][2]; + result[3] += cooefs[i][3]; + } + + ivec2 atlas_coord = ivec2(0, 0); + atlas_store(result[0], atlas_coord, 0); + atlas_store(result[1], atlas_coord, 1); + atlas_store(result[2], atlas_coord, 2); + atlas_store(result[3], atlas_coord, 3); + } +} \ No newline at end of file diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl index e235b795277..98932d0407c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl @@ -102,6 +102,14 @@ vec3 sample_cylinder(vec2 rand) return vec3(theta, cos_phi, sin_phi); } +vec3 sample_sphere(vec2 rand) +{ + float omega = rand.y * 2.0 * M_PI; + float cos_theta = rand.x * 2.0 - 1.0; + float sin_theta = safe_sqrt(1.0 - cos_theta * cos_theta); + return vec3(sin_theta * vec2(cos(omega), sin(omega)), cos_theta); +} + /** \} */ /* -------------------------------------------------------------------- */ 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 b99bf375106..4451d46282d 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 @@ -28,4 +28,14 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap) .additional_info("eevee_shared") .do_static_compilation(true); +/* Extract spherical harmonics band L0 + L1 from octahedral mapped reflection probe and update the + * world brick of the irradiance cache. */ +GPU_SHADER_CREATE_INFO(eevee_reflection_probe_update_irradiance) + .local_group_size(REFLECTION_PROBE_SH_GROUP_SIZE, 1) + .push_constant(Type::INT, "reflection_probe_index") + .image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_atlas_img") + .additional_info("eevee_shared", "eevee_reflection_probe_data") + .compute_source("eevee_reflection_probe_update_irradiance_comp.glsl") + .do_static_compilation(true); + /** \} */