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