Blender V5.0
subsurface_random_walk.h
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "kernel/bvh/bvh.h"
6
8
11
12#include "util/color.h"
13
15
16#ifdef __SUBSURFACE__
17
18/* Random walk subsurface scattering.
19 *
20 * "Practical and Controllable Subsurface Scattering for Production Path
21 * Tracing". Matt Jen-Yuan Chiang, Peter Kutz, Brent Burley. SIGGRAPH 2016. */
22
23/* Support for anisotropy from:
24 * "Path Traced Subsurface Scattering using Anisotropic Phase Functions
25 * and Non-Exponential Free Flights".
26 * Magnus Wrenninge, Ryusuke Villemin, Christophe Hery.
27 * https://graphics.pixar.com/library/PathTracedSubsurface/ */
28
29ccl_device void subsurface_random_walk_remap(const float albedo,
30 const float d,
31 const float g,
32 ccl_private float *sigma_t,
33 ccl_private float *alpha)
34{
35 /* Compute attenuation and scattering coefficients from albedo. */
36 const float g2 = g * g;
37 const float g3 = g2 * g;
38 const float g4 = g3 * g;
39 const float g5 = g4 * g;
40 const float g6 = g5 * g;
41 const float g7 = g6 * g;
42
43 const float A = 1.8260523782f + -1.28451056436f * g + -1.79904629312f * g2 +
44 9.19393289202f * g3 + -22.8215585862f * g4 + 32.0234874259f * g5 +
45 -23.6264803333f * g6 + 7.21067002658f * g7;
46 const float B = 4.98511194385f +
47 0.127355959438f *
48 expf(31.1491581433f * g + -201.847017512f * g2 + 841.576016723f * g3 +
49 -2018.09288505f * g4 + 2731.71560286f * g5 + -1935.41424244f * g6 +
50 559.009054474f * g7);
51 const float C = 1.09686102424f + -0.394704063468f * g + 1.05258115941f * g2 +
52 -8.83963712726f * g3 + 28.8643230661f * g4 + -46.8802913581f * g5 +
53 38.5402837518f * g6 + -12.7181042538f * g7;
54 const float D = 0.496310210422f + 0.360146581622f * g + -2.15139309747f * g2 +
55 17.8896899217f * g3 + -55.2984010333f * g4 + 82.065982243f * g5 +
56 -58.5106008578f * g6 + 15.8478295021f * g7;
57 const float E = 4.23190299701f +
58 0.00310603949088f *
59 expf(76.7316253952f * g + -594.356773233f * g2 + 2448.8834203f * g3 +
60 -5576.68528998f * g4 + 7116.60171912f * g5 + -4763.54467887f * g6 +
61 1303.5318055f * g7);
62 const float F = 2.40602999408f + -2.51814844609f * g + 9.18494908356f * g2 +
63 -79.2191708682f * g3 + 259.082868209f * g4 + -403.613804597f * g5 +
64 302.85712436f * g6 + -87.4370473567f * g7;
65
66 const float blend = powf(albedo, 0.25f);
67
68 *alpha = (1.0f - blend) * A * powf(atanf(B * albedo), C) +
69 blend * D * powf(atanf(E * albedo), F);
70 *alpha = clamp(*alpha, 0.0f, 0.999999f); // because of numerical precision
71
72 const float sigma_t_prime = 1.0f / fmaxf(d, 1e-16f);
73 *sigma_t = sigma_t_prime / (1.0f - g);
74}
75
76ccl_device void subsurface_random_walk_coefficients(const Spectrum albedo,
77 const Spectrum radius,
78 const float anisotropy,
79 ccl_private Spectrum *sigma_t,
80 ccl_private Spectrum *alpha,
81 ccl_private Spectrum *throughput)
82{
84 subsurface_random_walk_remap(GET_SPECTRUM_CHANNEL(albedo, i),
85 GET_SPECTRUM_CHANNEL(radius, i),
86 anisotropy,
87 &GET_SPECTRUM_CHANNEL(*sigma_t, i),
88 &GET_SPECTRUM_CHANNEL(*alpha, i));
89 }
90
91 /* Throughput already contains closure weight at this point, which includes the
92 * albedo, as well as closure mixing and Fresnel weights. Divide out the albedo
93 * which will be added through scattering. */
94 *throughput = safe_divide_color(*throughput, albedo);
95
96 /* With low albedo values (like 0.025) we get diffusion_length 1.0 and
97 * infinite phase functions. To avoid a sharp discontinuity as we go from
98 * such values to 0.0, increase alpha and reduce the throughput to compensate. */
99 const float min_alpha = 0.2f;
101 if (GET_SPECTRUM_CHANNEL(*alpha, i) < min_alpha) {
102 GET_SPECTRUM_CHANNEL(*throughput, i) *= GET_SPECTRUM_CHANNEL(*alpha, i) / min_alpha;
103 GET_SPECTRUM_CHANNEL(*alpha, i) = min_alpha;
104 }
105 }
106}
107
108/* References for Dwivedi sampling:
109 *
110 * [1] "A Zero-variance-based Sampling Scheme for Monte Carlo Subsurface Scattering"
111 * by Jaroslav Křivánek and Eugene d'Eon (SIGGRAPH 2014)
112 * https://cgg.mff.cuni.cz/~jaroslav/papers/2014-zerovar/
113 *
114 * [2] "Improving the Dwivedi Sampling Scheme"
115 * by Johannes Meng, Johannes Hanika, and Carsten Dachsbacher (EGSR 2016)
116 * https://cg.ivd.kit.edu/1951.php
117 *
118 * [3] "Zero-Variance Theory for Efficient Subsurface Scattering"
119 * by Eugene d'Eon and Jaroslav Křivánek (SIGGRAPH 2020)
120 * https://iliyan.com/publications/RenderingCourse2020
121 */
122
123ccl_device_forceinline float eval_phase_dwivedi(const float v,
124 const float phase_log,
125 const float cos_theta)
126{
127 /* Eq. 9 from [2] using precomputed log((v + 1) / (v - 1)) */
128 return 1.0f / ((v - cos_theta) * phase_log);
129}
130
131ccl_device_forceinline float sample_phase_dwivedi(const float v,
132 const float phase_log,
133 const float rand)
134{
135 /* Based on Eq. 10 from [2]: `v - (v + 1) * pow((v - 1) / (v + 1), rand)`
136 * Since we're already pre-computing `phase_log = log((v + 1) / (v - 1))` for the evaluation,
137 * we can implement the power function like this. */
138 return v - (v + 1.0f) * expf(-rand * phase_log);
139}
140
141ccl_device_forceinline float diffusion_length_dwivedi(const float alpha)
142{
143 /* Eq. 67 from [3] */
144 return 1.0f / sqrtf(1.0f - powf(alpha, 2.44294f - 0.0215813f * alpha + 0.578637f / alpha));
145}
146
147ccl_device_forceinline float3 direction_from_cosine(const float3 D,
148 const float cos_theta,
149 const float randv)
150{
151 const float phi = M_2PI_F * randv;
153
154 float3 T;
155 float3 B;
156 make_orthonormals(D, &T, &B);
157 return to_global(dir, T, B, D);
158}
159
160ccl_device_forceinline Spectrum subsurface_random_walk_pdf(Spectrum sigma_t,
161 const float t,
162 bool hit,
163 ccl_private Spectrum *transmittance)
164{
165 const Spectrum T = volume_color_transmittance(sigma_t, t);
166 if (transmittance) {
167 *transmittance = T;
168 }
169 return hit ? T : sigma_t * T;
170}
171
172/* Define the below variable to get the similarity code active,
173 * and the value represents the cutoff level */
174# define SUBSURFACE_RANDOM_WALK_SIMILARITY_LEVEL 9
175
176ccl_device_inline bool subsurface_random_walk(KernelGlobals kg,
178 RNGState rng_state,
179 ccl_private Ray &ray,
181{
182 const float3 P = INTEGRATOR_STATE(state, ray, P);
183 const float3 D = INTEGRATOR_STATE(state, ray, D);
184 const float ray_dP = INTEGRATOR_STATE(state, ray, dP);
185 const float time = INTEGRATOR_STATE(state, ray, time);
186 const float3 N = INTEGRATOR_STATE(state, subsurface, N);
187 const int object = INTEGRATOR_STATE(state, isect, object);
188 const int prim = INTEGRATOR_STATE(state, isect, prim);
189
190 /* Setup ray. */
191 ray.P = P;
192 ray.D = D;
193 ray.tmin = 0.0f;
194 ray.tmax = FLT_MAX;
195 ray.time = time;
196 ray.dP = ray_dP;
197 ray.dD = differential_zero_compact();
198 ray.self.object = object;
199 ray.self.prim = prim;
200 ray.self.light_object = OBJECT_NONE;
201 ray.self.light_prim = PRIM_NONE;
202
203 /* Convert subsurface to volume coefficients.
204 * The single-scattering albedo is named alpha to avoid confusion with the surface albedo. */
205 const Spectrum albedo = INTEGRATOR_STATE(state, subsurface, albedo);
206 const Spectrum radius = INTEGRATOR_STATE(state, subsurface, radius);
207 const float anisotropy = INTEGRATOR_STATE(state, subsurface, anisotropy);
208
209 Spectrum sigma_t;
210 Spectrum alpha;
211 Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
212 subsurface_random_walk_coefficients(albedo, radius, anisotropy, &sigma_t, &alpha, &throughput);
213 const Spectrum sigma_s = sigma_t * alpha;
214
215 /* Theoretically it should be better to use the exact alpha for the channel we're sampling at
216 * each bounce, but in practice there doesn't seem to be a noticeable difference in exchange
217 * for making the code significantly more complex and slower (if direction sampling depends on
218 * the sampled channel, we need to compute its PDF per-channel and consider it for MIS later on).
219 *
220 * Since the strength of the guided sampling increases as alpha gets lower, using a value that
221 * is too low results in fireflies while one that's too high just gives a bit more noise.
222 * Therefore, the code here uses the highest of the three albedos to be safe. */
223 const float diffusion_length = diffusion_length_dwivedi(reduce_max(alpha));
224
225 if (diffusion_length == 1.0f) {
226 /* With specific values of alpha the length might become 1, which in asymptotic makes phase to
227 * be infinite. After first bounce it will cause throughput to be 0. Do early output, avoiding
228 * numerical issues and extra unneeded work. */
229 return false;
230 }
231
232 /* Precompute term for phase sampling. */
233 const float phase_log = logf((diffusion_length + 1.0f) / (diffusion_length - 1.0f));
234
235 /* Modify state for RNGs, decorrelated from other paths. */
236 path_state_rng_scramble(&rng_state, 0xdeadbeef);
237
238 /* Random walk until we hit the surface again. */
239 bool hit = false;
240 bool have_opposite_interface = false;
241 float opposite_distance = 0.0f;
242
243 /* TODO: Disable for `alpha > 0.999` or so? */
244 /* Our heuristic, a compromise between guiding and classic. */
245 const float guided_fraction = 1.0f - fmaxf(0.5f, powf(fabsf(anisotropy), 0.125f));
246
247# ifdef SUBSURFACE_RANDOM_WALK_SIMILARITY_LEVEL
248 const Spectrum sigma_s_star = sigma_s * (1.0f - anisotropy);
249 const Spectrum sigma_t_star = sigma_t - sigma_s + sigma_s_star;
250 const Spectrum sigma_t_org = sigma_t;
251 const Spectrum sigma_s_org = sigma_s;
252 const float anisotropy_org = anisotropy;
253 const float guided_fraction_org = guided_fraction;
254# endif
255
256 for (int bounce = 0; bounce < BSSRDF_MAX_BOUNCES; bounce++) {
257 /* Advance random number offset. */
258 rng_state.rng_offset += PRNG_BOUNCE_NUM;
259
260# ifdef SUBSURFACE_RANDOM_WALK_SIMILARITY_LEVEL
261 // shadow with local variables according to depth
262 float anisotropy;
263 float guided_fraction;
264 Spectrum sigma_s;
265 Spectrum sigma_t;
266 if (bounce <= SUBSURFACE_RANDOM_WALK_SIMILARITY_LEVEL) {
267 anisotropy = anisotropy_org;
268 guided_fraction = guided_fraction_org;
269 sigma_t = sigma_t_org;
270 sigma_s = sigma_s_org;
271 }
272 else {
273 anisotropy = 0.0f;
274 guided_fraction = 0.75f; // back to isotropic heuristic from Blender
275 sigma_t = sigma_t_star;
276 sigma_s = sigma_s_star;
277 }
278# endif
279
280 /* Sample color channel, use MIS with balance heuristic. */
281 float rchannel = path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_COLOR_CHANNEL);
282 Spectrum channel_pdf;
283 const int channel = volume_sample_channel(alpha, throughput, &rchannel, &channel_pdf);
284 float sample_sigma_t = volume_channel_get(sigma_t, channel);
285 const float randt = path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_SCATTER_DISTANCE);
286
287 /* We need the result of the ray-cast to compute the full guided PDF, so just remember the
288 * relevant terms to avoid recomputing them later. */
289 float backward_fraction = 0.0f;
290 float forward_pdf_factor = 0.0f;
291 float forward_stretching = 1.0f;
292 float backward_pdf_factor = 0.0f;
293 float backward_stretching = 1.0f;
294
295 /* For the initial ray, we already know the direction, so just do classic distance sampling. */
296 if (bounce > 0) {
297 /* Decide whether we should use guided or classic sampling. */
298 const bool guided = (path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_GUIDE_STRATEGY) <
299 guided_fraction);
300
301 /* Determine if we want to sample away from the incoming interface.
302 * This only happens if we found a nearby opposite interface, and the probability for it
303 * depends on how close we are to it already.
304 * This probability term comes from the recorded presentation of [3]. */
305 bool guide_backward = false;
306 if (have_opposite_interface) {
307 /* Compute distance of the random walk between the tangent plane at the starting point
308 * and the assumed opposite interface (the parallel plane that contains the point we
309 * found in our ray query for the opposite side). */
310 const float x = clamp(dot(ray.P - P, -N), 0.0f, opposite_distance);
311 backward_fraction = 1.0f /
312 (1.0f + expf((opposite_distance - 2.0f * x) / diffusion_length));
313 guide_backward = path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_GUIDE_DIRECTION) <
314 backward_fraction;
315 }
316
317 /* Sample scattering direction. */
318 const float2 rand_scatter = path_state_rng_2D(kg, &rng_state, PRNG_SUBSURFACE_BSDF);
319 float cos_theta;
320 float hg_pdf;
321 if (guided) {
322 cos_theta = sample_phase_dwivedi(diffusion_length, phase_log, rand_scatter.x);
323 /* The backwards guiding distribution is just mirrored along `sd->N`, so swapping the
324 * sign here is enough to sample from that instead. */
325 if (guide_backward) {
327 }
328 const float3 newD = direction_from_cosine(N, cos_theta, rand_scatter.y);
329 hg_pdf = phase_henyey_greenstein(dot(ray.D, newD), anisotropy);
330 ray.D = newD;
331 }
332 else {
334 ray.D, anisotropy, rand_scatter, &hg_pdf);
335 cos_theta = dot(newD, N);
336 ray.D = newD;
337 }
338
339 /* Compute PDF factor caused by phase sampling (as the ratio of guided / classic).
340 * Since phase sampling is channel-independent, we can get away with applying a factor
341 * to the guided PDF, which implicitly means pulling out the classic PDF term and letting
342 * it cancel with an equivalent term in the numerator of the full estimator.
343 * For the backward PDF, we again reuse the same probability distribution with a sign swap.
344 */
345 forward_pdf_factor = M_1_2PI_F * eval_phase_dwivedi(diffusion_length, phase_log, cos_theta) /
346 hg_pdf;
347 backward_pdf_factor = M_1_2PI_F *
348 eval_phase_dwivedi(diffusion_length, phase_log, -cos_theta) / hg_pdf;
349
350 /* Prepare distance sampling.
351 * For the backwards case, this also needs the sign swapped since now directions against
352 * `sd->N` (and therefore with negative cos_theta) are preferred. */
353 forward_stretching = (1.0f - cos_theta / diffusion_length);
354 backward_stretching = (1.0f + cos_theta / diffusion_length);
355 if (guided) {
356 sample_sigma_t *= guide_backward ? backward_stretching : forward_stretching;
357 }
358 }
359
360 /* Sample distance along ray. */
361 float t = -logf(1.0f - randt) / sample_sigma_t;
362
363 /* On the first bounce, we use the ray-cast to check if the opposite side is nearby.
364 * If yes, we will later use backwards guided sampling in order to have a decent
365 * chance of connecting to it.
366 * TODO: Maybe use less than 10 times the mean free path? */
367 if (bounce == 0) {
368 ray.tmax = max(t, 10.0f / (reduce_min(sigma_t)));
369 }
370 else {
371 ray.tmax = t;
372 /* After the first bounce the object can intersect the same surface again */
373 ray.self.object = OBJECT_NONE;
374 ray.self.prim = PRIM_NONE;
375 }
376 scene_intersect_local<true>(kg, &ray, &ss_isect, object, nullptr, 1);
377 hit = (ss_isect.num_hits > 0);
378
379 if (hit) {
380 ray.tmax = ss_isect.hits[0].t;
381 }
382
383 if (bounce == 0) {
384 /* Check if we hit the opposite side. */
385 if (hit) {
386 have_opposite_interface = true;
387 opposite_distance = dot(ray.P + ray.tmax * ray.D - P, -N);
388 }
389 /* Apart from the opposite side check, we were supposed to only trace up to distance t,
390 * so check if there would have been a hit in that case. */
391 hit = ray.tmax < t;
392 }
393
394 /* Use the distance to the exit point for the throughput update if we found one. */
395 if (hit) {
396 t = ray.tmax;
397 }
398
399 /* Advance to new scatter location. */
400 ray.P += t * ray.D;
401
402 Spectrum transmittance;
403 Spectrum pdf = subsurface_random_walk_pdf(sigma_t, t, hit, &transmittance);
404 if (bounce > 0) {
405 /* Compute PDF just like we do for classic sampling, but with the stretched sigma_t. */
406 Spectrum guided_pdf = subsurface_random_walk_pdf(
407 forward_stretching * sigma_t, t, hit, nullptr);
408
409 if (have_opposite_interface) {
410 /* First step of MIS: Depending on geometry we might have two methods for guided
411 * sampling, so perform MIS between them. */
412 const Spectrum back_pdf = subsurface_random_walk_pdf(
413 backward_stretching * sigma_t, t, hit, nullptr);
414 guided_pdf = mix(
415 guided_pdf * forward_pdf_factor, back_pdf * backward_pdf_factor, backward_fraction);
416 }
417 else {
418 /* Just include phase sampling factor otherwise. */
419 guided_pdf *= forward_pdf_factor;
420 }
421
422 /* Now we apply the MIS balance heuristic between the classic and guided sampling. */
423 pdf = mix(pdf, guided_pdf, guided_fraction);
424 }
425
426 /* Finally, we're applying MIS again to combine the three color channels.
427 * Altogether, the MIS computation combines up to nine different estimators:
428 * {classic, guided, backward_guided} x {r, g, b} */
429 throughput *= (hit ? transmittance : sigma_s * transmittance) / dot(channel_pdf, pdf);
430
431 if (hit) {
432 /* If we hit the surface, we are done. */
433 break;
434 }
435 if (reduce_max(throughput) < VOLUME_THROUGHPUT_EPSILON) {
436 /* Avoid unnecessary work and precision issue when throughput gets really small. */
437 break;
438 }
439 }
440
441 if (hit) {
442 kernel_assert(isfinite_safe(throughput));
443
444 /* TODO(lukas): Which PDF should we report here? Entry bounce? The random walk? Just 1.0? */
446 kg,
447 state,
448 1.0f,
449 N,
450 D,
451 safe_divide_color(throughput, INTEGRATOR_STATE(state, path, throughput)),
452 albedo);
453
454 INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput;
455 }
456
457 return hit;
458}
459
460#endif /* __SUBSURFACE__ */
461
#define D
#define C
Definition RandGen.cpp:29
ATTR_WARN_UNUSED_RESULT const BMVert * v
#define A
ccl_device_inline float cos_theta(const float3 w)
ccl_device_inline Spectrum safe_divide_color(Spectrum a, Spectrum b, const float fallback=0.0f)
Definition color.h:388
reduce_max(value.rgb)") DEFINE_VALUE("REDUCE(lhs
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)
#define kernel_assert(cond)
#define ccl_device_forceinline
#define PRIM_NONE
#define OBJECT_NONE
#define ccl_private
const ThreadKernelGlobalsCPU * KernelGlobals
#define ccl_device_inline
#define BSSRDF_MAX_BOUNCES
#define FOREACH_SPECTRUM_CHANNEL(counter)
#define M_1_2PI_F
#define GET_SPECTRUM_CHANNEL(v, i)
#define logf(x)
#define expf(x)
#define powf(x, y)
#define CCL_NAMESPACE_END
ccl_device_forceinline float differential_zero_compact()
constexpr T clamp(T, U, U) RET
#define VOLUME_THROUGHPUT_EPSILON
ccl_device Spectrum volume_color_transmittance(Spectrum sigma, const float t)
ccl_device int volume_sample_channel(Spectrum albedo, Spectrum throughput, ccl_private float *rand, ccl_private Spectrum *pdf)
ccl_device float volume_channel_get(Spectrum value, const int channel)
ccl_device_forceinline void guiding_record_bssrdf_bounce(KernelGlobals kg, IntegratorState state, const float pdf, const float3 N, const float3 wo, const Spectrum weight, const Spectrum albedo)
@ PRNG_SUBSURFACE_COLOR_CHANNEL
@ PRNG_BOUNCE_NUM
@ PRNG_SUBSURFACE_BSDF
@ PRNG_SUBSURFACE_GUIDE_STRATEGY
@ PRNG_SUBSURFACE_SCATTER_DISTANCE
@ PRNG_SUBSURFACE_GUIDE_DIRECTION
ccl_device_inline bool isfinite_safe(const float f)
Definition math_base.h:348
ccl_device_inline float reduce_min(const float2 a)
ccl_device_inline void make_orthonormals(const float3 N, ccl_private float3 *a, ccl_private float3 *b)
static ulong state[N]
#define N
#define T
#define B
#define F
ccl_device_inline float path_state_rng_1D(KernelGlobals kg, const ccl_private RNGState *rng_state, const int dimension)
Definition path_state.h:353
ccl_device_inline void path_state_rng_scramble(ccl_private RNGState *rng_state, const int seed)
Definition path_state.h:345
ccl_device_inline float2 path_state_rng_2D(KernelGlobals kg, const ccl_private RNGState *rng_state, const int dimension)
Definition path_state.h:361
#define mix
#define fabsf
#define sqrtf
#define ccl_device
#define M_2PI_F
#define atanf
#define fmaxf
#define INTEGRATOR_STATE_WRITE(state, nested_struct, member)
Definition state.h:236
#define INTEGRATOR_STATE(state, nested_struct, member)
Definition state.h:235
IntegratorStateCPU * IntegratorState
Definition state.h:228
#define FLT_MAX
Definition stdcycles.h:14
uint rng_offset
Definition path_state.h:325
float x
float y
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
static int blend(const Tex *tex, const float texvec[3], TexResult *texres)
float3 Spectrum
ccl_device float3 phase_henyey_greenstein_sample(const float3 D, const float g, const float2 rand, ccl_private float *pdf)
Definition volume_util.h:36
ccl_device float phase_henyey_greenstein(const float cos_theta, const float g)
Definition volume_util.h:27