Cycles: new Microfacet-based Hair BSDF with elliptical cross-section support #105600

Merged
Weizhen Huang merged 114 commits from weizhen/blender:microfacet_hair into main 2023-08-18 12:46:20 +02:00
6 changed files with 53 additions and 68 deletions
Showing only changes of commit a9534f0fdd - Show all commits

View File

@ -15,15 +15,11 @@
CCL_NAMESPACE_BEGIN
typedef struct MicrofacetHairExtra {
/* TODO: is this necessary? */
typedef struct MicrofacetHairLobeFactor {
float R;
weizhen marked this conversation as resolved Outdated

Do you think it could make sense to support colors here? It would increase the space usage, so we should only do it if there's a realistic use case I guess.

Do you think it could make sense to support colors here? It would increase the space usage, so we should only do it if there's a realistic use case I guess.

Currently if the parametrization is set to Melanin Concentration, an extra Tint socket is enabled, which corresponds to the realistic case of dyed hair (there is melanin present in natural hair, extra pigments from the dye is added). The R, TT, TRT are non-physical modulation factors, I thinking adding colors there would make the hair appearance quite difficult to control, using Tint should be a more proper way.

Currently if the parametrization is set to Melanin Concentration, an extra Tint socket is enabled, which corresponds to the realistic case of dyed hair (there is melanin present in natural hair, extra pigments from the dye is added). The R, TT, TRT are non-physical modulation factors, I thinking adding colors there would make the hair appearance quite difficult to control, using Tint should be a more proper way.
float TT;
float TRT;
/* Geometry data. */
float4 geom;
} MicrofacetHairExtra;
} MicrofacetHairLobeFactor;
typedef struct MicrofacetHairBSDF {
SHADER_CLOSURE_BASE;
@ -43,14 +39,17 @@ typedef struct MicrofacetHairBSDF {
/* The ratio of the minor axis to the major axis. */
float aspect_ratio;
/* Extra closure. */
ccl_private MicrofacetHairExtra *extra;
/* Azimuthal offset. */
float h;
/* Extra closure for optional modulation factors. */
ccl_private MicrofacetHairLobeFactor *factor;
} MicrofacetHairBSDF;
static_assert(sizeof(ShaderClosure) >= sizeof(MicrofacetHairBSDF),
"MicrofacetHairBSDF is too large!");
static_assert(sizeof(ShaderClosure) >= sizeof(MicrofacetHairExtra),
"MicrofacetHairExtra is too large!");
static_assert(sizeof(ShaderClosure) >= sizeof(MicrofacetHairLobeFactor),
"MicrofacetHairLobeFactor is too large!");
#ifdef __HAIR__
/* Set up the hair closure. */
@ -69,32 +68,27 @@ ccl_device int bsdf_microfacet_hair_setup(ccl_private ShaderData *sd,
/* h -1..0..1 means the rays goes from grazing the hair, to hitting it at the center, to grazing

This is probably fine, but one consideration here is that all functions in the kernel share a namespace, so e.g. sin_theta might easily conflict with something else in the future.
Then again, this is probably general enough that we could just move it to util/ if that happens.

This is probably fine, but one consideration here is that all functions in the kernel share a namespace, so e.g. `sin_theta` might easily conflict with something else in the future. Then again, this is probably general enough that we could just move it to `util/` if that happens.
* the other edge. This is the cosine of the angle between sd->N and X. */
const float h = (sd->type & PRIMITIVE_CURVE_RIBBON) ? -sd->v : -dot(X, sd->N);
bsdf->h = (sd->type & PRIMITIVE_CURVE_RIBBON) ? -sd->v : -dot(X, sd->N);
kernel_assert(fabsf(h) < 1.0f + 1e-4f);
kernel_assert(isfinite_safe(X));
kernel_assert(isfinite_safe(h));
kernel_assert(fabsf(bsdf->h) < 1.0f + 1e-4f);
kernel_assert(isfinite_safe(bsdf->h));
if (bsdf->aspect_ratio != 1.0f && !(sd->type & PRIMITIVE_TRIANGLE)) {
if (bsdf->aspect_ratio > 1.0f) {
bsdf->aspect_ratio = 1.0f / bsdf->aspect_ratio;
/* Switch major and minor axis. */
const float3 minor_axis = safe_normalize(cross(
sd->dPdu, make_float3(bsdf->extra->geom.x, bsdf->extra->geom.y, bsdf->extra->geom.z)));
const float3 major_axis = safe_normalize(cross(minor_axis, sd->dPdu));
bsdf->extra->geom = make_float4(major_axis.x, major_axis.y, major_axis.z, h);
}
else {
bsdf->extra->geom.w = h;
const float3 minor_axis = safe_normalize(cross(sd->dPdu, bsdf->N));
bsdf->N = safe_normalize(cross(minor_axis, sd->dPdu));
}
}
else {
/* Align local frame with the ray direction so that `phi_i == 0`. */
bsdf->extra->geom = make_float4(X.x, X.y, X.z, h);
bsdf->N = X;
}
kernel_assert(!is_zero(bsdf->N) && isfinite_safe(bsdf->N));
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION;
}
@ -310,7 +304,7 @@ ccl_device float3 bsdf_microfacet_hair_eval_r(ccl_private const ShaderClosure *s
const float roughness2 = sqr(roughness);
const float eta = bsdf->eta;
if (bsdf->extra->R <= 0.0f) {
if (bsdf->factor->R <= 0.0f) {
return zero_float3();
}
@ -389,7 +383,7 @@ ccl_device float3 bsdf_microfacet_hair_eval_r(ccl_private const ShaderClosure *s
const float F = fresnel_dielectric_cos(dot(wi, wh), eta);
return make_float3(bsdf->extra->R * 0.125f * F * integral / projected_radius(e2, sinf(phi_i)));
return make_float3(bsdf->factor->R * 0.125f * F * integral / projected_radius(e2, sinf(phi_i)));
}
template<MicrofacetType m_type>
@ -405,7 +399,7 @@ ccl_device float3 bsdf_microfacet_hair_eval_tt_trt(KernelGlobals kg,
const float roughness2 = sqr(roughness);
const float eta = bsdf->eta;
if (bsdf->extra->TT <= 0.0f && bsdf->extra->TRT <= 0.0f) {
if (bsdf->factor->TT <= 0.0f && bsdf->factor->TRT <= 0.0f) {
return zero_float3();
}
@ -487,7 +481,7 @@ ccl_device float3 bsdf_microfacet_hair_eval_tt_trt(KernelGlobals kg,
-len(to_point(gamma_mi, b) - to_point(gamma_mt + M_PI_F, b))));

This appears 4 times I think, can we split it into a helper function?

This appears 4 times I think, can we split it into a helper function?
/* TT */
if (bsdf->extra->TT > 0.0f) {
if (bsdf->factor->TT > 0.0f) {
if (dot(wo, wt) >= inv_eta - 1e-5f) { /* Total internal reflection otherwise. */
float3 wh2 = -wt + inv_eta * wo;
const float rcp_norm_wh2 = 1.0f / len(wh2);
@ -507,14 +501,14 @@ ccl_device float3 bsdf_microfacet_hair_eval_tt_trt(KernelGlobals kg,
cos_hi2 * cos_ho2 * sqr(rcp_norm_wh2);
if (isfinite_safe(result)) {
S_tt += bsdf->extra->TT * result * arc_length(e2, gamma_mt);
S_tt += bsdf->factor->TT * result * arc_length(e2, gamma_mt);
}
}
}
}
/* TRT */
if (bsdf->extra->TRT > 0.0f) {
if (bsdf->factor->TRT > 0.0f) {
/* Sample wh2. */
const float2 sample2 = make_float2(lcg_step_float(&rng_quadrature),
lcg_step_float(&rng_quadrature));
@ -570,7 +564,7 @@ ccl_device float3 bsdf_microfacet_hair_eval_tt_trt(KernelGlobals kg,
sqr(rcp_norm_wh3);
if (isfinite_safe(result)) {
S_trt += bsdf->extra->TRT * result * arc_length(e2, gamma_mtr);
S_trt += bsdf->factor->TRT * result * arc_length(e2, gamma_mtr);
}
}
}
@ -595,7 +589,7 @@ ccl_device int bsdf_microfacet_hair_sample(const KernelGlobals kg,
*eta = bsdf->eta;
const float inv_eta = 1.0f / *eta;
if (bsdf->extra->R <= 0.0f && bsdf->extra->TT <= 0.0f && bsdf->extra->TRT <= 0.0f) {
if (bsdf->factor->R <= 0.0f && bsdf->factor->TT <= 0.0f && bsdf->factor->TRT <= 0.0f) {
/* Early out for inactive lobe. */
*pdf = 0.0f;
return LABEL_NONE;
@ -605,7 +599,7 @@ ccl_device int bsdf_microfacet_hair_sample(const KernelGlobals kg,
* . X major axis.
* . Y along the fiber tangent.
* . Z minor axis. */
const float3 X = float4_to_float3(bsdf->extra->geom);
const float3 X = bsdf->N;
const float3 Z = safe_normalize(cross(X, sd->dPdu));
const float3 Y = safe_normalize(cross(Z, X));
@ -624,7 +618,7 @@ ccl_device int bsdf_microfacet_hair_sample(const KernelGlobals kg,
const float d_i = projected_radius(e2, sin_phi_i);
weizhen marked this conversation as resolved Outdated

Same here.

Same here.
/* Treat as transparent material if intersection lies outside of the projected radius. */
if (fabsf(bsdf->extra->geom.w) > d_i) {
if (fabsf(bsdf->h) > d_i) {
*wo = -sd->wi;
*pdf = 1;
*eval = one_spectrum();
@ -675,7 +669,7 @@ ccl_device int bsdf_microfacet_hair_sample(const KernelGlobals kg,
float cos_theta_t1;
const float R1 = fresnel(dot(wi, wh1), *eta, &cos_theta_t1);
const float3 R = make_float3(bsdf->extra->R * R1);
const float3 R = make_float3(bsdf->factor->R * R1);
/* Sample TT lobe. */
const float3 wt = -refract_angle(wi, wh1, cos_theta_t1, inv_eta);
@ -710,7 +704,7 @@ ccl_device int bsdf_microfacet_hair_sample(const KernelGlobals kg,
wtt = -refract_angle(-wt, wh2, cos_theta_t2, *eta);
if (dot(wmt, -wtt) > 0.0f && cos_theta_t2 != 0.0f) {
TT = bsdf->extra->TT * T1 * A_t * T2;
TT = bsdf->factor->TT * T1 * A_t * T2;
}
/* Sample TRT lobe. */
@ -735,7 +729,7 @@ ccl_device int bsdf_microfacet_hair_sample(const KernelGlobals kg,
2.0f * cos(phi_tr - gamma_mt) :
-len(to_point(gamma_mt, b) - to_point(gamma_mtr, b))));
TRT = bsdf->extra->TRT * T1 * R2 * T3 * A_t * A_tr;
TRT = bsdf->factor->TRT * T1 * R2 * T3 * A_t * A_tr;
}
}
@ -815,7 +809,7 @@ ccl_device Spectrum bsdf_microfacet_hair_eval(KernelGlobals kg,
* . X major axis.
* . Y along the fiber tangent.
* . Z minor axis. */
const float3 X = float4_to_float3(bsdf->extra->geom);
const float3 X = bsdf->N;
const float3 Z = safe_normalize(cross(X, sd->dPdu));
const float3 Y = safe_normalize(cross(Z, X));
@ -825,7 +819,7 @@ ccl_device Spectrum bsdf_microfacet_hair_eval(KernelGlobals kg,
/* Treat as transparent material if intersection lies outside of the projected radius. */
const float e2 = 1.0f - sqr(bsdf->aspect_ratio);
if (fabsf(bsdf->extra->geom.w) > projected_radius(e2, sin_phi(local_I))) {
if (fabsf(bsdf->h) > projected_radius(e2, sin_phi(local_I))) {
*pdf = 0.0f;
return zero_spectrum();
}

View File

@ -1300,13 +1300,13 @@ ccl_device void osl_closure_microfacet_hair_setup(KernelGlobals kg,
return;
}
ccl_private MicrofacetHairExtra *extra = (ccl_private MicrofacetHairExtra *)closure_alloc_extra(
sd, sizeof(MicrofacetHairExtra));
if (!extra) {
ccl_private MicrofacetHairLobeFactor *factor = (ccl_private MicrofacetHairLobeFactor *)
closure_alloc_extra(sd, sizeof(MicrofacetHairLobeFactor));
if (!factor) {
return;
}
bsdf->N = ensure_valid_specular_reflection(sd->Ng, sd->wi, closure->N);
bsdf->N = closure->N;
bsdf->sigma = closure->sigma;
bsdf->roughness = closure->roughness;
bsdf->tilt = closure->tilt;
@ -1314,12 +1314,10 @@ ccl_device void osl_closure_microfacet_hair_setup(KernelGlobals kg,
bsdf->distribution_type = closure->distribution_type;
bsdf->aspect_ratio = closure->aspect_ratio;
bsdf->extra = extra;
bsdf->extra->R = closure->reflection;
bsdf->extra->TT = closure->transmission;
bsdf->extra->TRT = closure->secondary_reflection;
bsdf->extra->geom = make_float4(
closure->major_axis.x, closure->major_axis.y, closure->major_axis.z, 0.0f);
bsdf->factor = factor;
bsdf->factor->R = closure->reflection;
bsdf->factor->TT = closure->transmission;
bsdf->factor->TRT = closure->secondary_reflection;
sd->flag |= bsdf_microfacet_hair_setup(sd, bsdf);
#endif

View File

@ -290,7 +290,6 @@ OSL_CLOSURE_STRUCT_BEGIN(MicrofacetHair, microfacet_hair)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, FLOAT, float, reflection, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, FLOAT, float, transmission, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, FLOAT, float, secondary_reflection, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, VECTOR, packed_float3, major_axis, NULL)
OSL_CLOSURE_STRUCT_END(MicrofacetHair, microfacet_hair)
OSL_CLOSURE_STRUCT_BEGIN(VolumeAbsorption, absorption)

View File

@ -41,7 +41,6 @@ shader node_microfacet_hair_bsdf(color Color = color(0.017513, 0.005763, 0.00205
float RandomColor = 0.0,
color Tint = 1.0,
color AbsorptionCoefficient = color(0.245531, 0.52, 1.365),
normal Normal = Ng,
string parametrization = "Direct Coloring",
string distribution_type = "GGX",
float Offset = radians(2),
@ -103,13 +102,13 @@ shader node_microfacet_hair_bsdf(color Color = color(0.017513, 0.005763, 0.00205
}
int distribution_type_enum = (distribution_type == "GGX") ? 0 : 1;
normal major_axis = 0.0;
normal major_axis = 0.0;
if (AspectRatio != 1.0) {
getattribute("geom:N", major_axis);
}
BSDF = microfacet_hair(Normal,
BSDF = microfacet_hair(major_axis,
sigma,
roughness,
Offset,
@ -118,6 +117,5 @@ shader node_microfacet_hair_bsdf(color Color = color(0.017513, 0.005763, 0.00205
AspectRatio,
Reflection,
Transmission,
SecondaryReflection,
major_axis);
SecondaryReflection);
}

View File

@ -69,8 +69,7 @@ closure color microfacet_hair(normal N,
float aspect_ratio,
float reflection,
float transmission,
float secondary_reflection,
normal major_axis) BUILTIN;
float secondary_reflection) BUILTIN;
// Volume
closure color henyey_greenstein(float g) BUILTIN;

View File

@ -848,26 +848,24 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
ccl_private MicrofacetHairBSDF *bsdf = (ccl_private MicrofacetHairBSDF *)bsdf_alloc(
sd, sizeof(MicrofacetHairBSDF), weight);
if (bsdf) {
ccl_private MicrofacetHairExtra *extra = (ccl_private MicrofacetHairExtra *)
closure_alloc_extra(sd, sizeof(MicrofacetHairExtra));
ccl_private MicrofacetHairLobeFactor *factor = (ccl_private MicrofacetHairLobeFactor *)
closure_alloc_extra(sd, sizeof(MicrofacetHairLobeFactor));
if (!extra) {
if (!factor) {
break;
}
bsdf->extra = extra;
bsdf->extra->R = fmaxf(0.0f, R);
bsdf->extra->TT = fmaxf(0.0f, TT);
bsdf->extra->TRT = fmaxf(0.0f, TRT);
bsdf->factor = factor;
bsdf->factor->R = fmaxf(0.0f, R);
bsdf->factor->TT = fmaxf(0.0f, TT);
bsdf->factor->TRT = fmaxf(0.0f, TRT);
bsdf->aspect_ratio = stack_load_float_default(stack, aspect_ratio_ofs, data_node2.y);
if (bsdf->aspect_ratio != 1.0f) {
const AttributeDescriptor attr_descr_normal = find_attribute(kg, sd, attr_normal);
const float3 major_axis = curve_attribute_float3(kg, sd, attr_descr_normal, NULL, NULL);
/* Align ellipse major axis with the curve normal direction. */
bsdf->extra->geom = make_float4(major_axis.x, major_axis.y, major_axis.z, 0.0f);
const AttributeDescriptor attr_descr_normal = find_attribute(kg, sd, attr_normal);
bsdf->N = curve_attribute_float3(kg, sd, attr_descr_normal, NULL, NULL);
}
/* Random factors range: [-randomization/2, +randomization/2]. */
@ -878,7 +876,6 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
bsdf->distribution_type = clamp(
distribution_type, NODE_MICROFACET_HAIR_GGX, NODE_MICROFACET_HAIR_BECKMANN);
bsdf->N = N;
bsdf->roughness = roughness;
bsdf->tilt = tilt;
bsdf->eta = ior;