Cycles: Add Metallic Tint to Principled BSDF using F82-Tint model

With the default value, this is backwards-compatible.

Ref #99447

Pull Request: https://projects.blender.org/blender/blender/pulls/112551
This commit is contained in:
Lukas Stockner 2023-09-17 01:14:51 +02:00 committed by Brecht Van Lommel
parent 7eead8912d
commit 86156566a7
10 changed files with 159 additions and 30 deletions

View File

@ -26,6 +26,7 @@ enum MicrofacetFresnel {
DIELECTRIC_TINT, /* used by the OSL MaterialX closures */
CONDUCTOR,
GENERALIZED_SCHLICK,
F82_TINT,
};
typedef struct FresnelDielectricTint {
@ -46,6 +47,13 @@ typedef struct FresnelGeneralizedSchlick {
float exponent;
} FresnelGeneralizedSchlick;
typedef struct FresnelF82Tint {
/* Perpendicular reflectivity. */
Spectrum f0;
/* Precomputed (1-cos)^6 factor for edge tint. */
Spectrum b;
} FresnelF82Tint;
typedef struct MicrofacetBsdf {
SHADER_CLOSURE_BASE;
@ -236,6 +244,17 @@ ccl_device_forceinline void microfacet_fresnel(ccl_private const MicrofacetBsdf
*r_reflectance = fresnel_conductor(cos_theta_i, fresnel->n, fresnel->k);
*r_transmittance = zero_spectrum();
}
else if (bsdf->fresnel_type == MicrofacetFresnel::F82_TINT) {
/* F82-Tint model, described in "Novel aspects of the Adobe Standard Material" by Kutz et al.
* Essentially, this is the usual Schlick Fresnel with an additional cosI*(1-cosI)^6
* term which modulates the reflectivity around acos(1/7) degrees (ca. 82°). */
ccl_private FresnelF82Tint *fresnel = (ccl_private FresnelF82Tint *)bsdf->fresnel;
const float mu = saturatef(1.0f - cos_theta_i);
const float mu5 = sqr(sqr(mu)) * mu;
const Spectrum F_schlick = mix(fresnel->f0, one_spectrum(), mu5);
*r_reflectance = saturate(F_schlick - fresnel->b * cos_theta_i * mu5 * mu);
*r_transmittance = zero_spectrum();
}
else if (bsdf->fresnel_type == MicrofacetFresnel::GENERALIZED_SCHLICK) {
ccl_private FresnelGeneralizedSchlick *fresnel = (ccl_private FresnelGeneralizedSchlick *)
bsdf->fresnel;
@ -379,6 +398,14 @@ ccl_device Spectrum bsdf_microfacet_estimate_albedo(KernelGlobals kg,
}
reflectance = mix(fresnel->f0, fresnel->f90, s) * fresnel->reflection_tint;
}
else if (bsdf->fresnel_type == MicrofacetFresnel::F82_TINT) {
ccl_private FresnelF82Tint *fresnel = (ccl_private FresnelF82Tint *)bsdf->fresnel;
float rough = sqrtf(sqrtf(bsdf->alpha_x * bsdf->alpha_y));
const float s = lookup_table_read_3D(
kg, rough, cos_NI, 0.5f, kernel_data.tables.ggx_gen_schlick_s, 16, 16, 16);
/* TODO: Precompute B factor term and account for it here. */
reflectance = mix(fresnel->f0, one_spectrum(), s);
}
return reflectance + transmittance;
}
@ -738,6 +765,7 @@ ccl_device void bsdf_microfacet_setup_fresnel_generalized_schlick(
ccl_private FresnelGeneralizedSchlick *fresnel,
const bool preserve_energy)
{
fresnel->f0 = saturate(fresnel->f0);
bsdf->fresnel_type = MicrofacetFresnel::GENERALIZED_SCHLICK;
bsdf->fresnel = fresnel;
bsdf->sample_weight *= average(bsdf_microfacet_estimate_albedo(kg, sd, bsdf, true, true));
@ -780,6 +808,40 @@ ccl_device void bsdf_microfacet_setup_fresnel_generalized_schlick(
}
}
ccl_device void bsdf_microfacet_setup_fresnel_f82_tint(KernelGlobals kg,
ccl_private MicrofacetBsdf *bsdf,
ccl_private const ShaderData *sd,
ccl_private FresnelF82Tint *fresnel,
const Spectrum f82_tint,
const bool preserve_energy)
{
if (isequal(f82_tint, one_spectrum())) {
fresnel->b = zero_spectrum();
}
else {
/* Precompute the F82 term factor for the Fresnel model.
* In the classic F82 model, the F82 input directly determines the value of the Fresnel
* model at ~82°, similar to F0 and F90.
* With F82-Tint, on the other hand, the value at 82° is the value of the classic Schlick
* model multiplied by the tint input.
* Therefore, the factor follows by setting F82Tint(cosI) = FSchlick(cosI) - b*cosI*(1-cosI)^6
* and F82Tint(acos(1/7)) = FSchlick(acos(1/7)) * f82_tint and solving for b. */
const float f = 6.0f / 7.0f;
const float f5 = sqr(sqr(f)) * f;
const Spectrum F_schlick = mix(fresnel->f0, one_spectrum(), f5);
fresnel->b = F_schlick * (7.0f / (f5 * f)) * (one_spectrum() - f82_tint);
}
bsdf->fresnel_type = MicrofacetFresnel::F82_TINT;
bsdf->fresnel = fresnel;
bsdf->sample_weight *= average(bsdf_microfacet_estimate_albedo(kg, sd, bsdf, true, true));
if (preserve_energy) {
Spectrum Fss = mix(fresnel->f0, one_spectrum(), 1.0f / 21.0f) - fresnel->b * (1.0f / 126.0f);
microfacet_ggx_preserve_energy(kg, bsdf, sd, Fss);
}
}
ccl_device void bsdf_microfacet_setup_fresnel_constant(KernelGlobals kg,
ccl_private MicrofacetBsdf *bsdf,
ccl_private const ShaderData *sd,

View File

@ -474,6 +474,53 @@ ccl_device void osl_closure_microfacet_setup(KernelGlobals kg,
/* Special-purpose Microfacet closures */
ccl_device void osl_closure_microfacet_f82_tint_setup(
KernelGlobals kg,
ccl_private ShaderData *sd,
uint32_t path_flag,
float3 weight,
ccl_private const MicrofacetF82TintClosure *closure,
float3 *layer_albedo)
{
if (osl_closure_skip(kg, sd, path_flag, LABEL_GLOSSY | LABEL_REFLECT)) {
return;
}
ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)bsdf_alloc(
sd, sizeof(MicrofacetBsdf), rgb_to_spectrum(weight));
if (!bsdf) {
return;
}
ccl_private FresnelF82Tint *fresnel = (ccl_private FresnelF82Tint *)closure_alloc_extra(
sd, sizeof(FresnelF82Tint));
if (!fresnel) {
return;
}
bsdf->N = maybe_ensure_valid_specular_reflection(sd, closure->N);
bsdf->alpha_x = closure->alpha_x;
bsdf->alpha_y = closure->alpha_y;
bsdf->ior = 0.0f;
bsdf->T = closure->T;
bool preserve_energy = false;
/* Beckmann */
if (closure->distribution == make_string("beckmann", 14712237670914973463ull)) {
sd->flag |= bsdf_microfacet_beckmann_setup(bsdf);
}
/* GGX (either single- or multi-scattering) */
else {
sd->flag |= bsdf_microfacet_ggx_setup(bsdf);
preserve_energy = (closure->distribution == make_string("multi_ggx", 16842698693386468366ull));
}
fresnel->f0 = rgb_to_spectrum(closure->f0);
bsdf_microfacet_setup_fresnel_f82_tint(
kg, bsdf, sd, fresnel, rgb_to_spectrum(closure->f82), preserve_energy);
}
ccl_device void osl_closure_microfacet_multi_ggx_glass_setup(
KernelGlobals kg,
ccl_private ShaderData *sd,

View File

@ -84,6 +84,16 @@ OSL_CLOSURE_STRUCT_BEGIN(Microfacet, microfacet)
OSL_CLOSURE_STRUCT_MEMBER(Microfacet, INT, int, refract, NULL)
OSL_CLOSURE_STRUCT_END(Microfacet, microfacet)
OSL_CLOSURE_STRUCT_BEGIN(MicrofacetF82Tint, microfacet_f82_tint)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetF82Tint, STRING, DeviceString, distribution, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetF82Tint, VECTOR, packed_float3, N, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetF82Tint, VECTOR, packed_float3, T, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetF82Tint, FLOAT, float, alpha_x, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetF82Tint, FLOAT, float, alpha_y, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetF82Tint, VECTOR, packed_float3, f0, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetF82Tint, VECTOR, packed_float3, f82, NULL)
OSL_CLOSURE_STRUCT_END(MicrofacetF82Tint, microfacet)
OSL_CLOSURE_STRUCT_BEGIN(MicrofacetMultiGGXGlass, microfacet_multi_ggx_glass)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetMultiGGXGlass, VECTOR, packed_float3, N, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetMultiGGXGlass, FLOAT, float, alpha_x, NULL)

View File

@ -16,6 +16,7 @@ shader node_principled_bsdf(string distribution = "multi_ggx",
float Metallic = 0.0,
float Specular = 0.5,
float SpecularTint = 0.0,
color MetallicTint = 1.0,
float Roughness = 0.5,
float Anisotropic = 0.0,
float AnisotropicRotation = 0.0,
@ -97,8 +98,8 @@ shader node_principled_bsdf(string distribution = "multi_ggx",
if (Metallic > 0.0) {
color f0 = BaseColor;
color f90 = color(1.0);
MetallicBSDF = generalized_schlick_bsdf(
Normal, T, color(1.0), color(0.0), alpha_x, alpha_y, f0, f90, 5.0, distribution);
MetallicBSDF = microfacet_f82_tint(
distribution, Normal, T, alpha_x, alpha_y, f0, MetallicTint);
BSDF = mix(BSDF, MetallicBSDF, clamp(Metallic, 0.0, 1.0));
}

View File

@ -26,6 +26,9 @@ closure color ashikhmin_velvet(normal N, float sigma) BUILTIN;
closure color sheen(normal N, float roughness) BUILTIN;
closure color ambient_occlusion() BUILTIN;
closure color microfacet_f82_tint(
string distribution, vector N, vector T, float ax, float ay, color f0, color f82) BUILTIN;
/* Needed to pass along the color for multi-scattering saturation adjustment,
* otherwise could be replaced by microfacet() */
closure color microfacet_multi_ggx_glass(normal N, float ag, float eta, color C) BUILTIN;

View File

@ -78,7 +78,7 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
sheen_offset, sheen_tint_offset, sheen_roughness_offset, coat_offset,
coat_roughness_offset, coat_ior_offset, eta_offset, transmission_offset,
anisotropic_rotation_offset, coat_tint_offset, coat_normal_offset, dummy, alpha_offset,
emission_strength_offset, emission_offset;
emission_strength_offset, emission_offset, metallic_tint_offset;
uint4 data_node2 = read_node(kg, &offset);
float3 T = stack_load_float3(stack, data_node.y);
@ -87,8 +87,11 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
&roughness_offset,
&specular_tint_offset,
&anisotropic_offset);
svm_unpack_node_uchar4(
data_node.w, &sheen_offset, &sheen_tint_offset, &sheen_roughness_offset, &dummy);
svm_unpack_node_uchar4(data_node.w,
&sheen_offset,
&sheen_tint_offset,
&sheen_roughness_offset,
&metallic_tint_offset);
svm_unpack_node_uchar4(data_node2.x,
&eta_offset,
&transmission_offset,
@ -255,10 +258,10 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
if (reflective_caustics && metallic > CLOSURE_WEIGHT_CUTOFF) {
ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)bsdf_alloc(
sd, sizeof(MicrofacetBsdf), metallic * weight);
ccl_private FresnelGeneralizedSchlick *fresnel =
(bsdf != NULL) ? (ccl_private FresnelGeneralizedSchlick *)closure_alloc_extra(
sd, sizeof(FresnelGeneralizedSchlick)) :
NULL;
ccl_private FresnelF82Tint *fresnel =
(bsdf != NULL) ?
(ccl_private FresnelF82Tint *)closure_alloc_extra(sd, sizeof(FresnelF82Tint)) :
NULL;
if (bsdf && fresnel) {
bsdf->N = valid_reflection_N;
@ -268,15 +271,12 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
bsdf->alpha_y = alpha_y;
fresnel->f0 = rgb_to_spectrum(base_color);
fresnel->f90 = one_spectrum();
fresnel->exponent = 5.0f;
fresnel->reflection_tint = one_spectrum();
fresnel->transmission_tint = zero_spectrum();
const Spectrum f82 = rgb_to_spectrum(stack_load_float3(stack, metallic_tint_offset));
/* setup bsdf */
sd->flag |= bsdf_microfacet_ggx_setup(bsdf);
const bool is_multiggx = (distribution == CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID);
bsdf_microfacet_setup_fresnel_generalized_schlick(kg, bsdf, sd, fresnel, is_multiggx);
bsdf_microfacet_setup_fresnel_f82_tint(kg, bsdf, sd, fresnel, f82, is_multiggx);
/* Attenuate other components */
weight *= (1.0f - metallic);

View File

@ -2708,6 +2708,7 @@ NODE_DEFINE(PrincipledBsdfNode)
SOCKET_IN_FLOAT(specular, "Specular", 0.0f);
SOCKET_IN_FLOAT(roughness, "Roughness", 0.5f);
SOCKET_IN_FLOAT(specular_tint, "Specular Tint", 0.0f);
SOCKET_IN_COLOR(metallic_tint, "Metallic Tint", one_float3());
SOCKET_IN_FLOAT(anisotropic, "Anisotropic", 0.0f);
SOCKET_IN_FLOAT(sheen, "Sheen", 0.0f);
SOCKET_IN_FLOAT(sheen_roughness, "Sheen Roughness", 0.5f);
@ -2806,6 +2807,7 @@ void PrincipledBsdfNode::compile(SVMCompiler &compiler)
int specular_offset = compiler.stack_assign(input("Specular"));
int roughness_offset = compiler.stack_assign(input("Roughness"));
int specular_tint_offset = compiler.stack_assign(input("Specular Tint"));
int metallic_tint_offset = compiler.stack_assign(input("Metallic Tint"));
int anisotropic_offset = compiler.stack_assign(input("Anisotropic"));
int sheen_offset = compiler.stack_assign(input("Sheen"));
int sheen_roughness_offset = compiler.stack_assign(input("Sheen Roughness"));
@ -2839,7 +2841,7 @@ void PrincipledBsdfNode::compile(SVMCompiler &compiler)
compiler.encode_uchar4(
specular_offset, roughness_offset, specular_tint_offset, anisotropic_offset),
compiler.encode_uchar4(
sheen_offset, sheen_tint_offset, sheen_roughness_offset, SVM_STACK_INVALID));
sheen_offset, sheen_tint_offset, sheen_roughness_offset, metallic_tint_offset));
compiler.add_node(
compiler.encode_uchar4(

View File

@ -528,6 +528,7 @@ class PrincipledBsdfNode : public BsdfBaseNode {
NODE_SOCKET_API(float, specular)
NODE_SOCKET_API(float, roughness)
NODE_SOCKET_API(float, specular_tint)
NODE_SOCKET_API(float3, metallic_tint)
NODE_SOCKET_API(float, anisotropic)
NODE_SOCKET_API(float, sheen)
NODE_SOCKET_API(float, sheen_roughness)

View File

@ -31,6 +31,7 @@ void node_bsdf_principled(vec4 base_color,
vec3 subsurface_radius,
float subsurface_ior,
float subsurface_anisotropy,
vec4 metallic_tint,
float specular,
float specular_tint,
float anisotropic,

View File

@ -104,32 +104,34 @@ static void node_declare(NodeDeclarationBuilder &b)
.draw_buttons([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
uiItemR(layout, ptr, "distribution", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
});
spec.add_input<decl::Color>("Metallic Tint").default_value({1.0f, 1.0f, 1.0f, 1.0f});
#define SOCK_METALLIC_TINT_ID 13
spec.add_input<decl::Float>("Specular")
.default_value(0.5f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR);
#define SOCK_SPECULAR_ID 13
#define SOCK_SPECULAR_ID 14
spec.add_input<decl::Float>("Specular Tint")
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR);
#define SOCK_SPECULAR_TINT_ID 14
#define SOCK_SPECULAR_TINT_ID 15
spec.add_input<decl::Float>("Anisotropic")
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR);
#define SOCK_ANISOTROPIC_ID 15
#define SOCK_ANISOTROPIC_ID 16
spec.add_input<decl::Float>("Anisotropic Rotation")
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR);
#define SOCK_ANISOTROPIC_ROTATION_ID 16
#define SOCK_ANISOTROPIC_ROTATION_ID 17
spec.add_input<decl::Vector>("Tangent").hide_value();
#define SOCK_TANGENT_ID 17
#define SOCK_TANGENT_ID 18
/* Panel for Coat settings. */
PanelDeclarationBuilder &coat = b.add_panel("Coat").default_closed(true);
@ -141,14 +143,14 @@ static void node_declare(NodeDeclarationBuilder &b)
.description(
"Controls the intensity of the coat layer, both the reflection and the tinting. "
"Typically should be zero or one for physically-based materials");
#define SOCK_COAT_ID 18
#define SOCK_COAT_ID 19
coat.add_input<decl::Float>("Coat Roughness")
.default_value(0.03f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.description("The roughness of the coat layer");
#define SOCK_COAT_ROUGHNESS_ID 19
#define SOCK_COAT_ROUGHNESS_ID 20
coat.add_input<decl::Float>("Coat IOR")
.default_value(1.5f)
.min(1.0f)
@ -156,37 +158,37 @@ static void node_declare(NodeDeclarationBuilder &b)
.description(
"The index of refraction of the coat layer "
"(affects its reflectivity as well as the falloff of coat tinting)");
#define SOCK_COAT_IOR_ID 20
#define SOCK_COAT_IOR_ID 21
coat.add_input<decl::Color>("Coat Tint")
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.description(
"Adds a colored tint to the coat layer by modeling absorption in the layer. "
"Saturation increases at shallower angles, as the light travels farther "
"through the medium (depending on the Coat IOR)");
#define SOCK_COAT_TINT_ID 21
#define SOCK_COAT_TINT_ID 22
coat.add_input<decl::Vector>("Coat Normal").hide_value();
#define SOCK_COAT_NORMAL_ID 22
#define SOCK_COAT_NORMAL_ID 23
/* Panel for Sheen settings. */
PanelDeclarationBuilder &sheen = b.add_panel("Sheen").default_closed(true);
sheen.add_input<decl::Float>("Sheen").default_value(0.0f).min(0.0f).max(1.0f).subtype(
PROP_FACTOR);
#define SOCK_SHEEN_ID 23
#define SOCK_SHEEN_ID 24
sheen.add_input<decl::Float>("Sheen Roughness")
.default_value(0.5f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR);
#define SOCK_SHEEN_ROUGHNESS_ID 24
#define SOCK_SHEEN_ROUGHNESS_ID 25
sheen.add_input<decl::Color>("Sheen Tint").default_value({1.0f, 1.0f, 1.0f, 1.0f});
#define SOCK_SHEEN_TINT_ID 25
#define SOCK_SHEEN_TINT_ID 26
/* Panel for Emission settings. */
PanelDeclarationBuilder &emis = b.add_panel("Emission").default_closed(true);
emis.add_input<decl::Color>("Emission").default_value({1.0f, 1.0f, 1.0f, 1.0f});
#define SOCK_EMISSION_ID 26
#define SOCK_EMISSION_ID 27
emis.add_input<decl::Float>("Emission Strength").default_value(0.0).min(0.0f).max(1000000.0f);
#define SOCK_EMISSION_STRENGTH_ID 27
#define SOCK_EMISSION_STRENGTH_ID 28
}
static void node_shader_init_principled(bNodeTree * /*ntree*/, bNode *node)