Blender V5.0
bsdf_principled_hair_huang.h
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5/* This code implements the paper [A Microfacet-based Hair Scattering
6 * Model](https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.14588) by Weizhen Huang, Matthias B.
7 * Hullin and Johannes Hanika. */
8
9#pragma once
10
11#include "kernel/types.h"
12
17#include "kernel/sample/lcg.h"
18
20
22 /* Optional modulation factors. */
23 float R, TT, TRT;
24
25 /* Local coordinate system. X is stored as `bsdf->N`. */
27
28 /* Incident direction in local coordinate system. */
30
31 /* Projected radius from the view direction. */
32 float radius;
33
34 /* Squared Eccentricity. */
35 float e2;
36
37 /* The projected width of half a pixel at `sd->P` in `h` space. */
39
40 /* Valid integration interval, pre-divided by the radius so the range is [-1, 1]. */
42};
43
46
47 /* Absorption coefficient. */
49
50 /* Microfacet distribution roughness. */
51 float roughness;
52
53 /* Cuticle tilt angle. */
54 float tilt;
55
56 /* Index of refraction. */
57 float eta;
58
59 /* The ratio of the minor axis to the major axis. */
61
62 /* Azimuthal offset. */
63 float h;
64
65 /* Extra closure for optional modulation factors and local coordinate system. */
67};
68
69static_assert(sizeof(ShaderClosure) >= sizeof(HuangHairBSDF), "HuangHairBSDF is too large!");
70static_assert(sizeof(ShaderClosure) >= sizeof(HuangHairExtra), "HuangHairExtra is too large!");
71
72/* -------------------------------------------------------------------- */
75
76/* TODO(weizhen): add a namespace `huanghair` for the below utility functions. */
77
78/* Returns `sin(theta)` of the given direction. */
80{
81 return w.y;
82}
83
84/* Returns `cos(theta)` of the given direction. */
86{
87 return safe_sqrtf(sqr(w.x) + sqr(w.z));
88}
89
90/* Returns `tan(theta)` of the given direction. */
92{
93 return sin_theta(w) / cos_theta(w);
94}
95
96/* Returns `sin(phi)` and `cos(phi)` of the given direction. */
98{
99 return w.x / cos_theta(w);
100}
101
102/* Check whether the hair cross-section is circular. */
104{
105 return b == 1.0f;
106}
107
109{
110 const float c = cos_theta(w);
111 return make_float2(w.x / c, w.z / c);
112}
113
114/* Extract the theta coordinate from the given direction.
115 * -pi < theta < pi */
117{
118 return atan2f(sin_theta(w), cos_theta(w));
119}
120
121/* Extract the phi coordinate from the given direction, assuming `phi(wi) == 0`.
122 * -pi < phi < pi */
124{
125 return atan2f(w.x, w.z);
126}
127
128/* Extract theta and phi coordinates from the given direction, assuming `phi(wi) == 0`.
129 * -pi/2 < theta < pi/2, -pi < phi < pi */
134
135/* Conversion between `gamma` and `phi`. Notations see Figure 5 in the paper. */
136ccl_device_inline float to_phi(const float gamma, const float b)
137{
138 if (is_circular(b)) {
139 return gamma;
140 }
141 float sin_gamma;
142 float cos_gamma;
143 fast_sincosf(gamma, &sin_gamma, &cos_gamma);
144 return atan2f(b * sin_gamma, cos_gamma);
145}
146
147ccl_device_inline float to_gamma(const float phi, const float b)
148{
149 if (is_circular(b)) {
150 return phi;
151 }
152 float sin_phi;
153 float cos_phi;
154 fast_sincosf(phi, &sin_phi, &cos_phi);
155 return atan2f(sin_phi, b * cos_phi);
156}
157
158/* Intersecting `wi` with the ellipse defined by `x = sin_gamma, y = b * cos_gamma` results in
159 * equation `h = -cos_phi_i * sin_gamma + b * sin_phi_i * cos_gamma`. */
160ccl_device_inline float phi_to_h(const float phi, const float b, const float3 wi)
161{
162 if (is_circular(b)) {
163 return -fast_sinf(phi);
164 }
165
166 float sin_gamma, cos_gamma;
167 fast_sincosf(to_gamma(phi, b), &sin_gamma, &cos_gamma);
168 const float2 sin_cos_phi_i = sincos_phi(wi);
169 return -sin_cos_phi_i.y * sin_gamma + b * sin_cos_phi_i.x * cos_gamma;
170}
171
172/* Solve for `gamma` in equation `h = -cos_phi_i * sin_gamma + b * sin_phi_i * cos_gamma`.
173 * Also, make use of `r = sqrt(sqr(cos_phi_i) + sqr(b * sin_phi_i))` to pre-map `h` to [-1, 1]. */
174ccl_device_inline float h_to_gamma(const float h_div_r, const float b, const float3 wi)
175{
176 return is_circular(b) ? -asinf(h_div_r) : atan2f(wi.z, -b * wi.x) - acosf(-h_div_r);
177}
178
179/* Jacobian |d_gamma/d_h|, used for changing variable in the integration. */
180ccl_device_inline float d_gamma_d_h(const float2 sincos_phi_i, const float gamma, const float b)
181{
182 if (is_circular(b)) {
183 return safe_divide(1.0f, fast_cosf(gamma));
184 }
185
186 float sin_gamma, cos_gamma;
187 fast_sincosf(gamma, &sin_gamma, &cos_gamma);
188 return safe_divide(1.0f, sincos_phi_i.y * cos_gamma + b * sincos_phi_i.x * sin_gamma);
189}
190
191/* Compute the coordinate on the ellipse, given `gamma` and the aspect ratio between the minor axis
192 * and the major axis. */
193ccl_device_inline float2 to_point(const float gamma, const float b)
194{
195 float sin_gamma;
196 float cos_gamma;
197 fast_sincosf(gamma, &sin_gamma, &cos_gamma);
198 return make_float2(sin_gamma, b * cos_gamma);
199}
200
201/* Compute the vector direction given by `theta` and `gamma`. */
202ccl_device_inline float3 sphg_dir(const float theta, const float gamma, const float b)
203{
204 float sin_theta;
205 float cos_theta;
206 float sin_gamma;
207 float cos_gamma;
208 float sin_phi;
209 float cos_phi;
210
212 fast_sincosf(gamma, &sin_gamma, &cos_gamma);
213
214 if (is_circular(b) || fabsf(cos_gamma) < 1e-6f) {
215 sin_phi = sin_gamma;
216 cos_phi = cos_gamma;
217 }
218 else {
219 const float tan_gamma = sin_gamma / cos_gamma;
220 const float tan_phi = b * tan_gamma;
221 cos_phi = signf(cos_gamma) * inversesqrtf(sqr(tan_phi) + 1.0f);
222 sin_phi = cos_phi * tan_phi;
223 }
224 return make_float3(sin_phi * cos_theta, sin_theta, cos_phi * cos_theta);
225}
226
227ccl_device_inline float arc_length(const float e2, const float gamma)
228{
229 return e2 == 0 ? 1.0f : sqrtf(1.0f - e2 * sqr(sinf(gamma)));
230}
231
233{
234 return bsdf->extra->radius > bsdf->extra->pixel_coverage;
235}
236
238
239#ifdef __HAIR__
240/* Set up the hair closure. */
241ccl_device int bsdf_hair_huang_setup(ccl_private ShaderData *sd,
243 const uint32_t path_flag)
244{
245 bsdf->type = CLOSURE_BSDF_HAIR_HUANG_ID;
246
247 bsdf->roughness = clamp(bsdf->roughness, 0.001f, 1.0f);
248
249 /* Negate to keep it consistent with principled hair BSDF. */
250 bsdf->tilt = -bsdf->tilt;
251
252 /* Compute local frame. The Y axis is aligned with the curve tangent; the X axis is perpendicular
253 * to the ray direction for circular cross-sections, or aligned with the major axis for
254 * elliptical cross-sections. */
255 bsdf->extra->Y = safe_normalize(sd->dPdu);
256 const float3 X = safe_normalize(cross(sd->dPdu, sd->wi));
257
258 /* h from -1..0..1 means the rays goes from grazing the hair, to hitting it at the center, to
259 * grazing the other edge. This is the cosine of the angle between `sd->N` and `X`. */
260 bsdf->h = ((sd->type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) ? -sd->v : -dot(X, sd->N);
261
262 kernel_assert(fabsf(bsdf->h) < 1.0f + 1e-4f);
264
265 if (bsdf->aspect_ratio != 1.0f && (sd->type & PRIMITIVE_CURVE)) {
266 /* Adjust `bsdf->N` to be orthogonal to `sd->dPdu`. */
267 bsdf->N = safe_normalize(cross(sd->dPdu, safe_normalize(cross(bsdf->N, sd->dPdu))));
268 /* Align local frame with the curve normal. */
269 if (bsdf->aspect_ratio > 1.0f) {
270 /* Switch major and minor axis. */
271 bsdf->aspect_ratio = 1.0f / bsdf->aspect_ratio;
272 const float3 minor_axis = safe_normalize(cross(sd->dPdu, bsdf->N));
273 bsdf->N = safe_normalize(cross(minor_axis, sd->dPdu));
274 }
275 }
276 else {
277 /* Align local frame with the ray direction so that `phi_i == 0`. */
278 bsdf->N = X;
279 }
280
281 /* Fill extra closure. */
282 if (is_zero(bsdf->N) || !isfinite_safe(bsdf->N)) {
283 /* Construct arbitrary local coordinate system. The implementation should ensure smooth
284 * transition along the hair shaft. */
285 make_orthonormals(bsdf->extra->Y, &bsdf->extra->Z, &bsdf->N);
286 }
287 else {
288 bsdf->extra->Z = safe_normalize(cross(bsdf->N, sd->dPdu));
289 }
290
291 const float3 I = to_local(sd->wi, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
292 bsdf->extra->wi = I;
293 bsdf->extra->e2 = 1.0f - sqr(bsdf->aspect_ratio);
294 bsdf->extra->radius = bsdf->extra->e2 == 0 ?
295 1.0f :
296 sqrtf(1.0f - bsdf->extra->e2 * sqr(I.x) / (sqr(I.x) + sqr(I.z)));
297
298 /* Treat as transparent material if intersection lies outside of the projected radius. */
299 if (fabsf(bsdf->h) >= bsdf->extra->radius) {
300 /* Remove allocated closures. */
301 sd->num_closure--;
302 sd->num_closure_left += 2;
303 /* Allocate transparent closure. */
304 bsdf_transparent_setup(sd, bsdf->weight, path_flag);
305 return 0;
306 }
307
309}
310
311#endif /* __HAIR__ */
312
313/* Albedo correction, treat as glass. `rough` has already applied square root. */
315 const float mu,
316 const float rough,
317 const float ior)
318{
319 const bool inv_table = (ior < 1.0f);
320 const int ofs = inv_table ? kernel_data.tables.ggx_glass_inv_E : kernel_data.tables.ggx_glass_E;
321 const float z = sqrtf(fabsf((ior - 1.0f) / (ior + 1.0f)));
322 return 1.0f / lookup_table_read_3D(kg, rough, mu, z, ofs, 16, 16, 16);
323}
324
325/* Sample microfacets from a tilted mesonormal. */
326ccl_device_inline float3 sample_wh(const float roughness,
327 const float3 wi,
328 const float3 wm,
329 const float2 rand)
330{
331 /* Coordinate transformation for microfacet sampling. */
332 float3 s;
333 float3 t;
334 make_orthonormals(wm, &s, &t);
335
336 const float3 wi_wm = to_local(wi, s, t, wm);
337 const float3 wh_wm = microfacet_ggx_sample_vndf(wi_wm, roughness, roughness, rand);
338 const float3 wh = to_global(wh_wm, s, t, wm);
339 return wh;
340}
341
342/* Check micronormal/mesonormal direct visibility from direction `v`. */
344{
345 return (dot(v, h) > 0.0f && dot(v, m) > 0.0f);
346}
347
348/* Check micronormal/mesonormal direct visibility from directions `wi` and `wo`. */
350 const float3 wo,
351 const float3 m,
352 const float3 h)
353{
354 return microfacet_visible(wi, m, h) && microfacet_visible(wo, m, h);
355}
356
357/* Combined shadowing-masking term divided by the shadowing-masking in the incoming direction. */
358ccl_device_inline float bsdf_Go(const float alpha2, const float cos_NI, const float cos_NO)
359{
360 const float lambdaI = bsdf_lambda<MicrofacetType::GGX>(alpha2, cos_NI);
361 const float lambdaO = bsdf_lambda<MicrofacetType::GGX>(alpha2, cos_NO);
362 return (1.0f + lambdaI) / (1.0f + lambdaI + lambdaO);
363}
364
366 const ccl_private ShaderClosure *sc,
367 const float3 wi,
368 const float3 wo)
369{
371
372 if (bsdf->extra->R <= 0.0f) {
373 return zero_float3();
374 }
375
376 /* Get minor axis, assuming major axis is 1. */
377 const float b = bsdf->aspect_ratio;
378
379 const float3 wh = normalize(wi + wo);
380
381 const float roughness = bsdf->roughness;
382 const float roughness2 = sqr(roughness);
383
384 const float2 sincos_phi_i = sincos_phi(wi);
385
386 /* Maximal sample resolution. */
387 float res = roughness * 0.7f;
388
389 const float h_range = bsdf->extra->h.length();
390
391 /* Number of intervals should be even. */
392 const size_t intervals = 2 * (size_t)ceilf(h_range / res * 0.5f);
393
394 /* Modified resolution based on numbers of intervals. */
395 res = h_range / float(intervals);
396
397 /* Integrate using Composite Simpson's 1/3 rule. */
398 float integral = 0.0f;
399 for (size_t i = 0; i <= intervals; i++) {
400 const float h = bsdf->extra->h.min + i * res;
401 const float gamma_m = h_to_gamma(h, b, wi);
402 const float3 wm = sphg_dir(bsdf->tilt, gamma_m, b);
403
404 if (microfacet_visible(wi, wo, make_float3(wm.x, 0.0f, wm.z), wh)) {
405 const float jacobian = d_gamma_d_h(sincos_phi_i, gamma_m, b);
406 const float weight = ((i == 0 || i == intervals) ? 0.5f : (i % 2 + 1)) * jacobian;
407 const float cos_mi = dot(wm, wi);
408 const float G = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi, dot(wm, wo));
409 integral += weight * bsdf_D<MicrofacetType::GGX>(roughness2, dot(wm, wh)) * G *
410 arc_length(bsdf->extra->e2, gamma_m) *
411 bsdf_hair_huang_energy_scale(kg, cos_mi, sqrtf(roughness), bsdf->eta);
412 }
413 }
414
415 /* Simpson coefficient */
416 integral *= (2.0f / 3.0f * res);
417
418 const float F = fresnel_dielectric_cos(dot(wi, wh), bsdf->eta);
419
420 return make_spectrum(bsdf->extra->R * 0.25f * F * integral);
421}
422
423/* Approximate components beyond TRT (starting TRRT) by summing up a geometric series. Attenuations
424 * are approximated from previous interactions. */
425ccl_device Spectrum bsdf_hair_huang_eval_trrt(const float T, const float R, const Spectrum A)
426{
427 /* `T` could be zero due to total internal reflection. Clamp to avoid numerical issues. */
428 const float T_avg = max(1.0f - R, 1e-5f);
429 const Spectrum TRRT_avg = T * sqr(R) * T_avg * A * A * A;
430 return TRRT_avg / (one_spectrum() - A * (1.0f - T_avg));
431}
432
433/* Evaluate components beyond R using numerical integration. TT and TRT are computed via combined
434 * Monte Carlo-Simpson integration; components beyond TRRT are integrated via Simpson's method. */
436 const ccl_private ShaderClosure *sc,
437 const float3 wi,
438 const float3 wo,
439 ccl_private uint *rng_quadrature)
440{
442
443 if (bsdf->extra->TT <= 0.0f && bsdf->extra->TRT <= 0.0f) {
444 return zero_spectrum();
445 }
446
447 /* Get minor axis, assuming major axis is 1. */
448 const float b = bsdf->aspect_ratio;
449
450 const Spectrum mu_a = bsdf->sigma;
451 const float eta = bsdf->eta;
452 const float inv_eta = 1.0f / eta;
453
454 const float roughness = bsdf->roughness;
455 const float roughness2 = sqr(roughness);
456 const float sqrt_roughness = sqrtf(roughness);
457
458 const float2 sincos_phi_i = sincos_phi(wi);
459
460 float res = roughness * 0.8f;
461 const float h_range = bsdf->extra->h.length();
462 const size_t intervals = 2 * (size_t)ceilf(h_range / res * 0.5f);
463 res = h_range / intervals;
464
465 Spectrum S_tt = zero_spectrum();
466 Spectrum S_trt = zero_spectrum();
467 Spectrum S_trrt = zero_spectrum();
468 for (size_t i = 0; i <= intervals; i++) {
469 const float h = bsdf->extra->h.min + i * res;
470 const float gamma_mi = h_to_gamma(h, b, wi);
471
472 const float3 wmi = sphg_dir(bsdf->tilt, gamma_mi, b);
473 const float3 wmi_ = sphg_dir(0.0f, gamma_mi, b);
474
475 /* Sample `wh1`. */
476 const float2 sample1 = make_float2(lcg_step_float(rng_quadrature),
477 lcg_step_float(rng_quadrature));
478
479 const float3 wh1 = sample_wh(roughness, wi, wmi, sample1);
480 const float cos_hi1 = dot(wi, wh1);
481 if (!(cos_hi1 > 0.0f)) {
482 continue;
483 }
484
485 const float cos_mi1 = dot(wi, wmi);
486 float cos_theta_t1;
487 const float T1 = 1.0f - fresnel_dielectric(cos_hi1, eta, &cos_theta_t1);
488 const float scale1 = bsdf_hair_huang_energy_scale(kg, cos_mi1, sqrt_roughness, eta);
489
490 /* Refraction at the first interface. */
491 const float3 wt = refract_angle(wi, wh1, cos_theta_t1, inv_eta);
492 const float phi_t = dir_phi(wt);
493 const float gamma_mt = 2.0f * to_phi(phi_t, b) - gamma_mi;
494 const float3 wmt = sphg_dir(-bsdf->tilt, gamma_mt, b);
495 const float3 wmt_ = sphg_dir(0.0f, gamma_mt, b);
496
497 const float cos_mo1 = dot(-wt, wmi);
498 const float cos_mi2 = dot(-wt, wmt);
499 const float G1o = bsdf_Go(roughness2, cos_mi1, cos_mo1);
500 if (!microfacet_visible(wi, -wt, wmi, wh1) || !microfacet_visible(wi, -wt, wmi_, wh1)) {
501 continue;
502 }
503
504 const float jacobian = d_gamma_d_h(sincos_phi_i, gamma_mi, b);
505 const float weight = ((i == 0 || i == intervals) ? 0.5f : (i % 2 + 1)) * jacobian;
506
507 const Spectrum A_t = exp(mu_a / cos_theta(wt) *
508 (is_circular(b) ?
509 2.0f * cosf(gamma_mi - phi_t) :
510 -len(to_point(gamma_mi, b) - to_point(gamma_mt + M_PI_F, b))));
511
512 const float scale2 = bsdf_hair_huang_energy_scale(kg, cos_mi2, sqrt_roughness, inv_eta);
513
514 /* TT */
515 if (bsdf->extra->TT > 0.0f) {
516 if (dot(wo, wt) >= inv_eta - 1e-5f) { /* Total internal reflection otherwise. */
517 float3 wh2 = -wt + inv_eta * wo;
518 const float rcp_norm_wh2 = 1.0f / len(wh2);
519 wh2 *= rcp_norm_wh2;
520 const float cos_mh2 = dot(wmt, wh2);
521 if (cos_mh2 >= 0.0f) { /* Microfacet visibility from macronormal. */
522 const float cos_hi2 = dot(-wt, wh2);
523 const float cos_ho2 = dot(-wo, wh2);
524 const float cos_mo2 = dot(-wo, wmt);
525
526 const float T2 = (1.0f - fresnel_dielectric_cos(cos_hi2, inv_eta)) * scale2;
527 const float D2 = bsdf_D<MicrofacetType::GGX>(roughness2, cos_mh2);
528 const float G2 = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi2, cos_mo2);
529
530 const Spectrum result = weight * T1 * scale1 * T2 * D2 * G1o * G2 * A_t / cos_mo1 *
531 cos_mi1 * cos_hi2 * cos_ho2 * sqr(rcp_norm_wh2);
532
533 if (isfinite_safe(result)) {
534 S_tt += bsdf->extra->TT * result * arc_length(bsdf->extra->e2, gamma_mt);
535 }
536 }
537 }
538 }
539
540 /* TRT and beyond. */
541 if (bsdf->extra->TRT > 0.0f) {
542 /* Sample `wh2`. */
543 const float2 sample2 = make_float2(lcg_step_float(rng_quadrature),
544 lcg_step_float(rng_quadrature));
545 const float3 wh2 = sample_wh(roughness, -wt, wmt, sample2);
546 const float cos_hi2 = dot(-wt, wh2);
547 if (!(cos_hi2 > 0.0f)) {
548 continue;
549 }
550 const float R2 = fresnel_dielectric_cos(cos_hi2, inv_eta);
551
552 const float3 wtr = -reflect(wt, wh2);
553 if (dot(-wtr, wo) < inv_eta - 1e-5f) {
554 /* Total internal reflection. */
555 S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
556 continue;
557 }
558
559 if (!microfacet_visible(-wt, -wtr, wmt, wh2) || !microfacet_visible(-wt, -wtr, wmt_, wh2)) {
560 continue;
561 }
562
563 const float phi_tr = dir_phi(wtr);
564 const float gamma_mtr = gamma_mi - 2.0f * (to_phi(phi_t, b) - to_phi(phi_tr, b)) + M_PI_F;
565 const float3 wmtr = sphg_dir(-bsdf->tilt, gamma_mtr, b);
566 const float3 wmtr_ = sphg_dir(0.0f, gamma_mtr, b);
567
568 float3 wh3 = wtr + inv_eta * wo;
569 const float rcp_norm_wh3 = 1.0f / len(wh3);
570 wh3 *= rcp_norm_wh3;
571 const float cos_mh3 = dot(wmtr, wh3);
572 if (cos_mh3 < 0.0f || !microfacet_visible(wtr, -wo, wmtr, wh3) ||
573 !microfacet_visible(wtr, -wo, wmtr_, wh3))
574 {
575 S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
576 continue;
577 }
578
579 const float cos_hi3 = dot(wh3, wtr);
580 const float cos_ho3 = dot(wh3, -wo);
581 const float cos_mi3 = dot(wmtr, wtr);
582
583 const float T3 = (1.0f - fresnel_dielectric_cos(cos_hi3, inv_eta)) *
584 bsdf_hair_huang_energy_scale(kg, cos_mi3, sqrt_roughness, inv_eta);
585 const float D3 = bsdf_D<MicrofacetType::GGX>(roughness2, cos_mh3);
586
587 const Spectrum A_tr = exp(mu_a / cos_theta(wtr) *
588 -(is_circular(b) ?
589 2.0f * fabsf(cosf(phi_tr - gamma_mt)) :
590 len(to_point(gamma_mtr, b) - to_point(gamma_mt, b))));
591
592 const float cos_mo2 = dot(wmt, -wtr);
593 const float G2o = bsdf_Go(roughness2, cos_mi2, cos_mo2);
594 const float G3 = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi3, dot(wmtr, -wo));
595
596 const Spectrum result = weight * T1 * scale1 * R2 * scale2 * T3 * D3 * G1o * G2o * G3 * A_t *
597 A_tr / (cos_mo1 * cos_mo2) * cos_mi1 * cos_mi2 * cos_hi3 * cos_ho3 *
598 sqr(rcp_norm_wh3);
599
600 if (isfinite_safe(result)) {
601 S_trt += bsdf->extra->TRT * result * arc_length(bsdf->extra->e2, gamma_mtr);
602 }
603
604 S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
605 }
606 }
607
608 /* TRRT+ terms, following the approach in [A practical and controllable hair and fur model for
609 * production path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang, Matt Jen-Yuan, et
610 * al. */
611 const float M = longitudinal_scattering(
612 sin_theta(wi), cos_theta(wi), sin_theta(wo), cos_theta(wo), 4.0f * bsdf->roughness);
613 const float N = M_1_2PI_F;
614
615 const float simpson_coeff = 2.0f / 3.0f * res;
616
617 return ((S_tt + S_trt) * sqr(inv_eta) + S_trrt * M * N * M_2_PI_F) * simpson_coeff;
618}
619
621 const ccl_private ShaderClosure *sc,
622 ccl_private ShaderData *sd,
623 const float3 rand,
624 ccl_private Spectrum *eval,
626 ccl_private float *pdf,
627 ccl_private float2 *sampled_roughness)
628{
630
631 const float roughness = bsdf->roughness;
632 *sampled_roughness = make_float2(roughness, roughness);
633
634 kernel_assert(fabsf(bsdf->h) < bsdf->extra->radius);
635
636 /* Generate samples. */
637 float sample_lobe = rand.x;
638 const float sample_h = rand.y;
639 const float2 sample_h1 = make_float2(rand.z, lcg_step_float(&sd->lcg_state));
640 const float2 sample_h2 = make_float2(lcg_step_float(&sd->lcg_state),
641 lcg_step_float(&sd->lcg_state));
642 const float2 sample_h3 = make_float2(lcg_step_float(&sd->lcg_state),
643 lcg_step_float(&sd->lcg_state));
644
645 /* Get `wi` in local coordinate. */
646 const float3 wi = bsdf->extra->wi;
647
648 /* Get minor axis, assuming major axis is 1. */
649 const float b = bsdf->aspect_ratio;
650
651 /* Sample `h` for farfield model, as the computed intersection might have numerical issues. */
652 const float h_div_r = is_nearfield(bsdf) ? bsdf->h / bsdf->extra->radius :
653 (sample_h * 2.0f - 1.0f);
654 const float gamma_mi = h_to_gamma(h_div_r, b, wi);
655
656 /* Macronormal. */
657 const float3 wmi_ = sphg_dir(0, gamma_mi, b);
658
659 /* Mesonormal. */
660 float st;
661 float ct;
662 fast_sincosf(bsdf->tilt, &st, &ct);
663 const float3 wmi = make_float3(wmi_.x * ct, st, wmi_.z * ct);
664 const float cos_mi1 = dot(wmi, wi);
665
666 if (cos_mi1 < 0.0f || dot(wmi_, wi) < 0.0f) {
667 /* Macro/mesonormal invisible. */
668 *pdf = 0.0f;
669 return LABEL_NONE;
670 }
671
672 /* Sample R lobe. */
673 const float roughness2 = sqr(roughness);
674 const float sqrt_roughness = sqrtf(roughness);
675 const float3 wh1 = sample_wh(roughness, wi, wmi, sample_h1);
676 const float3 wr = -reflect(wi, wh1);
677
678 /* Ensure that this is a valid sample. */
679 if (!microfacet_visible(wi, wmi_, wh1)) {
680 *pdf = 0.0f;
681 return LABEL_NONE;
682 }
683
684 float cos_theta_t1;
685 const float R1 = fresnel_dielectric(dot(wi, wh1), bsdf->eta, &cos_theta_t1);
686 const float scale1 = bsdf_hair_huang_energy_scale(kg, cos_mi1, sqrt_roughness, bsdf->eta);
687 const float R = bsdf->extra->R * R1 * scale1 * microfacet_visible(wr, wmi_, wh1) *
688 bsdf_Go(roughness2, cos_mi1, dot(wmi, wr));
689
690 /* Sample TT lobe. */
691 const float inv_eta = 1.0f / bsdf->eta;
692 const float3 wt = refract_angle(wi, wh1, cos_theta_t1, inv_eta);
693 const float phi_t = dir_phi(wt);
694
695 const float gamma_mt = 2.0f * to_phi(phi_t, b) - gamma_mi;
696 const float3 wmt = sphg_dir(-bsdf->tilt, gamma_mt, b);
697 const float3 wmt_ = sphg_dir(0.0f, gamma_mt, b);
698
699 const float3 wh2 = sample_wh(roughness, -wt, wmt, sample_h2);
700
701 const float3 wtr = -reflect(wt, wh2);
702
703 float3 wh3;
704 float3 wtt;
705 float3 wtrt;
706 float3 wmtr;
707 float3 wtrrt;
708 Spectrum TT = zero_spectrum();
709 Spectrum TRT = zero_spectrum();
710 Spectrum TRRT = zero_spectrum();
711 const float cos_mi2 = dot(-wt, wmt);
712
713 if (cos_mi2 > 0.0f && microfacet_visible(-wt, wmi_, wh1) && microfacet_visible(-wt, wmt_, wh2)) {
714 const Spectrum mu_a = bsdf->sigma;
715 const Spectrum A_t = exp(mu_a / cos_theta(wt) *
716 (is_circular(b) ?
717 2.0f * cosf(phi_t - gamma_mi) :
718 -len(to_point(gamma_mi, b) - to_point(gamma_mt + M_PI_F, b))));
719
720 float cos_theta_t2;
721 const float R2 = fresnel_dielectric(dot(-wt, wh2), inv_eta, &cos_theta_t2);
722 const float T1 = (1.0f - R1) * scale1 * bsdf_Go(roughness2, cos_mi1, dot(wmi, -wt));
723 const float T2 = 1.0f - R2;
724 const float scale2 = bsdf_hair_huang_energy_scale(kg, cos_mi2, sqrt_roughness, inv_eta);
725
726 wtt = refract_angle(-wt, wh2, cos_theta_t2, bsdf->eta);
727
728 if (dot(wmt, -wtt) > 0.0f && T2 > 0.0f && microfacet_visible(-wtt, wmt_, wh2)) {
729 TT = bsdf->extra->TT * T1 * A_t * T2 * scale2 * bsdf_Go(roughness2, cos_mi2, dot(wmt, -wtt));
730 }
731
732 /* Sample TRT lobe. */
733 const float phi_tr = dir_phi(wtr);
734 const float gamma_mtr = gamma_mi - 2.0f * (to_phi(phi_t, b) - to_phi(phi_tr, b)) + M_PI_F;
735 wmtr = sphg_dir(-bsdf->tilt, gamma_mtr, b);
736
737 wh3 = sample_wh(roughness, wtr, wmtr, sample_h3);
738
739 float cos_theta_t3;
740 const float R3 = fresnel_dielectric(dot(wtr, wh3), inv_eta, &cos_theta_t3);
741
742 wtrt = refract_angle(wtr, wh3, cos_theta_t3, bsdf->eta);
743
744 const float cos_mi3 = dot(wmtr, wtr);
745 if (cos_mi3 > 0.0f) {
746 const Spectrum A_tr = exp(mu_a / cos_theta(wtr) *
747 -(is_circular(b) ?
748 2.0f * fabsf(cosf(phi_tr - gamma_mt)) :
749 len(to_point(gamma_mt, b) - to_point(gamma_mtr, b))));
750
751 const Spectrum TR = T1 * R2 * scale2 * A_t * A_tr *
752 bsdf_hair_huang_energy_scale(kg, cos_mi3, sqrt_roughness, inv_eta) *
753 bsdf_Go(roughness2, cos_mi2, dot(wmt, -wtr));
754
755 const float T3 = 1.0f - R3;
756
757 if (T3 > 0.0f && microfacet_visible(wtr, -wtrt, make_float3(wmtr.x, 0.0f, wmtr.z), wh3)) {
758 TRT = bsdf->extra->TRT * TR * make_spectrum(T3) *
759 bsdf_Go(roughness2, cos_mi3, dot(wmtr, -wtrt));
760 }
761
762 /* Sample TRRT+ terms, following the approach in [A practical and controllable hair and fur
763 * model for production path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang,
764 * Matt Jen-Yuan, et al. */
765
766 /* Sample `theta_o`. */
767 const float rand_theta = max(lcg_step_float(&sd->lcg_state), 1e-5f);
768 const float fac = 1.0f +
769 4.0f * bsdf->roughness *
770 logf(rand_theta + (1.0f - rand_theta) * expf(-0.5f / bsdf->roughness));
771 const float sin_theta_o = -fac * sin_theta(wi) +
772 cos_from_sin(fac) *
773 cosf(M_2PI_F * lcg_step_float(&sd->lcg_state)) * cos_theta(wi);
774 const float cos_theta_o = cos_from_sin(sin_theta_o);
775
776 /* Sample `phi_o`. */
777 const float phi_o = M_2PI_F * lcg_step_float(&sd->lcg_state);
778 float sin_phi_o;
779 float cos_phi_o;
780 fast_sincosf(phi_o, &sin_phi_o, &cos_phi_o);
781
782 /* Compute outgoing direction. */
783 wtrrt = make_float3(sin_phi_o * cos_theta_o, sin_theta_o, cos_phi_o * cos_theta_o);
784
785 /* Compute residual term by summing up the geometric series `A * T + A^2 * R * T + ...`.
786 * Attenuations are approximated from previous interactions. */
787 const Spectrum A_avg = sqrt(A_t * A_tr);
788 /* `T` could be zero due to total internal reflection. Clamp to avoid numerical issues. */
789 const float T_avg = max(0.5f * (T2 + T3), 1e-5f);
790 const Spectrum A_res = A_avg * T_avg / (one_spectrum() - A_avg * (1.0f - T_avg));
791
792 TRRT = TR * R3 * A_res * bsdf_Go(roughness2, cos_mi3, dot(wmtr, -reflect(wtr, wh3)));
793 }
794 }
795
796 /* Select lobe based on energy. */
797 const float r = R;
798 const float tt = average(TT);
799 const float trt = average(TRT);
800 const float trrt = average(TRRT);
801 const float total_energy = r + tt + trt + trrt;
802
803 if (total_energy == 0.0f) {
804 *pdf = 0.0f;
805 return LABEL_NONE;
806 }
807
808 float3 local_O;
809
810 sample_lobe *= total_energy;
811 if (sample_lobe < r) {
812 local_O = wr;
813 *eval = make_spectrum(total_energy);
814 }
815 else if (sample_lobe < (r + tt)) {
816 local_O = wtt;
817 *eval = TT / tt * total_energy;
818 }
819 else if (sample_lobe < (r + tt + trt)) {
820 local_O = wtrt;
821 *eval = TRT / trt * total_energy;
822 }
823 else {
824 local_O = wtrrt;
825 *eval = TRRT / trrt * make_spectrum(total_energy);
826 }
827
828 /* Transform `wo` to global coordinate system. */
829 *wo = to_global(local_O, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
830
831 /* Ensure the same pdf is returned for BSDF and emitter sampling. The importance sampling pdf is
832 * already factored in the value so this value is only used for MIS. */
833 *pdf = 1.0f;
834
836}
837
839 ccl_private ShaderData *sd,
840 const ccl_private ShaderClosure *sc,
841 const float3 wo,
842 ccl_private float *pdf)
843{
845
846 kernel_assert(fabsf(bsdf->h) < bsdf->extra->radius);
847
848 /* Transform `wi`/`wo` from global coordinate system to local. */
849 const float3 local_I = bsdf->extra->wi;
850 const float3 local_O = to_local(wo, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
851
852 /* TODO: better estimation of the pdf */
853 *pdf = 1.0f;
854
855 /* Early detection of `dot(wo, wmo) < 0`. */
856 const float tan_tilt = tanf(bsdf->tilt);
857 if (tan_tilt * tan_theta(local_O) < -1.0f) {
858 return zero_spectrum();
859 }
860
861 /* Compute visible azimuthal range from the incoming direction. */
862 const float half_span = acosf(fmaxf(-tan_tilt * tan_theta(local_I), 0.0f));
863 if (isnan_safe(half_span)) {
864 /* Early detection of `dot(wi, wmi) < 0`. */
865 return zero_spectrum();
866 }
867 const float r = bsdf->extra->radius;
868 const float b = bsdf->aspect_ratio;
869 const float phi_i = is_circular(b) ? 0.0f : dir_phi(local_I);
870
871 Interval<float> h = {phi_to_h(phi_i + half_span, b, local_I),
872 phi_to_h(phi_i - half_span, b, local_I)};
873
874 /* Length of the integral interval. */
875 float dh = 2.0f;
876
877 if (is_nearfield(bsdf)) {
878 /* Reduce the integration interval to the subset that's visible to the current pixel.
879 * Inspired by [An Efficient and Practical Near and Far Field Fur Reflectance Model]
880 * (https://sites.cs.ucsb.edu/~lingqi/publications/paper_fur2.pdf) by Ling-Qi Yan, Henrik Wann
881 * Jensen and Ravi Ramamoorthi. */
882 const float half_pixel = bsdf->extra->pixel_coverage;
883 const Interval<float> nearfield_h = intervals_intersection(
884 Interval<float>{-r, r}, {bsdf->h - half_pixel, bsdf->h + half_pixel});
885
886 dh = nearfield_h.length() / r;
887 h = intervals_intersection(h, nearfield_h);
888 }
889
890 /* Pre-divide by radius for easier conversion to `gamma`. */
891 h /= r;
892
893 /* Clamp for numerical stability at the boundaries. */
894 h = intervals_intersection(h, {-0.999f, 0.999f});
895
896 if (h.is_empty()) {
897 /* No overlap between the valid range and the visible range. Can happen at grazing `theta`
898 * angles. */
899 return zero_spectrum();
900 }
901
902 bsdf->extra->h = h;
903
904 const float projected_area = cos_theta(local_I) * dh;
905
906 return (bsdf_hair_huang_eval_r(kg, sc, local_I, local_O) +
907 bsdf_hair_huang_eval_residual(kg, sc, local_I, local_O, &sd->lcg_state)) /
908 projected_area;
909}
910
911/* Implements Filter Glossy by capping the effective roughness. */
912ccl_device void bsdf_hair_huang_blur(ccl_private ShaderClosure *sc, const float roughness)
913{
915
916 bsdf->roughness = fmaxf(roughness, bsdf->roughness);
917}
918
919/* Hair Albedo. Computed by summing up geometric series, assuming circular cross-section and
920 * specular reflection. */
922 const ccl_private ShaderClosure *sc)
923{
925
926 const float3 wmi = make_float3(bsdf->h, 0.0f, cos_from_sin(bsdf->h));
927 float cos_t;
928 const float f = fresnel_dielectric(dot(wmi, bsdf->extra->wi), bsdf->eta, &cos_t);
929 const float3 wt = refract_angle(bsdf->extra->wi, wmi, cos_t, 1.0f / bsdf->eta);
930 const Spectrum A = exp(2.0f * bsdf->sigma * cos_t / (1.0f - sqr(wt.y)));
931
932 return safe_divide(A - 2.0f * f * A + f, one_spectrum() - f * A);
933}
934
MINLINE float signf(float f)
MINLINE float safe_sqrtf(float a)
MINLINE float safe_divide(float a, float b)
unsigned int uint
#define X
ATTR_WARN_UNUSED_RESULT const BMVert * v
#define TR
Definition boxpack_2d.cc:75
#define A
ccl_device_forceinline float3 microfacet_ggx_sample_vndf(const float3 wi, const float alpha_x, const float alpha_y, const float2 rand)
ccl_device_inline float bsdf_D(const float alpha2, const float cos_NH)
ccl_device_inline float bsdf_lambda(const float alpha2, const float cos_N)
ccl_device_inline float bsdf_G(const float alpha2, const float cos_N)
ccl_device_inline float longitudinal_scattering(float sin_theta_i, const float cos_theta_i, const float sin_theta_o, const float cos_theta_o, const float v)
ccl_device float2 sincos_phi(const float3 w)
ccl_device_inline float cos_theta(const float3 w)
ccl_device_inline float d_gamma_d_h(const float2 sincos_phi_i, const float gamma, const float b)
ccl_device_inline float arc_length(const float e2, const float gamma)
ccl_device Spectrum bsdf_hair_huang_albedo(const ccl_private ShaderData *, const ccl_private ShaderClosure *sc)
ccl_device Spectrum bsdf_hair_huang_eval_residual(KernelGlobals kg, const ccl_private ShaderClosure *sc, const float3 wi, const float3 wo, ccl_private uint *rng_quadrature)
ccl_device_inline bool is_nearfield(const ccl_private HuangHairBSDF *bsdf)
ccl_device_inline float bsdf_Go(const float alpha2, const float cos_NI, const float cos_NO)
ccl_device Spectrum bsdf_hair_huang_eval_r(KernelGlobals kg, const ccl_private ShaderClosure *sc, const float3 wi, const float3 wo)
ccl_device_inline float phi_to_h(const float phi, const float b, const float3 wi)
ccl_device_inline float3 sphg_dir(const float theta, const float gamma, const float b)
ccl_device_inline float2 to_point(const float gamma, const float b)
ccl_device_inline float to_gamma(const float phi, const float b)
ccl_device Spectrum bsdf_hair_huang_eval_trrt(const float T, const float R, const Spectrum A)
ccl_device_inline bool microfacet_visible(const float3 v, const float3 m, const float3 h)
ccl_device_inline float2 dir_sph(const float3 w)
ccl_device_inline float tan_theta(const float3 w)
ccl_device void bsdf_hair_huang_blur(ccl_private ShaderClosure *sc, const float roughness)
ccl_device_forceinline float bsdf_hair_huang_energy_scale(KernelGlobals kg, const float mu, const float rough, const float ior)
ccl_device float sin_phi(const float3 w)
ccl_device_inline float3 sample_wh(const float roughness, const float3 wi, const float3 wm, const float2 rand)
ccl_device_inline float dir_phi(const float3 w)
ccl_device int bsdf_hair_huang_sample(const KernelGlobals kg, const ccl_private ShaderClosure *sc, ccl_private ShaderData *sd, const float3 rand, ccl_private Spectrum *eval, ccl_private float3 *wo, ccl_private float *pdf, ccl_private float2 *sampled_roughness)
ccl_device Spectrum bsdf_hair_huang_eval(KernelGlobals kg, ccl_private ShaderData *sd, const ccl_private ShaderClosure *sc, const float3 wo, ccl_private float *pdf)
ccl_device_inline float sin_theta(const float3 w)
ccl_device_inline float to_phi(const float gamma, const float b)
ccl_device_inline bool is_circular(const float b)
ccl_device_inline float dir_theta(const float3 w)
ccl_device_inline float h_to_gamma(const float h_div_r, const float b, const float3 wi)
CCL_NAMESPACE_BEGIN ccl_device void bsdf_transparent_setup(ccl_private ShaderData *sd, const Spectrum weight, const uint32_t path_flag)
ccl_device float fresnel_dielectric_cos(const float cosi, const float eta)
Definition bsdf_util.h:110
ccl_device_forceinline float fresnel_dielectric(const float cos_theta_i, const float eta, ccl_private float *r_cos_theta_t)
Definition bsdf_util.h:93
ccl_device_inline float3 refract_angle(const float3 incident, const float3 normal, const float cos_theta_t, const float inv_eta)
Definition bsdf_util.h:102
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
nullptr float
dot(value.rgb, luminance_coefficients)") DEFINE_VALUE("REDUCE(lhs
ccl_device_inline T to_global(const float2 p, const T X, const T Y)
ccl_device_inline float2 to_local(const T p, const T X, const T Y)
#define kernel_assert(cond)
#define kernel_data
#define M_2_PI_F
#define ccl_device_forceinline
#define one_spectrum
#define zero_spectrum
#define make_spectrum(f)
#define ccl_private
const ThreadKernelGlobalsCPU * KernelGlobals
#define ccl_device_inline
#define M_1_2PI_F
#define logf(x)
#define expf(x)
#define CCL_NAMESPACE_END
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
#define asinf(x)
#define acosf(x)
#define exp
VecBase< T, D > reflect(VecOp< T, D >, VecOp< T, D >) RET
VecBase< float, D > normalize(VecOp< float, D >) RET
#define sqrt
VecBase< float, 3 > cross(VecOp< float, 3 >, VecOp< float, 3 >) RET
constexpr T clamp(T, U, U) RET
@ CLOSURE_BSDF_HAIR_HUANG_ID
@ SD_BSDF_HAS_EVAL
@ SD_BSDF_NEEDS_LCG
@ SD_BSDF
@ SD_BSDF_HAS_TRANSMISSION
@ PRIMITIVE_CURVE_RIBBON
@ PRIMITIVE_CURVE
@ LABEL_NONE
@ LABEL_GLOSSY
@ LABEL_REFLECT
ccl_device float lcg_step_float(T rng)
Definition lcg.h:22
ccl_device float lookup_table_read_3D(KernelGlobals kg, const float x, float y, float z, const int offset, const int xsize, const int ysize, const int zsize)
ccl_device_inline bool isnan_safe(const float f)
Definition math_base.h:342
ccl_device_inline float inversesqrtf(const float f)
Definition math_base.h:529
ccl_device_inline Interval< T > intervals_intersection(const ccl_private Interval< T > &first, const ccl_private Interval< T > &second)
Definition math_base.h:901
ccl_device_inline bool isfinite_safe(const float f)
Definition math_base.h:348
ccl_device_inline float cos_from_sin(const float s)
Definition math_base.h:614
ccl_device void fast_sincosf(float x, ccl_private float *sine, ccl_private float *cosine)
Definition math_fast.h:144
ccl_device float fast_sinf(float x)
Definition math_fast.h:83
ccl_device float fast_cosf(float x)
Definition math_fast.h:118
ccl_device_inline bool is_zero(const float2 a)
ccl_device_inline float2 safe_normalize(const float2 a)
CCL_NAMESPACE_BEGIN ccl_device_inline float3 zero_float3()
Definition math_float3.h:17
ccl_device_inline void make_orthonormals(const float3 N, ccl_private float3 *a, ccl_private float3 *b)
#define M
#define N
#define T
#define F
#define R
#define T2
Definition md5.cpp:21
#define T3
Definition md5.cpp:22
#define T1
Definition md5.cpp:20
#define G(x, y, z)
float average(point a)
Definition node_math.h:144
#define I
#define sqr
#define fabsf
#define sqrtf
#define ccl_device
#define M_2PI_F
#define fmaxf
#define sinf
#define make_float2
#define tanf
#define M_PI_F
#define cosf
#define ceilf
#define atan2f
ccl_private HuangHairExtra * extra
ccl_device_inline_method T length() const
Definition math_base.h:885
ccl_device_inline_method bool is_empty() const
Definition math_base.h:874
float x
float y
float z
Definition sky_math.h:136
float y
Definition sky_math.h:136
float x
Definition sky_math.h:136
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
float3 Spectrum
uint len