EEVEE: SSS: Fix light leaking bewteen object at different depths

The SSS shader in Eevee has the following drawbacks (elaborated in {T79933}):

1. Glowing
2. Ringing. On low SSS jittering it is rendered a bunch of sharp lines
3. Overall blurriness due to the nature of the effect
4. Shadows near occlusions as in T65849
5. Too much SSS near the edge and on highly-tilted surfaces

{F9438636}
{F9427302}

In the original shader code there was a depth correction factor, as far as I can understand for fixing light bleeding from one object to another. But it was scaled incorrectly. I modified its scale to depend on SSS scale*radius and made it independent from the scene scale. The scale parameter (`-4`) is chosen so that it makes tilted surfaces to have visually the same SSS radius as straight surfaces (surfaces with normal pointed directly to the camera).

This depth correction factor alone fixes all the problems except for ringing (pt. 2). Because of float-point precision errors and irradiance interpolation some samples near the border of an object might leak light, causing sparkly or dashed (because of aliasing) patterns around the highlights. Switching from `texture()` to `texelFetch()` fixes this problem and makes textures on renders visually sharper.

An alternative solution would be to detect object borders and somehow prevent samples from crossing it. This can be done by:
1. Adding an `object_id` texture. I think it requires much more code changing and makes the shader more complicated. Again, `object_id` is not interpolatable.
2. Watch gradient of depth and discard samples if the gradient is too big. This solution depends on scene scale and requires more texture lookups. Since SSS is usually a minor effect, it probably doesn't require that level of accuracy.

I haven't notice it in practice, but I assume it can make visible SSS radius slightly off (up to 0.5 px in screen space, which is negligible). It is completely mitigated with render sampling.

Reviewed By: Clément Foucault
Differential Revision: https://developer.blender.org/D9740
This commit is contained in:
Mikhail 2021-03-03 14:52:01 +01:00 committed by Clément Foucault
parent f746b568f3
commit cd9a6a0f93
Notes: blender-bot 2023-02-14 09:48:25 +01:00
Referenced by issue #87068, Moire effect with particle hair and subsurface highlights in Eevee
2 changed files with 17 additions and 13 deletions

View File

@ -208,12 +208,14 @@ void EEVEE_subsurface_add_pass(EEVEE_ViewLayerData *sldata,
DRW_shgroup_stencil_mask(shgrp, sss_id);
{
eGPUSamplerState state = GPU_SAMPLER_DEFAULT;
DRWShadingGroup *grp = DRW_shgroup_create(EEVEE_shaders_subsurface_first_pass_sh_get(),
psl->sss_blur_ps);
DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex());
DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", depth_src);
DRW_shgroup_uniform_texture_ref(grp, "sssIrradiance", &effects->sss_irradiance);
DRW_shgroup_uniform_texture_ref(grp, "sssRadius", &effects->sss_radius);
DRW_shgroup_uniform_texture_ref_ex(grp, "sssIrradiance", &effects->sss_irradiance, state);
DRW_shgroup_uniform_texture_ref_ex(grp, "sssRadius", &effects->sss_radius, state);
DRW_shgroup_uniform_block(grp, "sssProfile", sss_profile);
DRW_shgroup_uniform_block(grp, "common_block", sldata->common_ubo);
DRW_shgroup_uniform_block(grp, "renderpass_block", sldata->renderpass_ubo.combined);
@ -223,9 +225,9 @@ void EEVEE_subsurface_add_pass(EEVEE_ViewLayerData *sldata,
grp = DRW_shgroup_create(EEVEE_shaders_subsurface_second_pass_sh_get(), psl->sss_resolve_ps);
DRW_shgroup_uniform_texture(grp, "utilTex", EEVEE_materials_get_util_tex());
DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", depth_src);
DRW_shgroup_uniform_texture_ref(grp, "sssIrradiance", &effects->sss_blur);
DRW_shgroup_uniform_texture_ref(grp, "sssAlbedo", &effects->sss_albedo);
DRW_shgroup_uniform_texture_ref(grp, "sssRadius", &effects->sss_radius);
DRW_shgroup_uniform_texture_ref_ex(grp, "sssIrradiance", &effects->sss_blur, state);
DRW_shgroup_uniform_texture_ref_ex(grp, "sssAlbedo", &effects->sss_albedo, state);
DRW_shgroup_uniform_texture_ref_ex(grp, "sssRadius", &effects->sss_radius, state);
DRW_shgroup_uniform_block(grp, "sssProfile", sss_profile);
DRW_shgroup_uniform_block(grp, "common_block", sldata->common_ubo);
DRW_shgroup_uniform_block(grp, "renderpass_block", sldata->renderpass_ubo.combined);

View File

@ -26,7 +26,7 @@ void main(void)
vec2 pixel_size = 1.0 / vec2(textureSize(depthBuffer, 0).xy); /* TODO precompute */
vec2 uvs = gl_FragCoord.xy * pixel_size;
vec3 sss_irradiance = texture(sssIrradiance, uvs).rgb;
float sss_radius = texture(sssRadius, uvs).r;
float sss_radius = texture(sssRadius, uvs).r * radii_max_radius.w;
float depth = texture(depthBuffer, uvs).r;
float depth_view = get_view_z_from_depth(depth);
@ -43,8 +43,7 @@ void main(void)
/* Compute kernel bounds in 2D. */
float homcoord = ProjectionMatrix[2][3] * depth_view + ProjectionMatrix[3][3];
vec2 scale = vec2(ProjectionMatrix[0][0], ProjectionMatrix[1][1]) * sss_radius / homcoord;
vec2 finalStep = scale * radii_max_radius.w;
finalStep *= 0.5; /* samples range -1..1 */
vec2 finalStep = scale * 0.5; /* samples range -1..1 */
/* Center sample */
vec3 accum = sss_irradiance * kernel[0].rgb;
@ -55,15 +54,18 @@ void main(void)
vec3 color = texture(sssIrradiance, sample_uv).rgb;
float sample_depth = texture(depthBuffer, sample_uv).r;
sample_depth = get_view_z_from_depth(sample_depth);
/* Depth correction factor. */
float depth_delta = depth_view - sample_depth;
float s = clamp(1.0 - exp(-(depth_delta * depth_delta) / (2.0 * sss_radius)), 0.0, 1.0);
/* Depth correction factor. See Real Time Realistic Skin Translucency 2010
* by Jimenez, eqs. 2 and 9, and D9740.
* Coefficient -2 follows from gaussian_profile() from gpu_material.c and
* from the definition of finalStep. */
float depth_delta = (depth_view - sample_depth) / sss_radius;
float s = exp(-2.0 * sqr(depth_delta));
/* Out of view samples. */
if (any(lessThan(sample_uv, vec2(0.0))) || any(greaterThan(sample_uv, vec2(1.0)))) {
s = 1.0;
s = 0.0;
}
/* Mix with first sample in failure case and apply kernel color. */
accum += kernel[i].rgb * mix(color, sss_irradiance, s);
accum += kernel[i].rgb * mix(sss_irradiance, color, s);
}
#if defined(FIRST_PASS)