EEVEE: Add support for GGX Multi-scatter

Based on http://jcgt.org/published/0008/01/03/

This is a simple trick that does *not* have a huge performance impact but
does work pretty well. It just modifies the Fresnel term to account for
the multibounce energy loss (coloration).

However this makes the shader variations count double. To avoid this we
use a uniform and pass the multiscatter use flag inside the sign of f90.
This is a bit hacky but avoids many code duplication.

This uses the simplification proposed by McAuley in
A Journey Through Implementing Multiscattering BRDFs and Area Lights

This does not handle area light differently than the IBL case but that's
already an issue in current implementation.

This is related to T68460.

Reviewed By: brecht
Differential Revision: https://developer.blender.org/D8912
This commit is contained in:
Clément Foucault 2020-09-19 00:06:45 +02:00
parent aa2e978bd8
commit 6f3c279d9e
Notes: blender-bot 2023-02-14 04:39:18 +01:00
Referenced by commit 7b9e47a35e, EEVEE: Fix Missing GGX multi-scattering on Glass BSDF
Referenced by issue #81820, difference in rendering of alpha material between 2.90 and 2.91 (Eevee)
Referenced by issue #68460, Multiscatter Glossy BSDF
8 changed files with 102 additions and 45 deletions

View File

@ -74,34 +74,36 @@ vec3 F_color_blend(float eta, float fresnel, vec3 f0_color)
return mix(f0_color, vec3(1.0), fac);
}
/* Fresnel */
vec3 F_schlick(vec3 f0, float cos_theta)
{
float fac = 1.0 - cos_theta;
float fac2 = fac * fac;
fac = fac2 * fac2 * fac;
/* Unreal specular matching : if specular color is below 2% intensity,
* (using green channel for intensity) treat as shadowning */
return saturate(50.0 * dot(f0, vec3(0.3, 0.6, 0.1))) * fac + (1.0 - fac) * f0;
}
/* Fresnel approximation for LTC area lights (not MRP) */
vec3 F_area(vec3 f0, vec3 f90, vec2 lut)
/* Fresnel split-sum approximation. */
vec3 F_brdf_single_scatter(vec3 f0, vec3 f90, vec2 lut)
{
/* Unreal specular matching : if specular color is below 2% intensity,
* treat as shadowning */
return saturate(50.0 * dot(f0, vec3(0.3, 0.6, 0.1))) * lut.y * f90 + lut.x * f0;
return saturate(50.0 * dot(f0, vec3(0.3, 0.6, 0.1))) * lut.y * abs(f90) + lut.x * f0;
}
/* Fresnel approximation for IBL */
vec3 F_ibl(vec3 f0, vec3 f90, vec2 lut)
/* Multi-scattering brdf approximation from :
* "A Multiple-Scattering Microfacet Model for Real-Time Image-based Lighting"
* by Carmelo J. Fdez-Agüera. */
vec3 F_brdf_multi_scatter(vec3 f0, vec3 f90, vec2 lut)
{
/* Unreal specular matching : if specular color is below 2% intensity,
* treat as shadowning */
return saturate(50.0 * dot(f0, vec3(0.3, 0.6, 0.1))) * lut.y * f90 + lut.x * f0;
vec3 FssEss = F_brdf_single_scatter(f0, f90, lut);
/* Hack to avoid many more shader variations. */
if (f90.g < 0.0) {
return FssEss;
}
float Ess = lut.x + lut.y;
float Ems = 1.0 - Ess;
vec3 Favg = f0 + (1.0 - f0) / 21.0;
vec3 Fms = FssEss * Favg / (1.0 - (1.0 - Ess) * Favg);
/* We don't do anything special for diffuse surfaces because the principle bsdf
* does not care about energy conservation of the specular layer for dielectrics. */
return FssEss + Fms * Ems;
}
#define F_brdf(f0, f90, lut) F_brdf_multi_scatter(f0, f90, lut)
/* GGX */
float D_ggx_opti(float NH, float a2)
{

View File

@ -250,12 +250,12 @@ void CLOSURE_NAME(vec3 N
# ifdef CLOSURE_GLOSSY
vec2 brdf_lut_lights = texture(utilTex, vec3(lut_uv, 1.0)).ba;
out_spec *= F_area(f0, f90, brdf_lut_lights.xy);
out_spec *= F_brdf(f0, f90, brdf_lut_lights.xy);
# endif
# ifdef CLOSURE_CLEARCOAT
vec2 brdf_lut_lights_clear = texture(utilTex, vec3(lut_uv_clear, 1.0)).ba;
out_spec_clear *= F_area(vec3(0.04), vec3(1.0), brdf_lut_lights_clear.xy);
out_spec_clear *= F_brdf(vec3(0.04), vec3(1.0), brdf_lut_lights_clear.xy);
out_spec += out_spec_clear * C_intensity;
# endif
@ -449,7 +449,7 @@ void CLOSURE_NAME(vec3 N
/* This factor is outputted to be used by SSR in order
* to match the intensity of the regular reflections. */
ssr_spec = F_ibl(f0, f90, brdf_lut);
ssr_spec = F_brdf(f0, f90, brdf_lut);
float spec_occlu = specular_occlusion(NV, final_ao, roughness);
/* The SSR pass recompute the occlusion to not apply it to the SSR */
@ -470,7 +470,7 @@ void CLOSURE_NAME(vec3 N
NV = dot(C_N, V);
vec2 C_uv = lut_coords(NV, C_roughness);
vec2 C_brdf_lut = texture(utilTex, vec3(C_uv, 1.0)).rg;
vec3 C_fresnel = F_ibl(vec3(0.04), vec3(1.0), C_brdf_lut) *
vec3 C_fresnel = F_brdf(vec3(0.04), vec3(1.0), C_brdf_lut) *
specular_occlusion(NV, final_ao, C_roughness);
out_spec += C_spec_accum.rgb * C_fresnel * C_intensity;

View File

@ -5,11 +5,12 @@ void node_bsdf_anisotropic(vec4 color,
float rotation,
vec3 N,
vec3 T,
float use_multiscatter,
out Closure result)
{
node_bsdf_glossy(color, roughness, N, -1, result);
node_bsdf_glossy(color, roughness, N, -1, use_multiscatter, result);
}
#else
/* Stub anisotropic because it is not compatible with volumetrics. */
# define node_bsdf_anisotropic(a, b, c, d, e, f, g) (g = CLOSURE_DEFAULT)
# define node_bsdf_anisotropic(a, b, c, d, e, f, g, result) (result = CLOSURE_DEFAULT)
#endif

View File

@ -1,10 +1,18 @@
#ifndef VOLUMETRICS
void node_bsdf_glossy(vec4 color, float roughness, vec3 N, float ssr_id, out Closure result)
void node_bsdf_glossy(
vec4 color, float roughness, vec3 N, float use_multiscatter, float ssr_id, out Closure result)
{
N = normalize(N);
vec3 out_spec, ssr_spec;
eevee_closure_glossy(
N, vec3(1.0), vec3(1.0), int(ssr_id), roughness, 1.0, true, out_spec, ssr_spec);
eevee_closure_glossy(N,
vec3(1.0),
use_multiscatter != 0.0 ? vec3(1.0) : vec3(-1.0), /* HACK */
int(ssr_id),
roughness,
1.0,
true,
out_spec,
ssr_spec);
vec3 vN = mat3(ViewMatrix) * N;
result = CLOSURE_DEFAULT;
result.radiance = render_pass_glossy_mask(vec3(1.0), out_spec) * color.rgb;
@ -12,5 +20,5 @@ void node_bsdf_glossy(vec4 color, float roughness, vec3 N, float ssr_id, out Clo
}
#else
/* Stub glossy because it is not compatible with volumetrics. */
# define node_bsdf_glossy(a, b, c, d, e) (e = CLOSURE_DEFAULT)
# define node_bsdf_glossy(a, b, c, d, e, result) (result = CLOSURE_DEFAULT)
#endif

View File

@ -60,6 +60,7 @@ void node_bsdf_principled(vec4 base_color,
vec3 CN,
vec3 T,
vec3 I,
float use_multiscatter,
float ssr_id,
float sss_id,
vec3 sss_scale,
@ -107,7 +108,9 @@ void node_bsdf_principled(vec4 base_color,
eevee_closure_principled(N,
mixed_ss_base_color,
f0,
f90,
/* HACK: Pass the multiscatter flag as the sign to not add closure
* variations or increase register usage. */
(use_multiscatter != 0.0) ? f90 : -f90,
int(ssr_id),
roughness,
CN,
@ -168,6 +171,7 @@ void node_bsdf_principled_dielectric(vec4 base_color,
vec3 CN,
vec3 T,
vec3 I,
float use_multiscatter,
float ssr_id,
float sss_id,
vec3 sss_scale,
@ -188,8 +192,19 @@ void node_bsdf_principled_dielectric(vec4 base_color,
float NV = dot(N, cameraVec);
principled_sheen(NV, ctint, sheen, sheen_tint, out_sheen, sheen_color);
eevee_closure_default(
N, diffuse, f0, f90, int(ssr_id), roughness, 1.0, true, out_diff, out_spec, ssr_spec);
eevee_closure_default(N,
diffuse,
f0,
/* HACK: Pass the multiscatter flag as the sign to not add closure
* variations or increase register usage. */
(use_multiscatter != 0.0) ? f90 : -f90,
int(ssr_id),
roughness,
1.0,
true,
out_diff,
out_spec,
ssr_spec);
result = CLOSURE_DEFAULT;
result.radiance = render_pass_glossy_mask(vec3(1.0), out_spec);
@ -226,6 +241,7 @@ void node_bsdf_principled_metallic(vec4 base_color,
vec3 CN,
vec3 T,
vec3 I,
float use_multiscatter,
float ssr_id,
float sss_id,
vec3 sss_scale,
@ -236,8 +252,17 @@ void node_bsdf_principled_metallic(vec4 base_color,
vec3 f90 = mix(vec3(1.0), base_color.rgb, (1.0 - specular) * metallic);
eevee_closure_glossy(
N, base_color.rgb, f90, int(ssr_id), roughness, 1.0, true, out_spec, ssr_spec);
eevee_closure_glossy(N,
base_color.rgb,
/* HACK: Pass the multiscatter flag as the sign to not add closure
* variations or increase register usage. */
(use_multiscatter != 0.0) ? f90 : -f90,
int(ssr_id),
roughness,
1.0,
true,
out_spec,
ssr_spec);
result = CLOSURE_DEFAULT;
result.radiance = render_pass_glossy_mask(vec3(1.0), out_spec);
@ -272,6 +297,7 @@ void node_bsdf_principled_clearcoat(vec4 base_color,
vec3 CN,
vec3 T,
vec3 I,
float use_multiscatter,
float ssr_id,
float sss_id,
vec3 sss_scale,
@ -284,7 +310,9 @@ void node_bsdf_principled_clearcoat(vec4 base_color,
eevee_closure_clearcoat(N,
base_color.rgb,
f90,
/* HACK: Pass the multiscatter flag as the sign to not add closure
* variations or increase register usage. */
(use_multiscatter != 0.0) ? f90 : -f90,
int(ssr_id),
roughness,
CN,
@ -331,6 +359,7 @@ void node_bsdf_principled_subsurface(vec4 base_color,
vec3 CN,
vec3 T,
vec3 I,
float use_multiscatter,
float ssr_id,
float sss_id,
vec3 sss_scale,
@ -357,7 +386,9 @@ void node_bsdf_principled_subsurface(vec4 base_color,
eevee_closure_skin(N,
mixed_ss_base_color,
f0,
f90,
/* HACK: Pass the multiscatter flag as the sign to not add closure variations
or increase register usage. */
(use_multiscatter != 0.0) ? f90 : -f90,
int(ssr_id),
roughness,
1.0,
@ -405,6 +436,7 @@ void node_bsdf_principled_glass(vec4 base_color,
vec3 CN,
vec3 T,
vec3 I,
float use_multiscatter,
float ssr_id,
float sss_id,
vec3 sss_scale,
@ -450,11 +482,11 @@ void node_bsdf_principled_glass(vec4 base_color,
#else
/* clang-format off */
/* Stub principled because it is not compatible with volumetrics. */
# define node_bsdf_principled(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_dielectric(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_metallic(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_clearcoat(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_subsurface(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_glass(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_dielectric(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_metallic(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_clearcoat(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_subsurface(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, result) (result = CLOSURE_DEFAULT)
# define node_bsdf_principled_glass(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, result) (result = CLOSURE_DEFAULT)
/* clang-format on */
#endif

View File

@ -53,7 +53,10 @@ static int node_shader_gpu_bsdf_anisotropic(GPUMaterial *mat,
GPU_material_flag_set(mat, GPU_MATFLAG_GLOSSY);
return GPU_stack_link(mat, node, "node_bsdf_anisotropic", in, out);
float use_multi_scatter = (node->custom1 == SHD_GLOSSY_MULTI_GGX) ? 1.0f : 0.0f;
return GPU_stack_link(
mat, node, "node_bsdf_anisotropic", in, out, GPU_constant(&use_multi_scatter));
}
/* node type definition */

View File

@ -54,7 +54,15 @@ static int node_shader_gpu_bsdf_glossy(GPUMaterial *mat,
GPU_material_flag_set(mat, GPU_MATFLAG_GLOSSY);
return GPU_stack_link(mat, node, "node_bsdf_glossy", in, out, GPU_constant(&node->ssr_id));
float use_multi_scatter = (node->custom1 == SHD_GLOSSY_MULTI_GGX) ? 1.0f : 0.0f;
return GPU_stack_link(mat,
node,
"node_bsdf_glossy",
in,
out,
GPU_constant(&use_multi_scatter),
GPU_constant(&node->ssr_id));
}
/* node type definition */

View File

@ -172,6 +172,8 @@ static int node_shader_gpu_bsdf_principled(GPUMaterial *mat,
flag |= GPU_MATFLAG_SSS;
}
float use_multi_scatter = (node->custom1 == SHD_GLOSSY_MULTI_GGX) ? 1.0f : 0.0f;
GPU_material_flag_set(mat, flag);
return GPU_stack_link(mat,
@ -180,6 +182,7 @@ static int node_shader_gpu_bsdf_principled(GPUMaterial *mat,
in,
out,
GPU_builtin(GPU_VIEW_POSITION),
GPU_constant(&use_multi_scatter),
GPU_constant(&node->ssr_id),
GPU_constant(&node->sss_id),
sss_scale);