Blender V5.0
bsdf_principled_hair_chiang.h
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2018-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0
4 *
5 * This code implements the paper [A practical and controllable hair and fur model for production
6 * path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang, Matt Jen-Yuan, et al. */
7
8#pragma once
9
10#ifndef __KERNEL_GPU__
11# include <cfenv>
12#endif
13
14#include "kernel/types.h"
15
17
19
21
24
25 /* Absorption coefficient. */
27 /* Variance of the underlying logistic distribution. */
28 float v;
29 /* Scale factor of the underlying logistic distribution. */
30 float s;
31 /* Cuticle tilt angle. */
32 float alpha;
33 /* IOR. */
34 float eta;
35 /* Effective variance for the diffuse bounce only. */
37
38 /* Azimuthal offset. */
39 float h;
40};
41
42static_assert(sizeof(ShaderClosure) >= sizeof(ChiangHairBSDF), "ChiangHairBSDF is too large!");
43
44/* Gives the change in direction in the normal plane for the given angles and p-th-order
45 * scattering. */
46ccl_device_inline float delta_phi(const int p, const float gamma_o, const float gamma_t)
47{
48 return 2.0f * p * gamma_t - 2.0f * gamma_o + p * M_PI_F;
49}
50
51/* Remaps the given angle to [-pi, pi]. */
52ccl_device_inline float wrap_angle(const float a)
53{
54 return (a + M_PI_F) - M_2PI_F * floorf((a + M_PI_F) / M_2PI_F) - M_PI_F;
55}
56
57/* Logistic distribution function. */
58ccl_device_inline float logistic(const float x, const float s)
59{
60 const float v = expf(-fabsf(x) / s);
61 return v / (s * sqr(1.0f + v));
62}
63
64/* Logistic cumulative density function. */
65ccl_device_inline float logistic_cdf(const float x, const float s)
66{
67 const float arg = -x / s;
68 /* expf() overflows if arg >= 89.0. */
69 if (arg > 88.0f) {
70 return 0.0f;
71 }
72 return 1.0f / (1.0f + expf(arg));
73}
74
75/* Numerical approximation to the Bessel function of the first kind. */
77{
78 x = sqr(x);
79 float val = 1.0f + 0.25f * x;
80 float pow_x_2i = sqr(x);
81 uint64_t i_fac_2 = 1;
82 int pow_4_i = 16;
83 for (int i = 2; i < 10; i++) {
84 i_fac_2 *= i * i;
85 const float newval = val + pow_x_2i / (pow_4_i * i_fac_2);
86 if (val == newval) {
87 return val;
88 }
89 val = newval;
90 pow_x_2i *= x;
91 pow_4_i *= 4;
92 }
93 return val;
94}
95
96/* Logarithm of the Bessel function of the first kind. */
98{
99 if (x > 12.0f) {
100 /* log(1/x) == -log(x) if x > 0.
101 * This is only used with positive cosines. */
102 return x + 0.5f * (1.f / (8.0f * x) - M_LN_2PI_F - logf(x));
103 }
104 return logf(bessel_I0(x));
105}
106
107/* Logistic distribution limited to the interval [-pi, pi]. */
108ccl_device_inline float trimmed_logistic(const float x, const float s)
109{
110 /* The logistic distribution is symmetric and centered around zero,
111 * so logistic_cdf(x, s) = 1 - logistic_cdf(-x, s).
112 * Therefore, logistic_cdf(x, s)-logistic_cdf(-x, s) = 1 - 2*logistic_cdf(-x, s) */
113 const float scaling_fac = 1.0f - 2.0f * logistic_cdf(-M_PI_F, s);
114 const float val = logistic(x, s);
115 return safe_divide(val, scaling_fac);
116}
117
118/* Sampling function for the trimmed logistic function. */
119ccl_device_inline float sample_trimmed_logistic(const float u, const float s)
120{
121 const float cdf_minuspi = logistic_cdf(-M_PI_F, s);
122 const float x = -s * logf(1.0f / (u * (1.0f - 2.0f * cdf_minuspi) + cdf_minuspi) - 1.0f);
123 return clamp(x, -M_PI_F, M_PI_F);
124}
125
126/* Azimuthal scattering function Np. */
128 float phi, const int p, const float s, float gamma_o, const float gamma_t)
129{
130 const float phi_o = wrap_angle(phi - delta_phi(p, gamma_o, gamma_t));
131 const float val = trimmed_logistic(phi_o, s);
132 return val;
133}
134
135/* Longitudinal scattering function Mp. */
137 const float cos_theta_i,
138 const float sin_theta_o,
139 const float cos_theta_o,
140 const float v)
141{
142 const float inv_v = 1.0f / v;
143 const float cos_arg = cos_theta_i * cos_theta_o * inv_v;
144 const float sin_arg = sin_theta_i * sin_theta_o * inv_v;
145 if (v <= 0.1f) {
146 const float i0 = log_bessel_I0(cos_arg);
147 const float val = expf(i0 - sin_arg - inv_v + 0.6931f + logf(0.5f * inv_v));
149 return val;
150 }
151 const float i0 = bessel_I0(cos_arg);
152 const float val = (expf(-sin_arg) * i0) / (sinhf(inv_v) * 2.0f * v);
154 return val;
155}
156
157#ifdef __HAIR__
158/* Set up the hair closure. */
159ccl_device int bsdf_hair_chiang_setup(ccl_private ShaderData *sd, ccl_private ChiangHairBSDF *bsdf)
160{
161 bsdf->type = CLOSURE_BSDF_HAIR_CHIANG_ID;
162 bsdf->v = clamp(bsdf->v, 0.001f, 1.0f);
163 bsdf->s = clamp(bsdf->s, 0.001f, 1.0f);
164 /* Apply Primary Reflection Roughness modifier. */
165 bsdf->m0_roughness = clamp(bsdf->m0_roughness * bsdf->v, 0.001f, 1.0f);
166
167 /* Map from roughness_u and roughness_v to variance and scale factor. */
168 bsdf->v = sqr(0.726f * bsdf->v + 0.812f * sqr(bsdf->v) + 3.700f * pow20(bsdf->v));
169 bsdf->s = (0.265f * bsdf->s + 1.194f * sqr(bsdf->s) + 5.372f * pow22(bsdf->s)) * M_SQRT_PI_8_F;
170 bsdf->m0_roughness = sqr(0.726f * bsdf->m0_roughness + 0.812f * sqr(bsdf->m0_roughness) +
171 3.700f * pow20(bsdf->m0_roughness));
172
173 /* Compute local frame, aligned to curve tangent and ray direction. */
174 const float3 X = safe_normalize(sd->dPdu);
175 const float3 Y = safe_normalize(cross(X, sd->wi));
176 const float3 Z = safe_normalize(cross(X, Y));
177
178 /* h -1..0..1 means the rays goes from grazing the hair, to hitting it at
179 * the center, to grazing the other edge. This is the sine of the angle
180 * between sd->Ng and Z, as seen from the tangent X. */
181
182 /* TODO: we convert this value to a cosine later and discard the sign, so
183 * we could probably save some operations. */
184 bsdf->h = ((sd->type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) ? -sd->v :
185 dot(cross(sd->Ng, X), Z);
186
187 kernel_assert(fabsf(bsdf->h) < 1.0f + 1e-4f);
190
191 bsdf->N = Y;
192 bsdf->alpha = -bsdf->alpha;
194}
195
196#endif /* __HAIR__ */
197
198/* Given the Fresnel term and transmittance, generate the attenuation terms for each bounce. */
200 const float f,
201 Spectrum T,
203 ccl_private float *Ap_energy)
204{
205 /* Primary specular (R). */
206 Ap[0] = make_spectrum(f);
207 Ap_energy[0] = f;
208
209 /* Transmission (TT). */
210 Spectrum col = sqr(1.0f - f) * T;
211 Ap[1] = col;
212 Ap_energy[1] = spectrum_to_gray(kg, col);
213
214 /* Secondary specular (TRT). */
215 col *= T * f;
216 Ap[2] = col;
217 Ap_energy[2] = spectrum_to_gray(kg, col);
218
219 /* Residual component (TRRT+). */
220 col *= safe_divide(T * f, one_spectrum() - T * f);
221 Ap[3] = col;
222 Ap_energy[3] = spectrum_to_gray(kg, col);
223
224 /* Normalize sampling weights. */
225 const float totweight = Ap_energy[0] + Ap_energy[1] + Ap_energy[2] + Ap_energy[3];
226 const float fac = safe_divide(1.0f, totweight);
227
228 Ap_energy[0] *= fac;
229 Ap_energy[1] *= fac;
230 Ap_energy[2] *= fac;
231 Ap_energy[3] *= fac;
232}
233
234/* Update sin_theta_o and cos_theta_o to account for scale tilt for each bounce. */
235ccl_device_inline void hair_alpha_angles(const float sin_theta_o,
236 const float cos_theta_o,
237 const float alpha,
238 ccl_private float *angles)
239{
240 const float sin_1alpha = sinf(alpha);
241 const float cos_1alpha = cos_from_sin(sin_1alpha);
242 const float sin_2alpha = 2.0f * sin_1alpha * cos_1alpha;
243 const float cos_2alpha = sqr(cos_1alpha) - sqr(sin_1alpha);
244 const float sin_4alpha = 2.0f * sin_2alpha * cos_2alpha;
245 const float cos_4alpha = sqr(cos_2alpha) - sqr(sin_2alpha);
246
247 angles[0] = sin_theta_o * cos_2alpha - cos_theta_o * sin_2alpha;
248 angles[1] = fabsf(cos_theta_o * cos_2alpha + sin_theta_o * sin_2alpha);
249 angles[2] = sin_theta_o * cos_1alpha + cos_theta_o * sin_1alpha;
250 angles[3] = fabsf(cos_theta_o * cos_1alpha - sin_theta_o * sin_1alpha);
251 angles[4] = sin_theta_o * cos_4alpha + cos_theta_o * sin_4alpha;
252 angles[5] = fabsf(cos_theta_o * cos_4alpha - sin_theta_o * sin_4alpha);
253}
254
255/* Evaluation function for our shader. */
257 const ccl_private ShaderData *sd,
258 const ccl_private ShaderClosure *sc,
259 const float3 wo,
260 ccl_private float *pdf)
261{
262 kernel_assert(isfinite_safe(sd->P) && isfinite_safe(sd->ray_length));
263
264 const ccl_private ChiangHairBSDF *bsdf = (const ccl_private ChiangHairBSDF *)sc;
265 const float3 Y = bsdf->N;
266
267 const float3 X = safe_normalize(sd->dPdu);
268 kernel_assert(fabsf(dot(X, Y)) < 1e-3f);
269 const float3 Z = safe_normalize(cross(X, Y));
270
271 /* local_I is the illumination direction. */
272 const float3 local_O = to_local(sd->wi, X, Y, Z);
273 const float3 local_I = to_local(wo, X, Y, Z);
274
275 const float sin_theta_o = local_O.x;
276 const float cos_theta_o = cos_from_sin(sin_theta_o);
277 const float phi_o = atan2f(local_O.z, local_O.y);
278
279 const float sin_theta_t = sin_theta_o / bsdf->eta;
280 const float cos_theta_t = cos_from_sin(sin_theta_t);
281
282 const float sin_gamma_o = bsdf->h;
283 const float cos_gamma_o = cos_from_sin(sin_gamma_o);
284 const float gamma_o = safe_asinf(sin_gamma_o);
285
286 const float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
287 const float cos_gamma_t = cos_from_sin(sin_gamma_t);
288 const float gamma_t = safe_asinf(sin_gamma_t);
289
290 const Spectrum T = exp(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
291 Spectrum Ap[4];
292 float Ap_energy[4];
294 kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap, Ap_energy);
295
296 const float sin_theta_i = local_I.x;
297 const float cos_theta_i = cos_from_sin(sin_theta_i);
298 const float phi_i = atan2f(local_I.z, local_I.y);
299
300 const float phi = phi_i - phi_o;
301
302 float angles[6];
303 hair_alpha_angles(sin_theta_o, cos_theta_o, bsdf->alpha, angles);
304
306 float F_energy = 0.0f;
307
308 /* Primary specular (R), Transmission (TT) and Secondary Specular (TRT). */
309 for (int i = 0; i < 3; i++) {
310 const float Mp = longitudinal_scattering(sin_theta_i,
311 cos_theta_i,
312 angles[2 * i],
313 angles[2 * i + 1],
314 (i == 0) ? bsdf->m0_roughness :
315 (i == 1) ? 0.25f * bsdf->v :
316 4.0f * bsdf->v);
317 const float Np = azimuthal_scattering(phi, i, bsdf->s, gamma_o, gamma_t);
318 F += Ap[i] * Mp * Np;
319 F_energy += Ap_energy[i] * Mp * Np;
321 }
322
323 /* Residual component (TRRT+). */
324 {
325 const float Mp = longitudinal_scattering(
326 sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f * bsdf->v);
327 const float Np = M_1_2PI_F;
328 F += Ap[3] * Mp * Np;
329 F_energy += Ap_energy[3] * Mp * Np;
331 }
332
333 *pdf = F_energy;
334 return F;
335}
336
337/* Sampling function for the hair shader. */
339 const ccl_private ShaderClosure *sc,
340 ccl_private ShaderData *sd,
341 float3 rand,
342 ccl_private Spectrum *eval,
344 ccl_private float *pdf,
345 ccl_private float2 *sampled_roughness)
346{
348
349 *sampled_roughness = make_float2(bsdf->m0_roughness, bsdf->m0_roughness);
350
351 const float3 Y = bsdf->N;
352
353 const float3 X = safe_normalize(sd->dPdu);
354 kernel_assert(fabsf(dot(X, Y)) < 1e-3f);
355 const float3 Z = safe_normalize(cross(X, Y));
356
357 /* `wo` in PBRT. */
358 const float3 local_O = to_local(sd->wi, X, Y, Z);
359
360 const float sin_theta_o = local_O.x;
361 const float cos_theta_o = cos_from_sin(sin_theta_o);
362 const float phi_o = atan2f(local_O.z, local_O.y);
363
364 const float sin_theta_t = sin_theta_o / bsdf->eta;
365 const float cos_theta_t = cos_from_sin(sin_theta_t);
366
367 const float sin_gamma_o = bsdf->h;
368 const float cos_gamma_o = cos_from_sin(sin_gamma_o);
369 const float gamma_o = safe_asinf(sin_gamma_o);
370
371 const float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
372 const float cos_gamma_t = cos_from_sin(sin_gamma_t);
373 const float gamma_t = safe_asinf(sin_gamma_t);
374
375 const Spectrum T = exp(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
376 Spectrum Ap[4];
377 float Ap_energy[4];
379 kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap, Ap_energy);
380
381 int p = 0;
382 for (; p < 3; p++) {
383 if (rand.z < Ap_energy[p]) {
384 break;
385 }
386 rand.z -= Ap_energy[p];
387 }
388 rand.z /= Ap_energy[p];
389
390 float v = bsdf->v;
391 if (p == 1) {
392 v *= 0.25f;
393 }
394 if (p >= 2) {
395 v *= 4.0f;
396 }
397
398 float angles[6];
399 hair_alpha_angles(sin_theta_o, cos_theta_o, bsdf->alpha, angles);
400 float sin_theta_o_tilted = sin_theta_o;
401 float cos_theta_o_tilted = cos_theta_o;
402 if (p < 3) {
403 sin_theta_o_tilted = angles[2 * p];
404 cos_theta_o_tilted = angles[2 * p + 1];
405 }
406 rand.z = max(rand.z, 1e-5f);
407 const float fac = 1.0f + v * logf(rand.z + (1.0f - rand.z) * expf(-2.0f / v));
408 const float sin_theta_i = -fac * sin_theta_o_tilted +
409 sin_from_cos(fac) * cosf(M_2PI_F * rand.y) * cos_theta_o_tilted;
410 const float cos_theta_i = cos_from_sin(sin_theta_i);
411
412 float phi;
413 if (p < 3) {
414 phi = delta_phi(p, gamma_o, gamma_t) + sample_trimmed_logistic(rand.x, bsdf->s);
415 }
416 else {
417 phi = M_2PI_F * rand.x;
418 }
419 const float phi_i = phi_o + phi;
420
422 float F_energy = 0.0f;
423
424 /* Primary specular (R), Transmission (TT) and Secondary Specular (TRT). */
425 for (int i = 0; i < 3; i++) {
426 const float Mp = longitudinal_scattering(sin_theta_i,
427 cos_theta_i,
428 angles[2 * i],
429 angles[2 * i + 1],
430 (i == 0) ? bsdf->m0_roughness :
431 (i == 1) ? 0.25f * bsdf->v :
432 4.0f * bsdf->v);
433 const float Np = azimuthal_scattering(phi, i, bsdf->s, gamma_o, gamma_t);
434 F += Ap[i] * Mp * Np;
435 F_energy += Ap_energy[i] * Mp * Np;
437 }
438
439 /* Residual component (TRRT+). */
440 {
441 const float Mp = longitudinal_scattering(
442 sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f * bsdf->v);
443 const float Np = M_1_2PI_F;
444 F += Ap[3] * Mp * Np;
445 F_energy += Ap_energy[3] * Mp * Np;
447 }
448
449 *eval = F;
450 *pdf = F_energy;
451 *wo = to_global(spherical_cos_to_direction(sin_theta_i, phi_i), Y, Z, X);
452
453 return LABEL_GLOSSY | ((p == 0) ? LABEL_REFLECT : LABEL_TRANSMIT);
454}
455
456/* Implements Filter Glossy by capping the effective roughness. */
457ccl_device void bsdf_hair_chiang_blur(ccl_private ShaderClosure *sc, const float roughness)
458{
460
461 bsdf->v = fmaxf(roughness, bsdf->v);
462 bsdf->s = fmaxf(roughness, bsdf->s);
463 bsdf->m0_roughness = fmaxf(roughness, bsdf->m0_roughness);
464}
465
466/* Hair Albedo. */
468 const ccl_private ShaderClosure *sc)
469{
471
472 const float cos_theta_o = cos_from_sin(dot(sd->wi, safe_normalize(sd->dPdu)));
473 const float cos_gamma_o = cos_from_sin(bsdf->h);
474 const float f = fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta);
475
476 const float roughness_scale = bsdf_principled_hair_albedo_roughness_scale(bsdf->v);
477 /* TODO(lukas): Adding the Fresnel term here as a workaround until the proper refactor. */
478 return exp(-sqrt(bsdf->sigma) * roughness_scale) + make_spectrum(f);
479}
480
MINLINE float safe_divide(float a, float b)
MINLINE float safe_asinf(float a)
#define X
#define Z
#define Y
ATTR_WARN_UNUSED_RESULT const BMVert * v
ccl_device_inline void hair_attenuation(KernelGlobals kg, const float f, Spectrum T, ccl_private Spectrum *Ap, ccl_private float *Ap_energy)
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_inline float delta_phi(const int p, const float gamma_o, const float gamma_t)
ccl_device int bsdf_hair_chiang_sample(KernelGlobals kg, const ccl_private 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_inline float trimmed_logistic(const float x, const float s)
ccl_device_inline void hair_alpha_angles(const float sin_theta_o, const float cos_theta_o, const float alpha, ccl_private float *angles)
ccl_device_inline float logistic_cdf(const float x, const float s)
ccl_device_inline float sample_trimmed_logistic(const float u, const float s)
ccl_device Spectrum bsdf_hair_chiang_eval(KernelGlobals kg, const ccl_private ShaderData *sd, const ccl_private ShaderClosure *sc, const float3 wo, ccl_private float *pdf)
ccl_device_inline float wrap_angle(const float a)
ccl_device_inline float logistic(const float x, const float s)
ccl_device_inline float bessel_I0(float x)
ccl_device void bsdf_hair_chiang_blur(ccl_private ShaderClosure *sc, const float roughness)
ccl_device_inline float azimuthal_scattering(float phi, const int p, const float s, float gamma_o, const float gamma_t)
ccl_device Spectrum bsdf_hair_chiang_albedo(const ccl_private ShaderData *sd, const ccl_private ShaderClosure *sc)
ccl_device_inline float log_bessel_I0(const float x)
ccl_device float fresnel_dielectric_cos(const float cosi, const float eta)
Definition bsdf_util.h:110
ccl_device_inline float bsdf_principled_hair_albedo_roughness_scale(const float azimuthal_roughness)
Definition bsdf_util.h:378
unsigned long long int uint64_t
dot(value.rgb, luminance_coefficients)") DEFINE_VALUE("REDUCE(lhs
ccl_device float3 spherical_cos_to_direction(const float cos_theta, const float phi)
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 M_SQRT_PI_8_F
#define one_spectrum
#define zero_spectrum
#define M_LN_2PI_F
#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
#define sinhf(x)
uint col
#define exp
#define sqrt
VecBase< float, 3 > cross(VecOp< float, 3 >, VecOp< float, 3 >) RET
constexpr T clamp(T, U, U) RET
@ CLOSURE_BSDF_HAIR_CHIANG_ID
@ SD_BSDF_HAS_EVAL
@ SD_BSDF
@ SD_BSDF_HAS_TRANSMISSION
@ PRIMITIVE_CURVE_RIBBON
@ PRIMITIVE_CURVE
@ LABEL_TRANSMIT
@ LABEL_GLOSSY
@ LABEL_REFLECT
ccl_device float spectrum_to_gray(KernelGlobals kg, Spectrum c)
ccl_device_inline float pow22(const float a)
Definition math_base.h:642
ccl_device_inline float sin_from_cos(const float c)
Definition math_base.h:609
ccl_device_inline float pow20(const float a)
Definition math_base.h:637
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_inline float2 safe_normalize(const float2 a)
#define T
#define F
#define sqr
#define floorf
#define fabsf
#define sqrtf
#define ccl_device
#define M_2PI_F
#define fmaxf
#define sinf
#define make_float2
#define M_PI_F
#define cosf
#define atan2f
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