Fix: EEVEE: Avoid loosing SSS small radius energy

This was caused by the effective radius of some
components were below the first sample radius.
This meant that the accumulation resulted in zero
weight for these components.

This patch introduce a minimum radius that can
be represented depending on the sample closest
to the center pixel. This makes sure that we
have at least one sample that will have
non-zero weight.

Fix #124031
This commit is contained in:
Clément Foucault 2024-07-03 15:09:11 +02:00
parent 44510662e9
commit e10dcc01bd
3 changed files with 15 additions and 11 deletions

@ -1987,12 +1987,14 @@ struct SubsurfaceData {
/** xy: 2D sample position [-1..1], zw: sample_bounds. */
/* NOTE(fclem) Using float4 for alignment. */
float4 samples[SSS_SAMPLE_MAX];
/** Sample index after which samples are not randomly rotated anymore. */
int jitter_threshold;
/** Number of samples precomputed in the set. */
int sample_len;
int _pad0;
/** WORKAROUND: To avoid invalid integral for components that have very small radius, we clamp
* the minimal radius. This add bias to the SSS effect but this is the simplest workaround I
* could find to ship this without visible artifact. */
float min_radius;
int _pad1;
int _pad2;
};
BLI_STATIC_ASSERT_ALIGN(SubsurfaceData, 16)

@ -21,13 +21,7 @@ namespace blender::eevee {
void SubsurfaceModule::end_sync()
{
data_.jitter_threshold = inst_.scene->eevee.sss_jitter_threshold;
if (data_.sample_len != inst_.scene->eevee.sss_samples) {
/* Convert sample count from old implementation which was using a separable filter. */
/* TODO(fclem) better remapping. */
// data_.sample_len = square_f(1 + 2 * inst_.scene->eevee.sss_samples);
data_.sample_len = 16;
}
data_.sample_len = 16;
{
PassSimple &pass = setup_ps_;
@ -113,15 +107,21 @@ void SubsurfaceModule::precompute_samples_location()
float rand_u = inst_.sampling.rng_get(SAMPLING_SSS_U);
float rand_v = inst_.sampling.rng_get(SAMPLING_SSS_V);
/* Find minimum radius that we can represent because we are only sampling the largest radius. */
data_.min_radius = 1.0f;
double golden_angle = M_PI * (3.0 - sqrt(5.0));
for (auto i : IndexRange(data_.sample_len)) {
float theta = golden_angle * i + M_PI * 2.0f * rand_u;
float x = (rand_v + i) / data_.sample_len;
float r = SubsurfaceModule::burley_sample(d, x);
data_.min_radius = min_ff(data_.min_radius, r);
data_.samples[i].x = cosf(theta) * r;
data_.samples[i].y = sinf(theta) * r;
data_.samples[i].z = 1.0f / burley_pdf(d, r);
}
/* Avoid float imprecision.*/
data_.min_radius = max_ff(data_.min_radius, 1e-4f);
inst_.uniform_data.push_update();
}

@ -112,7 +112,9 @@ void main(void)
}
/* Avoid too small radii that have float imprecision. */
vec3 clamped_sss_radius = max(vec3(1e-4), closure.sss_radius / max_radius) * max_radius;
vec3 clamped_sss_radius = max(vec3(uniform_buf.subsurface.min_radius),
closure.sss_radius / max_radius) *
max_radius;
/* Scale albedo because we can have HDR value caused by BSDF sampling. */
vec3 albedo = closure.color / max(1e-6, reduce_max(closure.color));
vec3 d = burley_setup(clamped_sss_radius, albedo);