Cycles: Multi-scale Principled Hair Huang Model #116094
|
@ -30,6 +30,9 @@ typedef struct HuangHairExtra {
|
|||
/* Squared Eccentricity. */
|
||||
float e2;
|
||||
|
||||
/* The projected width of half a pixel at `sd->P` in `h` space. */
|
||||
float pixel_coverage;
|
||||
|
||||
/* Valid integration interval. */
|
||||
float gamma_m_min, gamma_m_max;
|
||||
} HuangHairExtra;
|
||||
|
@ -181,6 +184,11 @@ ccl_device_inline float arc_length(float e2, float gamma)
|
|||
return e2 == 0 ? 1.0f : sqrtf(1.0f - e2 * sqr(sinf(gamma)));
|
||||
}
|
||||
|
||||
ccl_device_inline bool is_nearfield(ccl_private const HuangHairBSDF *bsdf)
|
||||
{
|
||||
return bsdf->extra->radius > bsdf->extra->pixel_coverage;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
#ifdef __HAIR__
|
||||
|
@ -355,11 +363,12 @@ ccl_device Spectrum bsdf_hair_huang_eval_r(KernelGlobals kg,
|
|||
}
|
||||
}
|
||||
|
||||
/* Simpson coefficient */
|
||||
integral *= (2.0f / 3.0f * res);
|
||||
|
||||
const float F = fresnel_dielectric_cos(dot(wi, wh), bsdf->eta);
|
||||
|
||||
return make_spectrum(bsdf->extra->R * 0.125f * F * integral / bsdf->extra->radius);
|
||||
return make_spectrum(bsdf->extra->R * 0.25f * F * integral);
|
||||
}
|
||||
|
||||
/* Approximate components beyond TRT (starting TRRT) by summing up a geometric series. Attenuations
|
||||
|
@ -552,8 +561,9 @@ ccl_device Spectrum bsdf_hair_huang_eval_residual(KernelGlobals kg,
|
|||
sin_theta(wi), cos_theta(wi), sin_theta(wo), cos_theta(wo), 4.0f * bsdf->roughness);
|
||||
const float N = M_1_2PI_F;
|
||||
|
||||
return ((S_tt + S_trt) * sqr(inv_eta) / bsdf->extra->radius + S_trrt * M * N * M_2_PI_F) * res /
|
||||
3.0f;
|
||||
const float simpson_coeff = 2.0f / 3.0f * res;
|
||||
|
||||
return ((S_tt + S_trt) * sqr(inv_eta) + S_trrt * M * N * M_2_PI_F) * simpson_coeff;
|
||||
}
|
||||
|
||||
ccl_device int bsdf_hair_huang_sample(const KernelGlobals kg,
|
||||
|
@ -588,7 +598,9 @@ ccl_device int bsdf_hair_huang_sample(const KernelGlobals kg,
|
|||
const float b = bsdf->aspect_ratio;
|
||||
const bool is_circular = (b == 1.0f);
|
||||
|
||||
const float h_div_r = sample_h * 2.0f - 1.0f;
|
||||
/* Sample `h` for farfield model, as the computed intersection might have numerical issues. */
|
||||
const float h_div_r = is_nearfield(bsdf) ? bsdf->h / bsdf->extra->radius :
|
||||
(sample_h * 2.0f - 1.0f);
|
||||
const float gamma_mi = h_to_gamma(h_div_r, b, wi);
|
||||
|
||||
/* Macronormal. */
|
||||
|
@ -806,20 +818,62 @@ ccl_device Spectrum bsdf_hair_huang_eval(KernelGlobals kg,
|
|||
/* Early detection of `dot(wi, wmi) < 0`. */
|
||||
return zero_spectrum();
|
||||
}
|
||||
const float r = bsdf->extra->radius;
|
||||
const float b = bsdf->aspect_ratio;
|
||||
const float phi_i = (b == 1.0f) ? 0.0f : dir_phi(local_I);
|
||||
const float gamma_m_min = to_gamma(phi_i - half_span, b);
|
||||
float gamma_m_min = to_gamma(phi_i - half_span, b);
|
||||
float gamma_m_max = to_gamma(phi_i + half_span, b);
|
||||
if (gamma_m_max < gamma_m_min) {
|
||||
gamma_m_max += M_2PI_F;
|
||||
}
|
||||
|
||||
bsdf->extra->gamma_m_min = gamma_m_min + 1e-3f;
|
||||
bsdf->extra->gamma_m_max = gamma_m_max - 1e-3f;
|
||||
/* Prevent numerical issues at the boundary. */
|
||||
gamma_m_min += 1e-3f;
|
||||
gamma_m_max -= 1e-3f;
|
||||
|
||||
/* Length of the integral interval. */
|
||||
float dh = 2.0f * r;
|
||||
|
||||
if (is_nearfield(bsdf)) {
|
||||
/* Reduce the integration interval to the subset that's visible to the current pixel.
|
||||
* Inspired by [An Efficient and Practical Near and Far Field Fur Reflectance Model]
|
||||
* (https://sites.cs.ucsb.edu/~lingqi/publications/paper_fur2.pdf) by Ling-Qi Yan, Henrik Wann
|
||||
* Jensen and Ravi Ramamoorthi. */
|
||||
const float h_max = min(bsdf->h + bsdf->extra->pixel_coverage, r);
|
||||
const float h_min = max(bsdf->h - bsdf->extra->pixel_coverage, -r);
|
||||
|
||||
/* At the boundaries the hair might not cover the whole pixel. */
|
||||
dh = h_max - h_min;
|
||||
|
||||
float nearfield_gamma_min = h_to_gamma(h_max / r, bsdf->aspect_ratio, local_I);
|
||||
float nearfield_gamma_max = h_to_gamma(h_min / r, bsdf->aspect_ratio, local_I);
|
||||
|
||||
if (nearfield_gamma_max < nearfield_gamma_min) {
|
||||
nearfield_gamma_max += M_2PI_F;
|
||||
}
|
||||
|
||||
/* Wrap range to compute the intersection. */
|
||||
if ((gamma_m_max - nearfield_gamma_min) > M_2PI_F) {
|
||||
gamma_m_min -= M_2PI_F;
|
||||
gamma_m_max -= M_2PI_F;
|
||||
}
|
||||
else if ((nearfield_gamma_max - gamma_m_min) > M_2PI_F) {
|
||||
nearfield_gamma_min -= M_2PI_F;
|
||||
nearfield_gamma_max -= M_2PI_F;
|
||||
}
|
||||
|
||||
gamma_m_min = fmaxf(gamma_m_min, nearfield_gamma_min);
|
||||
gamma_m_max = fminf(gamma_m_max, nearfield_gamma_max);
|
||||
}
|
||||
|
||||
bsdf->extra->gamma_m_min = gamma_m_min;
|
||||
bsdf->extra->gamma_m_max = gamma_m_max;
|
||||
|
||||
const float projected_area = cos_theta(local_I) * dh;
|
||||
|
||||
return (bsdf_hair_huang_eval_r(kg, sc, local_I, local_O) +
|
||||
bsdf_hair_huang_eval_residual(kg, sc, local_I, local_O, sd->lcg_state)) /
|
||||
cos_theta(local_I);
|
||||
projected_area;
|
||||
}
|
||||
|
||||
/* Implements Filter Glossy by capping the effective roughness. */
|
||||
|
|
|
@ -988,6 +988,21 @@ ccl_device void osl_closure_hair_huang_setup(KernelGlobals kg,
|
|||
bsdf->extra->TT = closure->tt_lobe;
|
||||
bsdf->extra->TRT = closure->trt_lobe;
|
||||
|
||||
bsdf->extra->pixel_coverage = 1.0f;
|
||||
|
||||
/* For camera ray, check if the hair covers more than one pixel, in which case a nearfield model
|
||||
* is needed to prevent ribbon-like appearance. */
|
||||
if ((path_flag & PATH_RAY_CAMERA) && (sd->type & PRIMITIVE_CURVE)) {
|
||||
/* Interpolate radius between curve keys. */
|
||||
const KernelCurve kcurve = kernel_data_fetch(curves, sd->prim);
|
||||
const int k0 = kcurve.first_key + PRIMITIVE_UNPACK_SEGMENT(sd->type);
|
||||
const int k1 = k0 + 1;
|
||||
const float radius = mix(
|
||||
kernel_data_fetch(curve_keys, k0).w, kernel_data_fetch(curve_keys, k1).w, sd->u);
|
||||
|
||||
bsdf->extra->pixel_coverage = 0.5f * sd->dP / radius;
|
||||
}
|
||||
|
||||
sd->flag |= bsdf_hair_huang_setup(sd, bsdf, path_flag);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -790,6 +790,21 @@ ccl_device
|
|||
bsdf->extra->TT = fmaxf(0.0f, TT);
|
||||
bsdf->extra->TRT = fmaxf(0.0f, TRT);
|
||||
|
||||
bsdf->extra->pixel_coverage = 1.0f;
|
||||
|
||||
/* For camera ray, check if the hair covers more than one pixel, in which case a
|
||||
* nearfield model is needed to prevent ribbon-like appearance. */
|
||||
if ((path_flag & PATH_RAY_CAMERA) && (sd->type & PRIMITIVE_CURVE)) {
|
||||
/* Interpolate radius between curve keys. */
|
||||
const KernelCurve kcurve = kernel_data_fetch(curves, sd->prim);
|
||||
const int k0 = kcurve.first_key + PRIMITIVE_UNPACK_SEGMENT(sd->type);
|
||||
const int k1 = k0 + 1;
|
||||
const float radius = mix(
|
||||
kernel_data_fetch(curve_keys, k0).w, kernel_data_fetch(curve_keys, k1).w, sd->u);
|
||||
|
||||
bsdf->extra->pixel_coverage = 0.5f * sd->dP / radius;
|
||||
}
|
||||
|
||||
bsdf->aspect_ratio = stack_load_float_default(stack, shared_ofs1, data_node3.w);
|
||||
if (bsdf->aspect_ratio != 1.0f) {
|
||||
/* Align ellipse major axis with the curve normal direction. */
|
||||
|
|
|
@ -844,7 +844,7 @@ class PrincipledHairBsdfNode : public BsdfBaseNode {
|
|||
NODE_SOCKET_API(float, random)
|
||||
/* Selected coloring parametrization. */
|
||||
NODE_SOCKET_API(NodePrincipledHairParametrization, parametrization)
|
||||
/* Selected scattering model (near-/far-field). */
|
||||
/* Selected scattering model (chiang/huang). */
|
||||
NODE_SOCKET_API(NodePrincipledHairModel, model)
|
||||
|
||||
virtual int get_feature()
|
||||
|
|
|
@ -4120,9 +4120,9 @@ static const EnumPropertyItem node_principled_hair_model_items[] = {
|
|||
"HUANG",
|
||||
0,
|
||||
"Huang",
|
||||
"Far-field hair scattering model by Huang et al. 2022, suitable for viewing from a distance, "
|
||||
"supports elliptical cross-sections and has more precise highlight in forward scattering "
|
||||
"directions"},
|
||||
"Multi-scale hair scattering model by Huang et al. 2022, suitable for viewing both up close "
|
||||
"and from a distance, supports elliptical cross-sections and has more precise highlight in "
|
||||
"forward scattering directions"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 74d32aff48a05236adf61a24d8c1b5b4c7c55097
|
||||
Subproject commit 4f2e16db7ce3bdea79f63c50a9bd5a645f23037f
|
Loading…
Reference in New Issue