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