Page MenuHome

EEVEE fix: Glowing Shadow (T79933)
ClosedPublic

Authored by Mikhail Matrosov (ktdfly) on Dec 3 2020, 6:44 PM.
Tokens
"Love" token, awarded by 295032."Love" token, awarded by mywa880."Like" token, awarded by yourhero."Love" token, awarded by blueprintrandom."Like" token, awarded by lucky3."Love" token, awarded by RedMser."Like" token, awarded by EAW.

Details

Summary

Problem

The SSS shader in Eevee has the following drawbacks (elaborated in T79933: EEVEE Glowing Shadow between Two Objects with SSS):

  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


Proposed solution

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.

Alternative solutions

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.

Limitations

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.

Diff Detail

Event Timeline

Mikhail Matrosov (ktdfly) requested review of this revision.Dec 3 2020, 6:44 PM
Mikhail Matrosov (ktdfly) created this revision.

The test file from T79933 used to make the comparison picture.

Evan Wilson (EAW) edited the summary of this revision. (Show Details)
Clément Foucault (fclem) requested changes to this revision.Dec 10 2020, 5:42 AM

I'm not sure why you are rewriting most of the code just to change from uvs to pixel coordinates when you only need to change finalStep and uvs. Moreover, you can change the sampler type for the sssIrradiance using DRW_shgroup_uniform_texture_ref_ex, knowing that the depth buffer always have interpolation disabled.

After giving a try to your solution (that I found quite adhoc) I think I found this to be a better more grounded result:

float depth_delta = abs(depth_view - sample_depth) / sss_radius;
float s = clamp(depth_delta * depth_delta, 0.0, 1.0);

This is way more explicit as it just quadratically proportional to the distance.

I don't know where the exponential function came from and could not find it in the reference paper, so I'm ok with removing it.

source/blender/draw/engines/eevee/shaders/effect_subsurface_frag.glsl
30

Do not name your variable generic names like xy. Use texel in this case to refer to the texel coordinate. In general, try to follow the convention from the surrounding code (migth also be from other files).

50

Style: Do not use array indexing [0] for vector type. Use .x or .r instead.

54

Run clang-format to fix your formating.

This revision now requires changes to proceed.Dec 10 2020, 5:42 AM

DRW_shgroup_uniform_texture_ref was already set up in eevee_subsurface.c.

Your quadratic decay

float depth_delta = abs(depth_view - sample_depth) / sss_radius;
float s = clamp(depth_delta * depth_delta, 0.0, 1.0);

might output pretty much the same result, but is not physically-based. The motivation for the exponent (as were in the original code as well) is the following:

  1. SSS light transmission might be approximated as a diffusion process. It is described with the same equations as Brownian motion and heat dissipation (wiki).
  2. A solution for diffusion equation in a uniform media gives a gausian kernel:
    That is the motivation behind gaussian approximation of SSS. You can select an option to use gaussian kernel as it is a pretty popular approximation and many scattering textures for 3D models were created to use it (e.g. Arnold skin). Gaussian kernel is also separatable (which is nice and fast) and yields reasonable results.
  3. Screen space SSS kernel doesn't account for depth, so in the original code there was a factor, correcting for this:
  4. Christensen-Burley kernel is much different from gaussian. It is poorly separatable, but it gives much more realistic SSS, because it is fitted to empirical data. Anyway, it is not a big deal to approximate it with a gaussian to correct for this relatively small effect.

The main blunder was to use incorrect scale, which were giving inconsistent results and artifacts (e.g. if you scale your scene 100 times). Also in the original code sigma was assumed to be subsurface radius, which is not. Blender SSS radius is not normalized to be mfp (mean free path) nor dispersion value sigma, that is why coefficient -4 appears here to be somewhat arbitrary, but it is not.

I'll revise the diff add more comments in the code on these substances as well.

DRW_shgroup_uniform_texture_ref_ex lets you override the sampler object to avoid filtering.

About the equations, from which paper are they from? (shame me, i can't find my own source).

I still don't understand why 4 (or 8 if you take the 2 factor in consideration) is physically based. I find it too powerful in my tests.

DRW_shgroup_uniform_texture_ref_ex Ok, I see, will change.

Equations are just a basic knowledge of statistical physics (I'm a scientist, formerly nuclear physics *adjusting my glasses*). Though, it can also be found in Real-Time Realistic Skin, eq. 9, referred from Separable SSS, p. 2.

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).

Well, my bad. Should've read the code better: gaussian_profile() gives exp(-8 (r/R)^2), but then r is divided in 2, that gives overall coefficient -2. Indeed, it looks marginally better on tilted surfaces, thought it is not as sharp at fighting light leaking across object border.

  1. Disabled interpolation
  2. Updated depth coefficient
  3. Code style and formatting
Mikhail Matrosov (ktdfly) marked 3 inline comments as done.Dec 13 2020, 10:15 PM
yourhero (yourhero) edited the summary of this revision. (Show Details)Dec 13 2020, 10:19 PM
Clément Foucault (fclem) requested changes to this revision.Dec 14 2020, 10:57 AM

You don't need to rewrite anything else than the s (which should be clamped by the way) expression if you use the right sampler .

source/blender/draw/engines/eevee/shaders/effect_subsurface_frag.glsl
73

Missing a clamp between 0 and 1.

This revision now requires changes to proceed.Dec 14 2020, 10:57 AM
Mikhail Matrosov (ktdfly) edited the summary of this revision. (Show Details)

Expression for s is guaranteed to be between 0 and 1, because it is exp of a non-positive number. Could you please clarify why do we need clamp() here?

Even if I use the right sampler, I find it adventurous to use float because of back-and-forth conversions from float to int coordinates. There's a better reason to use texture() instead of texelFetch(), as explained in OpenGL specs:

Unlike filtered texel accesses, texel fetches do not support LOD clamping or any texture wrap mode.
The results of the texel fetch are undefined
if the texel at (i,j,k) coordinates refer to a border texel outside the defined extents of the specified LOD

I wish you'd explained this, but I had to learn it the hard way.

Clément Foucault (fclem) requested changes to this revision.Dec 16 2020, 7:15 PM

I just want to point that most of your change does nothing, and arguably, does not really improve readability (which should be committed separately anyway). Only lines 58-70 have functional change in this file.

Please keep the patch to a minimal number of functional change. This is mostly to reduce the cognitive load of understanding the changes if something point to this patch.

source/blender/draw/engines/eevee/shaders/effect_subsurface_frag.glsl
46

Nitpick, Use style guideline for comment. https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Comments

Also just refer to D9740, no website (that's what we do for task too: TXXXX).

This revision now requires changes to proceed.Dec 16 2020, 7:15 PM
Mikhail Matrosov (ktdfly) marked an inline comment as done.

@Clément Foucault (fclem) I would greatly appreciate it if this fix can make it for the next LTS. It's a critical EEVEE fix for me and several other artists.

This revision was not accepted when it landed; it landed in state Needs Review.Mar 3 2021, 3:23 PM
This revision was automatically updated to reflect the committed changes.

Hi, author of the T65849 bug report here. I checked the same file with the newest Blender build and fantastic job as it looks way better!

However, there are still issues with the hair. It seems especially more so with the root of the hair, but not necessarily just the roots (check hair in front of the ears).





Hi, author of the T65849 bug report here. I checked the same file with the newest Blender build and fantastic job as it looks way better!
However, there are still issues with the hair. It seems especially more so with the root of the hair, but not necessarily just the roots (check hair in front of the ears).

This is not the goal of this patch. Like I said (somewhere else) this might be fixable with a per object ID mask but it's more complex to implement.

@KiJeon (0o00o0oo) As in T86404: EEVEE Subsurface Color Bleed, the result is similar to cycles. Though I notice you have an oversized SSS radius as for a human scale - [0.3, 0, 0] for a head of 1.9m height, which might be ok, if not for G and B radius. Making it [0.3, 0.1, 0.1] will almost completely solve the coloring bleed while making it look more natural. In fact, the correct set of parameters for a human skin in Eevee is:
Method: Christensen-Burley
Subsurface: 1.0
Radius: [10 mm, 5 mm, 4-5 mm] ± 20%