Cycles: Tweak Principled BSDF Subsurface parameters

Previously, the Principled BSDF used the Subsurface input to scale the radius.
When it was zero, it used a diffuse closure, otherwise a subsurface closure.
This sort of scaling input makes sense, but it should be specified in distance
units, rather than a 0..1 factor, so this commit changes the unit and renames
the input to Subsurface Scale.

Additionally, it adds support for mixing diffuse and subsurface components.
This is part of e.g. the OpenPBR spec, and the logic behind it is to support
modeling e.g. dirt or paint on top of skin. Before, materials would be either
fully diffuse (radius=0) or fully subsurface.

For typical materials, this mixing factor will be either zero or one
(just like metallic or transmission), but supporting fractional inputs makes
sense for e.g. smooth transitions at boundaries.

Another change is that there is no separate Subsurface Color anymore - before,
this was mixed with the Base Color using the Subsurface input as the factor,
but this was not really useful since that input was generally very small.

And finally, the handling of how the path enters the material for random walk
subsurface scattering is changed. Before, this always used lambertian (diffuse)
transmission, but this caused some problems, like overly white edges.

Instead, two different methods are now used, depending on the selected mode.
In Fixed Radius mode, the code assumes a simple medium boundary, and performs
refraction into the material using the main Roughness and IOR inputs.

Meanwhile, when not using Fixed Radius, the code assumes a more complex
boundary (as typically found on organic materials, e.g. skin), so the entry
bounce has a 50/50 chance of being either diffuse transmission or refraction
using the separate Subsurface IOR input and a fixed roughness of 1.
Credit for this method goes to Christophe Hery.

Pull Request: https://projects.blender.org/blender/blender/pulls/110989
This commit is contained in:
Lukas Stockner 2023-09-13 02:45:33 +02:00 committed by Lukas Stockner
parent d169403d96
commit d7aee5a580
13 changed files with 312 additions and 248 deletions

View File

@ -11,8 +11,11 @@ typedef struct Bssrdf {
Spectrum radius;
Spectrum albedo;
float roughness;
float anisotropy;
/* Parameters for refractive entry bounce. */
float ior;
float alpha;
} Bssrdf;
static_assert(sizeof(ShaderClosure) >= sizeof(Bssrdf), "Bssrdf is too large!");
@ -54,9 +57,7 @@ ccl_device float bssrdf_dipole_compute_alpha_prime(float rd, float fourthirdA)
return xmid;
}
ccl_device void bssrdf_setup_radius(ccl_private Bssrdf *bssrdf,
const ClosureType type,
const float eta)
ccl_device void bssrdf_setup_radius(ccl_private Bssrdf *bssrdf, const ClosureType type)
{
if (type == CLOSURE_BSSRDF_BURLEY_ID || type == CLOSURE_BSSRDF_RANDOM_WALK_FIXED_RADIUS_ID) {
/* Scale mean free path length so it gives similar looking result to older
@ -65,8 +66,8 @@ ccl_device void bssrdf_setup_radius(ccl_private Bssrdf *bssrdf,
}
else {
/* Adjust radius based on IOR and albedo. */
const float inv_eta = 1.0f / eta;
const float F_dr = inv_eta * (-1.440f * inv_eta + 0.710f) + 0.668f + 0.0636f * eta;
const float inv_eta = 1.0f / bssrdf->ior;
const float F_dr = inv_eta * (-1.440f * inv_eta + 0.710f) + 0.668f + 0.0636f * bssrdf->ior;
const float fourthirdA = (4.0f / 3.0f) * (1.0f + F_dr) /
(1.0f - F_dr); /* From Jensen's `Fdr` ratio formula. */
@ -281,17 +282,34 @@ ccl_device_inline ccl_private Bssrdf *bssrdf_alloc(ccl_private ShaderData *sd, S
ccl_device int bssrdf_setup(ccl_private ShaderData *sd,
ccl_private Bssrdf *bssrdf,
ClosureType type,
const float ior)
int path_flag,
ClosureType type)
{
/* Clamps protecting against bad/extreme and non physical values. */
bssrdf->anisotropy = clamp(bssrdf->anisotropy, 0.0f, 0.9f);
bssrdf->ior = clamp(bssrdf->ior, 1.01f, 3.8f);
int flag = 0;
if (type == CLOSURE_BSSRDF_RANDOM_WALK_ID) {
/* CLOSURE_BSSRDF_RANDOM_WALK_ID uses a fixed roughness. */
bssrdf->alpha = 1.0f;
}
/* Verify if the radii are large enough to sample without precision issues. */
int bssrdf_channels = SPECTRUM_CHANNELS;
Spectrum diffuse_weight = zero_spectrum();
/* Fall back to diffuse if the radius is smaller than a quarter pixel. */
float min_radius = max(0.25f * sd->dP, BSSRDF_MIN_RADIUS);
if (path_flag & PATH_RAY_DIFFUSE_ANCESTOR) {
/* Always fall back to diffuse after a diffuse ancestor. Can't see it well then and adds
* considerable noise due to probabilities of continuing the path getting lower and lower. */
min_radius = FLT_MAX;
}
FOREACH_SPECTRUM_CHANNEL (i) {
if (GET_SPECTRUM_CHANNEL(bssrdf->radius, i) < BSSRDF_MIN_RADIUS) {
if (GET_SPECTRUM_CHANNEL(bssrdf->radius, i) < min_radius) {
GET_SPECTRUM_CHANNEL(diffuse_weight, i) = GET_SPECTRUM_CHANNEL(bssrdf->weight, i);
GET_SPECTRUM_CHANNEL(bssrdf->weight, i) = 0.0f;
GET_SPECTRUM_CHANNEL(bssrdf->radius, i) = 0.0f;
@ -315,12 +333,12 @@ ccl_device int bssrdf_setup(ccl_private ShaderData *sd,
bssrdf->type = type;
bssrdf->sample_weight = fabsf(average(bssrdf->weight)) * bssrdf_channels;
bssrdf_setup_radius(bssrdf, type, ior);
bssrdf_setup_radius(bssrdf, type);
flag |= SD_BSSRDF;
}
else {
bssrdf->type = type;
bssrdf->type = CLOSURE_NONE_ID;
bssrdf->sample_weight = 0.0f;
}

View File

@ -95,7 +95,7 @@ KERNEL_STRUCT_BEGIN(subsurface)
KERNEL_STRUCT_MEMBER(subsurface, PackedSpectrum, albedo, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_MEMBER(subsurface, PackedSpectrum, radius, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_MEMBER(subsurface, float, anisotropy, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_MEMBER(subsurface, packed_float3, Ng, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_MEMBER(subsurface, packed_float3, N, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_END(subsurface)
/********************************** Volume Stack ******************************/

View File

@ -23,6 +23,60 @@ CCL_NAMESPACE_BEGIN
#ifdef __SUBSURFACE__
ccl_device_inline bool subsurface_entry_bounce(KernelGlobals kg,
ccl_private const Bssrdf *bssrdf,
ccl_private ShaderData *sd,
ccl_private RNGState *rng_state,
ccl_private float3 *wo)
{
float2 rand_bsdf = path_state_rng_2D(kg, rng_state, PRNG_SUBSURFACE_BSDF);
if (bssrdf->type == CLOSURE_BSSRDF_RANDOM_WALK_ID) {
/* CLOSURE_BSSRDF_RANDOM_WALK_ID has a 50% chance to sample a diffuse entry bounce.
* Also, for the refractive entry, it uses a fixed roughness of 1.0. */
if (rand_bsdf.x < 0.5f) {
rand_bsdf.x *= 2.0f;
float pdf;
sample_cos_hemisphere(-bssrdf->N, rand_bsdf, wo, &pdf);
return true;
}
rand_bsdf.x = 2.0f * (rand_bsdf.x - 0.5f);
}
const float cos_NI = dot(bssrdf->N, sd->wi);
if (cos_NI <= 0.0f) {
return false;
}
float3 X, Y, Z = bssrdf->N;
make_orthonormals(Z, &X, &Y);
const float alpha = bssrdf->alpha;
const float neta = 1.0f / bssrdf->ior;
/* Sample microfacet normal by transforming to/from local coordinates. */
const float3 local_I = make_float3(dot(X, sd->wi), dot(Y, sd->wi), cos_NI);
const float3 local_H = microfacet_ggx_sample_vndf(local_I, alpha, alpha, rand_bsdf);
const float3 H = X * local_H.x + Y * local_H.y + Z * local_H.z;
const float cos_HI = dot(H, sd->wi);
const float arg = 1.0f - (sqr(neta) * (1.0f - sqr(cos_HI)));
/* We clamp subsurface IOR to be above 1, so there should never be TIR. */
kernel_assert(arg >= 0.0f);
const float dnp = max(sqrtf(arg), 1e-7f);
const float nK = (neta * cos_HI) - dnp;
*wo = -(neta * sd->wi) + (nK * H);
return true;
/* Note: For a proper refractive GGX interface, we should be computing lambdaI and lambdaO
* and multiplying the throughput by BSDF/pdf, which for VNDF sampling works out to
* (1 + lambdaI) / (1 + lambdaI + lambdaO).
* However, this causes darkening due to the single-scattering approximation, which we'd
* then have to correct with a lookup table.
* Since we only really care about the directional distribution here, it's much easier to
* just skip all that instead. */
}
ccl_device int subsurface_bounce(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
@ -37,32 +91,44 @@ ccl_device int subsurface_bounce(KernelGlobals kg,
/* Setup ray into surface. */
INTEGRATOR_STATE_WRITE(state, ray, P) = sd->P;
INTEGRATOR_STATE_WRITE(state, ray, D) = bssrdf->N;
INTEGRATOR_STATE_WRITE(state, ray, tmin) = 0.0f;
INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX;
INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP);
INTEGRATOR_STATE_WRITE(state, ray, dD) = differential_zero_compact();
/* Pass along object info, reusing isect to save memory. */
INTEGRATOR_STATE_WRITE(state, subsurface, Ng) = sd->Ng;
uint32_t path_flag = (INTEGRATOR_STATE(state, path, flag) & ~PATH_RAY_CAMERA) |
((sc->type == CLOSURE_BSSRDF_BURLEY_ID) ? PATH_RAY_SUBSURFACE_DISK :
PATH_RAY_SUBSURFACE_RANDOM_WALK);
/* Advance random number offset for bounce. */
INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;
/* Compute weight, optionally including Fresnel from entry point. */
Spectrum weight = surface_shader_bssrdf_sample_weight(sd, sc);
INTEGRATOR_STATE_WRITE(state, path, throughput) *= weight;
uint32_t path_flag = (INTEGRATOR_STATE(state, path, flag) & ~PATH_RAY_CAMERA);
if (sc->type == CLOSURE_BSSRDF_BURLEY_ID) {
path_flag |= PATH_RAY_SUBSURFACE_DISK;
INTEGRATOR_STATE_WRITE(state, subsurface, N) = sd->Ng;
}
else {
path_flag |= PATH_RAY_SUBSURFACE_RANDOM_WALK;
/* Sample entry bounce into the material. */
RNGState rng_state;
path_state_rng_load(state, &rng_state);
float3 wo;
if (!subsurface_entry_bounce(kg, bssrdf, sd, &rng_state, &wo) || dot(sd->Ng, wo) >= 0.0f) {
/* Sampling failed, give up on this bounce. */
return LABEL_NONE;
}
INTEGRATOR_STATE_WRITE(state, ray, D) = wo;
INTEGRATOR_STATE_WRITE(state, subsurface, N) = sd->N;
}
if (sd->flag & SD_BACKFACING) {
path_flag |= PATH_RAY_SUBSURFACE_BACKFACING;
}
INTEGRATOR_STATE_WRITE(state, path, throughput) *= weight;
INTEGRATOR_STATE_WRITE(state, path, flag) = path_flag;
/* Advance random number offset for bounce. */
INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
if (INTEGRATOR_STATE(state, path, bounce) == 0) {
INTEGRATOR_STATE_WRITE(state, path, pass_diffuse_weight) = one_spectrum();

View File

@ -34,7 +34,7 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg,
const float3 P = INTEGRATOR_STATE(state, ray, P);
const float ray_dP = INTEGRATOR_STATE(state, ray, dP);
const float time = INTEGRATOR_STATE(state, ray, time);
const float3 Ng = INTEGRATOR_STATE(state, subsurface, Ng);
const float3 Ng = INTEGRATOR_STATE(state, subsurface, N);
const int object = INTEGRATOR_STATE(state, isect, object);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);

View File

@ -168,24 +168,14 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg,
ccl_private Ray &ray,
ccl_private LocalIntersection &ss_isect)
{
const float2 rand_bsdf = path_state_rng_2D(kg, &rng_state, PRNG_SUBSURFACE_BSDF);
const float3 P = INTEGRATOR_STATE(state, ray, P);
const float3 N = INTEGRATOR_STATE(state, ray, D);
const float3 D = INTEGRATOR_STATE(state, ray, D);
const float ray_dP = INTEGRATOR_STATE(state, ray, dP);
const float time = INTEGRATOR_STATE(state, ray, time);
const float3 Ng = INTEGRATOR_STATE(state, subsurface, Ng);
const float3 N = INTEGRATOR_STATE(state, subsurface, N);
const int object = INTEGRATOR_STATE(state, isect, object);
const int prim = INTEGRATOR_STATE(state, isect, prim);
/* Sample diffuse surface scatter into the object. */
float3 D;
float pdf;
sample_cos_hemisphere(-N, rand_bsdf, &D, &pdf);
if (dot(-Ng, D) <= 0.0f) {
return false;
}
/* Setup ray. */
ray.P = P;
ray.D = D;
@ -436,10 +426,11 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg,
if (hit) {
kernel_assert(isfinite_safe(throughput));
/* TODO(lukas): Which PDF should we report here? Entry bounce? The random walk? Just 1.0? */
guiding_record_bssrdf_bounce(
kg,
state,
pdf,
1.0f,
N,
D,
safe_divide_color(throughput, INTEGRATOR_STATE(state, path, throughput)),

View File

@ -740,23 +740,16 @@ ccl_device void osl_closure_bssrdf_setup(KernelGlobals kg,
return;
}
/* disable in case of diffuse ancestor, can't see it well then and
* adds considerably noise due to probabilities of continuing path
* getting lower and lower */
if (path_flag & PATH_RAY_DIFFUSE_ANCESTOR) {
bssrdf->radius = zero_spectrum();
}
else {
bssrdf->radius = closure->radius;
}
bssrdf->radius = closure->radius;
/* create one closure per color channel */
bssrdf->albedo = closure->albedo;
bssrdf->N = closure->N;
bssrdf->roughness = closure->roughness;
bssrdf->anisotropy = clamp(closure->anisotropy, 0.0f, 0.9f);
bssrdf->alpha = sqr(closure->roughness);
bssrdf->ior = closure->ior;
bssrdf->anisotropy = closure->anisotropy;
sd->flag |= bssrdf_setup(sd, bssrdf, type, clamp(closure->ior, 1.01f, 3.8f));
sd->flag |= bssrdf_setup(sd, bssrdf, path_flag, type);
}
/* Hair */

View File

@ -9,8 +9,8 @@ shader node_principled_bsdf(string distribution = "multi_ggx",
string subsurface_method = "random_walk",
color BaseColor = color(0.8, 0.8, 0.8),
float Subsurface = 0.0,
float SubsurfaceScale = 0.1,
vector SubsurfaceRadius = vector(1.0, 1.0, 1.0),
color SubsurfaceColor = color(0.7, 0.1, 0.1),
float SubsurfaceIOR = 1.4,
float SubsurfaceAnisotropy = 0.0,
float Metallic = 0.0,
@ -47,21 +47,21 @@ shader node_principled_bsdf(string distribution = "multi_ggx",
}
if (Metallic < 1.0 && Transmission < 1.0) {
color diffuse_color = mix(BaseColor, SubsurfaceColor, Subsurface);
BSDF = BaseColor * diffuse(Normal);
if (Subsurface > 1e-5) {
BSDF = diffuse_color * bssrdf(subsurface_method,
Normal,
Subsurface * SubsurfaceRadius,
diffuse_color,
"roughness",
Roughness,
"ior",
SubsurfaceIOR,
"anisotropy",
SubsurfaceAnisotropy);
}
else {
BSDF = diffuse_color * diffuse(Normal);
vector radius = SubsurfaceScale * SubsurfaceRadius;
float subsurface_ior = (subsurface_method == "random_walk") ? SubsurfaceIOR : IOR;
closure color SubsurfBSDF = bssrdf(subsurface_method,
Normal,
SubsurfaceScale * SubsurfaceRadius,
BaseColor,
"roughness",
Roughness,
"ior",
subsurface_ior,
"anisotropy",
SubsurfaceAnisotropy);
BSDF = mix(BSDF, BaseColor * SubsurfBSDF, Subsurface);
}
color f0 = color(F0_from_ior(IOR));

View File

@ -22,7 +22,6 @@ ccl_device_inline int svm_node_closure_bsdf_skip(KernelGlobals kg, int offset, u
read_node(kg, &offset);
read_node(kg, &offset);
read_node(kg, &offset);
read_node(kg, &offset);
}
return offset;
@ -71,7 +70,7 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
uint specular_offset, roughness_offset, specular_tint_offset, anisotropic_offset,
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, dummy;
anisotropic_rotation_offset, coat_tint_offset, coat_normal_offset, dummy;
uint4 data_node2 = read_node(kg, &offset);
float3 T = stack_load_float3(stack, data_node.y);
@ -82,8 +81,11 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
&anisotropic_offset);
svm_unpack_node_uchar4(
data_node.w, &sheen_offset, &sheen_tint_offset, &sheen_roughness_offset, &dummy);
svm_unpack_node_uchar4(
data_node2.x, &eta_offset, &transmission_offset, &anisotropic_rotation_offset, &dummy);
svm_unpack_node_uchar4(data_node2.x,
&eta_offset,
&transmission_offset,
&anisotropic_rotation_offset,
&coat_normal_offset);
svm_unpack_node_uchar4(
data_node2.w, &coat_offset, &coat_roughness_offset, &coat_ior_offset, &coat_tint_offset);
@ -118,27 +120,14 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
__uint_as_float(data_base_color.z),
__uint_as_float(data_base_color.w));
// get the additional coat normal and subsurface scattering radius
uint4 data_cn_ssr = read_node(kg, &offset);
float3 coat_normal = stack_valid(data_cn_ssr.x) ? stack_load_float3(stack, data_cn_ssr.x) :
sd->N;
coat_normal = maybe_ensure_valid_specular_reflection(sd, coat_normal);
float3 subsurface_radius = stack_valid(data_cn_ssr.y) ?
stack_load_float3(stack, data_cn_ssr.y) :
one_float3();
float subsurface_ior = stack_valid(data_cn_ssr.z) ? stack_load_float(stack, data_cn_ssr.z) :
1.4f;
float subsurface_anisotropy = stack_valid(data_cn_ssr.w) ?
stack_load_float(stack, data_cn_ssr.w) :
0.0f;
// get the subsurface scattering data
uint4 data_subsurf = read_node(kg, &offset);
// get the subsurface color
uint4 data_subsurface_color = read_node(kg, &offset);
float3 subsurface_color = stack_valid(data_subsurface_color.x) ?
stack_load_float3(stack, data_subsurface_color.x) :
make_float3(__uint_as_float(data_subsurface_color.y),
__uint_as_float(data_subsurface_color.z),
__uint_as_float(data_subsurface_color.w));
// get the additional coat normal
float3 coat_normal = stack_valid(coat_normal_offset) ?
stack_load_float3(stack, coat_normal_offset) :
sd->N;
coat_normal = maybe_ensure_valid_specular_reflection(sd, coat_normal);
Spectrum weight = closure_weight * mix_weight;
@ -327,44 +316,38 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
}
}
/* Diffuse component */
float3 diffuse_color = mix(base_color, subsurface_color, subsurface);
/* Diffuse/Subsurface component */
#ifdef __SUBSURFACE__
/* disable in case of diffuse ancestor, can't see it well then and
* adds considerably noise due to probabilities of continuing path
* getting lower and lower */
if ((subsurface > CLOSURE_WEIGHT_CUTOFF) && !(path_flag & PATH_RAY_DIFFUSE_ANCESTOR)) {
/* Skip in case of extremely low albedo. */
if (fabsf(average(diffuse_color)) > CLOSURE_WEIGHT_CUTOFF) {
ccl_private Bssrdf *bssrdf = bssrdf_alloc(sd, rgb_to_spectrum(diffuse_color) * weight);
ccl_private Bssrdf *bssrdf = bssrdf_alloc(sd,
rgb_to_spectrum(base_color) * subsurface * weight);
if (bssrdf) {
float3 subsurface_radius = stack_load_float3(stack, data_subsurf.y);
float subsurface_scale = stack_load_float(stack, data_subsurf.z);
if (bssrdf) {
bssrdf->radius = rgb_to_spectrum(subsurface_radius * subsurface);
bssrdf->albedo = rgb_to_spectrum(diffuse_color);
bssrdf->N = N;
bssrdf->roughness = roughness;
/* Clamps protecting against bad/extreme and non physical values. */
subsurface_ior = clamp(subsurface_ior, 1.01f, 3.8f);
bssrdf->anisotropy = clamp(subsurface_anisotropy, 0.0f, 0.9f);
/* setup bsdf */
sd->flag |= bssrdf_setup(sd, bssrdf, subsurface_method, subsurface_ior);
}
bssrdf->radius = rgb_to_spectrum(subsurface_radius * subsurface_scale);
bssrdf->albedo = rgb_to_spectrum(base_color);
bssrdf->N = N;
bssrdf->alpha = sqr(roughness);
bssrdf->ior = eta;
bssrdf->anisotropy = stack_load_float(stack, data_subsurf.w);
if (subsurface_method == CLOSURE_BSSRDF_RANDOM_WALK_ID) {
bssrdf->ior = stack_load_float(stack, data_subsurf.x);
}
/* setup bsdf */
sd->flag |= bssrdf_setup(sd, bssrdf, path_flag, subsurface_method);
}
else
#else
subsurface = 0.0f;
#endif
{
ccl_private DiffuseBsdf *bsdf = (ccl_private DiffuseBsdf *)bsdf_alloc(
sd, sizeof(DiffuseBsdf), rgb_to_spectrum(diffuse_color) * weight);
if (bsdf) {
bsdf->N = N;
ccl_private DiffuseBsdf *bsdf = (ccl_private DiffuseBsdf *)bsdf_alloc(
sd, sizeof(DiffuseBsdf), rgb_to_spectrum(base_color) * (1.0f - subsurface) * weight);
if (bsdf) {
bsdf->N = N;
/* setup bsdf */
sd->flag |= bsdf_diffuse_setup(bsdf);
}
/* setup bsdf */
sd->flag |= bsdf_diffuse_setup(bsdf);
}
break;
@ -797,22 +780,14 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
ccl_private Bssrdf *bssrdf = bssrdf_alloc(sd, weight);
if (bssrdf) {
/* disable in case of diffuse ancestor, can't see it well then and
* adds considerably noise due to probabilities of continuing path
* getting lower and lower */
if (path_flag & PATH_RAY_DIFFUSE_ANCESTOR)
param1 = 0.0f;
bssrdf->radius = rgb_to_spectrum(stack_load_float3(stack, data_node.z) * param1);
bssrdf->albedo = closure_weight;
bssrdf->N = N;
bssrdf->roughness = FLT_MAX;
bssrdf->ior = param2;
bssrdf->alpha = 1.0f;
bssrdf->anisotropy = stack_load_float(stack, data_node.w);
const float subsurface_ior = clamp(param2, 1.01f, 3.8f);
const float subsurface_anisotropy = stack_load_float(stack, data_node.w);
bssrdf->anisotropy = clamp(subsurface_anisotropy, 0.0f, 0.9f);
sd->flag |= bssrdf_setup(sd, bssrdf, (ClosureType)type, subsurface_ior);
sd->flag |= bssrdf_setup(sd, bssrdf, path_flag, (ClosureType)type);
}
break;

View File

@ -2660,10 +2660,10 @@ NODE_DEFINE(PrincipledBsdfNode)
subsurface_method_enum,
CLOSURE_BSSRDF_RANDOM_WALK_ID);
SOCKET_IN_COLOR(base_color, "Base Color", make_float3(0.8f, 0.8f, 0.8f));
SOCKET_IN_COLOR(subsurface_color, "Subsurface Color", make_float3(0.8f, 0.8f, 0.8f));
SOCKET_IN_COLOR(base_color, "Base Color", make_float3(0.8f, 0.8f, 0.8f))
SOCKET_IN_FLOAT(metallic, "Metallic", 0.0f);
SOCKET_IN_FLOAT(subsurface, "Subsurface", 0.0f);
SOCKET_IN_FLOAT(subsurface_scale, "Subsurface Scale", 0.1f);
SOCKET_IN_VECTOR(subsurface_radius, "Subsurface Radius", make_float3(0.1f, 0.1f, 0.1f));
SOCKET_IN_FLOAT(subsurface_ior, "Subsurface IOR", 1.4f);
SOCKET_IN_FLOAT(subsurface_anisotropy, "Subsurface Anisotropy", 0.0f);
@ -2774,57 +2774,38 @@ void PrincipledBsdfNode::attributes(Shader *shader, AttributeRequestSet *attribu
ShaderNode::attributes(shader, attributes);
}
void PrincipledBsdfNode::compile(SVMCompiler &compiler,
ShaderInput *p_metallic,
ShaderInput *p_subsurface,
ShaderInput *p_subsurface_radius,
ShaderInput *p_subsurface_ior,
ShaderInput *p_subsurface_anisotropy,
ShaderInput *p_specular,
ShaderInput *p_roughness,
ShaderInput *p_specular_tint,
ShaderInput *p_anisotropic,
ShaderInput *p_sheen,
ShaderInput *p_sheen_roughness,
ShaderInput *p_sheen_tint,
ShaderInput *p_coat,
ShaderInput *p_coat_roughness,
ShaderInput *p_coat_ior,
ShaderInput *p_coat_tint,
ShaderInput *p_ior,
ShaderInput *p_transmission,
ShaderInput *p_anisotropic_rotation)
void PrincipledBsdfNode::compile(SVMCompiler &compiler)
{
ShaderInput *base_color_in = input("Base Color");
ShaderInput *subsurface_color_in = input("Subsurface Color");
ShaderInput *normal_in = input("Normal");
ShaderInput *coat_normal_in = input("Coat Normal");
ShaderInput *tangent_in = input("Tangent");
ShaderInput *p_metallic = input("Metallic");
ShaderInput *p_subsurface = input("Subsurface");
float3 weight = one_float3();
compiler.add_node(NODE_CLOSURE_SET_WEIGHT, weight);
int normal_offset = compiler.stack_assign_if_linked(normal_in);
int coat_normal_offset = compiler.stack_assign_if_linked(coat_normal_in);
int tangent_offset = compiler.stack_assign_if_linked(tangent_in);
int specular_offset = compiler.stack_assign(p_specular);
int roughness_offset = compiler.stack_assign(p_roughness);
int specular_tint_offset = compiler.stack_assign(p_specular_tint);
int anisotropic_offset = compiler.stack_assign(p_anisotropic);
int sheen_offset = compiler.stack_assign(p_sheen);
int sheen_roughness_offset = compiler.stack_assign(p_sheen_roughness);
int sheen_tint_offset = compiler.stack_assign(p_sheen_tint);
int coat_offset = compiler.stack_assign(p_coat);
int coat_roughness_offset = compiler.stack_assign(p_coat_roughness);
int coat_ior_offset = compiler.stack_assign(p_coat_ior);
int coat_tint_offset = compiler.stack_assign(p_coat_tint);
int ior_offset = compiler.stack_assign(p_ior);
int transmission_offset = compiler.stack_assign(p_transmission);
int anisotropic_rotation_offset = compiler.stack_assign(p_anisotropic_rotation);
int subsurface_radius_offset = compiler.stack_assign(p_subsurface_radius);
int subsurface_ior_offset = compiler.stack_assign(p_subsurface_ior);
int subsurface_anisotropy_offset = compiler.stack_assign(p_subsurface_anisotropy);
int normal_offset = compiler.stack_assign_if_linked(input("Normal"));
int coat_normal_offset = compiler.stack_assign_if_linked(input("Coat Normal"));
int tangent_offset = compiler.stack_assign_if_linked(input("Tangent"));
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 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"));
int sheen_tint_offset = compiler.stack_assign(input("Sheen Tint"));
int coat_offset = compiler.stack_assign(input("Coat"));
int coat_roughness_offset = compiler.stack_assign(input("Coat Roughness"));
int coat_ior_offset = compiler.stack_assign(input("Coat IOR"));
int coat_tint_offset = compiler.stack_assign(input("Coat Tint"));
int ior_offset = compiler.stack_assign(input("IOR"));
int transmission_offset = compiler.stack_assign(input("Transmission"));
int anisotropic_rotation_offset = compiler.stack_assign(input("Anisotropic Rotation"));
int subsurface_radius_offset = compiler.stack_assign(input("Subsurface Radius"));
int subsurface_scale_offset = compiler.stack_assign(input("Subsurface Scale"));
int subsurface_ior_offset = compiler.stack_assign(input("Subsurface IOR"));
int subsurface_anisotropy_offset = compiler.stack_assign(input("Subsurface Anisotropy"));
compiler.add_node(NODE_CLOSURE_BSDF,
compiler.encode_uchar4(closure,
@ -2844,7 +2825,7 @@ void PrincipledBsdfNode::compile(SVMCompiler &compiler,
compiler.add_node(
compiler.encode_uchar4(
ior_offset, transmission_offset, anisotropic_rotation_offset, SVM_STACK_INVALID),
ior_offset, transmission_offset, anisotropic_rotation_offset, coat_normal_offset),
distribution,
subsurface_method,
compiler.encode_uchar4(
@ -2858,42 +2839,10 @@ void PrincipledBsdfNode::compile(SVMCompiler &compiler,
__float_as_int(bc_default.y),
__float_as_int(bc_default.z));
compiler.add_node(coat_normal_offset,
compiler.add_node(subsurface_ior_offset,
subsurface_radius_offset,
subsurface_ior_offset,
subsurface_scale_offset,
subsurface_anisotropy_offset);
float3 ss_default = get_float3(subsurface_color_in->socket_type);
compiler.add_node(((subsurface_color_in->link) ? compiler.stack_assign(subsurface_color_in) :
SVM_STACK_INVALID),
__float_as_int(ss_default.x),
__float_as_int(ss_default.y),
__float_as_int(ss_default.z));
}
void PrincipledBsdfNode::compile(SVMCompiler &compiler)
{
compile(compiler,
input("Metallic"),
input("Subsurface"),
input("Subsurface Radius"),
input("Subsurface IOR"),
input("Subsurface Anisotropy"),
input("Specular"),
input("Roughness"),
input("Specular Tint"),
input("Anisotropic"),
input("Sheen"),
input("Sheen Roughness"),
input("Sheen Tint"),
input("Coat"),
input("Coat Roughness"),
input("Coat IOR"),
input("Coat Tint"),
input("IOR"),
input("Transmission"),
input("Anisotropic Rotation"));
}
void PrincipledBsdfNode::compile(OSLCompiler &compiler)

View File

@ -517,30 +517,10 @@ class PrincipledBsdfNode : public BsdfBaseNode {
void expand(ShaderGraph *graph);
bool has_surface_bssrdf();
bool has_bssrdf_bump();
void compile(SVMCompiler &compiler,
ShaderInput *metallic,
ShaderInput *subsurface,
ShaderInput *subsurface_radius,
ShaderInput *subsurface_ior,
ShaderInput *subsurface_anisotropy,
ShaderInput *specular,
ShaderInput *roughness,
ShaderInput *specular_tint,
ShaderInput *anisotropic,
ShaderInput *sheen,
ShaderInput *sheen_roughness,
ShaderInput *sheen_tint,
ShaderInput *coat,
ShaderInput *coat_roughness,
ShaderInput *coat_ior,
ShaderInput *coat_tint,
ShaderInput *ior,
ShaderInput *transmission,
ShaderInput *anisotropic_rotation);
NODE_SOCKET_API(float3, base_color)
NODE_SOCKET_API(float3, subsurface_color)
NODE_SOCKET_API(float3, subsurface_radius)
NODE_SOCKET_API(float, subsurface_scale)
NODE_SOCKET_API(float, subsurface_ior)
NODE_SOCKET_API(float, subsurface_anisotropy)
NODE_SOCKET_API(float, metallic)

View File

@ -580,6 +580,86 @@ static void version_principled_bsdf_sheen(bNodeTree *ntree)
}
}
/* Convert subsurface inputs on the Principled BSDF. */
static void version_principled_bsdf_subsurface(bNodeTree *ntree)
{
/* - Create Subsurface Scale input
* - If a node's Subsurface input was connected or nonzero:
* - Make the Base Color a mix of old Base Color and Subsurface Color,
* using Subsurface as the mix factor
* - Move Subsurface link and default value to the new Subsurface Scale input
* - Set the Subsurface input to 1.0
* - Remove Subsurface Color input
*/
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type != SH_NODE_BSDF_PRINCIPLED) {
continue;
}
if (nodeFindSocket(node, SOCK_IN, "Subsurface Scale")) {
/* Node is already updated. */
continue;
}
/* Add Scale input */
bNodeSocket *scale_in = nodeAddStaticSocket(
ntree, node, SOCK_IN, SOCK_FLOAT, PROP_DISTANCE, "Subsurface Scale", "Subsurface Scale");
bNodeSocket *subsurf = nodeFindSocket(node, SOCK_IN, "Subsurface");
float *subsurf_val = version_cycles_node_socket_float_value(subsurf);
*version_cycles_node_socket_float_value(scale_in) = *subsurf_val;
if (subsurf->link == nullptr && *subsurf_val == 0.0f) {
/* Node doesn't use Subsurf, we're done here. */
continue;
}
/* Fix up Subsurface Color input */
bNodeSocket *base_col = nodeFindSocket(node, SOCK_IN, "Base Color");
bNodeSocket *subsurf_col = nodeFindSocket(node, SOCK_IN, "Subsurface Color");
float *base_col_val = version_cycles_node_socket_rgba_value(base_col);
float *subsurf_col_val = version_cycles_node_socket_rgba_value(subsurf_col);
/* If any of the three inputs is dynamic, we need a Mix node. */
if (subsurf->link || subsurf_col->link || base_col->link) {
bNode *mix = nodeAddStaticNode(nullptr, ntree, SH_NODE_MIX);
static_cast<NodeShaderMix *>(mix->storage)->data_type = SOCK_RGBA;
mix->locx = node->locx - 170;
mix->locy = node->locy - 120;
bNodeSocket *a_in = nodeFindSocket(mix, SOCK_IN, "A_Color");
bNodeSocket *b_in = nodeFindSocket(mix, SOCK_IN, "B_Color");
bNodeSocket *fac_in = nodeFindSocket(mix, SOCK_IN, "Factor_Float");
bNodeSocket *result_out = nodeFindSocket(mix, SOCK_OUT, "Result_Color");
copy_v4_v4(version_cycles_node_socket_rgba_value(a_in), base_col_val);
copy_v4_v4(version_cycles_node_socket_rgba_value(b_in), subsurf_col_val);
*version_cycles_node_socket_float_value(fac_in) = *subsurf_val;
if (base_col->link) {
nodeAddLink(ntree, base_col->link->fromnode, base_col->link->fromsock, mix, a_in);
nodeRemLink(ntree, base_col->link);
}
if (subsurf_col->link) {
nodeAddLink(ntree, subsurf_col->link->fromnode, subsurf_col->link->fromsock, mix, b_in);
nodeRemLink(ntree, subsurf_col->link);
}
if (subsurf->link) {
nodeAddLink(ntree, subsurf->link->fromnode, subsurf->link->fromsock, mix, fac_in);
nodeAddLink(ntree, subsurf->link->fromnode, subsurf->link->fromsock, node, scale_in);
nodeRemLink(ntree, subsurf->link);
}
nodeAddLink(ntree, mix, result_out, node, base_col);
}
/* Mix the fixed values. */
interp_v4_v4v4(base_col_val, base_col_val, subsurf_col_val, *subsurf_val);
/* Set node to 100% subsurface, 0% diffuse. */
*subsurf_val = 1.0f;
/* Delete Subsurface Color input */
nodeRemoveSocket(ntree, node, subsurf_col);
}
}
/* Replace old Principled Hair BSDF as a variant in the new Principled Hair BSDF. */
static void version_replace_principled_hair_model(bNodeTree *ntree)
{
@ -1096,6 +1176,8 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
if (ntree->type == NTREE_SHADER) {
/* Convert coat inputs on the Principled BSDF. */
version_principled_bsdf_coat(ntree);
/* Convert subsurface inputs on the Principled BSDF. */
version_principled_bsdf_subsurface(ntree);
}
}
FOREACH_NODETREE_END;

View File

@ -20,8 +20,8 @@ float principled_sheen(float NV, float rough)
void node_bsdf_principled(vec4 base_color,
float subsurface,
float subsurface_scale,
vec3 subsurface_radius,
vec4 subsurface_color,
float subsurface_ior,
float subsurface_anisotropy,
float metallic,
@ -155,10 +155,9 @@ void node_bsdf_principled(vec4 base_color,
/* Diffuse component */
if (true) {
vec3 diffuse_color = mix(base_color.rgb, subsurface_color.rgb, subsurface);
diffuse_data.sss_radius = subsurface_radius * subsurface;
diffuse_data.sss_radius = subsurface_radius * subsurface_scale;
diffuse_data.sss_id = uint(do_sss);
diffuse_data.color += weight * diffuse_color * coat_tint.rgb;
diffuse_data.color += weight * base_color.rgb * coat_tint.rgb;
}
/* Adjust the weight of picking the closure. */

View File

@ -25,21 +25,31 @@ static void node_declare(NodeDeclarationBuilder &b)
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR);
.subtype(PROP_FACTOR)
.description(
"Blend between diffuse surface and subsurface scattering. "
"Typically should be zero or one (either fully diffuse or subsurface)");
#define SOCK_SUBSURFACE_ID 1
b.add_input<decl::Float>("Subsurface Scale")
.default_value(0.05f)
.min(0.0f)
.max(10.0f)
.subtype(PROP_DISTANCE)
.description("Scale of the subsurface scattering (multiplied with Radius)");
#define SOCK_SUBSURFACE_SCALE_ID 2
b.add_input<decl::Vector>("Subsurface Radius")
.default_value({1.0f, 0.2f, 0.1f})
.min(0.0f)
.max(100.0f)
.compact();
#define SOCK_SUBSURFACE_RADIUS_ID 2
b.add_input<decl::Color>("Subsurface Color").default_value({0.8f, 0.8f, 0.8f, 1.0f});
#define SOCK_SUBSURFACE_COLOR_ID 3
.compact()
.description("Scattering radius to use for subsurface component (multiplied with Scale)");
#define SOCK_SUBSURFACE_RADIUS_ID 3
b.add_input<decl::Float>("Subsurface IOR")
.default_value(1.4f)
.min(1.01f)
.max(3.8f)
.subtype(PROP_FACTOR);
.subtype(PROP_FACTOR)
.description("Index of refraction used for rays that enter the subsurface component");
#define SOCK_SUBSURFACE_IOR_ID 4
b.add_input<decl::Float>("Subsurface Anisotropy")
.default_value(0.0f)
@ -266,11 +276,12 @@ static void node_shader_update_principled(bNodeTree *ntree, bNode *node)
{
const int sss_method = node->custom2;
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
if (STR_ELEM(sock->name, "Subsurface IOR", "Subsurface Anisotropy")) {
bke::nodeSetSocketAvailability(ntree, sock, sss_method != SHD_SUBSURFACE_BURLEY);
}
}
bke::nodeSetSocketAvailability(ntree,
nodeFindSocket(node, SOCK_IN, "Subsurface IOR"),
sss_method == SHD_SUBSURFACE_RANDOM_WALK);
bke::nodeSetSocketAvailability(ntree,
nodeFindSocket(node, SOCK_IN, "Subsurface Anisotropy"),
sss_method != SHD_SUBSURFACE_BURLEY);
}
} // namespace blender::nodes::node_shader_bsdf_principled_cc