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