EEVEE-Next: Lookdev Background Blur

This PR implements the background blurring for studiolight/lookdev HDRIs.
The visual appearance matches EEVEE-Classic closely.

**Technical details**

- LOD0 is skipped as the regular background color can be used. The
  regular background color is blended towards LOD1.
- Volume probe is mixed in to remove baked in artifacts in the higher LODs.

Pull Request: https://projects.blender.org/blender/blender/pulls/119872
This commit is contained in:
Jeroen Bakker 2024-03-28 12:18:37 +01:00
parent c056c58493
commit aedd5f2837
9 changed files with 51 additions and 9 deletions

@ -6573,8 +6573,7 @@ class VIEW3D_PT_shading_lighting(Panel):
col.prop(shading, "studiolight_intensity")
col.prop(shading, "studiolight_background_alpha")
if engine != 'BLENDER_EEVEE_NEXT':
col.prop(shading, "studiolight_background_blur")
col.prop(shading, "studiolight_background_blur")
col = split.column() # to align properly with above
elif shading.type == 'RENDERED':
@ -6599,8 +6598,7 @@ class VIEW3D_PT_shading_lighting(Panel):
col.prop(shading, "studiolight_intensity")
col.prop(shading, "studiolight_background_alpha")
engine = context.scene.render.engine
if engine != 'BLENDER_EEVEE_NEXT':
col.prop(shading, "studiolight_background_blur")
col.prop(shading, "studiolight_background_blur")
col = split.column() # to align properly with above

@ -202,6 +202,7 @@ class LightProbeModule {
friend class VolumeProbeModule;
friend class PlanarProbeModule;
friend class SphereProbeModule;
friend class BackgroundPipeline;
private:
Instance &inst_;

@ -72,6 +72,11 @@ class LookdevWorld {
{
return parameters_.background_opacity;
}
float background_blur_get()
{
return parameters_.blur;
}
};
/** \} */

@ -24,7 +24,9 @@ namespace blender::eevee {
* Used to draw background.
* \{ */
void BackgroundPipeline::sync(GPUMaterial *gpumat, const float background_opacity)
void BackgroundPipeline::sync(GPUMaterial *gpumat,
const float background_opacity,
const float background_blur)
{
Manager &manager = *inst_.manager;
RenderBuffers &rbufs = inst_.render_buffers;
@ -33,6 +35,9 @@ void BackgroundPipeline::sync(GPUMaterial *gpumat, const float background_opacit
world_ps_.state_set(DRW_STATE_WRITE_COLOR);
world_ps_.material_set(manager, gpumat);
world_ps_.push_constant("world_opacity_fade", background_opacity);
world_ps_.push_constant("world_background_blur", square_f(background_blur));
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&inst_.light_probes.world_sphere_);
world_ps_.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_data.atlas_coord));
world_ps_.bind_texture("utility_tx", inst_.pipelines.utility_tx);
/* RenderPasses & AOVs. Cleared by background (even if bad practice). */
world_ps_.bind_image("rp_color_img", &rbufs.rp_color_tx);
@ -41,6 +46,9 @@ void BackgroundPipeline::sync(GPUMaterial *gpumat, const float background_opacit
/* Required by validation layers. */
world_ps_.bind_resources(inst_.cryptomatte);
world_ps_.bind_resources(inst_.uniform_data);
world_ps_.bind_resources(inst_.sampling);
world_ps_.bind_resources(inst_.sphere_probes);
world_ps_.bind_resources(inst_.volume_probes);
world_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
/* To allow opaque pass rendering over it. */
world_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
@ -74,6 +82,8 @@ void WorldPipeline::sync(GPUMaterial *gpumat)
Manager &manager = *inst_.manager;
pass.material_set(manager, gpumat);
pass.push_constant("world_opacity_fade", 1.0f);
pass.push_constant("world_background_blur", 0.0f);
pass.push_constant("world_coord_packed", int4(0.0f));
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_image("rp_normal_img", dummy_renderpass_tx_);
pass.bind_image("rp_light_img", dummy_renderpass_tx_);
@ -89,6 +99,9 @@ void WorldPipeline::sync(GPUMaterial *gpumat)
/* Required by validation layers. */
pass.bind_resources(inst_.cryptomatte);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.volume_probes);
pass.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}

@ -40,7 +40,7 @@ class BackgroundPipeline {
public:
BackgroundPipeline(Instance &inst) : inst_(inst){};
void sync(GPUMaterial *gpumat, float background_opacity);
void sync(GPUMaterial *gpumat, float background_opacity, float background_blur);
void render(View &view);
};

@ -133,8 +133,9 @@ void World::sync()
float opacity = inst_.use_studio_light() ? lookdev_world_.background_opacity_get() :
inst_.film.background_opacity_get();
float background_blur = inst_.use_studio_light() ? lookdev_world_.background_blur_get() : 0.0;
inst_.pipelines.background.sync(gpumat, opacity);
inst_.pipelines.background.sync(gpumat, opacity, background_blur);
inst_.pipelines.world.sync(gpumat);
}

@ -63,8 +63,7 @@ void init_globals_curves()
/* Random cosine normal distribution on the hair surface. */
float noise = utility_tx_fetch(utility_tx, gl_FragCoord.xy, UTIL_BLUE_NOISE_LAYER).x;
# ifdef EEVEE_SAMPLING_DATA
/* Needs to check for SAMPLING_DATA,
* otherwise Surfel and World (?!?!) shader validation fails. */
/* Needs to check for SAMPLING_DATA, otherwise surfel shader validation fails. */
noise = fract(noise + sampling_rng_1D_get(SAMPLING_CURVES_U));
# endif
cos_theta = noise * 2.0 - 1.0;

@ -13,6 +13,9 @@
#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_volume_eval_lib.glsl)
vec4 closure_to_rgba(Closure cl)
{
@ -38,6 +41,23 @@ void main()
out_background.rgb = colorspace_safe_color(g_emission) * (1.0 - g_holdout);
out_background.a = saturate(average(g_transmittance)) * g_holdout;
if (g_data.ray_type == RAY_TYPE_CAMERA && world_background_blur != 0.0 &&
world_opacity_fade != 0.0)
{
float base_lod = sphere_probe_roughness_to_lod(world_background_blur);
float lod = max(1.0, base_lod);
float mix_factor = min(1.0, base_lod);
SphereProbeUvArea world_atlas_coord = reinterpret_as_atlas_coord(world_coord_packed);
vec4 probe_color = reflection_probes_sample(-g_data.N, lod, world_atlas_coord);
out_background.rgb = mix(out_background.rgb, probe_color.rgb, mix_factor);
SphericalHarmonicL1 volume_irradiance = lightprobe_irradiance_sample(
g_data.P, vec3(0.0), g_data.Ng);
vec3 radiance_sh = spherical_harmonics_evaluate_lambert(-g_data.N, volume_irradiance);
float radiance_mix_factor = sphere_probe_roughness_to_mix_fac(world_background_blur);
out_background.rgb = mix(out_background.rgb, radiance_sh, radiance_mix_factor);
}
/* World opacity. */
out_background = mix(vec4(0.0, 0.0, 0.0, 1.0), out_background, world_opacity_fade);

@ -218,9 +218,14 @@ GPU_SHADER_CREATE_INFO(eevee_surf_depth)
GPU_SHADER_CREATE_INFO(eevee_surf_world)
.push_constant(Type::FLOAT, "world_opacity_fade")
.push_constant(Type::FLOAT, "world_background_blur")
.push_constant(Type::IVEC4, "world_coord_packed")
.fragment_out(0, Type::VEC4, "out_background")
.fragment_source("eevee_surf_world_frag.glsl")
.additional_info("eevee_global_ubo",
"eevee_reflection_probe_data",
"eevee_volume_probe_data",
"eevee_sampling_data",
/* Optionally added depending on the material. */
// "eevee_render_pass_out",
// "eevee_cryptomatte_out",