From 5d2e351baf4b831f08887379fa873daf269a3514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 6 Aug 2023 16:30:12 +0200 Subject: [PATCH] EEVEE-Next: Irradiance Grid Compositing This allows irradiance volume that have less priority to transfer lighting to the ones with higher priority. Meaning interactive relighting from the world or lookdev HDRI is now supported if the world isn't baked inside the volume data. This should improve workflow with larger scenes and interactivity with light setups. To help setup with dynamic objects, this patch introduce 3 new parameter to remove some components from the irradiance grids. Pull Request: https://projects.blender.org/blender/blender/pulls/110838 --- .../bl_ui/properties_data_lightprobe.py | 6 + .../blenloader/intern/versioning_400.cc | 8 ++ .../draw/engines/eevee_next/eevee_instance.cc | 2 + .../eevee_next/eevee_irradiance_cache.cc | 119 +++++++++++++++--- .../eevee_next/eevee_irradiance_cache.hh | 11 ++ .../engines/eevee_next/eevee_lightprobe.hh | 2 + .../eevee_next/eevee_reflection_probes.cc | 1 + .../eevee_next/eevee_reflection_probes.hh | 1 + .../engines/eevee_next/eevee_shader_shared.hh | 21 +++- .../shaders/eevee_debug_surfels_frag.glsl | 13 +- .../shaders/eevee_lightprobe_eval_lib.glsl | 99 ++++++++++----- ...eevee_lightprobe_irradiance_load_comp.glsl | 49 ++++++-- .../eevee_lightprobe_irradiance_ray_comp.glsl | 52 ++++---- .../eevee_spherical_harmonics_lib.glsl | 106 +++++++++++++++- .../shaders/eevee_surf_capture_frag.glsl | 7 ++ .../shaders/eevee_surfel_light_comp.glsl | 8 +- .../shaders/eevee_surfel_ray_comp.glsl | 73 ++++++----- .../infos/eevee_irradiance_cache_info.hh | 11 +- .../makesdna/DNA_lightprobe_defaults.h | 1 + .../blender/makesdna/DNA_lightprobe_types.h | 11 ++ .../blender/makesrna/intern/rna_lightprobe.cc | 22 ++++ 21 files changed, 497 insertions(+), 126 deletions(-) diff --git a/scripts/startup/bl_ui/properties_data_lightprobe.py b/scripts/startup/bl_ui/properties_data_lightprobe.py index a2b3d7ff189..a2e93b5cc9e 100644 --- a/scripts/startup/bl_ui/properties_data_lightprobe.py +++ b/scripts/startup/bl_ui/properties_data_lightprobe.py @@ -130,6 +130,12 @@ class DATA_PT_lightprobe_eevee_next(DataButtonsPanel, Panel): col.separator() + col.prop(probe, "grid_capture_world") + col.prop(probe, "grid_capture_indirect") + col.prop(probe, "grid_capture_emission") + + col.separator() + row = col.row(align=True) row.prop(probe, "visibility_collection") row.prop(probe, "invert_visibility_collection", text="", icon='ARROW_LEFTRIGHT') diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index a8e034469ca..906820c3a92 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -594,5 +594,13 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) */ { /* Keep this block, even when empty. */ + + if (!DNA_struct_elem_find(fd->filesdna, "LightProbe", "float", "grid_flag")) { + LISTBASE_FOREACH (LightProbe *, lightprobe, &bmain->lightprobes) { + /* Keep old behavior of baking the whole lighting. */ + lightprobe->grid_flag = LIGHTPROBE_GRID_CAPTURE_WORLD | LIGHTPROBE_GRID_CAPTURE_INDIRECT | + LIGHTPROBE_GRID_CAPTURE_EMISSION; + } + } } } diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index a9f424444fc..355e8999751 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -586,6 +586,7 @@ void Instance::light_bake_irradiance( sampling.init(probe); while (!sampling.finished()) { context_wrapper([&]() { + GPU_debug_capture_begin(); /* Batch ray cast by pack of 16. Avoids too much overhead of the update function & context * switch. */ /* TODO(fclem): Could make the number of iteration depend on the computation time. */ @@ -613,6 +614,7 @@ void Instance::light_bake_irradiance( float progress = sampling.sample_index() / float(sampling.sample_count()); result_update(cache_frame, progress); + GPU_debug_capture_end(); }); if (stop()) { 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 d5187efe5fe..1ddd47b4d81 100644 --- a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc +++ b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc @@ -111,9 +111,9 @@ void IrradianceCache::bricks_free(Vector &bricks) void IrradianceCache::set_view(View & /*view*/) { - Vector grid_updates; Vector grid_loaded; + bool any_update = false; /* First allocate the needed bricks and populate the brick buffer. */ bricks_infos_buf_.clear(); for (IrradianceGrid &grid : inst_.light_probes.grid_map_.values()) { @@ -137,7 +137,9 @@ void IrradianceCache::set_view(View & /*view*/) /* Note that we reserve 1 slot for the world irradiance. */ if (grid_loaded.size() >= IRRADIANCE_GRID_MAX - 1) { - inst_.info = "Error: Too many grid visible"; + inst_.info = "Error: Too many irradiance grids in the scene"; + /* TODO frustum cull and only load visible grids. */ + // inst_.info = "Error: Too many grid visible"; continue; } @@ -151,10 +153,16 @@ void IrradianceCache::set_view(View & /*view*/) inst_.info = "Error: Irradiance grid allocation failed"; continue; } - - grid_updates.append(&grid); + grid.do_update = true; } + if (do_update_world_) { + /* Update grid composition if world changed. */ + grid.do_update = true; + } + + any_update = any_update || grid.do_update; + grid.brick_offset = bricks_infos_buf_.size(); bricks_infos_buf_.extend(grid.bricks); @@ -172,6 +180,15 @@ void IrradianceCache::set_view(View & /*view*/) grid_loaded.append(&grid); } + /* TODO: This is greedy update detection. We should check if a change can influence each grid + * before tagging update. But this is a bit too complex and update is quite cheap. So we update + * everything if there is any update on any grid. */ + if (any_update) { + for (IrradianceGrid *grid : grid_loaded) { + grid->do_update = true; + } + } + /* Then create brick & grid infos UBOs content. */ { /* Stable sorting of grids. */ @@ -219,8 +236,20 @@ void IrradianceCache::set_view(View & /*view*/) grids_infos_buf_.push_update(); } - /* Upload data for each grid that need to be inserted in the atlas. */ - for (IrradianceGrid *grid : grid_updates) { + /* Upload data for each grid that need to be inserted in the atlas. + * Upload by order of dependency. */ + /* Start at world index to not load any other grid (+1 because we decrement at loop start). */ + int grid_start_index = grid_loaded.size() + 1; + for (auto it = grid_loaded.rbegin(); it != grid_loaded.rend(); ++it) { + grid_start_index--; + + IrradianceGrid *grid = *it; + if (!grid->do_update) { + continue; + } + + grid->do_update = false; + LightProbeGridCacheFrame *cache = grid->cache->grid_static_cache; /* Staging textures are recreated for each light grid to avoid increasing VRAM usage. */ @@ -230,7 +259,7 @@ void IrradianceCache::set_view(View & /*view*/) draw::Texture irradiance_d_tx = {"irradiance_d_tx"}; draw::Texture validity_tx = {"validity_tx"}; - eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ; + eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_MIP_SWIZZLE_VIEW; int3 grid_size = int3(cache->size); if (cache->baking.L0) { irradiance_a_tx.ensure_3d(GPU_RGBA16F, grid_size, usage, (float *)cache->baking.L0); @@ -279,6 +308,32 @@ void IrradianceCache::set_view(View & /*view*/) validity_tx.ensure_3d(GPU_R16F, int3(1), usage, zero); } + bool visibility_available = cache->visibility.L0 != nullptr; + bool is_baking = cache->irradiance.L0 == nullptr; + + draw::Texture visibility_a_tx = {"visibility_a_tx"}; + draw::Texture visibility_b_tx = {"visibility_b_tx"}; + draw::Texture visibility_c_tx = {"visibility_c_tx"}; + draw::Texture visibility_d_tx = {"visibility_d_tx"}; + if (visibility_available) { + visibility_a_tx.ensure_3d(GPU_R16F, grid_size, usage, (float *)cache->visibility.L0); + visibility_b_tx.ensure_3d(GPU_R16F, grid_size, usage, (float *)cache->visibility.L1_a); + visibility_c_tx.ensure_3d(GPU_R16F, grid_size, usage, (float *)cache->visibility.L1_b); + visibility_d_tx.ensure_3d(GPU_R16F, grid_size, usage, (float *)cache->visibility.L1_c); + + GPU_texture_swizzle_set(visibility_a_tx, "111r"); + GPU_texture_swizzle_set(visibility_b_tx, "111r"); + GPU_texture_swizzle_set(visibility_c_tx, "111r"); + GPU_texture_swizzle_set(visibility_d_tx, "111r"); + } + else if (!is_baking) { + /* Missing visibility. Load default visibility L0 = 1, L1 = (0, 0, 0). */ + GPU_texture_swizzle_set(irradiance_a_tx, "rgb1"); + GPU_texture_swizzle_set(irradiance_b_tx, "rgb0"); + GPU_texture_swizzle_set(irradiance_c_tx, "rgb0"); + GPU_texture_swizzle_set(irradiance_d_tx, "rgb0"); + } + grid_upload_ps_.init(); grid_upload_ps_.shader_set(inst_.shaders.static_shader_get(LIGHTPROBE_IRRADIANCE_LOAD)); @@ -286,6 +341,8 @@ void IrradianceCache::set_view(View & /*view*/) grid_upload_ps_.push_constant("dilation_threshold", grid->dilation_threshold); grid_upload_ps_.push_constant("dilation_radius", grid->dilation_radius); grid_upload_ps_.push_constant("grid_index", grid->grid_index); + grid_upload_ps_.push_constant("grid_start_index", grid_start_index); + grid_upload_ps_.push_constant("grid_local_to_world", grid->object_to_world); grid_upload_ps_.bind_ubo("grids_infos_buf", &grids_infos_buf_); grid_upload_ps_.bind_ssbo("bricks_infos_buf", &bricks_infos_buf_); grid_upload_ps_.bind_texture("irradiance_a_tx", &irradiance_a_tx); @@ -294,10 +351,22 @@ void IrradianceCache::set_view(View & /*view*/) grid_upload_ps_.bind_texture("irradiance_d_tx", &irradiance_d_tx); grid_upload_ps_.bind_texture("validity_tx", &validity_tx); grid_upload_ps_.bind_image("irradiance_atlas_img", &irradiance_atlas_tx_); + /* NOTE: We are read and writting the same texture that we are sampling from. If that causes an + * issue, we should revert to manual trilinear interpolation. */ + grid_upload_ps_.bind_texture("irradiance_atlas_tx", &irradiance_atlas_tx_); + /* If visibility is invalid, either it is still baking and visibility is stored with + * irradiance, or it is missing and we sample a completely uniform visibility. */ + bool use_vis = visibility_available; + grid_upload_ps_.bind_texture("visibility_a_tx", use_vis ? &visibility_a_tx : &irradiance_a_tx); + grid_upload_ps_.bind_texture("visibility_b_tx", use_vis ? &visibility_b_tx : &irradiance_b_tx); + grid_upload_ps_.bind_texture("visibility_c_tx", use_vis ? &visibility_c_tx : &irradiance_c_tx); + grid_upload_ps_.bind_texture("visibility_d_tx", use_vis ? &visibility_d_tx : &irradiance_d_tx); /* Note that we take into account the padding border of each brick. */ int3 grid_size_in_bricks = math::divide_ceil(grid_size, int3(IRRADIANCE_GRID_BRICK_SIZE - 1)); grid_upload_ps_.dispatch(grid_size_in_bricks); + /* Sync with next load. */ + grid_upload_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH); inst_.manager->submit(grid_upload_ps_); @@ -308,6 +377,7 @@ void IrradianceCache::set_view(View & /*view*/) } do_full_update_ = false; + do_update_world_ = false; } void IrradianceCache::viewport_draw(View &view, GPUFrameBuffer *view_fb) @@ -330,6 +400,9 @@ void IrradianceCache::debug_pass_draw(View &view, GPUFrameBuffer *view_fb) case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE: inst_.info = "Debug Mode: Surfels Irradiance"; break; + case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_VISIBILITY: + inst_.info = "Debug Mode: Surfels Visibility"; + break; case eDebugMode::DEBUG_IRRADIANCE_CACHE_VALIDITY: inst_.info = "Debug Mode: Irradiance Validity"; break; @@ -351,6 +424,7 @@ void IrradianceCache::debug_pass_draw(View &view, GPUFrameBuffer *view_fb) switch (inst_.debug_mode) { case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL: case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_CLUSTER: + case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_VISIBILITY: case eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE: { if (cache->surfels == nullptr || cache->surfels_len == 0) { continue; @@ -544,6 +618,9 @@ void IrradianceBake::init(const Object &probe_object) surfel_density_ = lightprobe->surfel_density; min_distance_to_surface_ = lightprobe->grid_surface_bias; max_virtual_offset_ = lightprobe->grid_escape_bias; + capture_world_ = (lightprobe->grid_flag & LIGHTPROBE_GRID_CAPTURE_WORLD); + capture_indirect_ = (lightprobe->grid_flag & LIGHTPROBE_GRID_CAPTURE_INDIRECT); + capture_emission_ = (lightprobe->grid_flag & LIGHTPROBE_GRID_CAPTURE_EMISSION); } void IrradianceBake::sync() @@ -686,6 +763,14 @@ void IrradianceBake::surfels_create(const Object &probe_object) int3 grid_resolution = int3(&lightprobe->grid_resolution_x); float4x4 grid_local_to_world = invert(float4x4(probe_object.world_to_object)); + /* TODO(fclem): Options. */ + capture_info_buf_.capture_world_direct = capture_world_; + capture_info_buf_.capture_world_indirect = capture_world_ && capture_indirect_; + capture_info_buf_.capture_visibility_direct = !capture_world_; + capture_info_buf_.capture_visibility_indirect = !(capture_world_ && capture_indirect_); + capture_info_buf_.capture_indirect = capture_indirect_; + capture_info_buf_.capture_emission = capture_emission_; + dispatch_per_grid_sample_ = math::divide_ceil(grid_resolution, int3(IRRADIANCE_GRID_GROUP_SIZE)); capture_info_buf_.irradiance_grid_size = grid_resolution; capture_info_buf_.irradiance_grid_local_to_world = grid_local_to_world; @@ -959,7 +1044,8 @@ void IrradianceBake::read_surfels(LightProbeGridCacheFrame *cache_frame) if (!ELEM(inst_.debug_mode, eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_CLUSTER, eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL, - eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE)) + eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE, + eDebugMode::DEBUG_IRRADIANCE_CACHE_SURFELS_VISIBILITY)) { return; } @@ -1039,12 +1125,23 @@ LightProbeGridCacheFrame *IrradianceBake::read_result_packed() cache_frame->irradiance.L1_c = (float(*)[3])MEM_mallocN(coefficient_texture_size, __func__); cache_frame->connectivity.validity = (uint8_t *)MEM_mallocN(validity_texture_size, __func__); + size_t visibility_texture_size = sizeof(*cache_frame->irradiance.L0) * sample_count; + cache_frame->visibility.L0 = (float *)MEM_mallocN(visibility_texture_size, __func__); + cache_frame->visibility.L1_a = (float *)MEM_mallocN(visibility_texture_size, __func__); + cache_frame->visibility.L1_b = (float *)MEM_mallocN(visibility_texture_size, __func__); + cache_frame->visibility.L1_c = (float *)MEM_mallocN(visibility_texture_size, __func__); + /* TODO(fclem): This could be done on GPU if that's faster. */ for (auto i : IndexRange(sample_count)) { copy_v3_v3(cache_frame->irradiance.L0[i], cache_frame->baking.L0[i]); copy_v3_v3(cache_frame->irradiance.L1_a[i], cache_frame->baking.L1_a[i]); copy_v3_v3(cache_frame->irradiance.L1_b[i], cache_frame->baking.L1_b[i]); copy_v3_v3(cache_frame->irradiance.L1_c[i], cache_frame->baking.L1_c[i]); + + cache_frame->visibility.L0[i] = cache_frame->baking.L0[i][3]; + cache_frame->visibility.L1_a[i] = cache_frame->baking.L1_a[i][3]; + cache_frame->visibility.L1_b[i] = cache_frame->baking.L1_b[i][3]; + cache_frame->visibility.L1_c[i] = cache_frame->baking.L1_c[i][3]; cache_frame->connectivity.validity[i] = unit_float_to_uchar_clamp( cache_frame->baking.validity[i]); } @@ -1055,12 +1152,6 @@ LightProbeGridCacheFrame *IrradianceBake::read_result_packed() MEM_SAFE_FREE(cache_frame->baking.L1_c); MEM_SAFE_FREE(cache_frame->baking.validity); - // cache_frame->visibility.L0 = irradiance_only_L0_tx_.read(GPU_DATA_UBYTE); - // cache_frame->visibility.L1_a = irradiance_only_L1_a_tx_.read(GPU_DATA_UBYTE); - // cache_frame->visibility.L1_b = irradiance_only_L1_b_tx_.read(GPU_DATA_UBYTE); - // cache_frame->visibility.L1_c = irradiance_only_L1_c_tx_.read(GPU_DATA_UBYTE); - // cache_frame->connectivity.validity = validity_packed_.read(GPU_DATA_FLOAT); - return cache_frame; } 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 0418fa19e5d..945135afc6e 100644 --- a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh +++ b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh @@ -119,6 +119,13 @@ class IrradianceBake { */ float max_virtual_offset_ = 0.1f; + /** True if world lighting is recorded during irradiance capture. */ + bool capture_world_ = false; + /** True if indirect lighting is recorded during the light propagation. */ + bool capture_indirect_ = false; + /** True if emission is recorded during the light propagation. */ + bool capture_emission_ = false; + public: IrradianceBake(Instance &inst) : inst_(inst){}; @@ -170,6 +177,10 @@ class IrradianceCache { public: IrradianceBake bake; + /** True if world irradiance need to be updated. */ + /* TODO(fclem): move to private once world irradiance extraction is moved to irradiance cache. */ + bool do_update_world_ = true; + private: Instance &inst_; diff --git a/source/blender/draw/engines/eevee_next/eevee_lightprobe.hh b/source/blender/draw/engines/eevee_next/eevee_lightprobe.hh index bcf4cf93b57..066bab97622 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lightprobe.hh +++ b/source/blender/draw/engines/eevee_next/eevee_lightprobe.hh @@ -40,6 +40,8 @@ struct IrradianceGrid : public LightProbe, IrradianceGridData { const LightProbeObjectCache *cache = nullptr; /** List of associated atlas bricks that are used by this grid. */ Vector bricks; + /** True if the grid needs to be reuploaded & re-composited with other light-grids. */ + bool do_update; /** Index of the grid inside the grid UBO. */ int grid_index; /** Copy of surfel density for debugging purpose. */ 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 a427ff8d405..d6f0f7bad24 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc @@ -397,6 +397,7 @@ 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; + instance_.irradiance_cache.do_update_world_ = true; } void ReflectionProbeModule::do_world_update_irradiance_set(bool value) 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 09889bb0159..56c90f013e4 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh @@ -113,6 +113,7 @@ class ReflectionProbeModule { * NOTE: TextureFromPool doesn't support cube-maps. */ Texture cubemap_tx_ = {"Probe.Cubemap"}; + /** Index of the probe being updated. */ int reflection_probe_index_ = 0; bool update_probes_next_sample_ = false; 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 7db4b67beb6..21cb3c1744b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -55,12 +55,13 @@ enum eDebugMode : uint32_t { */ DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL = 3u, DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE = 4u, - DEBUG_IRRADIANCE_CACHE_SURFELS_CLUSTER = 5u, + DEBUG_IRRADIANCE_CACHE_SURFELS_VISIBILITY = 5u, + DEBUG_IRRADIANCE_CACHE_SURFELS_CLUSTER = 6u, /** * Display IrradianceCache virtual offset. */ - DEBUG_IRRADIANCE_CACHE_VIRTUAL_OFFSET = 6u, - DEBUG_IRRADIANCE_CACHE_VALIDITY = 7u, + DEBUG_IRRADIANCE_CACHE_VIRTUAL_OFFSET = 7u, + DEBUG_IRRADIANCE_CACHE_VALIDITY = 8u, /** * Show tiles depending on their status. */ @@ -930,8 +931,14 @@ static inline ShadowTileDataPacked shadow_tile_pack(ShadowTileData tile) * \{ */ struct SurfelRadiance { + /* Actually stores radiance and world (sky) visibility. Stored normalized. */ float4 front; float4 back; + /* Accumulated weights per face. */ + float front_weight; + float back_weight; + float _pad0; + float _pad1; }; BLI_STATIC_ASSERT_ALIGN(SurfelRadiance, 16) @@ -992,6 +999,14 @@ struct CaptureInfoData { float max_virtual_offset; /** Radius of surfels. */ float surfel_radius; + /** Capture options. */ + bool1 capture_world_direct; + bool1 capture_world_indirect; + bool1 capture_visibility_direct; + bool1 capture_visibility_indirect; + bool1 capture_indirect; + bool1 capture_emission; + int _pad0; }; BLI_STATIC_ASSERT_ALIGN(CaptureInfoData, 16) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_frag.glsl index 1248f8fb462..b1f71a6ef91 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_frag.glsl @@ -12,10 +12,10 @@ void main() { Surfel surfel = surfels_buf[surfel_index]; - vec3 radiance = vec3(0.0); - radiance += gl_FrontFacing ? surfel.radiance_direct.front.rgb : surfel.radiance_direct.back.rgb; - radiance += gl_FrontFacing ? surfel.radiance_indirect[1].front.rgb : - surfel.radiance_indirect[1].back.rgb; + vec4 radiance_vis = vec4(0.0); + radiance_vis += gl_FrontFacing ? surfel.radiance_direct.front : surfel.radiance_direct.back; + radiance_vis += gl_FrontFacing ? surfel.radiance_indirect[1].front : + surfel.radiance_indirect[1].back; switch (eDebugMode(debug_mode)) { default: @@ -26,7 +26,10 @@ void main() out_color = vec4(pow(debug_random_color(surfel.cluster_id), vec3(2.2)), 0.0); break; case DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE: - out_color = vec4(radiance, 0.0); + out_color = vec4(radiance_vis.rgb, 0.0); + break; + case DEBUG_IRRADIANCE_CACHE_SURFELS_VISIBILITY: + out_color = vec4(radiance_vis.aaa, 0.0); break; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl index 2e4578d340f..d3374f10c69 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl @@ -6,32 +6,42 @@ * - irradiance_atlas_tx */ +#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) #pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl) #pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) /** - * Return sample coordinates of the first SH coef in unormalized texture space. + * Return the brick coordinate inside the grid. */ -vec3 lightprobe_irradiance_grid_atlas_coord(IrradianceGridData grid_data, - vec3 lP, - vec3 lV, - vec3 lNg) +ivec3 lightprobe_irradiance_grid_brick_coord(vec3 lP) { - /* Shading point bias. */ - lP += lNg * grid_data.normal_bias; - lP += lV * grid_data.view_bias; - ivec3 brick_coord = ivec3((lP - 0.5) / float(IRRADIANCE_GRID_BRICK_SIZE - 1)); /* Avoid sampling adjacent bricks. */ - brick_coord = max(brick_coord, ivec3(0)); - /* Avoid sampling adjacent bricks. */ + return max(brick_coord, ivec3(0)); +} + +/** + * Return the local coordinated of the shading point inside the brick in unormalized coordinate. + */ +vec3 lightprobe_irradiance_grid_brick_local_coord(IrradianceGridData grid_data, + vec3 lP, + ivec3 brick_coord) +{ + /* Avoid sampling adjacent bricks around the origin. */ lP = max(lP, vec3(0.5)); /* Local position inside the brick (still in grid sample spacing unit). */ vec3 brick_lP = lP - vec3(brick_coord) * float(IRRADIANCE_GRID_BRICK_SIZE - 1); + return brick_lP; +} - int brick_index = lightprobe_irradiance_grid_brick_index_get(grid_data, brick_coord); - IrradianceBrick brick = irradiance_brick_unpack(bricks_infos_buf[brick_index]); - +/** + * Return the biased local brick local coordinated. + */ +vec3 lightprobe_irradiance_grid_bias_sample_coord(IrradianceGridData grid_data, + uvec2 brick_atlas_coord, + vec3 brick_lP, + vec3 lNg) +{ /* A cell is the interpolation region between 8 texels. */ vec3 cell_lP = brick_lP - 0.5; vec3 cell_start = floor(cell_lP); @@ -40,7 +50,7 @@ vec3 lightprobe_irradiance_grid_atlas_coord(IrradianceGridData grid_data, /* NOTE(fclem): Use uint to avoid signed int modulo. */ uint vis_comp = uint(cell_start.z) % 4u; /* Visibility is stored after the irradiance. */ - ivec3 vis_coord = ivec3(brick.atlas_coord, IRRADIANCE_GRID_BRICK_SIZE * 4) + ivec3(cell_start); + ivec3 vis_coord = ivec3(brick_atlas_coord, IRRADIANCE_GRID_BRICK_SIZE * 4) + ivec3(cell_start); /* Visibility is stored packed 1 cell per channel. */ vis_coord.z -= int(vis_comp); float cell_visibility = texelFetch(irradiance_atlas_tx, vis_coord, 0)[vis_comp]; @@ -75,7 +85,7 @@ vec3 lightprobe_irradiance_grid_atlas_coord(IrradianceGridData grid_data, /* Biases. See McGuire's presentation. */ positional_weight += 0.001; - geometry_weight = sqr(geometry_weight) + 0.2 + grid_data.facing_bias; + geometry_weight = square_f(geometry_weight) + 0.2 + grid_data.facing_bias; trilinear_weights[i] = saturate(positional_weight * geometry_weight * validity_weight); total_weight += trilinear_weights[i]; @@ -88,23 +98,18 @@ vec3 lightprobe_irradiance_grid_atlas_coord(IrradianceGridData grid_data, trilinear_coord += sample_position * trilinear_weights[i] * total_weight_inv; } /* Replace sampling coordinates with manually weighted trilinear coordinates. */ - brick_lP = 0.5 + cell_start + trilinear_coord; - - vec3 output_coord = vec3(vec2(brick.atlas_coord), 0.0) + brick_lP; - - return output_coord; + return 0.5 + cell_start + trilinear_coord; } -vec4 textureUnormalizedCoord(sampler3D tx, vec3 co) -{ - return texture(tx, co / vec3(textureSize(tx, 0))); -} - -SphericalHarmonicL1 lightprobe_irradiance_sample(sampler3D atlas_tx, vec3 P, vec3 V, vec3 Ng) +SphericalHarmonicL1 lightprobe_irradiance_sample( + sampler3D atlas_tx, vec3 P, vec3 V, vec3 Ng, const bool do_bias) { vec3 lP; - int grid_index; - for (grid_index = 0; grid_index < IRRADIANCE_GRID_MAX; grid_index++) { + int grid_index = 0; +#ifdef IRRADIANCE_GRID_UPLOAD + grid_index = grid_start_index; +#endif + for (; grid_index < IRRADIANCE_GRID_MAX; grid_index++) { /* Last grid is tagged as invalid to stop the iteration. */ if (grids_infos_buf[grid_index].grid_size.x == -1) { /* Sample the last grid instead. */ @@ -117,13 +122,33 @@ SphericalHarmonicL1 lightprobe_irradiance_sample(sampler3D atlas_tx, vec3 P, vec } } + IrradianceGridData grid_data = grids_infos_buf[grid_index]; + /* TODO(fclem): Make sure this is working as expected. */ - mat3x3 world_to_grid_transposed = mat3x3(grids_infos_buf[grid_index].world_to_grid_transposed); + mat3x3 world_to_grid_transposed = mat3x3(grid_data.world_to_grid_transposed); vec3 lNg = safe_normalize(world_to_grid_transposed * Ng); vec3 lV = safe_normalize(V * world_to_grid_transposed); - vec3 atlas_coord = lightprobe_irradiance_grid_atlas_coord( - grids_infos_buf[grid_index], lP, lV, lNg); + if (do_bias) { + /* Shading point bias. */ + lP += lNg * grid_data.normal_bias; + lP += lV * grid_data.view_bias; + } + else { + lNg = vec3(0.0); + } + + ivec3 brick_coord = lightprobe_irradiance_grid_brick_coord(lP); + int brick_index = lightprobe_irradiance_grid_brick_index_get(grid_data, brick_coord); + IrradianceBrick brick = irradiance_brick_unpack(bricks_infos_buf[brick_index]); + + vec3 brick_lP = lightprobe_irradiance_grid_brick_local_coord(grid_data, lP, brick_coord); + + /* Sampling point bias. */ + brick_lP = lightprobe_irradiance_grid_bias_sample_coord( + grid_data, brick.atlas_coord, brick_lP, lNg); + + vec3 atlas_coord = vec3(vec2(brick.atlas_coord), 0.0) + brick_lP; vec4 texture_coord = vec4(atlas_coord, float(IRRADIANCE_GRID_BRICK_SIZE)) / vec3(textureSize(atlas_tx, 0)).xyzz; @@ -138,6 +163,14 @@ SphericalHarmonicL1 lightprobe_irradiance_sample(sampler3D atlas_tx, vec3 P, vec return sh; } +/** + * Shorter version without bias. + */ +SphericalHarmonicL1 lightprobe_irradiance_sample(vec3 P) +{ + return lightprobe_irradiance_sample(irradiance_atlas_tx, P, vec3(0), vec3(0), false); +} + void lightprobe_eval(ClosureDiffuse diffuse, ClosureReflection reflection, vec3 P, @@ -149,7 +182,7 @@ void lightprobe_eval(ClosureDiffuse diffuse, /* NOTE: Use the diffuse normal for biasing the probe sampling location since it is smoother than * geometric normal. Could also try to use interp.N. */ SphericalHarmonicL1 irradiance = lightprobe_irradiance_sample( - irradiance_atlas_tx, P, V, diffuse.N); + irradiance_atlas_tx, P, V, diffuse.N, true); out_diffuse += spherical_harmonics_evaluate_lambert(diffuse.N, irradiance); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_load_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_load_comp.glsl index 8cf49139568..4ebd8e0213d 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_load_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_load_comp.glsl @@ -1,6 +1,7 @@ /** * Load an input lightgrid cache texture into the atlas. + * Takes care of dilating valid lighting into invalid samples and composite lightprobes. * * Each thread group will load a brick worth of data and add the needed padding texels. */ @@ -10,7 +11,7 @@ #pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) #pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) void atlas_store(vec4 sh_coefficient, ivec2 atlas_coord, int layer) { @@ -22,11 +23,18 @@ void atlas_store(vec4 sh_coefficient, ivec2 atlas_coord, int layer) SphericalHarmonicL1 irradiance_load(ivec3 input_coord) { input_coord = clamp(input_coord, ivec3(0), textureSize(irradiance_a_tx, 0) - 1); + SphericalHarmonicL1 sh; sh.L0.M0 = texelFetch(irradiance_a_tx, input_coord, 0); sh.L1.Mn1 = texelFetch(irradiance_b_tx, input_coord, 0); sh.L1.M0 = texelFetch(irradiance_c_tx, input_coord, 0); sh.L1.Mp1 = texelFetch(irradiance_d_tx, input_coord, 0); + + /* Load visibility separately as it might not be in the same texture. */ + sh.L0.M0.a = texelFetch(visibility_a_tx, input_coord, 0).a; + sh.L1.Mn1.a = texelFetch(visibility_b_tx, input_coord, 0).a; + sh.L1.M0.a = texelFetch(visibility_c_tx, input_coord, 0).a; + sh.L1.Mp1.a = texelFetch(visibility_d_tx, input_coord, 0).a; return sh; } @@ -34,6 +42,7 @@ void main() { int brick_index = lightprobe_irradiance_grid_brick_index_get(grids_infos_buf[grid_index], ivec3(gl_WorkGroupID)); + /* Brick coordinate in the source grid. */ ivec3 brick_coord = ivec3(gl_WorkGroupID); /* Add padding border to allow bilinear filtering. */ @@ -44,19 +53,19 @@ void main() IrradianceBrick brick = irradiance_brick_unpack(bricks_infos_buf[brick_index]); ivec2 output_coord = ivec2(brick.atlas_coord); - SphericalHarmonicL1 sh; + SphericalHarmonicL1 sh_local; float validity = texelFetch(validity_tx, input_coord, 0).r; if (validity > dilation_threshold) { /* Grid sample is valid. Simgle load. */ - sh = irradiance_load(input_coord); + sh_local = irradiance_load(input_coord); } else { /* Grid sample is invalid. Dilate adjacent samples inside the search region. */ /* NOTE: Still load the center sample and give it low weight in case there is not valid sample * in the neighborhood. */ float weight_accum = 1e-8; - sh = spherical_harmonics_mul(irradiance_load(input_coord), weight_accum); + sh_local = spherical_harmonics_mul(irradiance_load(input_coord), weight_accum); int radius = int(dilation_radius); for (int x = -radius; x <= radius; x++) { for (int y = -radius; y <= radius; y++) { @@ -76,24 +85,38 @@ void main() continue; } float weight = 1.0 / dist_sqr; - sh = spherical_harmonics_madd(irradiance_load(neighbor_coord), weight, sh); + sh_local = spherical_harmonics_madd(irradiance_load(neighbor_coord), weight, sh_local); weight_accum += weight; } } } float inv_weight_accum = safe_rcp(weight_accum); - sh = spherical_harmonics_mul(sh, inv_weight_accum); + sh_local = spherical_harmonics_mul(sh_local, inv_weight_accum); } /* Rotate Spherical Harmonic into world space. */ - mat3 world_to_grid_transposed = mat3(grids_infos_buf[grid_index].world_to_grid_transposed); - mat3 rotation = normalize(world_to_grid_transposed); - spherical_harmonics_L1_rotate(rotation, sh.L1); + mat3 grid_to_world_rot = normalize(mat3(grids_infos_buf[grid_index].world_to_grid_transposed)); + sh_local = spherical_harmonics_rotate(grid_to_world_rot, sh_local); - atlas_store(sh.L0.M0, output_coord, 0); - atlas_store(sh.L1.Mn1, output_coord, 1); - atlas_store(sh.L1.M0, output_coord, 2); - atlas_store(sh.L1.Mp1, output_coord, 3); + SphericalHarmonicL1 sh_visibility; + sh_visibility.L0.M0 = sh_local.L0.M0.aaaa; + sh_visibility.L1.Mn1 = sh_local.L1.Mn1.aaaa; + sh_visibility.L1.M0 = sh_local.L1.M0.aaaa; + sh_visibility.L1.Mp1 = sh_local.L1.Mp1.aaaa; + + vec3 P = lightprobe_irradiance_grid_sample_position( + grid_local_to_world, grids_infos_buf[grid_index].grid_size, input_coord); + + SphericalHarmonicL1 sh_distant = lightprobe_irradiance_sample(P); + /* Mask distant lighting by local visibility. */ + sh_distant = spherical_harmonics_triple_product(sh_visibility, sh_distant); + /* Add local lighting to distant lighting. */ + sh_local = spherical_harmonics_add(sh_local, sh_distant); + + atlas_store(sh_local.L0.M0, output_coord, 0); + atlas_store(sh_local.L1.Mn1, output_coord, 1); + atlas_store(sh_local.L1.M0, output_coord, 2); + atlas_store(sh_local.L1.Mp1, output_coord, 3); if (gl_LocalInvocationID.z % 4 == 0u) { /* Encode 4 cells into one volume sample. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl index 4cd4803310f..b27480e9bea 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl @@ -15,47 +15,59 @@ #pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl) #pragma BLENDER_REQUIRE(common_math_lib.glsl) -void irradiance_capture(vec3 L, vec3 irradiance, inout SphericalHarmonicL1 sh) +void irradiance_capture(vec3 L, vec3 irradiance, float visibility, inout SphericalHarmonicL1 sh) { vec3 lL = transform_direction(capture_info_buf.irradiance_grid_world_to_local_rotation, L); /* Spherical harmonics need to be weighted by sphere area. */ irradiance *= 4.0 * M_PI; + visibility *= 4.0 * M_PI; - spherical_harmonics_encode_signal_sample(lL, vec4(irradiance, 1.0), sh); + spherical_harmonics_encode_signal_sample(lL, vec4(irradiance, visibility), sh); } -void irradiance_capture(Surfel surfel, vec3 P, inout SphericalHarmonicL1 sh) +void irradiance_capture_surfel(Surfel surfel, vec3 P, inout SphericalHarmonicL1 sh) { vec3 L = safe_normalize(surfel.position - P); bool facing = dot(-L, surfel.normal) > 0.0; SurfelRadiance surfel_radiance_indirect = surfel.radiance_indirect[radiance_src]; - vec3 irradiance = vec3(0.0); - irradiance += facing ? surfel.radiance_direct.front.rgb : surfel.radiance_direct.back.rgb; + vec4 irradiance_vis = vec4(0.0); + irradiance_vis += facing ? surfel.radiance_direct.front : surfel.radiance_direct.back; /* NOTE: The indirect radiance is already normalized and this is wanted, because we are not * integrating the same signal and we would have the SH lagging behind the surfel integration * otherwise. */ - irradiance += facing ? surfel_radiance_indirect.front.rgb : surfel_radiance_indirect.back.rgb; + irradiance_vis += facing ? surfel_radiance_indirect.front : surfel_radiance_indirect.back; - irradiance_capture(L, irradiance, sh); + irradiance_capture(L, irradiance_vis.rgb, irradiance_vis.a, sh); } -void validity_capture(Surfel surfel, vec3 P, inout float validity) +void validity_capture_surfel(Surfel surfel, vec3 P, inout float validity) { vec3 L = safe_normalize(surfel.position - P); bool facing = dot(-L, surfel.normal) > 0.0; validity += float(facing); } -void validity_capture(vec3 L, inout float validity) +void validity_capture_world(vec3 L, inout float validity) { validity += 1.0; } -vec3 irradiance_sky_sample(vec3 R) +void irradiance_capture_world(vec3 L, inout SphericalHarmonicL1 sh) { - return reflection_probes_world_sample(R, 0.0).rgb; + vec3 radiance = vec3(0.0); + float visibility = 0.0; + + if (capture_info_buf.capture_world_direct) { + radiance = reflection_probes_world_sample(L, 0.0).rgb; + } + + if (capture_info_buf.capture_visibility_direct) { + visibility = 1.0; + } + + irradiance_capture(L, radiance, visibility, sh); } void main() @@ -109,24 +121,22 @@ void main() if (surfel_next > -1) { Surfel surfel = surfel_buf[surfel_next]; - irradiance_capture(surfel, P, sh); - validity_capture(surfel, P, validity); + irradiance_capture_surfel(surfel, P, sh); + validity_capture_surfel(surfel, P, validity); } else { - vec3 world_radiance = irradiance_sky_sample(-sky_L); - irradiance_capture(-sky_L, world_radiance, sh); - validity_capture(-sky_L, validity); + irradiance_capture_world(-sky_L, sh); + validity_capture_world(-sky_L, validity); } if (surfel_prev > -1) { Surfel surfel = surfel_buf[surfel_prev]; - irradiance_capture(surfel, P, sh); - validity_capture(surfel, P, validity); + irradiance_capture_surfel(surfel, P, sh); + validity_capture_surfel(surfel, P, validity); } else { - vec3 world_radiance = irradiance_sky_sample(sky_L); - irradiance_capture(sky_L, world_radiance, sh); - validity_capture(sky_L, validity); + irradiance_capture_world(sky_L, sh); + validity_capture_world(sky_L, validity); } /* Normalize for storage. We accumulated 2 samples. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl index 0a7c676d6bd..7c8adb66afd 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl @@ -194,13 +194,14 @@ vec4 spherical_harmonics_L2_evaluate(vec3 direction, SphericalHarmonicBandL2 L2) /** \name Rotation * \{ */ -void spherical_harmonics_L0_rotate(mat3x3 rotation, inout SphericalHarmonicBandL0 L0) +SphericalHarmonicBandL0 spherical_harmonics_L0_rotate(mat3x3 rotation, SphericalHarmonicBandL0 L0) { /* L0 band being a constant function (i.e: there is no directionallity) there is nothing to * rotate. This is a no-op. */ + return L0; } -void spherical_harmonics_L1_rotate(mat3x3 rotation, inout SphericalHarmonicBandL1 L1) +SphericalHarmonicBandL1 spherical_harmonics_L1_rotate(mat3x3 rotation, SphericalHarmonicBandL1 L1) { /* Convert L1 coefficients to per channel column. * Note the component shuffle to match blender coordinate system. */ @@ -209,17 +210,21 @@ void spherical_harmonics_L1_rotate(mat3x3 rotation, inout SphericalHarmonicBandL per_channel[0] = rotation * per_channel[0]; per_channel[1] = rotation * per_channel[1]; per_channel[2] = rotation * per_channel[2]; - /* Convert back to L1 coefficients to per channel column. + per_channel[3] = rotation * per_channel[3]; + /* Convert back from L1 coefficients to per channel column. * Note the component shuffle to match blender coordinate system. */ mat3x4 per_coef = transpose(per_channel); L1.Mn1 = per_coef[1]; L1.M0 = -per_coef[2]; L1.Mp1 = per_coef[0]; + return L1; } -void spherical_harmonics_L2_rotate(mat3x3 rotation, inout SphericalHarmonicBandL2 L2) +SphericalHarmonicL1 spherical_harmonics_rotate(mat3x3 rotation, SphericalHarmonicL1 sh) { - /* TODO */ + sh.L0 = spherical_harmonics_L0_rotate(rotation, sh.L0); + sh.L1 = spherical_harmonics_L1_rotate(rotation, sh.L1); + return sh; } /** \} */ @@ -329,6 +334,37 @@ void spherical_harmonics_pack(SphericalHarmonicL1 sh, /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Triple Product + * \{ */ + +SphericalHarmonicL1 spherical_harmonics_triple_product(SphericalHarmonicL1 a, + SphericalHarmonicL1 b) +{ + /** + * Addapted from : + * "Code Generation and Factoring for Fast Evaluation of Low-order Spherical Harmonic Products + * and Squares" Function "SH_product_3" + */ + SphericalHarmonicL1 sh; + sh.L0.M0 = 0.282094792 * a.L0.M0 * b.L0.M0; + + vec4 ta = 0.282094791 * a.L0.M0; + vec4 tb = 0.282094791 * b.L0.M0; + + sh.L1.Mn1 = ta * b.L1.Mn1 + tb * a.L1.Mn1; + sh.L0.M0 += 0.282094791 * (a.L1.Mn1 * b.L1.Mn1); + + sh.L1.M0 += ta * b.L1.M0 + tb * a.L1.M0; + sh.L0.M0 += 0.282094795 * (a.L1.M0 * b.L1.M0); + + sh.L1.Mp1 += ta * b.L1.Mp1 + tb * a.L1.Mp1; + sh.L0.M0 += 0.282094791 * (a.L1.Mp1 * b.L1.Mp1); + return sh; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Multiply Add * \{ */ @@ -447,3 +483,63 @@ SphericalHarmonicL2 spherical_harmonics_mul(SphericalHarmonicL2 a, float b) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Add + * \{ */ + +SphericalHarmonicBandL0 spherical_harmonics_L0_add(SphericalHarmonicBandL0 a, + SphericalHarmonicBandL0 b) +{ + SphericalHarmonicBandL0 result; + result.M0 = a.M0 + b.M0; + return result; +} + +SphericalHarmonicBandL1 spherical_harmonics_L1_add(SphericalHarmonicBandL1 a, + SphericalHarmonicBandL1 b) +{ + SphericalHarmonicBandL1 result; + result.Mn1 = a.Mn1 + b.Mn1; + result.M0 = a.M0 + b.M0; + result.Mp1 = a.Mp1 + b.Mp1; + return result; +} + +SphericalHarmonicBandL2 spherical_harmonics_L2_add(SphericalHarmonicBandL2 a, + SphericalHarmonicBandL2 b) +{ + SphericalHarmonicBandL2 result; + result.Mn2 = a.Mn2 + b.Mn2; + result.Mn1 = a.Mn1 + b.Mn1; + result.M0 = a.M0 + b.M0; + result.Mp1 = a.Mp1 + b.Mp1; + result.Mp2 = a.Mp2 + b.Mp2; + return result; +} + +SphericalHarmonicL0 spherical_harmonics_add(SphericalHarmonicL0 a, SphericalHarmonicL0 b) +{ + SphericalHarmonicL0 result; + result.L0 = spherical_harmonics_L0_add(a.L0, b.L0); + return result; +} + +SphericalHarmonicL1 spherical_harmonics_add(SphericalHarmonicL1 a, SphericalHarmonicL1 b) +{ + SphericalHarmonicL1 result; + result.L0 = spherical_harmonics_L0_add(a.L0, b.L0); + result.L1 = spherical_harmonics_L1_add(a.L1, b.L1); + return result; +} + +SphericalHarmonicL2 spherical_harmonics_add(SphericalHarmonicL2 a, SphericalHarmonicL2 b) +{ + SphericalHarmonicL2 result; + result.L0 = spherical_harmonics_L0_add(a.L0, b.L0); + result.L1 = spherical_harmonics_L1_add(a.L1, b.L1); + result.L2 = spherical_harmonics_L2_add(a.L2, b.L2); + return result; +} + +/** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_capture_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_capture_frag.glsl index 36ea01022ac..8a66961bc0b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_capture_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_capture_frag.glsl @@ -47,9 +47,16 @@ void main() surfel_buf[surfel_id].normal = gl_FrontFacing ? g_data.Ng : -g_data.Ng; surfel_buf[surfel_id].albedo_front = albedo; surfel_buf[surfel_id].radiance_direct.front.rgb = g_emission; + surfel_buf[surfel_id].radiance_direct.front.a = 0.0; /* TODO(fclem): 2nd surface evaluation. */ surfel_buf[surfel_id].albedo_back = albedo; surfel_buf[surfel_id].radiance_direct.back.rgb = g_emission; + surfel_buf[surfel_id].radiance_direct.back.a = 0.0; + + if (!capture_info_buf.capture_emission) { + surfel_buf[surfel_id].radiance_direct.front.rgb = vec3(0.0); + surfel_buf[surfel_id].radiance_direct.back.rgb = vec3(0.0); + } } } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl index 61e109f44c3..2189bba2717 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl @@ -77,7 +77,9 @@ void main() light_eval_surfel(diffuse_data, surfel.position, surfel.normal, thickness, diffuse_light); - surfel_buf[index].radiance_direct.front.rgb += diffuse_light * surfel.albedo_front; + if (capture_info_buf.capture_indirect) { + surfel_buf[index].radiance_direct.front.rgb += diffuse_light * surfel.albedo_front; + } diffuse_data.N = -surfel.normal; diffuse_light = vec3(0.0); @@ -85,5 +87,7 @@ void main() light_eval_surfel(diffuse_data, surfel.position, -surfel.normal, thickness, diffuse_light); - surfel_buf[index].radiance_direct.back.rgb += diffuse_light * surfel.albedo_back; + if (capture_info_buf.capture_indirect) { + surfel_buf[index].radiance_direct.back.rgb += diffuse_light * surfel.albedo_back; + } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl index 3e4204929e9..b1c0335f45f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl @@ -14,34 +14,42 @@ #pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(common_math_lib.glsl) -void radiance_transfer(inout Surfel surfel, vec3 in_radiance, vec3 L) +float avg_albedo(vec3 albedo) +{ + return saturate(dot(albedo, vec3(1.0 / 3.0))); +} + +void radiance_transfer(inout Surfel surfel, vec3 in_radiance, float in_visibility, vec3 L) { float NL = dot(surfel.normal, L); /* Lambertian BSDF. Albedo applied later depending on which side of the surfel was hit. */ - float bsdf = M_1_PI; + const float bsdf = M_1_PI; /* From "Global Illumination using Parallel Global Ray-Bundles" * Eq. 3: Outgoing light */ - vec3 out_radiance = (M_TAU / capture_info_buf.sample_count) * bsdf * in_radiance * abs(NL); + float transfert_fn = (M_TAU / capture_info_buf.sample_count) * bsdf * abs(NL); - SurfelRadiance surfel_radiance_indirect = surfel.radiance_indirect[radiance_dst]; + SurfelRadiance radiance = surfel.radiance_indirect[radiance_dst]; + float sample_weight = 1.0 / capture_info_buf.sample_count; bool front_facing = (NL > 0.0); if (front_facing) { /* Store radiance normalized for spherical harmonic accumulation and for visualization. */ - surfel_radiance_indirect.front.rgb *= surfel_radiance_indirect.front.w; - surfel_radiance_indirect.front += vec4(out_radiance * surfel.albedo_front, - 1.0 / capture_info_buf.sample_count); - surfel_radiance_indirect.front.rgb /= surfel_radiance_indirect.front.w; + radiance.front *= radiance.front_weight; + radiance.front += vec4(in_radiance, in_visibility) * transfert_fn * + vec4(surfel.albedo_front, avg_albedo(surfel.albedo_front)); + radiance.front_weight += sample_weight; + radiance.front /= radiance.front_weight; } else { /* Store radiance normalized for spherical harmonic accumulation and for visualization. */ - surfel_radiance_indirect.back.rgb *= surfel_radiance_indirect.back.w; - surfel_radiance_indirect.back += vec4(out_radiance * surfel.albedo_back, - 1.0 / capture_info_buf.sample_count); - surfel_radiance_indirect.back.rgb /= surfel_radiance_indirect.back.w; + radiance.back *= radiance.back_weight; + radiance.back += vec4(in_radiance, in_visibility) * transfert_fn * + vec4(surfel.albedo_back, avg_albedo(surfel.albedo_back)); + radiance.back_weight += sample_weight; + radiance.back /= radiance.back_weight; } - surfel.radiance_indirect[radiance_dst] = surfel_radiance_indirect; + surfel.radiance_indirect[radiance_dst] = radiance; } void radiance_transfer_surfel(inout Surfel receiver, Surfel sender) @@ -49,29 +57,38 @@ void radiance_transfer_surfel(inout Surfel receiver, Surfel sender) vec3 L = safe_normalize(sender.position - receiver.position); bool front_facing = dot(-L, sender.normal) > 0.0; - vec3 radiance; + vec4 radiance_vis; SurfelRadiance sender_radiance_indirect = sender.radiance_indirect[radiance_src]; if (front_facing) { - radiance = sender.radiance_direct.front.rgb; - radiance += sender_radiance_indirect.front.rgb * sender_radiance_indirect.front.w; + radiance_vis = sender.radiance_direct.front; + radiance_vis += sender_radiance_indirect.front * sender_radiance_indirect.front_weight; } else { - radiance = sender.radiance_direct.back.rgb; - radiance += sender_radiance_indirect.back.rgb * sender_radiance_indirect.back.w; + radiance_vis = sender.radiance_direct.back; + radiance_vis += sender_radiance_indirect.back * sender_radiance_indirect.back_weight; } - radiance_transfer(receiver, radiance, L); + if (!capture_info_buf.capture_indirect) { + radiance_vis.rgb = vec3(0.0); + } + + radiance_transfer(receiver, radiance_vis.rgb, radiance_vis.a, L); } -vec3 radiance_sky_sample(vec3 R) +void radiance_transfer_world(inout Surfel receiver, vec3 L) { - return reflection_probes_world_sample(R, 0.0).rgb; -} + vec3 radiance = vec3(0.0); + float visibility = 0.0; -void radiance_transfer_world(inout Surfel receiver, vec3 sky_L) -{ - vec3 radiance = radiance_sky_sample(-sky_L); - radiance_transfer(receiver, radiance, -sky_L); + if (capture_info_buf.capture_world_indirect) { + radiance = reflection_probes_world_sample(L, 0.0).rgb; + } + + if (capture_info_buf.capture_visibility_indirect) { + visibility = 1.0; + } + + radiance_transfer(receiver, radiance, visibility, L); } void main() @@ -90,7 +107,7 @@ void main() radiance_transfer_surfel(surfel, surfel_next); } else { - radiance_transfer_world(surfel, sky_L); + radiance_transfer_world(surfel, -sky_L); } if (surfel.prev > -1) { @@ -98,7 +115,7 @@ void main() radiance_transfer_surfel(surfel, surfel_prev); } else { - radiance_transfer_world(surfel, -sky_L); + radiance_transfer_world(surfel, sky_L); } surfel_buf[surfel_index] = surfel; diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh index 07d6e17b360..5d1e80742e9 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh @@ -85,7 +85,6 @@ GPU_SHADER_CREATE_INFO(eevee_surfel_light) GPU_SHADER_CREATE_INFO(eevee_surfel_cluster_build) .local_group_size(SURFEL_GROUP_SIZE) .additional_info("eevee_shared", "eevee_surfel_common", "draw_view") - .storage_buf(CAPTURE_BUF_SLOT, Qualifier::READ, "CaptureInfoData", "capture_info_buf") .image(0, GPU_R32I, Qualifier::READ_WRITE, ImageType::INT_3D, "cluster_list_img") .compute_source("eevee_surfel_cluster_build_comp.glsl") .do_static_compilation(true); @@ -169,8 +168,11 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load) .local_group_size(IRRADIANCE_GRID_BRICK_SIZE, IRRADIANCE_GRID_BRICK_SIZE, IRRADIANCE_GRID_BRICK_SIZE) + .define("IRRADIANCE_GRID_UPLOAD") .additional_info("eevee_shared") + .push_constant(Type::MAT4, "grid_local_to_world") .push_constant(Type::INT, "grid_index") + .push_constant(Type::INT, "grid_start_index") .push_constant(Type::FLOAT, "validity_threshold") .push_constant(Type::FLOAT, "dilation_threshold") .push_constant(Type::FLOAT, "dilation_radius") @@ -180,7 +182,12 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load) .sampler(1, ImageType::FLOAT_3D, "irradiance_b_tx") .sampler(2, ImageType::FLOAT_3D, "irradiance_c_tx") .sampler(3, ImageType::FLOAT_3D, "irradiance_d_tx") - .sampler(4, ImageType::FLOAT_3D, "validity_tx") + .sampler(4, ImageType::FLOAT_3D, "visibility_a_tx") + .sampler(5, ImageType::FLOAT_3D, "visibility_b_tx") + .sampler(6, ImageType::FLOAT_3D, "visibility_c_tx") + .sampler(7, ImageType::FLOAT_3D, "visibility_d_tx") + .sampler(8, ImageType::FLOAT_3D, "irradiance_atlas_tx") + .sampler(9, ImageType::FLOAT_3D, "validity_tx") .image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_atlas_img") .compute_source("eevee_lightprobe_irradiance_load_comp.glsl") .do_static_compilation(true); diff --git a/source/blender/makesdna/DNA_lightprobe_defaults.h b/source/blender/makesdna/DNA_lightprobe_defaults.h index 309e6ac2387..8e47bb6eb1a 100644 --- a/source/blender/makesdna/DNA_lightprobe_defaults.h +++ b/source/blender/makesdna/DNA_lightprobe_defaults.h @@ -39,6 +39,7 @@ .vis_blur = 0.2f, \ .intensity = 1.0f, \ .flag = LIGHTPROBE_FLAG_SHOW_INFLUENCE, \ + .grid_flag = LIGHTPROBE_GRID_CAPTURE_INDIRECT | LIGHTPROBE_GRID_CAPTURE_EMISSION, \ .resolution = LIGHT_PROBE_RESOLUTION_1024, \ } diff --git a/source/blender/makesdna/DNA_lightprobe_types.h b/source/blender/makesdna/DNA_lightprobe_types.h index a011ec6ac2c..302183a9e88 100644 --- a/source/blender/makesdna/DNA_lightprobe_types.h +++ b/source/blender/makesdna/DNA_lightprobe_types.h @@ -34,6 +34,9 @@ typedef struct LightProbe { char attenuation_type; /** Parallax type. */ char parallax_type; + /** Grid specific flags. */ + char grid_flag; + char _pad0[3]; /** Influence Radius. */ float distinf; @@ -68,6 +71,7 @@ typedef struct LightProbe { /** Irradiance grid: Dilation. */ float grid_dilation_threshold; float grid_dilation_radius; + char _pad1[4]; /** Surface element density for scene surface cache. In surfel per unit distance. */ float surfel_density; @@ -112,6 +116,13 @@ enum { LIGHTPROBE_FLAG_INVERT_GROUP = (1 << 5), }; +/* Probe->grid_flag */ +enum { + LIGHTPROBE_GRID_CAPTURE_WORLD = (1 << 0), + LIGHTPROBE_GRID_CAPTURE_INDIRECT = (1 << 1), + LIGHTPROBE_GRID_CAPTURE_EMISSION = (1 << 2), +}; + /* Probe->display */ enum { LIGHTPROBE_DISP_WIRE = 0, diff --git a/source/blender/makesrna/intern/rna_lightprobe.cc b/source/blender/makesrna/intern/rna_lightprobe.cc index 8ce6c3d2a8a..b460e468779 100644 --- a/source/blender/makesrna/intern/rna_lightprobe.cc +++ b/source/blender/makesrna/intern/rna_lightprobe.cc @@ -241,6 +241,28 @@ static void rna_def_lightprobe(BlenderRNA *brna) RNA_def_property_range(prop, 1.0f, 5.0f); RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc"); + prop = RNA_def_property(srna, "grid_capture_world", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "grid_flag", LIGHTPROBE_GRID_CAPTURE_WORLD); + RNA_def_property_ui_text( + prop, + "Capture World", + "Bake incoming light from the world, instead of just the visibility, " + "for more accurate lighting, but loose correct blending to surrounding irradiance volumes"); + RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, nullptr); + + prop = RNA_def_property(srna, "grid_capture_indirect", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "grid_flag", LIGHTPROBE_GRID_CAPTURE_INDIRECT); + RNA_def_property_ui_text(prop, + "Capture Indirect", + "Bake light bounces from light sources for more accurate lighting"); + RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, nullptr); + + prop = RNA_def_property(srna, "grid_capture_emission", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "grid_flag", LIGHTPROBE_GRID_CAPTURE_EMISSION); + RNA_def_property_ui_text( + prop, "Capture Emission", "Bake emissive surfaces for more accurate lighting"); + RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, nullptr); + prop = RNA_def_property(srna, "visibility_buffer_bias", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, nullptr, "vis_bias"); RNA_def_property_range(prop, 0.001f, 9999.0f);