EEVEE/EEVEE-Next: Split Diffuse and Subsurface closure

Even if related, they don't have the same performance
impact.

To avoid any performance hit, we replace the Diffuse
by a Subsurface Closure for legacy EEVEE and
use the subsurface closure only where needed for
EEVEE-Next leveraging the random sampling.

This increases the compatibility with cycles that
doesn't modulate the radius of the subsurface anymore.
This change is only present in EEVEE-Next.

This commit changes the principled BSDF code so that
it is easier to follow the flow of data.

For legacy EEVEE, the SSS switch is moved to a
`radius == -1` check.
This commit is contained in:
Clément Foucault
2024-01-09 16:27:30 +13:00
parent a39c16270c
commit ea989ebf94
14 changed files with 137 additions and 69 deletions

View File

@@ -27,9 +27,9 @@ vec3 out_ssr_N;
bool aov_is_valid = false;
vec3 out_aov;
bool output_sss(ClosureDiffuse diffuse, ClosureOutputDiffuse diffuse_out)
bool output_sss(ClosureSubsurface diffuse, ClosureOutputDiffuse diffuse_out)
{
if (diffuse.sss_id == 0u || !do_sss || !sssToggle || outputSssId == 0) {
if (diffuse.sss_radius.r == -1.0 || !do_sss || !sssToggle || outputSssId == 0) {
return false;
}
if (renderPassSSSColor) {
@@ -71,6 +71,7 @@ void output_aov(vec4 color, float value, uint hash)
}
/* Single BSDFs. */
CLOSURE_EVAL_FUNCTION_DECLARE_1(DiffuseBSDF, Diffuse)
Closure closure_eval(ClosureDiffuse diffuse)
{
@@ -83,8 +84,24 @@ Closure closure_eval(ClosureDiffuse diffuse)
CLOSURE_EVAL_FUNCTION_1(DiffuseBSDF, Diffuse);
Closure closure = CLOSURE_DEFAULT;
if (!output_sss(diffuse, out_Diffuse_0)) {
closure.radiance += out_Diffuse_0.radiance * diffuse.color * diffuse.weight;
closure.radiance += out_Diffuse_0.radiance * diffuse.color * diffuse.weight;
return closure;
}
/* NOTE: Reuse the diffuse eval function. */
Closure closure_eval(ClosureSubsurface subsurface)
{
/* Glue with the old system. */
CLOSURE_VARS_DECLARE_1(Diffuse);
in_Diffuse_0.N = subsurface.N;
in_Diffuse_0.albedo = subsurface.color;
CLOSURE_EVAL_FUNCTION_1(DiffuseBSDF, Diffuse);
Closure closure = CLOSURE_DEFAULT;
if (!output_sss(subsurface, out_Diffuse_0)) {
closure.radiance += out_Diffuse_0.radiance * subsurface.color * subsurface.weight;
}
return closure;
}
@@ -197,7 +214,7 @@ Closure closure_eval(ClosureReflection reflection, ClosureRefraction refraction)
/* Dielectric BSDF */
CLOSURE_EVAL_FUNCTION_DECLARE_2(DielectricBSDF, Diffuse, Glossy)
Closure closure_eval(ClosureDiffuse diffuse, ClosureReflection reflection)
Closure closure_eval(ClosureSubsurface diffuse, ClosureReflection reflection)
{
#if defined(DO_SPLIT_CLOSURE_EVAL)
Closure closure = closure_eval(diffuse);
@@ -230,7 +247,9 @@ Closure closure_eval(ClosureDiffuse diffuse, ClosureReflection reflection)
/* Specular BSDF */
CLOSURE_EVAL_FUNCTION_DECLARE_3(SpecularBSDF, Diffuse, Glossy, Glossy)
Closure closure_eval(ClosureDiffuse diffuse, ClosureReflection reflection, ClosureReflection coat)
Closure closure_eval(ClosureSubsurface diffuse,
ClosureReflection reflection,
ClosureReflection coat)
{
#if defined(DO_SPLIT_CLOSURE_EVAL)
Closure closure = closure_eval(diffuse);
@@ -267,7 +286,7 @@ Closure closure_eval(ClosureDiffuse diffuse, ClosureReflection reflection, Closu
/* Principled BSDF */
CLOSURE_EVAL_FUNCTION_DECLARE_4(PrincipledBSDF, Diffuse, Glossy, Glossy, Refraction)
Closure closure_eval(ClosureDiffuse diffuse,
Closure closure_eval(ClosureSubsurface diffuse,
ClosureReflection reflection,
ClosureReflection coat,
ClosureRefraction refraction)

View File

@@ -51,10 +51,10 @@ ClosureType closure_type_get(ClosureRefraction cl)
return CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID;
}
// ClosureType closure_type_get(ClosureSubsurface cl)
// {
// return CLOSURE_BSSRDF_BURLEY_ID;
// }
ClosureType closure_type_get(ClosureSubsurface cl)
{
return CLOSURE_BSSRDF_BURLEY_ID;
}
/**
* Returns true if the closure is to be selected based on the input weight.
@@ -127,11 +127,15 @@ Closure closure_eval(ClosureDiffuse diffuse)
{
ClosureUndetermined cl;
closure_base_copy(cl, diffuse);
/* TODO: Have dedicated ClosureSubsurface */
if (diffuse.sss_id != 0u) {
cl.type = CLOSURE_BSSRDF_BURLEY_ID;
cl.data.rgb = diffuse.sss_radius;
}
closure_select(g_diffuse_data, g_diffuse_rand, cl);
return Closure(0);
}
Closure closure_eval(ClosureSubsurface diffuse)
{
ClosureUndetermined cl;
closure_base_copy(cl, diffuse);
cl.data.rgb = diffuse.sss_radius;
closure_select(g_diffuse_data, g_diffuse_rand, cl);
return Closure(0);
}

View File

@@ -97,9 +97,7 @@ void main(void)
return;
}
/* TODO SSS closure. */
ClosureDiffuse closure = to_closure_diffuse(gbuffer_closure_get(gbuf, 0));
ClosureSubsurface closure = to_closure_subsurface(gbuffer_closure_get(gbuf, 0));
float max_radius = reduce_max(closure.sss_radius);
float homcoord = ProjectionMatrix[2][3] * vP.z + ProjectionMatrix[3][3];

View File

@@ -26,12 +26,10 @@ void main(void)
GBufferReader gbuf = gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel);
if (gbuffer_closure_get(gbuf, 0).type == CLOSURE_BSSRDF_BURLEY_ID) {
/* TODO SSS closure. */
vec3 radiance = imageLoad(direct_light_img, texel).rgb +
imageLoad(indirect_light_img, texel).rgb;
ClosureDiffuse closure = to_closure_diffuse(gbuffer_closure_get(gbuf, 0));
ClosureSubsurface closure = to_closure_subsurface(gbuffer_closure_get(gbuf, 0));
float max_radius = reduce_max(closure.sss_radius);
imageStore(radiance_img, texel, vec4(radiance, 0.0));

View File

@@ -174,8 +174,13 @@ struct ClosureDiffuse {
float weight;
vec3 color;
vec3 N;
};
struct ClosureSubsurface {
float weight;
vec3 color;
vec3 N;
vec3 sss_radius;
uint sss_id;
};
struct ClosureTranslucent {
@@ -234,7 +239,14 @@ ClosureDiffuse to_closure_diffuse(ClosureUndetermined cl)
ClosureDiffuse closure;
closure.N = cl.N;
closure.color = cl.color;
/* TODO(fclem): BSSSRDF closure. */
return closure;
}
ClosureSubsurface to_closure_subsurface(ClosureUndetermined cl)
{
ClosureSubsurface closure;
closure.N = cl.N;
closure.color = cl.color;
closure.sss_radius = cl.data.xyz;
return closure;
}

View File

@@ -8,7 +8,6 @@ void node_bsdf_diffuse(vec4 color, float roughness, vec3 N, float weight, out Cl
diffuse_data.weight = weight;
diffuse_data.color = color.rgb;
diffuse_data.N = N;
diffuse_data.sss_id = 0u;
result = closure_eval(diffuse_data);
}

View File

@@ -39,18 +39,26 @@ void node_eevee_specular(vec4 diffuse,
float alpha = (1.0 - transp) * weight;
#ifdef GPU_SHADER_EEVEE_LEGACY_DEFINES
/* EEVEE Legacy evaluates the subsurface as a diffuse closure.
* So this has no performance penalty. However, using a separate closure for subsurface
* (just like for EEVEE-Next) would induce a huge performance hit. */
ClosureSubsurface diffuse_data;
#else
ClosureDiffuse diffuse_data;
#endif
diffuse_data.weight = alpha;
diffuse_data.color = diffuse.rgb;
diffuse_data.N = N;
diffuse_data.sss_id = 0u;
#ifdef GPU_SHADER_EEVEE_LEGACY_DEFINES
/* WORKAROUND: Nasty workaround to the current interface with the closure evaluation.
* Ideally the occlusion input should be move to the output node or removed all-together.
* This is temporary to avoid a regression in 3.2 and should be removed after EEVEE-Next rewrite.
*/
diffuse_data.sss_radius.r = occlusion;
diffuse_data.sss_radius.g = -1.0; /* Flag */
#endif
ClosureReflection reflection_data;
reflection_data.weight = alpha;

View File

@@ -27,8 +27,6 @@ void node_bsdf_hair(vec4 color,
hair_data.weight = weight;
hair_data.color = color.rgb;
hair_data.N = g_data.N;
hair_data.sss_radius = vec3(0.0);
hair_data.sss_id = 0u;
#endif
result = closure_eval(hair_data);
}
@@ -68,8 +66,6 @@ void node_bsdf_hair_principled(vec4 color,
hair_data.weight = weight;
hair_data.color = color.rgb;
hair_data.N = g_data.N;
hair_data.sss_radius = vec3(0.0);
hair_data.sss_id = 0u;
#endif
result = closure_eval(hair_data);
}

View File

@@ -59,7 +59,7 @@ void node_bsdf_principled(vec4 base_color,
const float do_coat,
const float do_refraction,
const float do_multiscatter,
float do_sss,
const float do_sss,
out Closure result)
{
/* Match cycles. */
@@ -87,13 +87,6 @@ void node_bsdf_principled(vec4 base_color,
base_color = max(base_color, vec4(0.0));
vec4 clamped_base_color = min(base_color, vec4(1.0));
vec4 diffuse_sss_base_color = base_color;
if (subsurface_weight > 0.0) {
/* Subsurface Scattering materials behave unpredictably with values greater than 1.0 in Cycles.
* So it's clamped there and we clamp here for consistency with Cycles. */
diffuse_sss_base_color = mix(diffuse_sss_base_color, clamped_base_color, subsurface_weight);
}
N = safe_normalize(N);
CN = safe_normalize(CN);
vec3 V = coordinate_incoming(g_data.P);
@@ -106,19 +99,14 @@ void node_bsdf_principled(vec4 base_color,
weight *= alpha;
/* First layer: Sheen */
ClosureDiffuse diffuse_data;
diffuse_data.N = N;
vec3 sheen_data_color = vec3(0.0);
if (sheen_weight > 0.0) {
/* TODO: Maybe sheen_weight should be specular. */
vec3 sheen_color = sheen_weight * sheen_tint.rgb * principled_sheen(NV, sheen_roughness);
diffuse_data.color = weight * sheen_color;
sheen_data_color = weight * sheen_color;
/* Attenuate lower layers */
weight *= max((1.0 - math_reduce_max(sheen_color)), 0.0);
}
else {
diffuse_data.color = vec3(0.0);
}
/* Second layer: Coat */
ClosureReflection coat_data;
@@ -218,25 +206,69 @@ void node_bsdf_principled(vec4 base_color,
bsdf_lut(F0, F90, vec3(0.0), NV, roughness, eta, do_multiscatter != 0.0, reflectance, unused);
reflection_data.color += weight * reflectance;
/* Adjust the weight of picking the closure. */
reflection_data.color *= coat_tint.rgb;
reflection_data.weight = math_average(reflection_data.color);
reflection_data.color *= safe_rcp(reflection_data.weight);
/* Attenuate lower layers */
weight *= max((1.0 - math_reduce_max(reflectance)), 0.0);
}
/* Diffuse component */
if (true) {
diffuse_data.sss_radius = subsurface_weight *
max(subsurface_radius * subsurface_scale, vec3(0.0));
diffuse_data.sss_id = uint(do_sss);
diffuse_data.color += weight * diffuse_sss_base_color.rgb * coat_tint.rgb;
#ifdef GPU_SHADER_EEVEE_LEGACY_DEFINES
/* EEVEE Legacy evaluates the subsurface as a diffuse closure.
* So this has no performance penalty. However, using a separate closure for subsurface
* (just like for EEVEE-Next) would induce a huge performance hit. */
ClosureSubsurface diffuse_data;
#else
ClosureDiffuse diffuse_data;
#endif
diffuse_data.N = N;
subsurface_radius = max(subsurface_radius * subsurface_scale, vec3(0.0));
/* Subsurface component */
if (subsurface_weight > 0.0) {
#ifdef GPU_SHADER_EEVEE_LEGACY_DEFINES
/* For Legacy EEVEE, Subsurface is just part of the diffuse. Just evaluate the mixed color. */
/* Subsurface Scattering materials behave unpredictably with values greater than 1.0 in
* Cycles. So it's clamped there and we clamp here for consistency with Cycles. */
base_color = mix(base_color, clamped_base_color, subsurface_weight);
if (do_sss == 0.0) {
diffuse_data.sss_radius = vec3(-1);
}
else {
diffuse_data.sss_radius = subsurface_weight * subsurface_radius;
}
#else
ClosureSubsurface sss_data;
sss_data.N = N;
sss_data.sss_radius = subsurface_radius;
/* Subsurface Scattering materials behave unpredictably with values greater than 1.0 in
* Cycles. So it's clamped there and we clamp here for consistency with Cycles. */
sss_data.color = (subsurface_weight * weight) * clamped_base_color.rgb * coat_tint.rgb;
/* Add energy of the sheen layer until we have proper sheen BSDF. */
sss_data.color += sheen_data_color;
sss_data.weight = math_average(sss_data.color);
sss_data.color *= safe_rcp(sss_data.weight);
result = closure_eval(sss_data);
/* Attenuate lower layers */
weight *= max((1.0 - subsurface_weight), 0.0);
#endif
}
/* Adjust the weight of picking the closure. */
reflection_data.color *= coat_tint.rgb;
reflection_data.weight = math_average(reflection_data.color);
reflection_data.color *= safe_rcp(reflection_data.weight);
/* Diffuse component */
if (true) {
diffuse_data.color = weight * base_color.rgb * coat_tint.rgb;
/* Add energy of the sheen layer until we have proper sheen BSDF. */
diffuse_data.color += sheen_data_color;
diffuse_data.weight = math_average(diffuse_data.color);
diffuse_data.color *= safe_rcp(diffuse_data.weight);
diffuse_data.weight = math_average(diffuse_data.color);
diffuse_data.color *= safe_rcp(diffuse_data.weight);
}
/* Ref. #98190: Defines are optimizations for old compilers.
* Might become unnecessary with EEVEE-Next. */

View File

@@ -13,7 +13,6 @@ void node_bsdf_sheen(vec4 color, float roughness, vec3 N, float weight, out Clos
diffuse_data.weight = weight;
diffuse_data.color = color.rgb;
diffuse_data.N = N;
diffuse_data.sss_id = 0u;
result = closure_eval(diffuse_data);
}

View File

@@ -9,7 +9,7 @@ void node_subsurface_scattering(vec4 color,
float anisotropy,
vec3 N,
float weight,
float do_sss,
const float do_sss,
out Closure result)
{
color = max(color, vec4(0.0));
@@ -18,12 +18,16 @@ void node_subsurface_scattering(vec4 color,
ior = max(ior, 1e-5);
N = safe_normalize(N);
ClosureDiffuse diffuse_data;
diffuse_data.weight = weight;
diffuse_data.color = color.rgb;
diffuse_data.N = N;
diffuse_data.sss_radius = radius * scale;
diffuse_data.sss_id = uint(do_sss);
ClosureSubsurface sss_data;
sss_data.weight = weight;
sss_data.color = color.rgb;
sss_data.N = N;
sss_data.sss_radius = radius * scale;
result = closure_eval(diffuse_data);
#ifdef GPU_SHADER_EEVEE_LEGACY_DEFINES
if (do_sss == 0.0) {
sss_data.sss_radius = vec3(-1);
}
#endif
result = closure_eval(sss_data);
}

View File

@@ -13,7 +13,6 @@ void node_bsdf_toon(
diffuse_data.weight = weight;
diffuse_data.color = color.rgb;
diffuse_data.N = N;
diffuse_data.sss_id = 0u;
result = closure_eval(diffuse_data);
}

View File

@@ -330,7 +330,7 @@ static int node_shader_gpu_bsdf_principled(GPUMaterial *mat,
GPU_constant(&use_coat_f),
GPU_constant(&use_refract_f),
GPU_constant(&use_multi_scatter),
GPU_uniform(&use_sss));
GPU_constant(&use_sss));
}
static void node_shader_update_principled(bNodeTree *ntree, bNode *node)

View File

@@ -59,7 +59,7 @@ static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat,
GPU_material_flag_set(mat, GPU_MATFLAG_DIFFUSE | GPU_MATFLAG_SUBSURFACE);
return GPU_stack_link(mat, node, "node_subsurface_scattering", in, out, GPU_uniform(&use_sss));
return GPU_stack_link(mat, node, "node_subsurface_scattering", in, out, GPU_constant(&use_sss));
}
static void node_shader_update_subsurface_scattering(bNodeTree *ntree, bNode *node)