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
7 changed files with 127 additions and 31 deletions
Showing only changes of commit f0529cfabc - Show all commits

View File

@ -17,8 +17,6 @@ typedef struct MicrofacetHairExtra {
float TT;
float TRT;
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 aspect_ratio;
/* Geometry data. */
float4 geom;
} MicrofacetHairExtra;
@ -41,6 +39,9 @@ typedef struct MicrofacetHairBSDF {
/* Circular/Elliptical */
int cross_section;
/* The ratio of the minor axis to the major axis. */
float aspect_ratio;
/* Extra closure. */
ccl_private MicrofacetHairExtra *extra;
} MicrofacetHairBSDF;
@ -74,8 +75,20 @@ ccl_device int bsdf_microfacet_hair_setup(ccl_private ShaderData *sd,
kernel_assert(isfinite_safe(h));
if (bsdf->cross_section == NODE_MICROFACET_HAIR_ELLIPTIC) {
/* Local frame is independent of the ray direction for elliptical hairs. */
bsdf->extra->geom.w = h;
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));
/* Local frame is independent of the ray direction for elliptical hairs. */
bsdf->extra->geom = make_float4(major_axis.x, major_axis.y, major_axis.z, h);
}
else {
bsdf->extra->geom.w = h;
}
}
else {
bsdf->extra->geom = make_float4(X.x, X.y, X.z, h);
@ -802,7 +815,7 @@ ccl_device float3 bsdf_microfacet_hair_eval_r_elliptic(ccl_private const ShaderC
/* get elliptical cross section characteristic */
const float a = 1.0f;
/* TODO: rename this as aspect ratio? e is in [0, 0.85], b is in [0.52, 1]. */
const float b = bsdf->extra->aspect_ratio;
const float b = bsdf->aspect_ratio;
const float e2 = 1.0f - sqr(b / a);
/* this follows blender's convention (unlike the circular case?) */
@ -898,7 +911,7 @@ ccl_device float3 bsdf_microfacet_hair_eval_tt_trt_elliptic(KernelGlobals kg,
return zero_float3();
}
/* this follows blender's convention (unlike the circular case?) */
/* TODO: switch wi and wo? */
const float3 wo = wi_;
const float3 wi = wo_;
@ -923,7 +936,7 @@ ccl_device float3 bsdf_microfacet_hair_eval_tt_trt_elliptic(KernelGlobals kg,
/* get elliptical cross section characteristic */
const float a = 1.0f;
const float b = bsdf->extra->aspect_ratio;
const float b = bsdf->aspect_ratio;
const float e2 = 1.0f - sqr(b / a); /* Squared Eccentricity. */
const float gamma_m_min = to_gamma(phi_m_min, a, b) + 1e-3f;
@ -1094,7 +1107,7 @@ ccl_device Spectrum bsdf_microfacet_hair_eval_elliptic(KernelGlobals kg,
const float3 wo = make_float3(dot(omega_in, X), dot(omega_in, Y), dot(omega_in, Z));
/* Treat as transparent material if intersection lies outside of the projected radius. */
const float e2 = 1.0f - sqr(bsdf->extra->aspect_ratio);
const float e2 = 1.0f - sqr(bsdf->aspect_ratio);
const float radius = sqrtf(1.0f - e2 * sqr(sin_phi(wi)));
if (fabsf(bsdf->extra->geom.w) > radius) {
*pdf = 0.0f;
@ -1136,7 +1149,7 @@ ccl_device int bsdf_microfacet_hair_sample_elliptic(const KernelGlobals kg,
/* get elliptical cross section characteristic */
const float a = 1.0f;
const float b = bsdf->extra->aspect_ratio;
const float b = bsdf->aspect_ratio;
const float e2 = 1.0f - sqr(b / a);
/* macronormal */
@ -1345,7 +1358,7 @@ ccl_device Spectrum bsdf_microfacet_hair_eval(KernelGlobals kg,
{
ccl_private MicrofacetHairBSDF *bsdf = (ccl_private MicrofacetHairBSDF *)sc;
if (bsdf->cross_section == NODE_MICROFACET_HAIR_CIRCULAR || bsdf->extra->aspect_ratio == 1.0f) {
if (bsdf->cross_section == NODE_MICROFACET_HAIR_CIRCULAR || bsdf->aspect_ratio == 1.0f) {
return bsdf_microfacet_hair_eval_circular(kg, sd, sc, omega_in, pdf);
}
@ -1365,7 +1378,7 @@ ccl_device int bsdf_microfacet_hair_sample(KernelGlobals kg,
{
ccl_private MicrofacetHairBSDF *bsdf = (ccl_private MicrofacetHairBSDF *)sc;
if (bsdf->cross_section == NODE_MICROFACET_HAIR_CIRCULAR || bsdf->extra->aspect_ratio == 1.0f) {
if (bsdf->cross_section == NODE_MICROFACET_HAIR_CIRCULAR || bsdf->aspect_ratio == 1.0f) {
return bsdf_microfacet_hair_sample_circular(
kg, sc, sd, randu, randv, eval, omega_in, pdf, sampled_roughness, eta);
}

View File

@ -23,6 +23,7 @@
#include "kernel/closure/bsdf_toon.h"
#include "kernel/closure/bsdf_hair.h"
#include "kernel/closure/bsdf_hair_principled.h"
#include "kernel/closure/bsdf_hair_microfacet.h"
#include "kernel/closure/bsdf_principled_diffuse.h"
#include "kernel/closure/bsdf_principled_sheen.h"
#include "kernel/closure/volume.h"
@ -1121,6 +1122,49 @@ ccl_device void osl_closure_principled_hair_setup(KernelGlobals kg,
#endif
}
ccl_device void osl_closure_microfacet_hair_setup(KernelGlobals kg,
ccl_private ShaderData *sd,
uint32_t path_flag,
float3 weight,
ccl_private const MicrofacetHairClosure *closure)
{
#ifdef __HAIR__
if (osl_closure_skip(kg, sd, path_flag, LABEL_GLOSSY)) {
return;
}
ccl_private MicrofacetHairBSDF *bsdf = (ccl_private MicrofacetHairBSDF *)bsdf_alloc(
sd, sizeof(MicrofacetHairBSDF), rgb_to_spectrum(weight));
if (!bsdf) {
return;
}
ccl_private MicrofacetHairExtra *extra = (ccl_private MicrofacetHairExtra *)closure_alloc_extra(
sd, sizeof(MicrofacetHairExtra));
if (!extra) {
return;
}
bsdf->N = ensure_valid_reflection(sd->Ng, sd->I, closure->N);
bsdf->sigma = closure->sigma;
bsdf->roughness = closure->roughness;
bsdf->tilt = closure->tilt;
bsdf->eta = closure->eta;
bsdf->cross_section = closure->cross_section;
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);
sd->flag |= bsdf_microfacet_hair_setup(sd, bsdf);
#endif
}
/* Volume */
ccl_device void osl_closure_absorption_setup(KernelGlobals kg,

View File

@ -245,6 +245,21 @@ OSL_CLOSURE_STRUCT_BEGIN(PrincipledHair, principled_hair)
OSL_CLOSURE_STRUCT_MEMBER(PrincipledHair, FLOAT, float, eta, NULL)
OSL_CLOSURE_STRUCT_END(PrincipledHair, principled_hair)
OSL_CLOSURE_STRUCT_BEGIN(MicrofacetHair, microfacet_hair)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, VECTOR, packed_float3, N, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, VECTOR, packed_float3, sigma, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, FLOAT, float, roughness, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, FLOAT, float, tilt, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, FLOAT, float, eta, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, INT, int, distribution_type, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, INT, int, cross_section, NULL)
OSL_CLOSURE_STRUCT_MEMBER(MicrofacetHair, FLOAT, float, aspect_ratio, NULL)
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)
OSL_CLOSURE_STRUCT_END(VolumeAbsorption, absorption)

View File

@ -51,6 +51,10 @@ shader node_microfacet_hair_bsdf(color Color = color(0.017513, 0.005763, 0.00205
float IOR = 1.55,
string AttrRandom = "geom:curve_random",
float Random = 0.0,
float AspectRatio = 0.85,
float Reflection = 1.0,
float Transmission = 1.0,
float SecondaryReflection = 1.0,
output closure color BSDF = 0)
{
@ -99,5 +103,26 @@ shader node_microfacet_hair_bsdf(color Color = color(0.017513, 0.005763, 0.00205
sigma = m_sigma_from_concentration(0.0, 0.8054375);
}
BSDF = microfacet_hair(Normal, sigma, roughness, Offset, IOR);
int distribution_type_enum = (distribution_type == "GGX") ? 0 : 1;
int cross_section_enum = 0;
normal major_axis = 0.0;
if (cross_section == "Elliptical") {
cross_section_enum = 1;
major_axis = 1.0;
getattribute("geom:N", major_axis);
}
BSDF = microfacet_hair(Normal,
sigma,
roughness,
Offset,
IOR,
distribution_type_enum,
cross_section_enum,
AspectRatio,
Reflection,
Transmission,
SecondaryReflection,
major_axis);
}

View File

@ -60,8 +60,18 @@ closure color principled_hair(normal N,
float coat,
float alpha,
float eta) BUILTIN;
closure color
microfacet_hair(normal N, color sigma, float roughness, float tilt, float eta) BUILTIN;
closure color microfacet_hair(normal N,
color sigma,
float roughness,
float tilt,
float eta,
int distribution_type,
int cross_section,
float aspect_ratio,
float reflection,
float transmission,
float secondary_reflection,
normal major_axis) BUILTIN;
// Volume
closure color henyey_greenstein(float g) BUILTIN;

View File

@ -1002,27 +1002,16 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
}
if (cross_section == NODE_MICROFACET_HAIR_ELLIPTIC) {
uint aspect_ratio_ofs, temp;
svm_unpack_node_uchar4(data_node5.x, &aspect_ratio_ofs, &temp, &temp, &temp);
float aspect_ratio = stack_load_float_default(stack, aspect_ratio_ofs, data_node5.y);
/* Aspect Ratio */
bsdf->extra->aspect_ratio = (aspect_ratio > 1.0f) ? 1.0f / aspect_ratio : aspect_ratio;
bsdf->aspect_ratio = stack_load_float_default(stack, aspect_ratio_ofs, data_node5.y);
const AttributeDescriptor attr_descr_normal = find_attribute(kg, sd, data_node5.z);
const float3 normal = curve_attribute_float3(kg, sd, attr_descr_normal, NULL, NULL);
const float3 binormal = safe_normalize(cross(sd->dPdu, normal));
const float3 major_axis = curve_attribute_float3(kg, sd, attr_descr_normal, NULL, NULL);
/* Align X axis with the ellipse major axis. */
if (aspect_ratio > 1.0f) {
const float3 normal = safe_normalize(cross(binormal, sd->dPdu));
bsdf->extra->geom = make_float4(normal.x, normal.y, normal.z, 0.0f);
}
else {
bsdf->extra->geom = make_float4(binormal.x, binormal.y, binormal.z, 0.0f);
}
/* 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);
}
sd->flag |= bsdf_microfacet_hair_setup(sd, bsdf);

View File

@ -3653,8 +3653,8 @@ NODE_DEFINE(MicrofacetHairBsdfNode)
/* Hair cross-section type specified as enum. */
static NodeEnum cross_section_enum;
distribution_type_enum.insert("Circular", NODE_MICROFACET_HAIR_CIRCULAR);
distribution_type_enum.insert("Elliptical", NODE_MICROFACET_HAIR_ELLIPTIC);
cross_section_enum.insert("Circular", NODE_MICROFACET_HAIR_CIRCULAR);
cross_section_enum.insert("Elliptical", NODE_MICROFACET_HAIR_ELLIPTIC);
SOCKET_ENUM(cross_section, "Cross Section", cross_section_enum, NODE_MICROFACET_HAIR_CIRCULAR);
/* Initialize sockets to their default values. */