Blender V4.5
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_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. */
327 KernelGlobals kg, const float roughness, const float3 wi, const float3 wm, const float2 rand)
328{
329 /* Coordinate transformation for microfacet sampling. */
330 float3 s;
331 float3 t;
332 make_orthonormals(wm, &s, &t);
333
334 const float3 wi_wm = to_local(wi, s, t, wm);
335 const float3 wh_wm = microfacet_ggx_sample_vndf(wi_wm, roughness, roughness, rand);
336 const float3 wh = to_global(wh_wm, s, t, wm);
337 return wh;
338}
339
340/* Check micronormal/mesonormal direct visibility from direction `v`. */
342{
343 return (dot(v, h) > 0.0f && dot(v, m) > 0.0f);
344}
345
346/* Check micronormal/mesonormal direct visibility from directions `wi` and `wo`. */
348 const float3 wo,
349 const float3 m,
350 const float3 h)
351{
352 return microfacet_visible(wi, m, h) && microfacet_visible(wo, m, h);
353}
354
355/* Combined shadowing-masking term divided by the shadowing-masking in the incoming direction. */
356ccl_device_inline float bsdf_Go(const float alpha2, const float cos_NI, const float cos_NO)
357{
358 const float lambdaI = bsdf_lambda<MicrofacetType::GGX>(alpha2, cos_NI);
359 const float lambdaO = bsdf_lambda<MicrofacetType::GGX>(alpha2, cos_NO);
360 return (1.0f + lambdaI) / (1.0f + lambdaI + lambdaO);
361}
362
364 const ccl_private ShaderClosure *sc,
365 const float3 wi,
366 const float3 wo)
367{
369
370 if (bsdf->extra->R <= 0.0f) {
371 return zero_float3();
372 }
373
374 /* Get minor axis, assuming major axis is 1. */
375 const float b = bsdf->aspect_ratio;
376
377 const float3 wh = normalize(wi + wo);
378
379 const float roughness = bsdf->roughness;
380 const float roughness2 = sqr(roughness);
381
382 const float2 sincos_phi_i = sincos_phi(wi);
383
384 /* Maximal sample resolution. */
385 float res = roughness * 0.7f;
386
387 const float h_range = bsdf->extra->h.length();
388
389 /* Number of intervals should be even. */
390 const size_t intervals = 2 * (size_t)ceilf(h_range / res * 0.5f);
391
392 /* Modified resolution based on numbers of intervals. */
393 res = h_range / float(intervals);
394
395 /* Integrate using Composite Simpson's 1/3 rule. */
396 float integral = 0.0f;
397 for (size_t i = 0; i <= intervals; i++) {
398 const float h = bsdf->extra->h.min + i * res;
399 const float gamma_m = h_to_gamma(h, b, wi);
400 const float3 wm = sphg_dir(bsdf->tilt, gamma_m, b);
401
402 if (microfacet_visible(wi, wo, make_float3(wm.x, 0.0f, wm.z), wh)) {
403 const float jacobian = d_gamma_d_h(sincos_phi_i, gamma_m, b);
404 const float weight = ((i == 0 || i == intervals) ? 0.5f : (i % 2 + 1)) * jacobian;
405 const float cos_mi = dot(wm, wi);
406 const float G = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi, dot(wm, wo));
407 integral += weight * bsdf_D<MicrofacetType::GGX>(roughness2, dot(wm, wh)) * G *
408 arc_length(bsdf->extra->e2, gamma_m) *
409 bsdf_hair_huang_energy_scale(kg, cos_mi, sqrtf(roughness), bsdf->eta);
410 }
411 }
412
413 /* Simpson coefficient */
414 integral *= (2.0f / 3.0f * res);
415
416 const float F = fresnel_dielectric_cos(dot(wi, wh), bsdf->eta);
417
418 return make_spectrum(bsdf->extra->R * 0.25f * F * integral);
419}
420
421/* Approximate components beyond TRT (starting TRRT) by summing up a geometric series. Attenuations
422 * are approximated from previous interactions. */
423ccl_device Spectrum bsdf_hair_huang_eval_trrt(const float T, const float R, const Spectrum A)
424{
425 /* `T` could be zero due to total internal reflection. Clamp to avoid numerical issues. */
426 const float T_avg = max(1.0f - R, 1e-5f);
427 const Spectrum TRRT_avg = T * sqr(R) * T_avg * A * A * A;
428 return TRRT_avg / (one_spectrum() - A * (1.0f - T_avg));
429}
430
431/* Evaluate components beyond R using numerical integration. TT and TRT are computed via combined
432 * Monte Carlo-Simpson integration; components beyond TRRT are integrated via Simpson's method. */
434 const ccl_private ShaderClosure *sc,
435 const float3 wi,
436 const float3 wo,
437 ccl_private uint *rng_quadrature)
438{
440
441 if (bsdf->extra->TT <= 0.0f && bsdf->extra->TRT <= 0.0f) {
442 return zero_spectrum();
443 }
444
445 /* Get minor axis, assuming major axis is 1. */
446 const float b = bsdf->aspect_ratio;
447
448 const Spectrum mu_a = bsdf->sigma;
449 const float eta = bsdf->eta;
450 const float inv_eta = 1.0f / eta;
451
452 const float roughness = bsdf->roughness;
453 const float roughness2 = sqr(roughness);
454 const float sqrt_roughness = sqrtf(roughness);
455
456 const float2 sincos_phi_i = sincos_phi(wi);
457
458 float res = roughness * 0.8f;
459 const float h_range = bsdf->extra->h.length();
460 const size_t intervals = 2 * (size_t)ceilf(h_range / res * 0.5f);
461 res = h_range / intervals;
462
463 Spectrum S_tt = zero_spectrum();
464 Spectrum S_trt = zero_spectrum();
465 Spectrum S_trrt = zero_spectrum();
466 for (size_t i = 0; i <= intervals; i++) {
467 const float h = bsdf->extra->h.min + i * res;
468 const float gamma_mi = h_to_gamma(h, b, wi);
469
470 const float3 wmi = sphg_dir(bsdf->tilt, gamma_mi, b);
471 const float3 wmi_ = sphg_dir(0.0f, gamma_mi, b);
472
473 /* Sample `wh1`. */
474 const float2 sample1 = make_float2(lcg_step_float(rng_quadrature),
475 lcg_step_float(rng_quadrature));
476
477 const float3 wh1 = sample_wh(kg, roughness, wi, wmi, sample1);
478 const float cos_hi1 = dot(wi, wh1);
479 if (!(cos_hi1 > 0.0f)) {
480 continue;
481 }
482
483 const float cos_mi1 = dot(wi, wmi);
484 float cos_theta_t1;
485 const float T1 = 1.0f - fresnel_dielectric(cos_hi1, eta, &cos_theta_t1);
486 const float scale1 = bsdf_hair_huang_energy_scale(kg, cos_mi1, sqrt_roughness, eta);
487
488 /* Refraction at the first interface. */
489 const float3 wt = refract_angle(wi, wh1, cos_theta_t1, inv_eta);
490 const float phi_t = dir_phi(wt);
491 const float gamma_mt = 2.0f * to_phi(phi_t, b) - gamma_mi;
492 const float3 wmt = sphg_dir(-bsdf->tilt, gamma_mt, b);
493 const float3 wmt_ = sphg_dir(0.0f, gamma_mt, b);
494
495 const float cos_mo1 = dot(-wt, wmi);
496 const float cos_mi2 = dot(-wt, wmt);
497 const float G1o = bsdf_Go(roughness2, cos_mi1, cos_mo1);
498 if (!microfacet_visible(wi, -wt, wmi, wh1) || !microfacet_visible(wi, -wt, wmi_, wh1)) {
499 continue;
500 }
501
502 const float jacobian = d_gamma_d_h(sincos_phi_i, gamma_mi, b);
503 const float weight = ((i == 0 || i == intervals) ? 0.5f : (i % 2 + 1)) * jacobian;
504
505 const Spectrum A_t = exp(mu_a / cos_theta(wt) *
506 (is_circular(b) ?
507 2.0f * cosf(gamma_mi - phi_t) :
508 -len(to_point(gamma_mi, b) - to_point(gamma_mt + M_PI_F, b))));
509
510 const float scale2 = bsdf_hair_huang_energy_scale(kg, cos_mi2, sqrt_roughness, inv_eta);
511
512 /* TT */
513 if (bsdf->extra->TT > 0.0f) {
514 if (dot(wo, wt) >= inv_eta - 1e-5f) { /* Total internal reflection otherwise. */
515 float3 wh2 = -wt + inv_eta * wo;
516 const float rcp_norm_wh2 = 1.0f / len(wh2);
517 wh2 *= rcp_norm_wh2;
518 const float cos_mh2 = dot(wmt, wh2);
519 if (cos_mh2 >= 0.0f) { /* Microfacet visibility from macronormal. */
520 const float cos_hi2 = dot(-wt, wh2);
521 const float cos_ho2 = dot(-wo, wh2);
522 const float cos_mo2 = dot(-wo, wmt);
523
524 const float T2 = (1.0f - fresnel_dielectric_cos(cos_hi2, inv_eta)) * scale2;
525 const float D2 = bsdf_D<MicrofacetType::GGX>(roughness2, cos_mh2);
526 const float G2 = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi2, cos_mo2);
527
528 const Spectrum result = weight * T1 * scale1 * T2 * D2 * G1o * G2 * A_t / cos_mo1 *
529 cos_mi1 * cos_hi2 * cos_ho2 * sqr(rcp_norm_wh2);
530
531 if (isfinite_safe(result)) {
532 S_tt += bsdf->extra->TT * result * arc_length(bsdf->extra->e2, gamma_mt);
533 }
534 }
535 }
536 }
537
538 /* TRT and beyond. */
539 if (bsdf->extra->TRT > 0.0f) {
540 /* Sample `wh2`. */
541 const float2 sample2 = make_float2(lcg_step_float(rng_quadrature),
542 lcg_step_float(rng_quadrature));
543 const float3 wh2 = sample_wh(kg, roughness, -wt, wmt, sample2);
544 const float cos_hi2 = dot(-wt, wh2);
545 if (!(cos_hi2 > 0.0f)) {
546 continue;
547 }
548 const float R2 = fresnel_dielectric_cos(cos_hi2, inv_eta);
549
550 const float3 wtr = -reflect(wt, wh2);
551 if (dot(-wtr, wo) < inv_eta - 1e-5f) {
552 /* Total internal reflection. */
553 S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
554 continue;
555 }
556
557 if (!microfacet_visible(-wt, -wtr, wmt, wh2) || !microfacet_visible(-wt, -wtr, wmt_, wh2)) {
558 continue;
559 }
560
561 const float phi_tr = dir_phi(wtr);
562 const float gamma_mtr = gamma_mi - 2.0f * (to_phi(phi_t, b) - to_phi(phi_tr, b)) + M_PI_F;
563 const float3 wmtr = sphg_dir(-bsdf->tilt, gamma_mtr, b);
564 const float3 wmtr_ = sphg_dir(0.0f, gamma_mtr, b);
565
566 float3 wh3 = wtr + inv_eta * wo;
567 const float rcp_norm_wh3 = 1.0f / len(wh3);
568 wh3 *= rcp_norm_wh3;
569 const float cos_mh3 = dot(wmtr, wh3);
570 if (cos_mh3 < 0.0f || !microfacet_visible(wtr, -wo, wmtr, wh3) ||
571 !microfacet_visible(wtr, -wo, wmtr_, wh3))
572 {
573 S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
574 continue;
575 }
576
577 const float cos_hi3 = dot(wh3, wtr);
578 const float cos_ho3 = dot(wh3, -wo);
579 const float cos_mi3 = dot(wmtr, wtr);
580
581 const float T3 = (1.0f - fresnel_dielectric_cos(cos_hi3, inv_eta)) *
582 bsdf_hair_huang_energy_scale(kg, cos_mi3, sqrt_roughness, inv_eta);
583 const float D3 = bsdf_D<MicrofacetType::GGX>(roughness2, cos_mh3);
584
585 const Spectrum A_tr = exp(mu_a / cos_theta(wtr) *
586 -(is_circular(b) ?
587 2.0f * fabsf(cosf(phi_tr - gamma_mt)) :
588 len(to_point(gamma_mtr, b) - to_point(gamma_mt, b))));
589
590 const float cos_mo2 = dot(wmt, -wtr);
591 const float G2o = bsdf_Go(roughness2, cos_mi2, cos_mo2);
592 const float G3 = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi3, dot(wmtr, -wo));
593
594 const Spectrum result = weight * T1 * scale1 * R2 * scale2 * T3 * D3 * G1o * G2o * G3 * A_t *
595 A_tr / (cos_mo1 * cos_mo2) * cos_mi1 * cos_mi2 * cos_hi3 * cos_ho3 *
596 sqr(rcp_norm_wh3);
597
598 if (isfinite_safe(result)) {
599 S_trt += bsdf->extra->TRT * result * arc_length(bsdf->extra->e2, gamma_mtr);
600 }
601
602 S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
603 }
604 }
605
606 /* TRRT+ terms, following the approach in [A practical and controllable hair and fur model for
607 * production path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang, Matt Jen-Yuan, et
608 * al. */
609 const float M = longitudinal_scattering(
610 sin_theta(wi), cos_theta(wi), sin_theta(wo), cos_theta(wo), 4.0f * bsdf->roughness);
611 const float N = M_1_2PI_F;
612
613 const float simpson_coeff = 2.0f / 3.0f * res;
614
615 return ((S_tt + S_trt) * sqr(inv_eta) + S_trrt * M * N * M_2_PI_F) * simpson_coeff;
616}
617
619 const ccl_private ShaderClosure *sc,
620 ccl_private ShaderData *sd,
621 const float3 rand,
622 ccl_private Spectrum *eval,
624 ccl_private float *pdf,
625 ccl_private float2 *sampled_roughness)
626{
628
629 const float roughness = bsdf->roughness;
630 *sampled_roughness = make_float2(roughness, roughness);
631
632 kernel_assert(fabsf(bsdf->h) < bsdf->extra->radius);
633
634 /* Generate samples. */
635 float sample_lobe = rand.x;
636 const float sample_h = rand.y;
637 const float2 sample_h1 = make_float2(rand.z, lcg_step_float(&sd->lcg_state));
638 const float2 sample_h2 = make_float2(lcg_step_float(&sd->lcg_state),
639 lcg_step_float(&sd->lcg_state));
640 const float2 sample_h3 = make_float2(lcg_step_float(&sd->lcg_state),
641 lcg_step_float(&sd->lcg_state));
642
643 /* Get `wi` in local coordinate. */
644 const float3 wi = bsdf->extra->wi;
645
646 /* Get minor axis, assuming major axis is 1. */
647 const float b = bsdf->aspect_ratio;
648
649 /* Sample `h` for farfield model, as the computed intersection might have numerical issues. */
650 const float h_div_r = is_nearfield(bsdf) ? bsdf->h / bsdf->extra->radius :
651 (sample_h * 2.0f - 1.0f);
652 const float gamma_mi = h_to_gamma(h_div_r, b, wi);
653
654 /* Macronormal. */
655 const float3 wmi_ = sphg_dir(0, gamma_mi, b);
656
657 /* Mesonormal. */
658 float st;
659 float ct;
660 fast_sincosf(bsdf->tilt, &st, &ct);
661 const float3 wmi = make_float3(wmi_.x * ct, st, wmi_.z * ct);
662 const float cos_mi1 = dot(wmi, wi);
663
664 if (cos_mi1 < 0.0f || dot(wmi_, wi) < 0.0f) {
665 /* Macro/mesonormal invisible. */
666 *pdf = 0.0f;
667 return LABEL_NONE;
668 }
669
670 /* Sample R lobe. */
671 const float roughness2 = sqr(roughness);
672 const float sqrt_roughness = sqrtf(roughness);
673 const float3 wh1 = sample_wh(kg, roughness, wi, wmi, sample_h1);
674 const float3 wr = -reflect(wi, wh1);
675
676 /* Ensure that this is a valid sample. */
677 if (!microfacet_visible(wi, wmi_, wh1)) {
678 *pdf = 0.0f;
679 return LABEL_NONE;
680 }
681
682 float cos_theta_t1;
683 const float R1 = fresnel_dielectric(dot(wi, wh1), bsdf->eta, &cos_theta_t1);
684 const float scale1 = bsdf_hair_huang_energy_scale(kg, cos_mi1, sqrt_roughness, bsdf->eta);
685 const float R = bsdf->extra->R * R1 * scale1 * microfacet_visible(wr, wmi_, wh1) *
686 bsdf_Go(roughness2, cos_mi1, dot(wmi, wr));
687
688 /* Sample TT lobe. */
689 const float inv_eta = 1.0f / bsdf->eta;
690 const float3 wt = refract_angle(wi, wh1, cos_theta_t1, inv_eta);
691 const float phi_t = dir_phi(wt);
692
693 const float gamma_mt = 2.0f * to_phi(phi_t, b) - gamma_mi;
694 const float3 wmt = sphg_dir(-bsdf->tilt, gamma_mt, b);
695 const float3 wmt_ = sphg_dir(0.0f, gamma_mt, b);
696
697 const float3 wh2 = sample_wh(kg, roughness, -wt, wmt, sample_h2);
698
699 const float3 wtr = -reflect(wt, wh2);
700
701 float3 wh3;
702 float3 wtt;
703 float3 wtrt;
704 float3 wmtr;
705 float3 wtrrt;
706 Spectrum TT = zero_spectrum();
707 Spectrum TRT = zero_spectrum();
708 Spectrum TRRT = zero_spectrum();
709 const float cos_mi2 = dot(-wt, wmt);
710
711 if (cos_mi2 > 0.0f && microfacet_visible(-wt, wmi_, wh1) && microfacet_visible(-wt, wmt_, wh2)) {
712 const Spectrum mu_a = bsdf->sigma;
713 const Spectrum A_t = exp(mu_a / cos_theta(wt) *
714 (is_circular(b) ?
715 2.0f * cosf(phi_t - gamma_mi) :
716 -len(to_point(gamma_mi, b) - to_point(gamma_mt + M_PI_F, b))));
717
718 float cos_theta_t2;
719 const float R2 = fresnel_dielectric(dot(-wt, wh2), inv_eta, &cos_theta_t2);
720 const float T1 = (1.0f - R1) * scale1 * bsdf_Go(roughness2, cos_mi1, dot(wmi, -wt));
721 const float T2 = 1.0f - R2;
722 const float scale2 = bsdf_hair_huang_energy_scale(kg, cos_mi2, sqrt_roughness, inv_eta);
723
724 wtt = refract_angle(-wt, wh2, cos_theta_t2, bsdf->eta);
725
726 if (dot(wmt, -wtt) > 0.0f && T2 > 0.0f && microfacet_visible(-wtt, wmt_, wh2)) {
727 TT = bsdf->extra->TT * T1 * A_t * T2 * scale2 * bsdf_Go(roughness2, cos_mi2, dot(wmt, -wtt));
728 }
729
730 /* Sample TRT lobe. */
731 const float phi_tr = dir_phi(wtr);
732 const float gamma_mtr = gamma_mi - 2.0f * (to_phi(phi_t, b) - to_phi(phi_tr, b)) + M_PI_F;
733 wmtr = sphg_dir(-bsdf->tilt, gamma_mtr, b);
734
735 wh3 = sample_wh(kg, roughness, wtr, wmtr, sample_h3);
736
737 float cos_theta_t3;
738 const float R3 = fresnel_dielectric(dot(wtr, wh3), inv_eta, &cos_theta_t3);
739
740 wtrt = refract_angle(wtr, wh3, cos_theta_t3, bsdf->eta);
741
742 const float cos_mi3 = dot(wmtr, wtr);
743 if (cos_mi3 > 0.0f) {
744 const Spectrum A_tr = exp(mu_a / cos_theta(wtr) *
745 -(is_circular(b) ?
746 2.0f * fabsf(cosf(phi_tr - gamma_mt)) :
747 len(to_point(gamma_mt, b) - to_point(gamma_mtr, b))));
748
749 const Spectrum TR = T1 * R2 * scale2 * A_t * A_tr *
750 bsdf_hair_huang_energy_scale(kg, cos_mi3, sqrt_roughness, inv_eta) *
751 bsdf_Go(roughness2, cos_mi2, dot(wmt, -wtr));
752
753 const float T3 = 1.0f - R3;
754
755 if (T3 > 0.0f && microfacet_visible(wtr, -wtrt, make_float3(wmtr.x, 0.0f, wmtr.z), wh3)) {
756 TRT = bsdf->extra->TRT * TR * make_spectrum(T3) *
757 bsdf_Go(roughness2, cos_mi3, dot(wmtr, -wtrt));
758 }
759
760 /* Sample TRRT+ terms, following the approach in [A practical and controllable hair and fur
761 * model for production path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang,
762 * Matt Jen-Yuan, et al. */
763
764 /* Sample `theta_o`. */
765 const float rand_theta = max(lcg_step_float(&sd->lcg_state), 1e-5f);
766 const float fac = 1.0f +
767 4.0f * bsdf->roughness *
768 logf(rand_theta + (1.0f - rand_theta) * expf(-0.5f / bsdf->roughness));
769 const float sin_theta_o = -fac * sin_theta(wi) +
770 cos_from_sin(fac) *
771 cosf(M_2PI_F * lcg_step_float(&sd->lcg_state)) * cos_theta(wi);
772 const float cos_theta_o = cos_from_sin(sin_theta_o);
773
774 /* Sample `phi_o`. */
775 const float phi_o = M_2PI_F * lcg_step_float(&sd->lcg_state);
776 float sin_phi_o;
777 float cos_phi_o;
778 fast_sincosf(phi_o, &sin_phi_o, &cos_phi_o);
779
780 /* Compute outgoing direction. */
781 wtrrt = make_float3(sin_phi_o * cos_theta_o, sin_theta_o, cos_phi_o * cos_theta_o);
782
783 /* Compute residual term by summing up the geometric series `A * T + A^2 * R * T + ...`.
784 * Attenuations are approximated from previous interactions. */
785 const Spectrum A_avg = sqrt(A_t * A_tr);
786 /* `T` could be zero due to total internal reflection. Clamp to avoid numerical issues. */
787 const float T_avg = max(0.5f * (T2 + T3), 1e-5f);
788 const Spectrum A_res = A_avg * T_avg / (one_spectrum() - A_avg * (1.0f - T_avg));
789
790 TRRT = TR * R3 * A_res * bsdf_Go(roughness2, cos_mi3, dot(wmtr, -reflect(wtr, wh3)));
791 }
792 }
793
794 /* Select lobe based on energy. */
795 const float r = R;
796 const float tt = average(TT);
797 const float trt = average(TRT);
798 const float trrt = average(TRRT);
799 const float total_energy = r + tt + trt + trrt;
800
801 if (total_energy == 0.0f) {
802 *pdf = 0.0f;
803 return LABEL_NONE;
804 }
805
806 float3 local_O;
807
808 sample_lobe *= total_energy;
809 if (sample_lobe < r) {
810 local_O = wr;
811 *eval = make_spectrum(total_energy);
812 }
813 else if (sample_lobe < (r + tt)) {
814 local_O = wtt;
815 *eval = TT / tt * total_energy;
816 }
817 else if (sample_lobe < (r + tt + trt)) {
818 local_O = wtrt;
819 *eval = TRT / trt * total_energy;
820 }
821 else {
822 local_O = wtrrt;
823 *eval = TRRT / trrt * make_spectrum(total_energy);
824 }
825
826 /* Transform `wo` to global coordinate system. */
827 *wo = to_global(local_O, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
828
829 /* Ensure the same pdf is returned for BSDF and emitter sampling. The importance sampling pdf is
830 * already factored in the value so this value is only used for MIS. */
831 *pdf = 1.0f;
832
834}
835
837 ccl_private ShaderData *sd,
838 const ccl_private ShaderClosure *sc,
839 const float3 wo,
840 ccl_private float *pdf)
841{
843
844 kernel_assert(fabsf(bsdf->h) < bsdf->extra->radius);
845
846 /* Transform `wi`/`wo` from global coordinate system to local. */
847 const float3 local_I = bsdf->extra->wi;
848 const float3 local_O = to_local(wo, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
849
850 /* TODO: better estimation of the pdf */
851 *pdf = 1.0f;
852
853 /* Early detection of `dot(wo, wmo) < 0`. */
854 const float tan_tilt = tanf(bsdf->tilt);
855 if (tan_tilt * tan_theta(local_O) < -1.0f) {
856 return zero_spectrum();
857 }
858
859 /* Compute visible azimuthal range from the incoming direction. */
860 const float half_span = acosf(fmaxf(-tan_tilt * tan_theta(local_I), 0.0f));
861 if (isnan_safe(half_span)) {
862 /* Early detection of `dot(wi, wmi) < 0`. */
863 return zero_spectrum();
864 }
865 const float r = bsdf->extra->radius;
866 const float b = bsdf->aspect_ratio;
867 const float phi_i = is_circular(b) ? 0.0f : dir_phi(local_I);
868
869 Interval<float> h = {phi_to_h(phi_i + half_span, b, local_I),
870 phi_to_h(phi_i - half_span, b, local_I)};
871
872 /* Length of the integral interval. */
873 float dh = 2.0f;
874
875 if (is_nearfield(bsdf)) {
876 /* Reduce the integration interval to the subset that's visible to the current pixel.
877 * Inspired by [An Efficient and Practical Near and Far Field Fur Reflectance Model]
878 * (https://sites.cs.ucsb.edu/~lingqi/publications/paper_fur2.pdf) by Ling-Qi Yan, Henrik Wann
879 * Jensen and Ravi Ramamoorthi. */
880 const float half_pixel = bsdf->extra->pixel_coverage;
881 const Interval<float> nearfield_h = intervals_intersection(
882 Interval<float>{-r, r}, {bsdf->h - half_pixel, bsdf->h + half_pixel});
883
884 dh = nearfield_h.length() / r;
885 h = intervals_intersection(h, nearfield_h);
886 }
887
888 /* Pre-divide by radius for easier conversion to `gamma`. */
889 h /= r;
890
891 /* Clamp for numerical stability at the boundaries. */
892 h = intervals_intersection(h, {-0.999f, 0.999f});
893
894 if (h.is_empty()) {
895 /* No overlap between the valid range and the visible range. Can happen at grazing `theta`
896 * angles. */
897 return zero_spectrum();
898 }
899
900 bsdf->extra->h = h;
901
902 const float projected_area = cos_theta(local_I) * dh;
903
904 return (bsdf_hair_huang_eval_r(kg, sc, local_I, local_O) +
905 bsdf_hair_huang_eval_residual(kg, sc, local_I, local_O, &sd->lcg_state)) /
906 projected_area;
907}
908
909/* Implements Filter Glossy by capping the effective roughness. */
910ccl_device void bsdf_hair_huang_blur(ccl_private ShaderClosure *sc, const float roughness)
911{
913
914 bsdf->roughness = fmaxf(roughness, bsdf->roughness);
915}
916
917/* Hair Albedo. Computed by summing up geometric series, assuming circular cross-section and
918 * specular reflection. */
920 const ccl_private ShaderClosure *sc)
921{
923
924 const float3 wmi = make_float3(bsdf->h, 0.0f, cos_from_sin(bsdf->h));
925 float cos_t;
926 const float f = fresnel_dielectric(dot(wmi, bsdf->extra->wi), bsdf->eta, &cos_t);
927 const float3 wt = refract_angle(bsdf->extra->wi, wmi, cos_t, 1.0f / bsdf->eta);
928 const Spectrum A = exp(2.0f * bsdf->sigma * cos_t / (1.0f - sqr(wt.y)));
929
930 return safe_divide(A - 2.0f * f * A + f, one_spectrum() - f * A);
931}
932
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_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 float dir_phi(const float3 w)
ccl_device_inline float3 sample_wh(KernelGlobals kg, const float roughness, const float3 wi, const float3 wm, const float2 rand)
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 Spectrum bsdf_hair_huang_albedo(const ccl_private ShaderData *sd, const ccl_private ShaderClosure *sc)
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:86
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:69
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:78
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
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 ccl_device
#define zero_spectrum
#define M_2PI_F
#define make_spectrum(f)
#define ccl_private
const ThreadKernelGlobalsCPU * KernelGlobals
#define ccl_device_inline
#define M_1_2PI_F
#define M_PI_F
#define logf(x)
#define sinf(x)
#define cosf(x)
#define expf(x)
#define tanf(x)
#define CCL_NAMESPACE_END
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
#define atan2f(x, y)
#define asinf(x)
#define fmaxf(x, y)
#define ceilf(x)
#define acosf(x)
ccl_device_forceinline float2 make_float2(const float x, const float y)
#define fabsf(x)
#define sqrtf(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 sqr(const float a)
Definition math_base.h:600
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:886
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:610
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 float average(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:15
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:20
#define T3
Definition md5.cpp:21
#define T1
Definition md5.cpp:19
#define G(x, y, z)
#define I
ccl_private HuangHairExtra * extra
ccl_device_inline_method T length() const
Definition math_base.h:870
ccl_device_inline_method bool is_empty() const
Definition math_base.h:859
float x
float y
float z
Definition sky_float3.h:27
float y
Definition sky_float3.h:27
float x
Definition sky_float3.h:27
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
float3 Spectrum
uint len