1.3.77
 
Loading...
Searching...
No Matches
rayHit.cu
Go to the documentation of this file.
1
17#include <optix_world.h>
18#include <optixu/optixu_math_namespace.h>
19#include <optixu/optixu_matrix_namespace.h>
20
21#include "RayTracing.cuh"
22#include "BufferIndexing.h"
23
24using namespace optix;
25
26rtDeclareVariable(optix::Ray, ray, rtCurrentRay, );
27rtDeclareVariable(float, t_hit, rtIntersectionDistance, );
28rtDeclareVariable(PerRayData, prd, rtPayload, );
29
30rtDeclareVariable(unsigned int, UUID, attribute UUID, );
31
32// ---------------------------------------------------------------------------
33// Translucent cover (glass/plastic) optical model
34// ---------------------------------------------------------------------------
35
42static __device__ __inline__ float3 glass_tau_rho_alpha(float cos_theta, float n, float KL) {
43 cos_theta = fmaxf(1e-4f, fminf(1.f, cos_theta)); // guard grazing/degenerate
44 const float theta = acos_safe(cos_theta);
45 const float sin_t = sinf(theta);
46 const float sin_tr = sin_t / n; // Snell
47 const float cos_tr = sqrtf(fmaxf(0.f, 1.f - sin_tr * sin_tr));
48 const float theta_r = asin_safe(sin_tr);
49
50 float r_par, r_per;
51 if (theta < 1e-3f) {
52 const float r0 = ((n - 1.f) / (n + 1.f)) * ((n - 1.f) / (n + 1.f));
53 r_par = r0;
54 r_per = r0;
55 } else {
56 const float s_minus = sinf(theta_r - theta);
57 const float s_plus = sinf(theta_r + theta);
58 const float t_minus = tanf(theta_r - theta);
59 const float t_plus = tanf(theta_r + theta);
60 r_per = (s_minus * s_minus) / fmaxf(1e-12f, s_plus * s_plus); // perpendicular
61 r_par = (t_minus * t_minus) / fmaxf(1e-12f, t_plus * t_plus); // parallel
62 }
63
64 const float tau_a = (KL > 0.f) ? expf(-KL / fmaxf(1e-4f, cos_tr)) : 1.f; // Bouguer absorption
65
66 float tau = 0.f, rho = 0.f;
67 for (int pol = 0; pol < 2; pol++) {
68 const float r = (pol == 0) ? r_per : r_par;
69 const float denom = fmaxf(1e-6f, 1.f - (r * tau_a) * (r * tau_a));
70 const float tau_i = tau_a * (1.f - r) * (1.f - r) / denom;
71 const float rho_i = r * (1.f + tau_a * tau_i);
72 tau += 0.5f * tau_i;
73 rho += 0.5f * rho_i;
74 }
75 tau = fmaxf(0.f, fminf(1.f, tau));
76 rho = fmaxf(0.f, fminf(1.f, rho));
77 float alpha = 1.f - tau - rho;
78 if (alpha < 0.f) {
79 alpha = 0.f;
80 } // numerical guard
81 return make_float3(tau, rho, alpha);
82}
83
88static __device__ __inline__ float coverNormalFaceCos(uint hit_position, const float3 &ray_dir, bool &face_top) {
89 float m[16];
90 for (uint i = 0; i < 16; i++) {
91 m[i] = transform_matrix[optix::make_uint2(i, hit_position)];
92 }
93 float3 normal = make_float3(0.f, 0.f, 1.f);
94 const uint ptype = primitive_type[hit_position];
95 if (ptype == 0 || ptype == 3) { // patch or tile
96 float3 s0 = make_float3(0, 0, 0), s1 = make_float3(1, 0, 0), s2 = make_float3(0, 1, 0);
97 d_transformPoint(m, s0);
98 d_transformPoint(m, s1);
99 d_transformPoint(m, s2);
100 normal = cross(s1 - s0, s2 - s0);
101 } else if (ptype == 1) { // triangle
102 float3 v0 = make_float3(0, 0, 0), v1 = make_float3(0, 1, 0), v2 = make_float3(1, 1, 0);
103 d_transformPoint(m, v0);
104 d_transformPoint(m, v1);
105 d_transformPoint(m, v2);
106 normal = cross(v1 - v0, v2 - v0);
107 } else if (ptype == 2) { // disk
108 float3 v0 = make_float3(0, 0, 0), v1 = make_float3(1, 0, 0), v2 = make_float3(0, 1, 0);
109 d_transformPoint(m, v0);
110 d_transformPoint(m, v1);
111 d_transformPoint(m, v2);
112 normal = cross(v1 - v0, v2 - v0);
113 }
114 const float nmag = d_magnitude(normal);
115 if (nmag < 1e-12f) {
116 face_top = true;
117 return 1.f;
118 }
119 normal = normal / nmag;
120 face_top = dot(normal, ray_dir) < 0.f;
121 return fminf(1.f, fabsf(dot(ray_dir, normal)));
122}
123
124RT_PROGRAM void closest_hit_direct() {
125
126 uint hit_position = primitive_positions[UUID];
127
128 // Bounds check: skip if position is invalid
129 if (hit_position == UINT_MAX) {
130 return;
131 }
132
133 if ((periodic_flag.x == 1 || periodic_flag.y == 1) && primitive_type[hit_position] == 5) { // periodic boundary condition
134
135 prd.hit_periodic_boundary = true;
136
137 float3 ray_origin = ray.origin + t_hit * ray.direction;
138
139 float eps = 1e-5;
140
141 float2 xbounds = make_float2(bbox_vertices[make_uint2(0, 0)].x, bbox_vertices[make_uint2(1, 1)].x);
142 float2 ybounds = make_float2(bbox_vertices[make_uint2(0, 0)].y, bbox_vertices[make_uint2(1, 1)].y);
143
144 float width_x = xbounds.y - xbounds.x;
145 float width_y = ybounds.y - ybounds.x;
146
147 prd.periodic_hit = ray_origin;
148 if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.x) <= eps) { //-x facing boundary
149 prd.periodic_hit.x += +width_x - eps;
150 } else if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.y) <= eps) { //+x facing boundary
151 prd.periodic_hit.x += -width_x + eps;
152 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.x) <= eps) { //-y facing boundary
153 prd.periodic_hit.y += +width_y - eps;
154 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.y) <= eps) { //+y facing boundary
155 prd.periodic_hit.y += -width_y + eps;
156 }
157 }
158};
159
160RT_PROGRAM void closest_hit_diffuse() {
161
162 // Convert UUIDs to array positions for buffer indexing
163 uint origin_UUID = prd.origin_UUID;
164 uint origin_position = primitive_positions[origin_UUID];
165 uint hit_position = primitive_positions[UUID];
166
167 // Bounds check: skip if positions are invalid
168 if (origin_position == UINT_MAX || hit_position == UINT_MAX) {
169 return;
170 }
171
172 // Create indexers for buffer access
173 RadiationBufferIndexer rad_indexer(Nprimitives, Nbands_launch);
174 MaterialPropertyIndexer mat_indexer(Nsources, Nprimitives, Nbands_global);
175 CameraMaterialIndexer cam_mat_indexer(Nsources, Nprimitives, Nbands_global, Ncameras);
176
177 if ((periodic_flag.x == 1 || periodic_flag.y == 1) && primitive_type[hit_position] == 5) { // periodic boundary condition
178
179 prd.hit_periodic_boundary = true;
180
181 float3 ray_origin = ray.origin + t_hit * ray.direction;
182
183 float eps = 1e-5;
184
185 float2 xbounds = make_float2(bbox_vertices[make_uint2(0, 0)].x, bbox_vertices[make_uint2(1, 1)].x);
186 float2 ybounds = make_float2(bbox_vertices[make_uint2(0, 0)].y, bbox_vertices[make_uint2(1, 1)].y);
187
188 float width_x = xbounds.y - xbounds.x;
189 float width_y = ybounds.y - ybounds.x;
190
191 prd.periodic_hit = ray_origin;
192 if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.x) <= eps) { //-x facing boundary
193 prd.periodic_hit.x += +width_x - eps;
194 } else if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.y) <= eps) { //+x facing boundary
195 prd.periodic_hit.x += -width_x + eps;
196 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.x) <= eps) { //-y facing boundary
197 prd.periodic_hit.y += +width_y - eps;
198 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.y) <= eps) { //+y facing boundary
199 prd.periodic_hit.y += -width_y + eps;
200 }
201
202 } else {
203
204 // Note: UUID corresponds to the object that the ray hit (i.e., where energy is coming from), and UUID_origin is the object the ray originated from (i.e., where the energy is being recieved)
205
206 // find out if we hit top or bottom surface
207 float3 normal;
208
209 float m[16];
210 for (uint i = 0; i < 16; i++) {
211 m[i] = transform_matrix[optix::make_uint2(i, hit_position)];
212 }
213
214 if (primitive_type[hit_position] == 0 || primitive_type[hit_position] == 3) { // hit patch or tile
215 float3 s0 = make_float3(0, 0, 0);
216 float3 s1 = make_float3(1, 0, 0);
217 float3 s2 = make_float3(0, 1, 0);
218 d_transformPoint(m, s0);
219 d_transformPoint(m, s1);
220 d_transformPoint(m, s2);
221 normal = cross(s1 - s0, s2 - s0);
222 } else if (primitive_type[hit_position] == 1) { // hit triangle
223 float3 v0 = make_float3(0, 0, 0);
224 d_transformPoint(m, v0);
225 float3 v1 = make_float3(0, 1, 0);
226 d_transformPoint(m, v1);
227 float3 v2 = make_float3(1, 1, 0);
228 d_transformPoint(m, v2);
229 normal = cross(v1 - v0, v2 - v0);
230 } else if (primitive_type[hit_position] == 2) { // hit disk
231 float3 v0 = make_float3(0, 0, 0);
232 d_transformPoint(m, v0);
233 float3 v1 = make_float3(1, 0, 0);
234 d_transformPoint(m, v1);
235 float3 v2 = make_float3(0, 1, 0);
236 d_transformPoint(m, v2);
237 normal = cross(v1 - v0, v2 - v0);
238 } else if (primitive_type[hit_position] == 4) { // hit voxel
239 float3 vmin = make_float3(-0.5, -0.5, -0.5);
240 d_transformPoint(m, vmin);
241 float3 vmax = make_float3(0.5, 0.5, 0.5);
242 d_transformPoint(m, vmax);
243 }
244 normal = normalize(normal);
245
246 bool face = dot(normal, ray.direction) < 0;
247
248 int b = -1;
249 for (int b_global = 0; b_global < Nbands_global; b_global++) {
250
251 if (band_launch_flag[b_global] == 0) {
252 continue;
253 }
254 b++;
255
256 // Use BufferIndexer for radiation buffers: [primitive][band]
257 // NOTE: b should match the band's index in the original band_labels array
258 // This is correct as long as band_launch_flag isn't modified after launch
259 size_t ind_origin = rad_indexer(origin_position, b);
260 size_t ind_hit = rad_indexer(hit_position, b);
261
262 double strength;
263 if (face || primitive_type[hit_position] == 4) {
264 strength = radiation_out_top[ind_hit] * prd.strength;
265 } else {
266 strength = radiation_out_bottom[ind_hit] * prd.strength;
267 }
268
269 if (strength == 0) {
270 continue;
271 }
272
273 // Use BufferIndexer for material properties: [source][primitive][band]
274 size_t radprop_ind_global = mat_indexer(prd.source_ID, origin_position, b_global);
275 float t_rho = rho[radprop_ind_global];
276 float t_tau = tau[radprop_ind_global];
277
278 // Check if ray was launched from voxel (type 4)
279 if (primitive_type[origin_position] == 4) { // ray was launched from voxel
280
281 // float kappa = t_rho; //just a reminder that rho is actually the absorption coefficient
282 // float sigma_s = t_tau; //just a reminder that tau is actually the scattering coefficient
283 // float beta = kappa + sigma_s;
284 //
285 // // absorption
286 // atomicAdd(&radiation_in[ind_origin], strength * exp(-beta * 0.5 * prd.area) * kappa / beta);
287 //
288 // // scattering
289 // atomicAdd(&scatter_buff_top[ind_origin], strength * exp(-beta * 0.5 * prd.area) * sigma_s / beta);
290
291 } else { // ray was NOT launched from voxel
292
293 // absorption - calculate with defensive check for energy conservation violations
294 float absorption_factor = 1.f - t_rho - t_tau;
295 float contribution = strength * absorption_factor;
296
297
298#ifndef NDEBUG
299 if (absorption_factor < -1e-5f) {
300 printf("ERROR: Negative absorption! rho=%.6f, tau=%.6f, origin_UUID=%u\n", t_rho, t_tau, origin_UUID);
301 absorption_factor = 0.f;
302 }
303#endif
304 atomicAdd(&radiation_in[ind_origin], contribution);
305
306 if ((t_rho > 0 || t_tau > 0) && strength > 0) {
307 if (prd.face) { // reflection from top, transmission from bottom
308 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_rho); // reflection
309 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_tau); // transmission
310 } else { // reflection from bottom, transmission from top
311 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_rho); // reflection
312 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_tau); // transmission
313 }
314 }
315 if (Ncameras > 0) {
316 // Use BufferIndexer for camera material: [source][primitive][band][camera]
317 size_t indc = cam_mat_indexer(prd.source_ID, origin_position, b_global, camera_ID);
318 float t_rho_cam = rho_cam[indc];
319 float t_tau_cam = tau_cam[indc];
320 if ((t_rho_cam > 0 || t_tau_cam > 0) && strength > 0) {
321 if (prd.face) { // reflection from top, transmission from bottom
322 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_rho_cam); // reflection
323 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_tau_cam); // transmission
324 } else { // reflection from bottom, transmission from top
325 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_rho_cam); // reflection
326 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_tau_cam); // transmission
327 }
328 }
329 // Note: Don't accumulate scattered radiation to radiation_specular
330 // Specular should only reflect DIRECT source radiation (accumulated in miss_direct)
331 }
332 }
333
334 // if( primitive_type[UUID] == 4 ){ //if we hit a voxel, reduce strength and launch another ray
335 // optix::Ray ray_transmit = optix::make_Ray(ray.origin+(t_hit+prd.area+1e-5)*ray.direction, ray.direction, ray.ray_type, 1e-4, RT_DEFAULT_MAX);
336 // PerRayData prd_transmit = prd;
337 // float beta = rho[UUID]+tau[UUID];
338 // prd_transmit.strength = prd.strength*(1.f-exp(-beta*0.5*prd.area));
339 // rtTrace( top_object, ray_transmit, prd_transmit);
340 // }
341 }
342 }
343}
344
345RT_PROGRAM void closest_hit_camera() {
346
347 // Convert UUID to array position
348 uint hit_position = primitive_positions[UUID];
349
350 // Bounds check: skip if position is invalid
351 if (hit_position == UINT_MAX) {
352 return;
353 }
354
355 // For cameras, origin_UUID is actually the pixel index (not a primitive UUID!)
356 uint pixel_index = prd.origin_UUID;
357 size_t Npixels = camera_resolution_full.x * camera_resolution_full.y;
358
359 // Create indexers
360 RadiationBufferIndexer rad_indexer(Npixels, Nbands_launch); // Use Npixels for camera radiation buffer
361 SourceFluxIndexer source_flux_indexer(Nsources, Nbands_launch);
362 SpecularRadiationIndexer spec_indexer(Nsources, Ncameras, Nprimitives, Nbands_launch);
363
364 if ((periodic_flag.x == 1 || periodic_flag.y == 1) && primitive_type[hit_position] == 5) { // periodic boundary condition
365
366 prd.hit_periodic_boundary = true;
367
368 float3 ray_origin = ray.origin + t_hit * ray.direction;
369
370 float eps = 1e-5;
371
372 float2 xbounds = make_float2(bbox_vertices[make_uint2(0, 0)].x, bbox_vertices[make_uint2(1, 1)].x);
373 float2 ybounds = make_float2(bbox_vertices[make_uint2(0, 0)].y, bbox_vertices[make_uint2(1, 1)].y);
374
375 float width_x = xbounds.y - xbounds.x;
376 float width_y = ybounds.y - ybounds.x;
377
378 prd.periodic_hit = ray_origin;
379 if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.x) <= eps) { //-x facing boundary
380 prd.periodic_hit.x += +width_x - eps;
381 } else if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.y) <= eps) { //+x facing boundary
382 prd.periodic_hit.x += -width_x + eps;
383 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.x) <= eps) { //-y facing boundary
384 prd.periodic_hit.y += +width_y - eps;
385 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.y) <= eps) { //+y facing boundary
386 prd.periodic_hit.y += -width_y + eps;
387 }
388
389 } else {
390
391 // Note: UUID corresponds to the object that the ray hit (i.e., where energy is coming from), and UUID_origin is the object the ray originated from (i.e., where the energy is being received)
392
393 // find out if we hit top or bottom surface per each ray
394 float3 normal;
395
396 float m[16];
397 for (uint i = 0; i < 16; i++) {
398 m[i] = transform_matrix[optix::make_uint2(i, hit_position)];
399 }
400
401 if (primitive_type[hit_position] == 0 || primitive_type[hit_position] == 3) { // hit patch or tile
402 float3 s0 = make_float3(0, 0, 0);
403 float3 s1 = make_float3(1, 0, 0);
404 float3 s2 = make_float3(0, 1, 0);
405 d_transformPoint(m, s0);
406 d_transformPoint(m, s1);
407 d_transformPoint(m, s2);
408 normal = cross(s1 - s0, s2 - s0);
409 } else if (primitive_type[hit_position] == 1) { // hit triangle
410 float3 v0 = make_float3(0, 0, 0);
411 d_transformPoint(m, v0);
412 float3 v1 = make_float3(0, 1, 0);
413 d_transformPoint(m, v1);
414 float3 v2 = make_float3(1, 1, 0);
415 d_transformPoint(m, v2);
416 normal = cross(v1 - v0, v2 - v0);
417 } else if (primitive_type[hit_position] == 2) { // hit disk
418 float3 v0 = make_float3(0, 0, 0);
419 d_transformPoint(m, v0);
420 float3 v1 = make_float3(1, 0, 0);
421 d_transformPoint(m, v1);
422 float3 v2 = make_float3(0, 1, 0);
423 d_transformPoint(m, v2);
424 normal = cross(v1 - v0, v2 - v0);
425 } else if (primitive_type[hit_position] == 4) { // hit voxel
426 float3 vmin = make_float3(-0.5, -0.5, -0.5);
427 d_transformPoint(m, vmin);
428 float3 vmax = make_float3(0.5, 0.5, 0.5);
429 d_transformPoint(m, vmax);
430 }
431 normal = normalize(normal);
432
433 bool face = dot(normal, ray.direction) < 0;
434
435
436 float3 camera_normal = d_rotatePoint(make_float3(0, 0, 1), -0.5 * M_PI + camera_direction.x, 0.5f * M_PI - camera_direction.y);
437
438 double strength;
439 for (size_t b = 0; b < Nbands_launch; b++) {
440
441 // Check if any light sources are between camera and hit point
442 float source_radiance = 0.0f;
443 for (uint s = 0; s < Nsources; s++) {
444 float flux = source_fluxes[s * Nbands_launch + b];
445 if (flux <= 0.0f)
446 continue;
447
448 uint source_type = source_types[s];
449
450 if (source_type == 1) {
451 // SPHERE
452 float radius = source_widths[s].x * 0.5f;
453 float3 oc = ray.origin - source_positions[s];
454 float b = dot(oc, ray.direction);
455 float c = dot(oc, oc) - radius * radius;
456 float discriminant = b * b - c;
457
458 if (discriminant >= 0.0f) {
459 float t_sphere = -b - sqrtf(discriminant);
460 if (t_sphere > 0.0f && t_sphere < t_hit) {
461 float area = 4.0f * M_PI * radius * radius;
462 source_radiance += (flux / area) / M_PI;
463 }
464 }
465 } else if (source_type == 3) {
466 // RECTANGLE
467 float transform[16];
468 d_makeTransformMatrix(source_rotations[s], transform);
469 float3 normal = make_float3(transform[2], transform[6], transform[10]);
470
471 float denom = dot(ray.direction, normal);
472 if (denom < -1e-6f) {
473 float3 oc = source_positions[s] - ray.origin;
474 float t_rect = dot(oc, normal) / denom;
475
476 if (t_rect > 0.0f && t_rect < t_hit) {
477 float3 hit_point = ray.origin + t_rect * ray.direction;
478 float3 local_hit = hit_point - source_positions[s];
479 float inv_transform[16];
480 d_invertMatrix(transform, inv_transform);
481 d_transformPoint(inv_transform, local_hit);
482
483 if (fabsf(local_hit.x) <= source_widths[s].x * 0.5f && fabsf(local_hit.y) <= source_widths[s].y * 0.5f) {
484 float area = source_widths[s].x * source_widths[s].y;
485 float cos_angle = -denom;
486 source_radiance += (flux / area) * cos_angle / M_PI;
487 }
488 }
489 }
490 } else if (source_type == 4) {
491 // DISK
492 float transform[16];
493 d_makeTransformMatrix(source_rotations[s], transform);
494 float3 normal = make_float3(transform[2], transform[6], transform[10]);
495
496 float denom = dot(ray.direction, normal);
497 if (denom < -1e-6f) {
498 float3 oc = source_positions[s] - ray.origin;
499 float t_disk = dot(oc, normal) / denom;
500
501 if (t_disk > 0.0f && t_disk < t_hit) {
502 float3 hit_point = ray.origin + t_disk * ray.direction;
503 float3 offset = hit_point - source_positions[s];
504 float dist_sq = dot(offset, offset);
505
506 float radius = source_widths[s].x;
507 if (dist_sq <= radius * radius) {
508 float area = M_PI * radius * radius;
509 float cos_angle = -denom;
510 source_radiance += (flux / area) * cos_angle / M_PI;
511 }
512 }
513 }
514 }
515 }
516
517 // Use BufferIndexer: [primitive][band]
518 size_t ind_hit = rad_indexer(hit_position, b);
519
520 if (face || primitive_type[hit_position] == 4) {
521 strength = radiation_out_top[ind_hit] * prd.strength;
522 } else {
523 strength = radiation_out_bottom[ind_hit] * prd.strength;
524 }
525
526 if (source_radiance > 0.0f) {
527 strength += source_radiance * prd.strength;
528 }
529
530 // specular reflection
531 // Only compute specular on iteration 0 to prevent accumulation across scattering iterations.
532 // radiation_specular contains per-source, camera-weighted incident radiation
533
534 double strength_spec = 0;
535 if (specular_reflection_enabled > 0 && specular_exponent[hit_position] > 0.f && scattering_iteration == 0) {
536
537 // For each source, compute specular contribution
538 for (int rr = 0; rr < Nsources; rr++) {
539
540 // Get camera-weighted incident radiation from this source
541 // Already has source color and camera response weighting applied
542 // Use BufferIndexer: [source][camera][primitive][band]
543 size_t ind_specular = spec_indexer(rr, camera_ID, hit_position, b);
544 float spec = radiation_specular[ind_specular];
545
546 // Apply default 0.25 scaling factor (typical Fresnel reflectance for dielectrics is ~4%,
547 // but this accounts for specular lobe concentration and typical surface roughness)
548 spec *= 0.25f;
549
550 if (spec > 0) {
551 // Determine light direction based on source type
552 float3 light_direction;
553 if (source_types[rr] == 0 || source_types[rr] == 2) {
554 // Collimated or sunsphere: parallel rays from source direction
555 light_direction = normalize(source_positions[rr]);
556 } else {
557 // Sphere, disk, or rectangle: direction from hit point to source center
558 float3 hit_point = ray.origin + t_hit * ray.direction;
559 light_direction = normalize(source_positions[rr] - hit_point);
560 }
561
562 // Blinn-Phong specular direction (half-vector)
563 float3 specular_direction = normalize(light_direction - ray.direction);
564
565 float exponent = specular_exponent[hit_position];
566 double scale_coefficient = 1.0;
567 if (specular_reflection_enabled == 2) { // if we are using the scale coefficient
568 scale_coefficient = specular_scale[hit_position];
569 }
570
571 strength_spec += spec * scale_coefficient * pow(max(0.f, dot(specular_direction, normal)), exponent) * (exponent + 2.f) /
572 (double(launch_dim.x) * 2.f * M_PI); // launch_dim.x is the number of rays launched per pixel, so we divide by it to get the average flux per ray. (exponent+2)/2pi normalizes reflected distribution to unity.
573 }
574 }
575 }
576
577 // absorption
578 // For cameras, use pixel index directly (no UUID lookup needed)
579 // Use BufferIndexer: [pixel][band]
580 size_t ind_camera = rad_indexer(pixel_index, b);
581
582 atomicAdd(&radiation_in_camera[ind_camera],
583 (strength + strength_spec) / M_PI); // note: pi factor is to convert from flux to intensity assuming surface is Lambertian. We don't multiply by the solid angle by convention to avoid very small numbers.
584 }
585 }
586}
587
588RT_PROGRAM void closest_hit_pixel_label() {
589
590 uint origin_UUID = prd.origin_UUID;
591
592 uint hit_position = primitive_positions[UUID];
593
594 if ((periodic_flag.x == 1 || periodic_flag.y == 1) && primitive_type[hit_position] == 5) { // periodic boundary condition
595
596 prd.hit_periodic_boundary = true;
597
598 float3 ray_origin = ray.origin + t_hit * ray.direction;
599
600 float eps = 1e-5;
601
602 float2 xbounds = make_float2(bbox_vertices[make_uint2(0, 0)].x, bbox_vertices[make_uint2(1, 1)].x);
603 float2 ybounds = make_float2(bbox_vertices[make_uint2(0, 0)].y, bbox_vertices[make_uint2(1, 1)].y);
604
605 float width_x = xbounds.y - xbounds.x;
606 float width_y = ybounds.y - ybounds.x;
607
608 prd.periodic_hit = ray_origin;
609 if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.x) <= eps) { //-x facing boundary
610 prd.periodic_hit.x += +width_x - eps;
611 } else if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.y) <= eps) { //+x facing boundary
612 prd.periodic_hit.x += -width_x + eps;
613 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.x) <= eps) { //-y facing boundary
614 prd.periodic_hit.y += +width_y - eps;
615 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.y) <= eps) { //+y facing boundary
616 prd.periodic_hit.y += -width_y + eps;
617 }
618
619 } else {
620
621 // Note: UUID corresponds to the object that the ray hit (i.e., where energy is coming from), and UUID_origin is the object the ray originated from (i.e., where the energy is being received)
622 // Note: We are reserving a value of 0 for the sky, so we will store UUID+1
623 camera_pixel_label[origin_UUID] = UUID + 1;
624
625 float depth = prd.strength + t_hit;
626 float3 camera_direction3 = d_rotatePoint(make_float3(1, 0, 0), -0.5 * M_PI + camera_direction.x, 0.5f * M_PI - camera_direction.y);
627 camera_pixel_depth[origin_UUID] = abs(dot(camera_direction3, ray.direction)) * depth;
628 }
629}
630
631// ---------------------------------------------------------------------------
632// Translucent cover (glass/plastic) any-hit programs
633// ---------------------------------------------------------------------------
634// A glass cover is not an occluder: the ray passes through it (rtIgnoreIntersection) attenuated by the
635// Fresnel+Bouguer tau(theta), which accumulates per band in prd.cover_transmittance and is applied to
636// the deposited flux in the miss programs. The cover's own reflected/absorbed energy is deposited here.
637// Mirrors coverAnyHitBody() in optix8/OptiX8DeviceCode.cu. Direct rays use the per-source flux; diffuse
638// rays use the (isotropic) sky flux for the cover's rho/alpha bookkeeping — the transmittance
639// accumulation itself is exact and flux-independent.
640
641RT_PROGRAM void any_hit_direct() {
642
643 if (glass_enabled == 0u) {
644 return; // glass model not in use this launch — accept the hit (normal occlusion)
645 }
646
647 uint hit_position = primitive_positions[UUID];
648 if (hit_position == UINT_MAX) {
649 return;
650 }
651 // The intersection program already skips self-hits (origin_UUID == UUID); guard defensively.
652 if (UUID == prd.origin_UUID) {
653 rtIgnoreIntersection();
654 return;
655 }
656
657 MaterialPropertyIndexer mat_indexer(Nsources, Nprimitives, Nbands_global);
658 RadiationBufferIndexer rad_indexer(Nprimitives, Nbands_launch);
659 SourceFluxIndexer source_flux_indexer(Nsources, Nbands_launch);
660
661 // Pass-through only if this primitive is glass in EVERY launched band. A single ray carries all
662 // launched bands, so a primitive that is glass in some launched bands but opaque in others cannot be
663 // both transmitted and blocked — treat it as a normal (opaque) occluder for the whole ray (accept
664 // the hit). This matches the OptiX 8 and Vulkan backends.
665 {
666 int b_check = -1;
667 for (int b_global = 0; b_global < Nbands_global; b_global++) {
668 if (band_launch_flag[b_global] == 0) {
669 continue;
670 }
671 b_check++;
672 size_t mat_ind = mat_indexer(prd.source_ID, hit_position, b_global);
673 if (is_glass[mat_ind] == 0.f) {
674 return; // opaque in this launched band → block the ray
675 }
676 }
677 }
678
679 bool face_top;
680 float cos_theta = coverNormalFaceCos(hit_position, ray.direction, face_top);
681
682 int b = -1;
683 for (int b_global = 0; b_global < Nbands_global; b_global++) {
684 if (band_launch_flag[b_global] == 0) {
685 continue;
686 }
687 b++;
688 if (b >= HELIOS_MAX_RADIATION_BANDS) {
689 break; // host guarantees this never trips
690 }
691
692 size_t mat_ind = mat_indexer(prd.source_ID, hit_position, b_global);
693 float3 tra = glass_tau_rho_alpha(cos_theta, glass_n[mat_ind], glass_KL[mat_ind]); // (tau, rho, alpha)
694
695 size_t flux_idx = source_flux_indexer(prd.source_ID, b);
696 double ext_flux = source_fluxes[flux_idx];
697 double incoming = prd.strength * ext_flux * (double) prd.cover_transmittance[b];
698
699 size_t cov_ind = rad_indexer(hit_position, b);
700 if (incoming > 0.0) {
701 atomicFloatAdd(&radiation_in[cov_ind], (float) (incoming * (double) tra.z)); // absorbed
702 // Reflection leaves from the hit face; transmission continues out the far face.
703 if (face_top) {
704 atomicFloatAdd(&scatter_buff_top[cov_ind], (float) (incoming * (double) tra.y));
705 } else {
706 atomicFloatAdd(&scatter_buff_bottom[cov_ind], (float) (incoming * (double) tra.y));
707 }
708 }
709
710 prd.cover_transmittance[b] *= tra.x; // attenuate the through-beam for this band
711 }
712
713 // All launched bands are glass: pass the (attenuated) ray through toward the source.
714 rtIgnoreIntersection();
715}
716
717RT_PROGRAM void any_hit_diffuse() {
718
719 if (glass_enabled == 0u) {
720 return;
721 }
722
723 uint hit_position = primitive_positions[UUID];
724 if (hit_position == UINT_MAX) {
725 return;
726 }
727 if (UUID == prd.origin_UUID) {
728 rtIgnoreIntersection();
729 return;
730 }
731
732 MaterialPropertyIndexer mat_indexer(Nsources, Nprimitives, Nbands_global);
733 RadiationBufferIndexer rad_indexer(Nprimitives, Nbands_launch);
734
735 // Pass-through only if glass in EVERY launched band; otherwise treat as opaque (accept → block).
736 // Matches the OptiX 8 and Vulkan backends.
737 {
738 int b_check = -1;
739 for (int b_global = 0; b_global < Nbands_global; b_global++) {
740 if (band_launch_flag[b_global] == 0) {
741 continue;
742 }
743 b_check++;
744 size_t mat_ind = mat_indexer(prd.source_ID, hit_position, b_global);
745 if (is_glass[mat_ind] == 0.f) {
746 return; // opaque in this launched band → block the ray
747 }
748 }
749 }
750
751 bool face_top;
752 float cos_theta = coverNormalFaceCos(hit_position, ray.direction, face_top);
753
754 int b = -1;
755 for (int b_global = 0; b_global < Nbands_global; b_global++) {
756 if (band_launch_flag[b_global] == 0) {
757 continue;
758 }
759 b++;
760 if (b >= HELIOS_MAX_RADIATION_BANDS) {
761 break;
762 }
763
764 size_t mat_ind = mat_indexer(prd.source_ID, hit_position, b_global);
765 float3 tra = glass_tau_rho_alpha(cos_theta, glass_n[mat_ind], glass_KL[mat_ind]);
766
767 double ext_flux = diffuse_flux[b];
768 double incoming = prd.strength * ext_flux * (double) prd.cover_transmittance[b];
769
770 size_t cov_ind = rad_indexer(hit_position, b);
771 if (incoming > 0.0) {
772 atomicFloatAdd(&radiation_in[cov_ind], (float) (incoming * (double) tra.z));
773 if (face_top) {
774 atomicFloatAdd(&scatter_buff_top[cov_ind], (float) (incoming * (double) tra.y));
775 } else {
776 atomicFloatAdd(&scatter_buff_bottom[cov_ind], (float) (incoming * (double) tra.y));
777 }
778 }
779
780 prd.cover_transmittance[b] *= tra.x;
781 }
782
783 // All launched bands are glass: pass the (attenuated) ray through toward the sky.
784 rtIgnoreIntersection();
785}
786
787RT_PROGRAM void miss_direct() {
788
789 // Convert UUID to array position
790 uint origin_position = primitive_positions[prd.origin_UUID];
791
792 // Create indexers
793 RadiationBufferIndexer rad_indexer(Nprimitives, Nbands_launch);
794 MaterialPropertyIndexer mat_indexer(Nsources, Nprimitives, Nbands_global);
795 SourceFluxIndexer source_flux_indexer(Nsources, Nbands_launch);
796 CameraMaterialIndexer cam_mat_indexer(Nsources, Nprimitives, Nbands_global, Ncameras);
797 SourceCameraFluxIndexer source_cam_flux_indexer(Nsources, Nbands_launch, Ncameras);
798 SpecularRadiationIndexer spec_indexer(Nsources, Ncameras, Nprimitives, Nbands_launch);
799
800 int b = -1;
801 for (int b_global = 0; b_global < Nbands_global; b_global++) {
802
803 if (band_launch_flag[b_global] == 0) {
804 continue;
805 }
806 b++;
807
808 // Use BufferIndexer: [primitive][band]
809 size_t ind_origin = rad_indexer(origin_position, b);
810
811 // Use BufferIndexer: [source][primitive][band]
812 size_t radprop_ind_global = mat_indexer(prd.source_ID, origin_position, b_global);
813 float t_rho = rho[radprop_ind_global];
814 float t_tau = tau[radprop_ind_global];
815
816 // Use BufferIndexer: [source][band]
817 size_t flux_idx = source_flux_indexer(prd.source_ID, b);
818 float source_flux = source_fluxes[flux_idx];
819 // Attenuation from any translucent covers (glass/plastic) the ray passed through, per band.
820 float cover_tau = (b < HELIOS_MAX_RADIATION_BANDS) ? prd.cover_transmittance[b] : 1.f;
821 double strength = prd.strength * source_flux * cover_tau;
822 float absorption = strength * (1.f - t_rho - t_tau);
823
824 // absorption
825 atomicAdd(&radiation_in[ind_origin], absorption);
826
827 if (t_rho > 0 || t_tau > 0) {
828 if (prd.face) { // reflection from top, transmission from bottom
829 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_rho); // reflection
830 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_tau); // transmission
831 } else { // reflection from bottom, transmission from top
832 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_rho); // reflection
833 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_tau); // transmission
834 }
835 }
836 if (Ncameras > 0) {
837 // Use BufferIndexer: [source][primitive][band][camera]
838 size_t indc = cam_mat_indexer(prd.source_ID, origin_position, b_global, camera_ID);
839 float t_rho_cam = rho_cam[indc];
840 float t_tau_cam = tau_cam[indc];
841 if ((t_rho_cam > 0 || t_tau_cam > 0) && strength > 0) {
842 if (prd.face) { // reflection from top, transmission from bottom
843 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_rho_cam); // reflection
844 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_tau_cam); // transmission
845 } else { // reflection from bottom, transmission from top
846 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_rho_cam); // reflection
847 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_tau_cam); // transmission
848 }
849 }
850 // Accumulate incident radiation for specular for ALL cameras (per source, camera-weighted).
851 // Direct rays are launched once (not per camera), so we must populate every camera's slot
852 // in radiation_specular here so each camera's closest-hit can read its own data. (Mirrors
853 // the OptiX 8 path in OptiX8DeviceCode.cu; without the camera loop only camera_ID==0 was
854 // populated and additional cameras saw no specular highlight.)
855 // Apply camera spectral response weighting: ∫(source × camera) / ∫(source)
856 if (strength > 0) {
857 for (unsigned int cam = 0; cam < Ncameras; cam++) {
858 // Use BufferIndexer: [source][band][camera]
859 size_t weight_ind = source_cam_flux_indexer(prd.source_ID, b, cam);
860 float camera_weight = source_fluxes_cam[weight_ind];
861 // Use BufferIndexer: [source][camera][primitive][band] (note different order!)
862 size_t ind_specular = spec_indexer(prd.source_ID, cam, origin_position, b);
863 atomicFloatAdd(&radiation_specular[ind_specular], strength * camera_weight);
864 }
865 }
866 }
867 }
868}
869
870// Unified device function to evaluate diffuse angular distribution
871// Supports three modes with automatic priority-based selection:
872// Priority 1: Power-law (Harrison & Coombes) if K > 0
873// Priority 2: Prague sky model if params.w > 0 (valid normalization)
874// Priority 3: Isotropic (uniform) otherwise
875__device__ float evaluateDiffuseAngularDistribution(const float3 &ray_dir, const float3 &peak_dir, float power_law_K, float power_law_norm, const float4 &prague_params) {
876
877 // Priority 1: Power-law (if K > 0)
878 if (power_law_K > 0.0f) {
879 float psi = acos_safe(dot(peak_dir, ray_dir));
880 psi = fmaxf(psi, M_PI / 180.0f); // Avoid singularity at 1 degree
881 return powf(psi, -power_law_K) * power_law_norm;
882 }
883
884 // Priority 2: Prague (if params.w > 0, indicating valid normalization)
885 if (prague_params.w > 0.0f) {
886 // Angular distance from sun (degrees)
887 float gamma = acos_safe(dot(ray_dir, peak_dir)) * 180.0f / float(M_PI);
888
889 // Zenith angle
890 float cos_theta = fmaxf(ray_dir.z, 0.0f);
891
892 // Circumsolar + horizon brightening
893 // params: (circ_strength, circ_width, horizon_brightness, normalization)
894 float pattern = (1.0f + prague_params.x * expf(-gamma / prague_params.y)) * (1.0f + (prague_params.z - 1.0f) * (1.0f - cos_theta));
895
896 // Multiply by π to account for cosine-weighted sampling PDF (cos×sin/π)
897 // This ensures correct Monte Carlo integration for Prague angular distribution
898 return pattern * prague_params.w * M_PI;
899 }
900
901 // Priority 3: Isotropic
902 // For isotropic diffuse with cosine-weighted sampling (PDF = cos×sin/π):
903 // The π from the PDF denominator must appear in the Monte Carlo weight
904 return 1.0f;
905}
906
907RT_PROGRAM void miss_diffuse() {
908
909 // Convert UUID to array position
910 uint origin_position = primitive_positions[prd.origin_UUID];
911
912 // Create indexers
913 RadiationBufferIndexer rad_indexer(Nprimitives, Nbands_launch);
914 MaterialPropertyIndexer mat_indexer(Nsources, Nprimitives, Nbands_global);
915 CameraMaterialIndexer cam_mat_indexer(Nsources, Nprimitives, Nbands_global, Ncameras);
916
917 int b = -1;
918 for (size_t b_global = 0; b_global < Nbands_global; b_global++) {
919
920 if (band_launch_flag[b_global] == 0) {
921 continue;
922 }
923 b++;
924
925 if (diffuse_flux[b] > 0.f) {
926
927 // Use BufferIndexer: [primitive][band]
928 size_t ind_origin = rad_indexer(origin_position, b);
929
930 // Use BufferIndexer: [source][primitive][band]
931 size_t radprop_ind_global = mat_indexer(prd.source_ID, origin_position, b_global);
932 float t_rho = rho[radprop_ind_global];
933 float t_tau = tau[radprop_ind_global];
934
935 // Attenuation from any translucent covers (glass/plastic) the sky ray passed through, per band.
936 float cover_tau = (b < HELIOS_MAX_RADIATION_BANDS) ? prd.cover_transmittance[b] : 1.f;
937
938 // Check if ray was launched from voxel (type 4)
939 if (primitive_type[origin_position] == 4) { // ray was launched from voxel
940
941 float kappa = t_rho; // just a reminder that rho is actually the absorption coefficient
942 float sigma_s = t_tau; // just a reminder that tau is actually the scattering coefficient
943 float beta = kappa + sigma_s;
944
945 // absorption
946 atomicAdd(&radiation_in[ind_origin], diffuse_flux[b] * prd.strength * cover_tau * kappa / beta);
947
948 // scattering
949 atomicAdd(&scatter_buff_top[ind_origin], diffuse_flux[b] * prd.strength * cover_tau * sigma_s / beta);
950
951 } else { // ray was NOT launched from voxel
952
953 // Use unified distribution function (supports power-law, Prague, and isotropic modes)
954 float fd = evaluateDiffuseAngularDistribution(ray.direction, diffuse_peak_dir[b], diffuse_extinction[b], diffuse_dist_norm[b], sky_radiance_params[b]);
955
956 float strength = fd * diffuse_flux[b] * prd.strength * cover_tau;
957
958 // absorption
959 atomicAdd(&radiation_in[ind_origin], strength * (1.f - t_rho - t_tau));
960
961 if (t_rho > 0 || t_tau > 0) {
962 if (prd.face) { // reflection from top, transmission from bottom
963 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_rho); // reflection
964 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_tau); // transmission
965 } else { // reflection from bottom, transmission from top
966 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_rho); // reflection
967 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_tau); // transmission
968 }
969 }
970 if (Ncameras > 0) {
971 // Use BufferIndexer: [source][primitive][band][camera]
972 size_t indc = cam_mat_indexer(prd.source_ID, origin_position, b_global, camera_ID);
973 float t_rho_cam = rho_cam[indc];
974 float t_tau_cam = tau_cam[indc];
975 if ((t_rho_cam > 0 || t_tau_cam > 0) && prd.strength > 0) {
976 if (prd.face) { // reflection from top, transmission from bottom
977 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_rho_cam); // reflection
978 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_tau_cam); // transmission
979 } else { // reflection from bottom, transmission from top
980 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_rho_cam); // reflection
981 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_tau_cam); // transmission
982 }
983 }
984 // Note: Don't accumulate diffuse sky radiation to radiation_specular
985 // Specular should only reflect DIRECT source radiation (accumulated in miss_direct)
986 }
987 }
988 }
989 }
990}
991
992RT_PROGRAM void miss_camera() {
993
994 // For cameras, origin_UUID is actually the pixel index (not a primitive UUID!)
995 uint pixel_index = prd.origin_UUID;
996 size_t Npixels = camera_resolution_full.x * camera_resolution_full.y;
997
998 // Create indexer
999 RadiationBufferIndexer rad_indexer(Npixels, Nbands_launch); // Use Npixels for camera radiation buffer
1000
1001 for (size_t b = 0; b < Nbands_launch; b++) {
1002
1003 float radiance = 0.0f;
1004
1005 // Check all light sources
1006 for (uint s = 0; s < Nsources; s++) {
1007 float flux = source_fluxes[s * Nbands_launch + b];
1008 if (flux <= 0.0f)
1009 continue;
1010
1011 uint source_type = source_types[s];
1012
1013 if (source_type == 0 || source_type == 2) {
1014 // COLLIMATED or SUN_SPHERE
1015 float cos_sun_angle = dot(ray.direction, sun_direction);
1016 if (cos_sun_angle >= solar_disk_cos_angle && solar_disk_radiance[b] > 0.0f) {
1017 radiance += solar_disk_radiance[b];
1018 }
1019 } else if (source_type == 1) {
1020 // SPHERE
1021 float radius = source_widths[s].x * 0.5f;
1022 if (d_raySphereIntersect(ray.origin, ray.direction, source_positions[s], radius)) {
1023 float area = 4.0f * M_PI * radius * radius;
1024 radiance += (flux / area) / M_PI;
1025 }
1026 } else if (source_type == 3) {
1027 // RECTANGLE
1028 float cos_angle;
1029 if (d_rayRectangleIntersect(ray.origin, ray.direction, source_positions[s], source_widths[s].x, source_widths[s].y, source_rotations[s], cos_angle)) {
1030 float area = source_widths[s].x * source_widths[s].y;
1031 radiance += (flux / area) * cos_angle / M_PI;
1032 }
1033 } else if (source_type == 4) {
1034 // DISK
1035 float cos_angle;
1036 float radius = source_widths[s].x;
1037 if (d_rayDiskIntersect(ray.origin, ray.direction, source_positions[s], radius, source_rotations[s], cos_angle)) {
1038 float area = M_PI * radius * radius;
1039 radiance += (flux / area) * cos_angle / M_PI;
1040 }
1041 }
1042 }
1043
1044 // Fallback to sky radiance if no sources visible
1045 if (radiance <= 0.0f && camera_sky_radiance[b] > 0.f) {
1046 // Evaluate directional sky radiance using unified distribution function
1047 // camera_sky_radiance[b] contains the base zenith sky radiance (W/m²/sr) from Prague model
1048 // For camera, power-law is disabled (K=0, norm=1), so Prague params are used
1049 float angular_weight = evaluateDiffuseAngularDistribution(ray.direction, sun_direction,
1050 0.0f, // No power-law for camera
1051 1.0f,
1052 sky_radiance_params[b]); // Prague params
1053
1054 radiance = camera_sky_radiance[b] * angular_weight;
1055 }
1056
1057 // Isotropic sky emission/longwave: when band has emission enabled, the user-set diffuse_flux
1058 // represents hemispherical sky thermal/longwave irradiance. Convert to isotropic radiance.
1059 if (band_emission_flag[b] != 0u && camera_diffuse_flux[b] > 0.f) {
1060 radiance += camera_diffuse_flux[b] / M_PI;
1061 }
1062
1063 if (radiance > 0.0f) {
1064 // Accumulate radiance directly (same as surface hits accumulate radiation_out)
1065 // Units: W/m²/sr
1066 // Monte Carlo averaging: prd.strength = 1/N_rays
1067 // Use BufferIndexer: [pixel][band]
1068 size_t ind_camera = rad_indexer(pixel_index, b);
1069 atomicAdd(&radiation_in_camera[ind_camera], radiance * prd.strength);
1070 }
1071 }
1072}
1073
1074RT_PROGRAM void miss_pixel_label() {
1075
1076 camera_pixel_depth[prd.origin_UUID] = -1;
1077}