Blender V5.0
subsurface_disk.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
10
13
15
17
18#ifdef __SUBSURFACE__
19
20/* BSSRDF using disk based importance sampling.
21 *
22 * BSSRDF Importance Sampling, SIGGRAPH 2013
23 * http://library.imageworks.com/pdfs/imageworks-library-BSSRDF-sampling.pdf
24 */
25
26ccl_device_inline Spectrum subsurface_disk_eval(const Spectrum radius,
27 const float disk_r,
28 const float r)
29{
30 const Spectrum eval = bssrdf_eval(radius, r);
31 const float pdf = bssrdf_pdf(radius, disk_r);
32 return (pdf > 0.0f) ? eval / pdf : zero_spectrum();
33}
34
35/* Subsurface scattering step, from a point on the surface to other
36 * nearby points on the same object. */
37ccl_device_inline bool subsurface_disk(KernelGlobals kg,
39 RNGState rng_state,
40 ccl_private Ray &ray,
42
43{
44 float2 rand_disk = path_state_rng_2D(kg, &rng_state, PRNG_SUBSURFACE_DISK);
45
46 /* Read shading point info from integrator state. */
47 const float3 P = INTEGRATOR_STATE(state, ray, P);
48 const float ray_dP = INTEGRATOR_STATE(state, ray, dP);
49 const float time = INTEGRATOR_STATE(state, ray, time);
50 const float3 Ng = INTEGRATOR_STATE(state, subsurface, N);
51 const int object = INTEGRATOR_STATE(state, isect, object);
52 const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
53
54 /* Read subsurface scattering parameters. */
55 const Spectrum radius = INTEGRATOR_STATE(state, subsurface, radius);
56
57 /* Pick random axis in local frame and point on disk. */
58 float3 disk_N;
59 float3 disk_T;
60 float3 disk_B;
61 float pick_pdf_N;
62 float pick_pdf_T;
63 float pick_pdf_B;
64
65 disk_N = Ng;
66 make_orthonormals(disk_N, &disk_T, &disk_B);
67
68 if (rand_disk.y < 0.5f) {
69 pick_pdf_N = 0.5f;
70 pick_pdf_T = 0.25f;
71 pick_pdf_B = 0.25f;
72 rand_disk.y *= 2.0f;
73 }
74 else if (rand_disk.y < 0.75f) {
75 const float3 tmp = disk_N;
76 disk_N = disk_T;
77 disk_T = tmp;
78 pick_pdf_N = 0.25f;
79 pick_pdf_T = 0.5f;
80 pick_pdf_B = 0.25f;
81 rand_disk.y = (rand_disk.y - 0.5f) * 4.0f;
82 }
83 else {
84 const float3 tmp = disk_N;
85 disk_N = disk_B;
86 disk_B = tmp;
87 pick_pdf_N = 0.25f;
88 pick_pdf_T = 0.25f;
89 pick_pdf_B = 0.5f;
90 rand_disk.y = (rand_disk.y - 0.75f) * 4.0f;
91 }
92
93 /* Sample point on disk. */
94 const float phi = M_2PI_F * rand_disk.y;
95 float disk_height;
96 float disk_r;
97
98 bssrdf_sample(radius, rand_disk.x, &disk_r, &disk_height);
99
100 const float3 disk_P = to_global(polar_to_cartesian(disk_r, phi), disk_T, disk_B);
101
102 /* Create ray. */
103 ray.P = P + disk_N * disk_height + disk_P;
104 ray.D = -disk_N;
105 ray.tmin = 0.0f;
106 ray.tmax = 2.0f * disk_height;
107 ray.dP = ray_dP;
108 ray.dD = differential_zero_compact();
109 ray.time = time;
110 ray.self.object = OBJECT_NONE;
111 ray.self.prim = PRIM_NONE;
112 ray.self.light_object = OBJECT_NONE;
113 ray.self.light_prim = PRIM_NONE;
114
115 /* Intersect with the same object. if multiple intersections are found it
116 * will use at most BSSRDF_MAX_HITS hits, a random subset of all hits. */
117 uint lcg_state = lcg_state_init(
118 rng_state.rng_pixel, rng_state.rng_offset, rng_state.sample, 0x68bc21eb);
119 const int max_hits = BSSRDF_MAX_HITS;
120
121 scene_intersect_local(kg, &ray, &ss_isect, object, &lcg_state, max_hits);
122 const int num_eval_hits = min(ss_isect.num_hits, max_hits);
123 if (num_eval_hits == 0) {
124 return false;
125 }
126
127 /* Sort for consistent renders between CPU and GPU, independent of the BVH
128 * traversal algorithm. */
129 sort_intersections_and_normals(ss_isect.hits, ss_isect.Ng, num_eval_hits);
130
131 Spectrum weights[BSSRDF_MAX_HITS]; /* TODO: zero? */
132 float sum_weights = 0.0f;
133
134 for (int hit = 0; hit < num_eval_hits; hit++) {
135 /* Get geometric normal. */
136 const int object = ss_isect.hits[hit].object;
137 const int object_flag = kernel_data_fetch(object_flag, object);
138 float3 hit_Ng = ss_isect.Ng[hit];
139 if (path_flag & PATH_RAY_SUBSURFACE_BACKFACING) {
140 hit_Ng = -hit_Ng;
141 }
142 if (object_negative_scale_applied(object_flag)) {
143 hit_Ng = -hit_Ng;
144 }
145
146 if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
147 /* Transform normal to world space. */
148 Transform itfm;
149 object_fetch_transform_motion_test(kg, object, time, &itfm);
150 hit_Ng = normalize(transform_direction_transposed(&itfm, hit_Ng));
151 }
152
153 /* Quickly retrieve P and Ng without setting up ShaderData. */
154 const float3 hit_P = ray.P + ray.D * ss_isect.hits[hit].t;
155
156 /* Probability densities for local frame axes. */
157 const float pdf_N = pick_pdf_N * fabsf(dot(disk_N, hit_Ng));
158 const float pdf_T = pick_pdf_T * fabsf(dot(disk_T, hit_Ng));
159 const float pdf_B = pick_pdf_B * fabsf(dot(disk_B, hit_Ng));
160
161 /* Multiple importance sample between 3 axes, power heuristic
162 * found to be slightly better than balance heuristic. pdf_N
163 * in the MIS weight and denominator cancelled out. */
164 float w = pdf_N / (sqr(pdf_N) + sqr(pdf_T) + sqr(pdf_B));
165 if (ss_isect.num_hits > max_hits) {
166 w *= ss_isect.num_hits / (float)max_hits;
167 }
168
169 /* Real distance to sampled point. */
170 const float r = len(hit_P - P);
171
172 /* Evaluate profiles. */
173 const Spectrum weight = subsurface_disk_eval(radius, disk_r, r) * w;
174
175 /* Store result. */
176 ss_isect.Ng[hit] = hit_Ng;
177 weights[hit] = weight;
178 sum_weights += average(fabs(weight));
179 }
180
181 if (sum_weights == 0.0f) {
182 return false;
183 }
184
185 /* Use importance resampling, sampling one of the hits proportional to weight. */
186 const float rand_resample = path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_DISK_RESAMPLE);
187 const float r = rand_resample * sum_weights;
188 float partial_sum = 0.0f;
189
190 for (int hit = 0; hit < num_eval_hits; hit++) {
191 const Spectrum weight = weights[hit];
192 const float sample_weight = average(fabs(weight));
193 const float next_sum = partial_sum + sample_weight;
194
195 if (r < next_sum) {
196 /* Return exit point. */
197 const Spectrum resampled_weight = weight * sum_weights / sample_weight;
198 INTEGRATOR_STATE_WRITE(state, path, throughput) *= resampled_weight;
199 ss_isect.hits[0] = ss_isect.hits[hit];
200 ss_isect.Ng[0] = ss_isect.Ng[hit];
201
202 ray.P = ray.P + ray.D * ss_isect.hits[hit].t;
203 ray.D = ss_isect.Ng[hit];
204 ray.tmin = 0.0f;
205 ray.tmax = 1.0f;
206
208 kg, state, 1.0f, Ng, -Ng, resampled_weight, INTEGRATOR_STATE(state, subsurface, albedo));
209 return true;
210 }
211
212 partial_sum = next_sum;
213 }
214
215 return false;
216}
217
218#endif /* __SUBSURFACE__ */
219
unsigned int uint
ccl_device void bssrdf_sample(const Spectrum radius, float xi, ccl_private float *r, ccl_private float *h)
Definition bssrdf.h:222
ccl_device_forceinline float bssrdf_pdf(const Spectrum radius, const float r)
Definition bssrdf.h:262
ccl_device_forceinline Spectrum bssrdf_eval(const Spectrum radius, const float r)
Definition bssrdf.h:253
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
nullptr float
dot(value.rgb, luminance_coefficients)") DEFINE_VALUE("REDUCE(lhs
ccl_device_inline void sort_intersections_and_normals(ccl_private Intersection *hits, ccl_private float3 *Ng, uint num_hits)
ccl_device_inline T to_global(const float2 p, const T X, const T Y)
ccl_device_inline float2 polar_to_cartesian(const float r, const float phi)
#define kernel_data_fetch(name, index)
#define PRIM_NONE
#define zero_spectrum
#define OBJECT_NONE
#define ccl_private
const ThreadKernelGlobalsCPU * KernelGlobals
#define ccl_device_inline
#define BSSRDF_MAX_HITS
#define CCL_NAMESPACE_END
ccl_device_forceinline float differential_zero_compact()
VecBase< float, D > normalize(VecOp< float, D >) RET
ccl_device_inline bool object_negative_scale_applied(const int object_flag)
ccl_device_inline Transform object_fetch_transform_motion_test(KernelGlobals kg, const int object, const float time, ccl_private Transform *itfm)
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_DISK_RESAMPLE
@ PRNG_SUBSURFACE_DISK
@ PATH_RAY_SUBSURFACE_BACKFACING
@ SD_OBJECT_TRANSFORM_APPLIED
ccl_device_inline uint lcg_state_init(const uint rng_hash, const uint rng_offset, const uint sample, const uint scramble)
Definition lcg.h:45
ccl_device_inline float2 fabs(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
float average(point a)
Definition node_math.h:144
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 float2 path_state_rng_2D(KernelGlobals kg, const ccl_private RNGState *rng_state, const int dimension)
Definition path_state.h:361
#define sqr
#define fabsf
#define M_2PI_F
#define min(a, b)
Definition sort.cc:36
#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
uint rng_pixel
Definition path_state.h:324
uint rng_offset
Definition path_state.h:325
float x
float y
ccl_device_inline float3 transform_direction_transposed(const ccl_private Transform *t, const float3 a)
Definition transform.h:149
float3 Spectrum
uint len
uint8_t flag
Definition wm_window.cc:145