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
5 changed files with 74 additions and 103 deletions
Showing only changes of commit 8125657606 - Show all commits

View File

@ -272,10 +272,13 @@ static void ExportCurveSegments(Scene *scene, Hair *hair, ParticleCurveData *CDa
if (hair->num_curves())
return;
Attribute *attr_normal = NULL;
Attribute *attr_intercept = NULL;
Attribute *attr_length = NULL;
Attribute *attr_random = NULL;
if (hair->need_attribute(scene, ATTR_STD_VERTEX_NORMAL))
attr_normal = hair->attributes.add(ATTR_STD_VERTEX_NORMAL);
if (hair->need_attribute(scene, ATTR_STD_CURVE_INTERCEPT))
attr_intercept = hair->attributes.add(ATTR_STD_CURVE_INTERCEPT);
if (hair->need_attribute(scene, ATTR_STD_CURVE_LENGTH))
@ -325,6 +328,11 @@ static void ExportCurveSegments(Scene *scene, Hair *hair, ParticleCurveData *CDa
num_curve_keys++;
}
if (attr_normal != NULL) {
/* TODO: compute geometry normals. */
attr_normal->add(make_float3(1.0f, 0.0f, 0.0f));
}
if (attr_length != NULL) {
attr_length->add(CData->curve_length[curve]);
}
@ -881,10 +889,14 @@ static void export_hair_curves(Scene *scene,
int *curve_shader = hair->get_curve_shader().data();
/* Add requested attributes. */
float3 *attr_normal = NULL;
float *attr_intercept = NULL;
float *attr_length = NULL;
float *attr_random = NULL;
if (hair->need_attribute(scene, ATTR_STD_VERTEX_NORMAL)) {
attr_normal = hair->attributes.add(ATTR_STD_VERTEX_NORMAL)->data_float3();
}
if (hair->need_attribute(scene, ATTR_STD_CURVE_INTERCEPT)) {
attr_intercept = hair->attributes.add(ATTR_STD_CURVE_INTERCEPT)->data_float();
}
@ -912,6 +924,11 @@ static void export_hair_curves(Scene *scene,
const float3 co = get_float3(b_attr_position.data[point_offset].vector());
const float radius = b_attr_radius ? b_attr_radius->data[point_offset].value() : 0.005f;
if (attr_normal) {
/* TODO: compute geometry normals. */
attr_normal[point_offset] = make_float3(1.0f, 0.0f, 0.0f);
}
curve_keys[point_offset] = co;
curve_radius[point_offset] = radius;

View File

@ -17,13 +17,6 @@ typedef struct MicrofacetHairExtra {
float TRT;
float eccentricity;
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 twist_rate;
float attr_descr_intercept;
float attr_descr_length;
float axis_rot;
float diffraction_weight;
float pad1, pad2, pad3;
/* Geometry data. */
float4 geom;
@ -73,15 +66,20 @@ ccl_device int bsdf_microfacet_hair_setup(ccl_private ShaderData *sd,
* the center, to grazing the other edge. This is the sine of the angle
* between sd->Ng and Z, as seen from the tangent X. */
/* TODO: we convert this value to a cosine later and discard the sign, so
* we could probably save some operations. */
float h = (sd->type & PRIMITIVE_CURVE_RIBBON) ? -sd->v : -dot(X, sd->Ng);

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.
kernel_assert(fabsf(h) < 1.0f + 1e-4f);
kernel_assert(isfinite_safe(X));
kernel_assert(isfinite_safe(h));
bsdf->extra->geom = make_float4(X.x, X.y, X.z, h);
if (bsdf->model_type == NODE_MICROFACET_HAIR_ELLIPTIC_GGX ||
bsdf->model_type == NODE_MICROFACET_HAIR_ELLIPTIC_BECKMANN) {
/* Local frame is independent of the ray direction for elliptical hairs. */
bsdf->extra->geom.w = h;
}
else {
bsdf->extra->geom = make_float4(X.x, X.y, X.z, h);
}
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION;
}
@ -1126,23 +1124,10 @@ ccl_device Spectrum bsdf_microfacet_hair_eval_elliptic(KernelGlobals kg,
{
/* get local wi(convention is reversed from other hair bcsdfs) */
ccl_private MicrofacetHairBSDF *bsdf = (ccl_private MicrofacetHairBSDF *)sc;
const float3 X0 = float4_to_float3(bsdf->extra->geom);
const float3 Y = safe_normalize(sd->dPdu);
const float3 Z0 = safe_normalize(cross(X0, Y));
/* rotate (Z,X) around Y */
const float curve_parameter = bsdf->extra->attr_descr_intercept;
const float curve_length = bsdf->extra->attr_descr_length;
const float curve_twist_rate = bsdf->extra->twist_rate;
const float curve_twist_start = bsdf->extra->axis_rot;
const float twist_angle = M_2PI_F * (curve_twist_start +
curve_twist_rate * curve_parameter * curve_length);
const float sin_twist_angle = sinf(twist_angle);
const float cos_twist_angle = cosf(twist_angle);
const float3 Z = cos_twist_angle * Z0 + sin_twist_angle * X0;
const float3 X = -sin_twist_angle * Z0 + cos_twist_angle * X0;
const float3 X = float4_to_float3(bsdf->extra->geom);
float3 Y = sd->dPdu;
const float3 Z = safe_normalize(cross(X, Y));
Y = safe_normalize(cross(Z, X));
const float3 wi = make_float3(dot(sd->I, X), dot(sd->I, Y), dot(sd->I, Z));
const float3 wo = make_float3(dot(omega_in, X), dot(omega_in, Y), dot(omega_in, Z));
@ -1179,20 +1164,10 @@ ccl_device int bsdf_microfacet_hair_sample_elliptic(const KernelGlobals kg,
}
/* get local wi (convention is reversed from other hair bcsdfs) */
const float3 X0 = float4_to_float3(bsdf->extra->geom);
const float3 Y = safe_normalize(sd->dPdu);
const float3 Z0 = safe_normalize(cross(X0, Y));
/* const float curve_parameter = bsdf->extra->attr_descr_intercept; */
/* const float curve_length = bsdf->extra->attr_descr_length; */
/* TODO: compute the ellipse orientation based on the curve normal or user-defined normal? */
const float twist_angle = 0.0f;
const float sin_twist_angle = sinf(twist_angle);
const float cos_twist_angle = cosf(twist_angle);
const float3 Z = cos_twist_angle * Z0 + sin_twist_angle * X0;
const float3 X = -sin_twist_angle * Z0 + cos_twist_angle * X0;
const float3 X = float4_to_float3(bsdf->extra->geom);
float3 Y = sd->dPdu;
const float3 Z = safe_normalize(cross(X, Y));
Y = safe_normalize(cross(Z, X));
const float3 wi = make_float3(dot(sd->I, X), dot(sd->I, Y), dot(sd->I, Z));

View File

@ -884,8 +884,6 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
uint4 data_node3 = read_node(kg, &offset);
uint4 data_node4 = read_node(kg, &offset);
uint4 data_node5 = read_node(kg, &offset);
uint4 data_node6 = read_node(kg, &offset);
uint4 data_node7 = read_node(kg, &offset);
Spectrum weight = sd->svm_closure_weight * mix_weight;
@ -894,9 +892,9 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
float alpha = stack_load_float_default(stack, offset_ofs, data_node.z);
float ior = stack_load_float_default(stack, ior_ofs, data_node.w);
uint coat_ofs, melanin_ofs, melanin_redness_ofs, absorption_coefficient_ofs;
uint Blur_ofs, melanin_ofs, melanin_redness_ofs, absorption_coefficient_ofs;
svm_unpack_node_uchar4(data_node2.x,
&coat_ofs,
&Blur_ofs,
&melanin_ofs,
&melanin_redness_ofs,
&absorption_coefficient_ofs);
@ -905,7 +903,7 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
svm_unpack_node_uchar4(
data_node3.x, &tint_ofs, &random_ofs, &random_color_ofs, &random_roughness_ofs);
const AttributeDescriptor attr_descr_random = find_attribute(kg, sd, data_node4.y);
const AttributeDescriptor attr_descr_random = find_attribute(kg, sd, data_node3.w);
float random = 0.0f;
if (attr_descr_random.offset != ATTR_STD_NOT_FOUND) {
random = primitive_surface_attribute_float(kg, sd, attr_descr_random, NULL, NULL);
@ -914,16 +912,13 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
random = stack_load_float_default(stack, random_ofs, data_node3.y);
}
uint R_ofs, TT_ofs, TRT_ofs, temp;
uint Blur_ofs, model_type;
uint R_ofs, TT_ofs, TRT_ofs, model_type;
svm_unpack_node_uchar4(data_node5.x, &R_ofs, &TT_ofs, &TRT_ofs, &temp);
float R = stack_load_float_default(stack, R_ofs, data_node5.y);
float TT = stack_load_float_default(stack, TT_ofs, data_node5.z);
float TRT = stack_load_float_default(stack, TRT_ofs, data_node5.w);
svm_unpack_node_uchar4(data_node6.x, &temp, &Blur_ofs, &temp, &model_type);
float blur = stack_load_float_default(stack, Blur_ofs, data_node6.z);
svm_unpack_node_uchar4(data_node4.x, &R_ofs, &TT_ofs, &TRT_ofs, &model_type);
float R = stack_load_float_default(stack, R_ofs, data_node4.y);
float TT = stack_load_float_default(stack, TT_ofs, data_node4.z);
float TRT = stack_load_float_default(stack, TRT_ofs, data_node4.w);
float blur = stack_load_float_default(stack, Blur_ofs, data_node2.y);
ccl_private MicrofacetHairBSDF *bsdf = (ccl_private MicrofacetHairBSDF *)bsdf_alloc(
sd, sizeof(MicrofacetHairBSDF), weight);
@ -941,8 +936,7 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
bsdf->blur = clamp(blur, 0.0f, 1.0f);
/* Random factors range: [-randomization/2, +randomization/2]. */
float random_roughness = stack_load_float_default(
stack, random_roughness_ofs, data_node3.w);
float random_roughness = param2;
float factor_random_roughness = 1.0f + 2.0f * (random - 0.5f) * random_roughness;
float roughness = param1 * factor_random_roughness;
@ -1023,21 +1017,18 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg,
if (model_type == NODE_MICROFACET_HAIR_ELLIPTIC_GGX ||
model_type == NODE_MICROFACET_HAIR_ELLIPTIC_BECKMANN) {
uint eccentricity_ofs;
svm_unpack_node_uchar4(data_node7.x, &eccentricity_ofs, &temp, &temp, &temp);
uint eccentricity_ofs, temp;
svm_unpack_node_uchar4(data_node5.x, &eccentricity_ofs, &temp, &temp, &temp);
float eccentricity = stack_load_float_default(stack, eccentricity_ofs, data_node7.y);
float eccentricity = stack_load_float_default(stack, eccentricity_ofs, data_node5.y);
/* Eccentricity */
bsdf->extra->eccentricity = (eccentricity > 1.f) ? 1.f / eccentricity : eccentricity;
const AttributeDescriptor attr_descr_intercept = find_attribute(kg, sd, data_node4.z);
bsdf->extra->attr_descr_intercept = curve_attribute_float(
kg, sd, attr_descr_intercept, NULL, NULL);
const AttributeDescriptor attr_descr_length = find_attribute(kg, sd, data_node4.w);
bsdf->extra->attr_descr_length = curve_attribute_float(
kg, sd, attr_descr_length, NULL, NULL);
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));
bsdf->extra->geom = make_float4(binormal.x, binormal.y, binormal.z, 0.0f);
}
sd->flag |= bsdf_microfacet_hair_setup(sd, bsdf);

View File

@ -611,6 +611,9 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name)
}
else if (geometry->geometry_type == Geometry::HAIR) {
switch (std) {
case ATTR_STD_VERTEX_NORMAL:
attr = add(name, TypeDesc::TypeNormal, ATTR_ELEMENT_CURVE_KEY);
break;
case ATTR_STD_UV:
attr = add(name, TypeFloat2, ATTR_ELEMENT_CURVE);
break;

View File

@ -3697,9 +3697,8 @@ MicrofacetHairBsdfNode::MicrofacetHairBsdfNode() : BsdfBaseNode(get_node_type())
/* Enable retrieving Hair Info -> Random if Random isn't linked. */
void MicrofacetHairBsdfNode::attributes(Shader *shader, AttributeRequestSet *attributes)
{
/* Make sure we have these 2 for elliptical cross section tracking */
attributes->add(ATTR_STD_CURVE_INTERCEPT);
attributes->add(ATTR_STD_CURVE_LENGTH);
/* Make sure we have the normal for elliptical cross section tracking */
attributes->add(ATTR_STD_VERTEX_NORMAL);
if (!input("Random")->link) {
attributes->add(ATTR_STD_CURVE_RANDOM);
@ -3753,63 +3752,49 @@ void MicrofacetHairBsdfNode::compile(SVMCompiler &compiler)
compiler.add_node(
NODE_CLOSURE_BSDF,
/* Socket IDs can be packed 4 at a time into a single data packet */
compiler.encode_uchar4(closure, roughness_ofs, 0, compiler.closure_mix_weight_offset()),
compiler.encode_uchar4(
closure, roughness_ofs, random_roughness_ofs, compiler.closure_mix_weight_offset()),
/* The rest are stored as unsigned integers */
__float_as_uint(roughness),
0);
__float_as_uint(random_roughness));
/* data node */
compiler.add_node(normal_ofs,
compiler.encode_uchar4(offset_ofs, ior_ofs, color_ofs, parametrization),
__float_as_uint(offset),
__float_as_uint(ior));
/* data node 2 */
compiler.add_node(
compiler.encode_uchar4(0, melanin_ofs, melanin_redness_ofs, absorption_coefficient_ofs),
0,
__float_as_uint(melanin),
__float_as_uint(melanin_redness));
compiler.add_node(compiler.encode_uchar4(compiler.stack_assign_if_linked(Blur_in),
melanin_ofs,
melanin_redness_ofs,
absorption_coefficient_ofs),
__float_as_uint(Blur),
__float_as_uint(melanin),
__float_as_uint(melanin_redness));
/* data node 3 */
compiler.add_node(
compiler.encode_uchar4(tint_ofs, random_in_ofs, random_color_ofs, random_roughness_ofs),
__float_as_uint(random),
__float_as_uint(random_color),
__float_as_uint(random_roughness));
int attr_intercept = compiler.attribute(ATTR_STD_CURVE_INTERCEPT);
int attr_length = compiler.attribute(ATTR_STD_CURVE_LENGTH);
compiler.add_node(compiler.encode_uchar4(tint_ofs, random_in_ofs, random_color_ofs, 0),
__float_as_uint(random),
__float_as_uint(random_color),
attr_random);
/* data node 4 */
compiler.add_node(
compiler.encode_uchar4(
SVM_STACK_INVALID, SVM_STACK_INVALID, SVM_STACK_INVALID, SVM_STACK_INVALID),
attr_random,
attr_intercept,
attr_length);
/* data node 5 */
compiler.add_node(compiler.encode_uchar4(compiler.stack_assign_if_linked(R_in),
compiler.stack_assign_if_linked(TT_in),
compiler.stack_assign_if_linked(TRT_in),
SVM_STACK_INVALID),
model_type),
__float_as_uint(R),
__float_as_uint(TT),
__float_as_uint(TRT));
/* data node 6 */
compiler.add_node(
compiler.encode_uchar4(0, compiler.stack_assign_if_linked(Blur_in), 0, model_type),
0,
__float_as_uint(Blur),
0);
/* data node 7 */
/* data node 5 */
compiler.add_node(compiler.encode_uchar4(compiler.stack_assign_if_linked(eccentricity_in),
SVM_STACK_INVALID,
SVM_STACK_INVALID,
SVM_STACK_INVALID),
__float_as_uint(eccentricity),
0,
compiler.attribute(ATTR_STD_VERTEX_NORMAL),
0);
}