From 5e43bf3483e58ddd59e0c19e48bc1f32e665e12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Fri, 6 Oct 2023 23:37:38 +0200 Subject: [PATCH] EEVEE-Next: Refactor Lightprobe irradiance evaluation This refactor the lightprobes sample so that we always query the spherical probe and the volume probe. Then, given the BSDF type, we reconstruct the incoming radiance differently depending on the ray probability blending between the spherical and volume probe depending on ray probability. Moreover, we implement cubemap normalization using volume probe spherical harmonic data at spherical probe position and at the shading point position. Pull Request: https://projects.blender.org/blender/blender/pulls/113301 --- source/blender/draw/CMakeLists.txt | 1 + .../draw/engines/eevee_next/eevee_defines.hh | 1 + .../draw/engines/eevee_next/eevee_pipeline.cc | 2 + .../draw/engines/eevee_next/eevee_raytrace.cc | 2 + .../eevee_next/eevee_reflection_probes.cc | 29 ++++ .../eevee_next/eevee_reflection_probes.hh | 6 + .../draw/engines/eevee_next/eevee_shader.cc | 2 + .../draw/engines/eevee_next/eevee_shader.hh | 1 + .../engines/eevee_next/eevee_shader_shared.hh | 12 ++ .../draw/engines/eevee_next/eevee_view.cc | 1 + .../draw/engines/eevee_next/eevee_volume.cc | 1 + .../shaders/eevee_lightprobe_eval_lib.glsl | 135 +++++++++++++++--- .../eevee_ray_trace_fallback_comp.glsl | 16 ++- .../shaders/eevee_ray_trace_screen_comp.glsl | 12 +- .../eevee_reflection_probe_eval_lib.glsl | 6 +- .../shaders/eevee_reflection_probe_lib.glsl | 33 ++++- .../eevee_reflection_probe_select_comp.glsl | 28 ++++ .../eevee_spherical_harmonics_lib.glsl | 21 +++ .../shaders/eevee_surf_forward_frag.glsl | 19 ++- .../shaders/infos/eevee_deferred_info.hh | 6 +- .../infos/eevee_irradiance_cache_info.hh | 5 +- .../shaders/infos/eevee_material_info.hh | 1 + .../infos/eevee_reflection_probe_info.hh | 14 +- .../shaders/infos/eevee_tracing_info.hh | 11 +- 24 files changed, 321 insertions(+), 44 deletions(-) create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index c1fb3c56154..8b22bd171b5 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -536,6 +536,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_select_comp.glsl engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl engines/eevee_next/shaders/eevee_renderpass_lib.glsl engines/eevee_next/shaders/eevee_sampling_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 0c21d1369ec..04d6179b3fc 100644 --- a/source/blender/draw/engines/eevee_next/eevee_defines.hh +++ b/source/blender/draw/engines/eevee_next/eevee_defines.hh @@ -32,6 +32,7 @@ /* Reflection Probes. */ #define REFLECTION_PROBES_MAX 256 #define REFLECTION_PROBE_GROUP_SIZE 16 +#define REFLECTION_PROBE_SELECT_GROUP_SIZE 64 /* Number of additional pixels on the border of an octahedral map to reserve for fixing seams. * Border size requires depends on the max number of mipmap levels. */ #define REFLECTION_PROBE_MIPMAP_LEVELS 5 diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 44aab2ad758..5754545c012 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -283,6 +283,8 @@ void ForwardPipeline::sync() inst_.shadows.bind_resources(&opaque_ps_); inst_.sampling.bind_resources(&opaque_ps_); inst_.hiz_buffer.bind_resources(&opaque_ps_); + inst_.irradiance_cache.bind_resources(&opaque_ps_); + inst_.reflection_probes.bind_resources(&opaque_ps_); } opaque_single_sided_ps_ = &opaque_ps_.sub("SingleSided"); diff --git a/source/blender/draw/engines/eevee_next/eevee_raytrace.cc b/source/blender/draw/engines/eevee_next/eevee_raytrace.cc index da96ed84779..30ac791d940 100644 --- a/source/blender/draw/engines/eevee_next/eevee_raytrace.cc +++ b/source/blender/draw/engines/eevee_next/eevee_raytrace.cc @@ -106,6 +106,7 @@ void RayTraceModule::sync() inst_.bind_uniform_data(&pass); inst_.hiz_buffer.bind_resources(&pass); inst_.sampling.bind_resources(&pass); + inst_.irradiance_cache.bind_resources(&pass); inst_.reflection_probes.bind_resources(&pass); pass.dispatch(ray_dispatch_buf_); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); @@ -120,6 +121,7 @@ void RayTraceModule::sync() pass.bind_image("ray_radiance_img", &ray_radiance_tx_); pass.bind_texture("depth_tx", &depth_tx); inst_.bind_uniform_data(&pass); + inst_.irradiance_cache.bind_resources(&pass); inst_.reflection_probes.bind_resources(&pass); pass.dispatch(ray_dispatch_buf_); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); 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 3496c31592b..009f35d3f42 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc @@ -71,6 +71,7 @@ void ReflectionProbeModule::init() pass.dispatch(int2(1, 1)); } } + void ReflectionProbeModule::begin_sync() { for (ReflectionProbe &reflection_probe : probes_.values()) { @@ -84,6 +85,17 @@ void ReflectionProbeModule::begin_sync() update_probes_this_sample_ = true; instance_.sampling.reset(); } + + { + PassSimple &pass = select_ps_; + pass.init(); + pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_SELECT)); + pass.push_constant("reflection_probe_count", &reflection_probe_count_); + pass.bind_ssbo("reflection_probe_buf", &data_buf_); + instance_.irradiance_cache.bind_resources(&pass); + pass.dispatch(&dispatch_probe_select_); + pass.barrier(GPU_BARRIER_UNIFORM); + } } int ReflectionProbeModule::needed_layers_get() const @@ -615,4 +627,21 @@ void ReflectionProbeModule::update_probes_texture_mipmaps() GPU_texture_update_mipmap_chain(probes_tx_); } +void ReflectionProbeModule::set_view(View & /*view*/) +{ + reflection_probe_count_ = 0; + /* TODO(@fclem): Refactor to have a better way than this to get the correct count. + * Eventually we should have the unclamped number of reflection probe. */ + for (int i = 0; i < REFLECTION_PROBES_MAX; i++) { + if (data_buf_[i].layer == -1) { + break; + } + reflection_probe_count_++; + } + + dispatch_probe_select_ = int3( + divide_ceil_u(reflection_probe_count_, REFLECTION_PROBE_SELECT_GROUP_SIZE), 1, 1); + instance_.manager->submit(select_ps_); +} + } // namespace blender::eevee 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 8dac69c5569..a993a254835 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh @@ -84,8 +84,10 @@ class ReflectionProbeModule { PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"}; PassSimple update_irradiance_ps_ = {"Probe.UpdateIrradiance"}; + PassSimple select_ps_ = {"Probe.Select"}; int3 dispatch_probe_pack_ = int3(0); + int3 dispatch_probe_select_ = int3(0); /** * Texture containing a cube-map where the probe should be rendering to. @@ -95,6 +97,8 @@ class ReflectionProbeModule { Texture cubemap_tx_ = {"Probe.Cubemap"}; /** Index of the probe being updated. */ int reflection_probe_index_ = 0; + /** Number of the probe to process in the select phase. */ + int reflection_probe_count_ = 0; bool update_probes_next_sample_ = false; bool update_probes_this_sample_ = false; @@ -119,6 +123,8 @@ class ReflectionProbeModule { void do_world_update_set(bool value); void do_world_update_irradiance_set(bool value); + void set_view(View &view); + void debug_print() const; private: diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index 069f0be65cb..af48ed0b183 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -204,6 +204,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_reflection_probe_remap"; case REFLECTION_PROBE_UPDATE_IRRADIANCE: return "eevee_reflection_probe_update_irradiance"; + case REFLECTION_PROBE_SELECT: + return "eevee_reflection_probe_select"; 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 e909b65a517..e0169346101 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader.hh @@ -97,6 +97,7 @@ enum eShaderType { REFLECTION_PROBE_REMAP, REFLECTION_PROBE_UPDATE_IRRADIANCE, + REFLECTION_PROBE_SELECT, 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 2666533af2f..31574881dba 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -1278,6 +1278,12 @@ static inline float3 burley_eval(float3 d, float r) /** \name Reflection Probes * \{ */ +struct ReflectionProbeLowFreqLight { + packed_float3 direction; + float ambient; +}; +BLI_STATIC_ASSERT_ALIGN(ReflectionProbeLowFreqLight, 16) + /** Mapping data to locate a reflection probe in texture. */ struct ReflectionProbeData { /** @@ -1308,6 +1314,12 @@ struct ReflectionProbeData { * LOD factor for mipmap selection. */ float lod_factor; + + /** + * Irradiance at the probe location encoded as spherical harmonics. + * Only contain the average luminance. Used for cubemap normalization. + */ + ReflectionProbeLowFreqLight low_freq_light; }; BLI_STATIC_ASSERT_ALIGN(ReflectionProbeData, 16) diff --git a/source/blender/draw/engines/eevee_next/eevee_view.cc b/source/blender/draw/engines/eevee_next/eevee_view.cc index c236a80770e..7402a72aeda 100644 --- a/source/blender/draw/engines/eevee_next/eevee_view.cc +++ b/source/blender/draw/engines/eevee_next/eevee_view.cc @@ -124,6 +124,7 @@ void ShadingView::render() /* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */ inst_.lights.set_view(render_view_new_, extent_); + inst_.reflection_probes.set_view(render_view_new_); inst_.volume.draw_prepass(render_view_new_); diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index f0bc38ba7ab..1f85b462931 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -261,6 +261,7 @@ void VolumeModule::end_sync() scatter_ps_.shader_set(inst_.shaders.static_shader_get( data_.use_lights ? VOLUME_SCATTER_WITH_LIGHTS : VOLUME_SCATTER)); inst_.lights.bind_resources(&scatter_ps_); + inst_.reflection_probes.bind_resources(&scatter_ps_); inst_.irradiance_cache.bind_resources(&scatter_ps_); inst_.shadows.bind_resources(&scatter_ps_); inst_.sampling.bind_resources(&scatter_ps_); 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 8be9bb9786f..97feda3fa3c 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 @@ -12,6 +12,7 @@ #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) #pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl) #pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_reflection_probe_eval_lib.glsl) /** * Return the brick coordinate inside the grid. @@ -105,6 +106,21 @@ vec3 lightprobe_irradiance_grid_bias_sample_coord(IrradianceGridData grid_data, return 0.5 + cell_start + trilinear_coord; } +SphericalHarmonicL1 lightprobe_irradiance_sample_atlas(sampler3D atlas_tx, vec3 atlas_coord) +{ + vec4 texture_coord = vec4(atlas_coord, float(IRRADIANCE_GRID_BRICK_SIZE)) / + vec3(textureSize(atlas_tx, 0)).xyzz; + SphericalHarmonicL1 sh; + sh.L0.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0); + texture_coord.z += texture_coord.w; + sh.L1.Mn1 = textureLod(atlas_tx, texture_coord.xyz, 0.0); + texture_coord.z += texture_coord.w; + sh.L1.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0); + texture_coord.z += texture_coord.w; + sh.L1.Mp1 = textureLod(atlas_tx, texture_coord.xyz, 0.0); + return sh; +} + SphericalHarmonicL1 lightprobe_irradiance_sample( sampler3D atlas_tx, vec3 P, vec3 V, vec3 Ng, const bool do_bias) { @@ -154,27 +170,24 @@ SphericalHarmonicL1 lightprobe_irradiance_sample( 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; - SphericalHarmonicL1 sh; - sh.L0.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0); - texture_coord.z += texture_coord.w; - sh.L1.Mn1 = textureLod(atlas_tx, texture_coord.xyz, 0.0); - texture_coord.z += texture_coord.w; - sh.L1.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0); - texture_coord.z += texture_coord.w; - sh.L1.Mp1 = textureLod(atlas_tx, texture_coord.xyz, 0.0); - return sh; + return lightprobe_irradiance_sample_atlas(atlas_tx, atlas_coord); +} + +SphericalHarmonicL1 lightprobe_irradiance_world() +{ + return lightprobe_irradiance_sample_atlas(irradiance_atlas_tx, vec3(0.0)); } -/** - * Shorter version without bias. - */ SphericalHarmonicL1 lightprobe_irradiance_sample(vec3 P) { return lightprobe_irradiance_sample(irradiance_atlas_tx, P, vec3(0), vec3(0), false); } +SphericalHarmonicL1 lightprobe_irradiance_sample(vec3 P, vec3 V, vec3 Ng) +{ + return lightprobe_irradiance_sample(irradiance_atlas_tx, P, V, Ng, true); +} + void lightprobe_eval(ClosureDiffuse diffuse, ClosureReflection reflection, vec3 P, @@ -185,8 +198,98 @@ 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, true); + SphericalHarmonicL1 irradiance = lightprobe_irradiance_sample(P, V, diffuse.N); out_diffuse += spherical_harmonics_evaluate_lambert(diffuse.N, irradiance); } + +#ifdef REFLECTION_PROBE + +struct LightProbeSample { + SphericalHarmonicL1 volume_irradiance; + int spherical_id; +}; + +/** + * Return cached lightprobe data at P. + * Ng and V are use for biases. + */ +LightProbeSample lightprobe_load(vec3 P, vec3 Ng, vec3 V) +{ + LightProbeSample result; + result.volume_irradiance = lightprobe_irradiance_sample(P, V, Ng); + result.spherical_id = reflection_probes_find_closest(P); + return result; +} + +/** + * Return spherical sample normalized by irradianced at sample position. + * This avoid most of light leaking and reduce the need for many local probes. + */ +vec3 lightprobe_spherical_sample_normalized(int probe_index, + vec3 L, + float lod, + SphericalHarmonicL1 P_sh) +{ + ReflectionProbeData probe = reflection_probe_buf[probe_index]; + ReflectionProbeLowFreqLight shading_sh = reflection_probes_extract_low_freq(P_sh); + vec3 normalization_factor = reflection_probes_normalization_eval( + L, shading_sh, probe.low_freq_light); + return normalization_factor * reflection_probes_sample(L, lod, probe).rgb; +} + +float pdf_to_lod(float pdf) +{ + return 1.0; /* TODO */ +} + +vec3 lightprobe_eval_direction(LightProbeSample samp, vec3 L, float pdf) +{ + vec3 radiance_sh = lightprobe_spherical_sample_normalized( + samp.spherical_id, L, pdf_to_lod(pdf), samp.volume_irradiance); + + return radiance_sh; +} + +float lightprobe_roughness_to_cube_sh_mix_fac(float roughness) +{ + /* Temporary. Do something better. */ + return square(saturate(roughness * 4.0 - 2.0)); +} + +float lightprobe_roughness_to_lod(float roughness) +{ + /* Temporary. Do something better. */ + return sqrt(roughness) * 11.0; +} + +vec3 lightprobe_eval(LightProbeSample samp, ClosureDiffuse diffuse, vec3 V, vec2 noise) +{ + vec3 radiance_sh = spherical_harmonics_evaluate_lambert(diffuse.N, samp.volume_irradiance); + return radiance_sh; +} + +vec3 lightprobe_specular_dominant_dir(vec3 N, vec3 V, float roughness) +{ + vec3 R = -reflect(V, N); + float smoothness = 1.0 - roughness; + float fac = smoothness * (sqrt(smoothness) + roughness); + return normalize(mix(N, R, fac)); +} + +vec3 lightprobe_eval(LightProbeSample samp, ClosureReflection reflection, vec3 V, vec2 noise) +{ + vec3 L = lightprobe_specular_dominant_dir(reflection.N, V, reflection.roughness); + /* TODO: Right now generate a dependency hell. */ + // vec3 L = ray_generate_direction(noise, reflection, V, pdf); + + float lod = lightprobe_roughness_to_lod(reflection.roughness); + vec3 radiance_cube = lightprobe_spherical_sample_normalized( + samp.spherical_id, L, lod, samp.volume_irradiance); + + float fac = lightprobe_roughness_to_cube_sh_mix_fac(reflection.roughness); + vec3 radiance_sh = spherical_harmonics_evaluate_lambert(L, samp.volume_irradiance); + return mix(radiance_cube, radiance_sh, fac); +} + +#endif /* REFLECTION_PROBE */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl index 3c757a652c1..27a3ed17247 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl @@ -6,7 +6,7 @@ * Does not use any tracing method. Only rely on local light probes to get the incoming radiance. */ -#pragma BLENDER_REQUIRE(eevee_reflection_probe_eval_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) #pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl) @@ -41,12 +41,16 @@ void main() ray.origin = P; ray.direction = ray_data.xyz; - int closest_probe_id = reflection_probes_find_closest(P); - ReflectionProbeData probe = reflection_probe_buf[closest_probe_id]; - vec3 radiance = reflection_probes_sample(ray.direction, 0.0, probe).rgb; + vec3 V = cameraVec(P); + + /* Using ray direction as geometric normal to bias the sampling position. + * This is faster than loading the gbuffer again and averages between reflected and normal + * direction over many rays. */ + vec3 Ng = ray.direction; + LightProbeSample samp = lightprobe_load(P, Ng, V); + vec3 radiance = lightprobe_eval_direction(samp, ray.direction, safe_rcp(ray_pdf_inv)); /* Set point really far for correct reprojection of background. */ - /* TODO(fclem): Could use probe depth / parallax. */ - float hit_time = 10000.0; + float hit_time = 1000.0; float luma = max(1e-8, max_v3(radiance)); radiance *= 1.0 - max(0.0, luma - uniform_buf.raytrace.brightness_clamp) / luma; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl index 21f952f1eab..98187fdc3cd 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl @@ -6,7 +6,7 @@ * Use screen space tracing against depth buffer to find intersection with the scene. */ -#pragma BLENDER_REQUIRE(eevee_reflection_probe_eval_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) #pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl) @@ -37,6 +37,7 @@ void main() } vec3 P = get_world_space_from_depth(uv, depth); + vec3 V = cameraVec(P); Ray ray; ray.origin = P; ray.direction = ray_data.xyz; @@ -87,10 +88,13 @@ void main() // } } else { + /* Using ray direction as geometric normal to bias the sampling position. + * This is faster than loading the gbuffer again and averages between reflected and normal + * direction over many rays. */ + vec3 Ng = ray.direction; /* Fallback to nearest light-probe. */ - int closest_probe_id = reflection_probes_find_closest(P); - ReflectionProbeData probe = reflection_probe_buf[closest_probe_id]; - radiance = reflection_probes_sample(ray.direction, 0.0, probe).rgb; + LightProbeSample samp = lightprobe_load(P, Ng, V); + radiance = lightprobe_eval_direction(samp, ray.direction, safe_rcp(ray_pdf_inv)); /* Set point really far for correct reprojection of background. */ hit.time = 10000.0; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl index e711be6e76b..d7477a3a83e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl @@ -8,12 +8,13 @@ #pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl) +#ifdef REFLECTION_PROBE int reflection_probes_find_closest(vec3 P) { int closest_index = 0; float closest_distance = FLT_MAX; - /* ReflectionProbeData doesn't contain any gab, exit at first item that is invalid. */ + /* ReflectionProbeData doesn't contain any gap, exit at first item that is invalid. */ for (int index = 1; reflection_probe_buf[index].layer != -1 && index < REFLECTION_PROBES_MAX; index++) { @@ -25,6 +26,7 @@ int reflection_probes_find_closest(vec3 P) } return closest_index; } +#endif /* REFLECTION_PROBE */ #ifdef EEVEE_UTILITY_TX vec4 reflection_probe_eval(ClosureReflection reflection, @@ -32,7 +34,7 @@ vec4 reflection_probe_eval(ClosureReflection reflection, vec3 V, ReflectionProbeData probe_data) { - ivec3 texture_size = textureSize(reflectionProbes, 0); + ivec3 texture_size = textureSize(reflection_probes_tx, 0); float lod_cube_max = min(log2(float(texture_size.x)) - float(probe_data.layer_subdivision) + 1.0, float(REFLECTION_PROBE_MIPMAP_LEVELS)); 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 9044851622c..580cc794c6d 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 @@ -2,16 +2,18 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(eevee_cubemap_lib.glsl) +#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) #pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) +#ifdef REFLECTION_PROBE vec4 reflection_probes_sample(vec3 L, float lod, ReflectionProbeData probe_data) { vec2 octahedral_uv_packed = octahedral_uv_from_direction(L); vec2 texel_size = vec2(1.0 / float(1 << (11 - probe_data.layer_subdivision))); vec2 octahedral_uv = octahedral_uv_to_layer_texture_coords( octahedral_uv_packed, probe_data, texel_size); - return textureLod(reflectionProbes, vec3(octahedral_uv, probe_data.layer), lod); + return textureLod(reflection_probes_tx, vec3(octahedral_uv, probe_data.layer), lod); } vec3 reflection_probes_world_sample(vec3 L, float lod) @@ -19,3 +21,30 @@ 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; } +#endif + +ReflectionProbeLowFreqLight reflection_probes_extract_low_freq(SphericalHarmonicL1 sh) +{ + /* To avoid color shift and negative values, we reduce saturation and directionnality. */ + ReflectionProbeLowFreqLight result; + result.ambient = sh.L0.M0.r + sh.L0.M0.g + sh.L0.M0.b; + + mat3x4 L1_per_band; + L1_per_band[0] = sh.L1.Mn1; + L1_per_band[1] = sh.L1.M0; + L1_per_band[2] = sh.L1.Mp1; + + mat4x3 L1_per_comp = transpose(L1_per_band); + result.direction = L1_per_comp[0] + L1_per_comp[1] + L1_per_comp[2]; + + return result; +} + +vec3 reflection_probes_normalization_eval(vec3 L, + ReflectionProbeLowFreqLight numerator, + ReflectionProbeLowFreqLight denominator) +{ + /* TODO(fclem): Adjusting directionnality is tricky. + * Needs to be revisited later on. For now only use the ambient term. */ + return vec3(numerator.ambient * safe_rcp(denominator.ambient)); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl new file mode 100644 index 00000000000..d017740a4ee --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl @@ -0,0 +1,28 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** + * Select spherical probes to be considered during render and copy irradiance data from the + * irradiance cache from each spherical probe location except for the world probe. + */ + +#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) + +void main() +{ + int idx = int(gl_GlobalInvocationID.x); + if (idx >= reflection_probe_count) { + return; + } + + SphericalHarmonicL1 sh; + if (idx == 0) { + sh = lightprobe_irradiance_world(); + } + else { + sh = lightprobe_irradiance_sample(reflection_probe_buf[idx].pos.xyz); + } + + reflection_probe_buf[idx].low_freq_light = reflection_probes_extract_low_freq(sh); +} 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 3022d49d6d2..682c84c91e6 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 @@ -191,6 +191,27 @@ vec4 spherical_harmonics_L2_evaluate(vec3 direction, SphericalHarmonicBandL2 L2) spherical_harmonics_L2_Mp2(direction) * L2.Mp2; } +vec3 spherical_harmonics_evaluate(vec3 direction, SphericalHarmonicL0 sh) +{ + vec3 radiance = spherical_harmonics_L0_evaluate(direction, sh.L0).rgb; + return max(vec3(0.0), radiance); +} + +vec3 spherical_harmonics_evaluate(vec3 direction, SphericalHarmonicL1 sh) +{ + vec3 radiance = spherical_harmonics_L0_evaluate(direction, sh.L0).rgb + + spherical_harmonics_L1_evaluate(direction, sh.L1).rgb; + return max(vec3(0.0), radiance); +} + +vec3 spherical_harmonics_evaluate(vec3 direction, SphericalHarmonicL2 sh) +{ + vec3 radiance = spherical_harmonics_L0_evaluate(direction, sh.L0).rgb + + spherical_harmonics_L1_evaluate(direction, sh.L1).rgb + + spherical_harmonics_L2_evaluate(direction, sh.L2).rgb; + return max(vec3(0.0), radiance); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl index 92ed4ec6bf9..93a8a86cc01 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl @@ -9,6 +9,7 @@ */ #pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ambient_occlusion_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) @@ -26,19 +27,26 @@ vec4 closure_to_rgba(Closure cl) vec3 refraction_light = vec3(0.0); float shadow = 1.0; + vec3 V = cameraVec(g_data.P); float vP_z = dot(cameraForward, g_data.P) - dot(cameraForward, cameraPos); light_eval(g_diffuse_data, g_reflection_data, g_data.P, g_data.Ng, - cameraVec(g_data.P), + V, vP_z, 0.01 /* TODO(fclem) thickness. */, diffuse_light, reflection_light, shadow); + vec2 noise_probe = interlieved_gradient_noise(gl_FragCoord.xy, vec2(0, 1), vec2(0.0)); + LightProbeSample samp = lightprobe_load(g_data.P, g_data.Ng, V); + + diffuse_light += lightprobe_eval(samp, g_diffuse_data, V, noise_probe); + reflection_light += lightprobe_eval(samp, g_reflection_data, V, noise_probe); + vec4 out_color; out_color.rgb = g_emission; out_color.rgb += g_diffuse_data.color * g_diffuse_data.weight * diffuse_light; @@ -76,19 +84,26 @@ void main() vec3 refraction_light = vec3(0.0); float shadow = 1.0; + vec3 V = cameraVec(g_data.P); float vP_z = dot(cameraForward, g_data.P) - dot(cameraForward, cameraPos); light_eval(g_diffuse_data, g_reflection_data, g_data.P, g_data.Ng, - cameraVec(g_data.P), + V, vP_z, thickness, diffuse_light, reflection_light, shadow); + vec2 noise_probe = interlieved_gradient_noise(gl_FragCoord.xy, vec2(0, 1), vec2(0.0)); + LightProbeSample samp = lightprobe_load(g_data.P, g_data.Ng, V); + + diffuse_light += lightprobe_eval(samp, g_diffuse_data, V, noise_probe); + reflection_light += lightprobe_eval(samp, g_reflection_data, V, noise_probe); + g_diffuse_data.color *= g_diffuse_data.weight; g_reflection_data.color *= g_reflection_data.weight; g_refraction_data.color *= g_refraction_data.weight; diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh index dbbf92b7a00..2cdda2f6b3b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh @@ -11,9 +11,9 @@ image(slot, format, Qualifier::READ, ImageType::FLOAT_2D, name, Frequency::PASS) GPU_SHADER_CREATE_INFO(eevee_gbuffer_data) - .sampler(7, ImageType::UINT_2D, "gbuf_header_tx") - .sampler(8, ImageType::FLOAT_2D_ARRAY, "gbuf_closure_tx") - .sampler(9, ImageType::FLOAT_2D_ARRAY, "gbuf_color_tx"); + .sampler(8, ImageType::UINT_2D, "gbuf_header_tx") + .sampler(9, ImageType::FLOAT_2D_ARRAY, "gbuf_closure_tx") + .sampler(10, ImageType::FLOAT_2D_ARRAY, "gbuf_color_tx"); GPU_SHADER_CREATE_INFO(eevee_deferred_light) .fragment_source("eevee_deferred_light_frag.glsl") 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 660665291cb..4fa3fd76a3d 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 @@ -195,7 +195,7 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load) .compute_source("eevee_lightprobe_irradiance_load_comp.glsl") .do_static_compilation(true); -GPU_SHADER_CREATE_INFO(eevee_lightprobe_data) +GPU_SHADER_CREATE_INFO(eevee_volume_probe_data) .uniform_buf(IRRADIANCE_GRID_BUF_SLOT, "IrradianceGridData", "grids_infos_buf[IRRADIANCE_GRID_MAX]") @@ -204,4 +204,7 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_data) .storage_buf(IRRADIANCE_BRICK_BUF_SLOT, Qualifier::READ, "uint", "bricks_infos_buf[]") .sampler(IRRADIANCE_ATLAS_TEX_SLOT, ImageType::FLOAT_3D, "irradiance_atlas_tx"); +GPU_SHADER_CREATE_INFO(eevee_lightprobe_data) + .additional_info("eevee_reflection_probe_data", "eevee_volume_probe_data"); + /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 7131da02e8b..32a25307717 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -166,6 +166,7 @@ GPU_SHADER_CREATE_INFO(eevee_surf_forward) .fragment_source("eevee_surf_forward_frag.glsl") .additional_info("eevee_global_ubo", "eevee_light_data", + "eevee_lightprobe_data", "eevee_utility_texture", "eevee_sampling_data", "eevee_shadow_data", 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 098af14b905..7e118f4d281 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 @@ -10,10 +10,11 @@ * \{ */ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data) + .define("REFLECTION_PROBE") .uniform_buf(REFLECTION_PROBE_BUF_SLOT, "ReflectionProbeData", "reflection_probe_buf[REFLECTION_PROBES_MAX]") - .sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflectionProbes"); + .sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflection_probes_tx"); /* Sample cubemap and remap into an octahedral texture. */ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap) @@ -38,4 +39,15 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_update_irradiance) .compute_source("eevee_reflection_probe_update_irradiance_comp.glsl") .do_static_compilation(true); +GPU_SHADER_CREATE_INFO(eevee_reflection_probe_select) + .local_group_size(REFLECTION_PROBE_SELECT_GROUP_SIZE) + .storage_buf(0, + Qualifier::READ_WRITE, + "ReflectionProbeData", + "reflection_probe_buf[REFLECTION_PROBES_MAX]") + .push_constant(Type::INT, "reflection_probe_count") + .additional_info("eevee_shared", "eevee_volume_probe_data") + .compute_source("eevee_reflection_probe_select_comp.glsl") + .do_static_compilation(true); + /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh index 7371ec5919c..d4b49aaee0d 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh @@ -65,15 +65,12 @@ EEVEE_RAYTRACE_CLOSURE_VARIATION(eevee_ray_generate) GPU_SHADER_CREATE_INFO(eevee_ray_trace_fallback) .do_static_compilation(true) .local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE) - .additional_info("eevee_shared", - "eevee_global_ubo", - "draw_view", - "eevee_reflection_probe_data") + .additional_info("eevee_shared", "eevee_global_ubo", "draw_view", "eevee_lightprobe_data") .image(0, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "ray_data_img") .image(1, RAYTRACE_RAYTIME_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_time_img") .image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_radiance_img") .sampler(1, ImageType::DEPTH_2D, "depth_tx") - .storage_buf(4, Qualifier::READ, "uint", "tiles_coord_buf[]") + .storage_buf(5, Qualifier::READ, "uint", "tiles_coord_buf[]") .compute_source("eevee_ray_trace_fallback_comp.glsl"); GPU_SHADER_CREATE_INFO(eevee_ray_trace_screen) @@ -83,13 +80,13 @@ GPU_SHADER_CREATE_INFO(eevee_ray_trace_screen) "eevee_sampling_data", "draw_view", "eevee_hiz_data", - "eevee_reflection_probe_data") + "eevee_lightprobe_data") .image(0, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "ray_data_img") .image(1, RAYTRACE_RAYTIME_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_time_img") .image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_radiance_img") .sampler(0, ImageType::FLOAT_2D, "screen_radiance_tx") .sampler(1, ImageType::DEPTH_2D, "depth_tx") - .storage_buf(4, Qualifier::READ, "uint", "tiles_coord_buf[]") + .storage_buf(5, Qualifier::READ, "uint", "tiles_coord_buf[]") .compute_source("eevee_ray_trace_screen_comp.glsl"); EEVEE_RAYTRACE_CLOSURE_VARIATION(eevee_ray_trace_screen)