Blender V4.5
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_RIBBON) ? -sd->v : dot(cross(sd->Ng, X), Z);
185
186 kernel_assert(fabsf(bsdf->h) < 1.0f + 1e-4f);
189
190 bsdf->N = Y;
191 bsdf->alpha = -bsdf->alpha;
193}
194
195#endif /* __HAIR__ */
196
197/* Given the Fresnel term and transmittance, generate the attenuation terms for each bounce. */
199 const float f,
200 Spectrum T,
202 ccl_private float *Ap_energy)
203{
204 /* Primary specular (R). */
205 Ap[0] = make_spectrum(f);
206 Ap_energy[0] = f;
207
208 /* Transmission (TT). */
209 Spectrum col = sqr(1.0f - f) * T;
210 Ap[1] = col;
211 Ap_energy[1] = spectrum_to_gray(kg, col);
212
213 /* Secondary specular (TRT). */
214 col *= T * f;
215 Ap[2] = col;
216 Ap_energy[2] = spectrum_to_gray(kg, col);
217
218 /* Residual component (TRRT+). */
219 col *= safe_divide(T * f, one_spectrum() - T * f);
220 Ap[3] = col;
221 Ap_energy[3] = spectrum_to_gray(kg, col);
222
223 /* Normalize sampling weights. */
224 const float totweight = Ap_energy[0] + Ap_energy[1] + Ap_energy[2] + Ap_energy[3];
225 const float fac = safe_divide(1.0f, totweight);
226
227 Ap_energy[0] *= fac;
228 Ap_energy[1] *= fac;
229 Ap_energy[2] *= fac;
230 Ap_energy[3] *= fac;
231}
232
233/* Update sin_theta_o and cos_theta_o to account for scale tilt for each bounce. */
234ccl_device_inline void hair_alpha_angles(const float sin_theta_o,
235 const float cos_theta_o,
236 const float alpha,
237 ccl_private float *angles)
238{
239 const float sin_1alpha = sinf(alpha);
240 const float cos_1alpha = cos_from_sin(sin_1alpha);
241 const float sin_2alpha = 2.0f * sin_1alpha * cos_1alpha;
242 const float cos_2alpha = sqr(cos_1alpha) - sqr(sin_1alpha);
243 const float sin_4alpha = 2.0f * sin_2alpha * cos_2alpha;
244 const float cos_4alpha = sqr(cos_2alpha) - sqr(sin_2alpha);
245
246 angles[0] = sin_theta_o * cos_2alpha - cos_theta_o * sin_2alpha;
247 angles[1] = fabsf(cos_theta_o * cos_2alpha + sin_theta_o * sin_2alpha);
248 angles[2] = sin_theta_o * cos_1alpha + cos_theta_o * sin_1alpha;
249 angles[3] = fabsf(cos_theta_o * cos_1alpha - sin_theta_o * sin_1alpha);
250 angles[4] = sin_theta_o * cos_4alpha + cos_theta_o * sin_4alpha;
251 angles[5] = fabsf(cos_theta_o * cos_4alpha - sin_theta_o * sin_4alpha);
252}
253
254/* Evaluation function for our shader. */
256 const ccl_private ShaderData *sd,
257 const ccl_private ShaderClosure *sc,
258 const float3 wo,
259 ccl_private float *pdf)
260{
261 kernel_assert(isfinite_safe(sd->P) && isfinite_safe(sd->ray_length));
262
263 const ccl_private ChiangHairBSDF *bsdf = (const ccl_private ChiangHairBSDF *)sc;
264 const float3 Y = bsdf->N;
265
266 const float3 X = safe_normalize(sd->dPdu);
267 kernel_assert(fabsf(dot(X, Y)) < 1e-3f);
268 const float3 Z = safe_normalize(cross(X, Y));
269
270 /* local_I is the illumination direction. */
271 const float3 local_O = to_local(sd->wi, X, Y, Z);
272 const float3 local_I = to_local(wo, X, Y, Z);
273
274 const float sin_theta_o = local_O.x;
275 const float cos_theta_o = cos_from_sin(sin_theta_o);
276 const float phi_o = atan2f(local_O.z, local_O.y);
277
278 const float sin_theta_t = sin_theta_o / bsdf->eta;
279 const float cos_theta_t = cos_from_sin(sin_theta_t);
280
281 const float sin_gamma_o = bsdf->h;
282 const float cos_gamma_o = cos_from_sin(sin_gamma_o);
283 const float gamma_o = safe_asinf(sin_gamma_o);
284
285 const float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
286 const float cos_gamma_t = cos_from_sin(sin_gamma_t);
287 const float gamma_t = safe_asinf(sin_gamma_t);
288
289 const Spectrum T = exp(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
290 Spectrum Ap[4];
291 float Ap_energy[4];
293 kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap, Ap_energy);
294
295 const float sin_theta_i = local_I.x;
296 const float cos_theta_i = cos_from_sin(sin_theta_i);
297 const float phi_i = atan2f(local_I.z, local_I.y);
298
299 const float phi = phi_i - phi_o;
300
301 float angles[6];
302 hair_alpha_angles(sin_theta_o, cos_theta_o, bsdf->alpha, angles);
303
305 float F_energy = 0.0f;
306
307 /* Primary specular (R), Transmission (TT) and Secondary Specular (TRT). */
308 for (int i = 0; i < 3; i++) {
309 const float Mp = longitudinal_scattering(sin_theta_i,
310 cos_theta_i,
311 angles[2 * i],
312 angles[2 * i + 1],
313 (i == 0) ? bsdf->m0_roughness :
314 (i == 1) ? 0.25f * bsdf->v :
315 4.0f * bsdf->v);
316 const float Np = azimuthal_scattering(phi, i, bsdf->s, gamma_o, gamma_t);
317 F += Ap[i] * Mp * Np;
318 F_energy += Ap_energy[i] * Mp * Np;
320 }
321
322 /* Residual component (TRRT+). */
323 {
324 const float Mp = longitudinal_scattering(
325 sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f * bsdf->v);
326 const float Np = M_1_2PI_F;
327 F += Ap[3] * Mp * Np;
328 F_energy += Ap_energy[3] * Mp * Np;
330 }
331
332 *pdf = F_energy;
333 return F;
334}
335
336/* Sampling function for the hair shader. */
338 const ccl_private ShaderClosure *sc,
339 ccl_private ShaderData *sd,
340 float3 rand,
341 ccl_private Spectrum *eval,
343 ccl_private float *pdf,
344 ccl_private float2 *sampled_roughness)
345{
347
348 *sampled_roughness = make_float2(bsdf->m0_roughness, bsdf->m0_roughness);
349
350 const float3 Y = bsdf->N;
351
352 const float3 X = safe_normalize(sd->dPdu);
353 kernel_assert(fabsf(dot(X, Y)) < 1e-3f);
354 const float3 Z = safe_normalize(cross(X, Y));
355
356 /* `wo` in PBRT. */
357 const float3 local_O = to_local(sd->wi, X, Y, Z);
358
359 const float sin_theta_o = local_O.x;
360 const float cos_theta_o = cos_from_sin(sin_theta_o);
361 const float phi_o = atan2f(local_O.z, local_O.y);
362
363 const float sin_theta_t = sin_theta_o / bsdf->eta;
364 const float cos_theta_t = cos_from_sin(sin_theta_t);
365
366 const float sin_gamma_o = bsdf->h;
367 const float cos_gamma_o = cos_from_sin(sin_gamma_o);
368 const float gamma_o = safe_asinf(sin_gamma_o);
369
370 const float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
371 const float cos_gamma_t = cos_from_sin(sin_gamma_t);
372 const float gamma_t = safe_asinf(sin_gamma_t);
373
374 const Spectrum T = exp(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
375 Spectrum Ap[4];
376 float Ap_energy[4];
378 kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap, Ap_energy);
379
380 int p = 0;
381 for (; p < 3; p++) {
382 if (rand.z < Ap_energy[p]) {
383 break;
384 }
385 rand.z -= Ap_energy[p];
386 }
387 rand.z /= Ap_energy[p];
388
389 float v = bsdf->v;
390 if (p == 1) {
391 v *= 0.25f;
392 }
393 if (p >= 2) {
394 v *= 4.0f;
395 }
396
397 float angles[6];
398 hair_alpha_angles(sin_theta_o, cos_theta_o, bsdf->alpha, angles);
399 float sin_theta_o_tilted = sin_theta_o;
400 float cos_theta_o_tilted = cos_theta_o;
401 if (p < 3) {
402 sin_theta_o_tilted = angles[2 * p];
403 cos_theta_o_tilted = angles[2 * p + 1];
404 }
405 rand.z = max(rand.z, 1e-5f);
406 const float fac = 1.0f + v * logf(rand.z + (1.0f - rand.z) * expf(-2.0f / v));
407 const float sin_theta_i = -fac * sin_theta_o_tilted +
408 sin_from_cos(fac) * cosf(M_2PI_F * rand.y) * cos_theta_o_tilted;
409 const float cos_theta_i = cos_from_sin(sin_theta_i);
410
411 float phi;
412 if (p < 3) {
413 phi = delta_phi(p, gamma_o, gamma_t) + sample_trimmed_logistic(rand.x, bsdf->s);
414 }
415 else {
416 phi = M_2PI_F * rand.x;
417 }
418 const float phi_i = phi_o + phi;
419
421 float F_energy = 0.0f;
422
423 /* Primary specular (R), Transmission (TT) and Secondary Specular (TRT). */
424 for (int i = 0; i < 3; i++) {
425 const float Mp = longitudinal_scattering(sin_theta_i,
426 cos_theta_i,
427 angles[2 * i],
428 angles[2 * i + 1],
429 (i == 0) ? bsdf->m0_roughness :
430 (i == 1) ? 0.25f * bsdf->v :
431 4.0f * bsdf->v);
432 const float Np = azimuthal_scattering(phi, i, bsdf->s, gamma_o, gamma_t);
433 F += Ap[i] * Mp * Np;
434 F_energy += Ap_energy[i] * Mp * Np;
436 }
437
438 /* Residual component (TRRT+). */
439 {
440 const float Mp = longitudinal_scattering(
441 sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f * bsdf->v);
442 const float Np = M_1_2PI_F;
443 F += Ap[3] * Mp * Np;
444 F_energy += Ap_energy[3] * Mp * Np;
446 }
447
448 *eval = F;
449 *pdf = F_energy;
450 *wo = to_global(spherical_cos_to_direction(sin_theta_i, phi_i), Y, Z, X);
451
452 return LABEL_GLOSSY | ((p == 0) ? LABEL_REFLECT : LABEL_TRANSMIT);
453}
454
455/* Implements Filter Glossy by capping the effective roughness. */
456ccl_device void bsdf_hair_chiang_blur(ccl_private ShaderClosure *sc, const float roughness)
457{
459
460 bsdf->v = fmaxf(roughness, bsdf->v);
461 bsdf->s = fmaxf(roughness, bsdf->s);
462 bsdf->m0_roughness = fmaxf(roughness, bsdf->m0_roughness);
463}
464
465/* Hair Albedo. */
467 const ccl_private ShaderClosure *sc)
468{
470
471 const float cos_theta_o = cos_from_sin(dot(sd->wi, safe_normalize(sd->dPdu)));
472 const float cos_gamma_o = cos_from_sin(bsdf->h);
473 const float f = fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta);
474
475 const float roughness_scale = bsdf_principled_hair_albedo_roughness_scale(bsdf->v);
476 /* TODO(lukas): Adding the Fresnel term here as a workaround until the proper refactor. */
477 return exp(-sqrt(bsdf->sigma) * roughness_scale) + make_spectrum(f);
478}
479
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:86
ccl_device_inline float bsdf_principled_hair_albedo_roughness_scale(const float azimuthal_roughness)
Definition bsdf_util.h:294
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 ccl_device
#define zero_spectrum
#define M_2PI_F
#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 M_PI_F
#define logf(x)
#define sinf(x)
#define cosf(x)
#define expf(x)
#define CCL_NAMESPACE_END
#define atan2f(x, y)
#define fmaxf(x, y)
#define floorf(x)
ccl_device_forceinline float2 make_float2(const float x, const float y)
#define sinhf(x)
#define fabsf(x)
#define sqrtf(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
@ LABEL_TRANSMIT
@ LABEL_GLOSSY
@ LABEL_REFLECT
ccl_device float spectrum_to_gray(KernelGlobals kg, Spectrum c)
ccl_device_inline float sqr(const float a)
Definition math_base.h:600
ccl_device_inline float pow22(const float a)
Definition math_base.h:632
ccl_device_inline float sin_from_cos(const float c)
Definition math_base.h:605
ccl_device_inline float pow20(const float a)
Definition math_base.h:627
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_inline float2 safe_normalize(const float2 a)
#define T
#define F
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