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); + /** \} */