1.3.64
 
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
32RT_PROGRAM void closest_hit_direct() {
33
34 uint hit_position = primitive_positions[UUID];
35
36 // Bounds check: skip if position is invalid
37 if (hit_position == UINT_MAX) {
38 return;
39 }
40
41 if ((periodic_flag.x == 1 || periodic_flag.y == 1) && primitive_type[hit_position] == 5) { // periodic boundary condition
42
43 prd.hit_periodic_boundary = true;
44
45 float3 ray_origin = ray.origin + t_hit * ray.direction;
46
47 float eps = 1e-5;
48
49 float2 xbounds = make_float2(bbox_vertices[make_uint2(0, 0)].x, bbox_vertices[make_uint2(1, 1)].x);
50 float2 ybounds = make_float2(bbox_vertices[make_uint2(0, 0)].y, bbox_vertices[make_uint2(1, 1)].y);
51
52 float width_x = xbounds.y - xbounds.x;
53 float width_y = ybounds.y - ybounds.x;
54
55 prd.periodic_hit = ray_origin;
56 if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.x) <= eps) { //-x facing boundary
57 prd.periodic_hit.x += +width_x - eps;
58 } else if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.y) <= eps) { //+x facing boundary
59 prd.periodic_hit.x += -width_x + eps;
60 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.x) <= eps) { //-y facing boundary
61 prd.periodic_hit.y += +width_y - eps;
62 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.y) <= eps) { //+y facing boundary
63 prd.periodic_hit.y += -width_y + eps;
64 }
65 }
66};
67
68RT_PROGRAM void closest_hit_diffuse() {
69
70 // Convert UUIDs to array positions for buffer indexing
71 uint origin_UUID = prd.origin_UUID;
72 uint origin_position = primitive_positions[origin_UUID];
73 uint hit_position = primitive_positions[UUID];
74
75 // Bounds check: skip if positions are invalid
76 if (origin_position == UINT_MAX || hit_position == UINT_MAX) {
77 return;
78 }
79
80 // Create indexers for buffer access
81 RadiationBufferIndexer rad_indexer(Nprimitives, Nbands_launch);
82 MaterialPropertyIndexer mat_indexer(Nsources, Nprimitives, Nbands_global);
83 CameraMaterialIndexer cam_mat_indexer(Nsources, Nprimitives, Nbands_global, Ncameras);
84
85 if ((periodic_flag.x == 1 || periodic_flag.y == 1) && primitive_type[hit_position] == 5) { // periodic boundary condition
86
87 prd.hit_periodic_boundary = true;
88
89 float3 ray_origin = ray.origin + t_hit * ray.direction;
90
91 float eps = 1e-5;
92
93 float2 xbounds = make_float2(bbox_vertices[make_uint2(0, 0)].x, bbox_vertices[make_uint2(1, 1)].x);
94 float2 ybounds = make_float2(bbox_vertices[make_uint2(0, 0)].y, bbox_vertices[make_uint2(1, 1)].y);
95
96 float width_x = xbounds.y - xbounds.x;
97 float width_y = ybounds.y - ybounds.x;
98
99 prd.periodic_hit = ray_origin;
100 if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.x) <= eps) { //-x facing boundary
101 prd.periodic_hit.x += +width_x - eps;
102 } else if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.y) <= eps) { //+x facing boundary
103 prd.periodic_hit.x += -width_x + eps;
104 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.x) <= eps) { //-y facing boundary
105 prd.periodic_hit.y += +width_y - eps;
106 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.y) <= eps) { //+y facing boundary
107 prd.periodic_hit.y += -width_y + eps;
108 }
109
110 } else {
111
112 // 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)
113
114 // find out if we hit top or bottom surface
115 float3 normal;
116
117 float m[16];
118 for (uint i = 0; i < 16; i++) {
119 m[i] = transform_matrix[optix::make_uint2(i, hit_position)];
120 }
121
122 if (primitive_type[hit_position] == 0 || primitive_type[hit_position] == 3) { // hit patch or tile
123 float3 s0 = make_float3(0, 0, 0);
124 float3 s1 = make_float3(1, 0, 0);
125 float3 s2 = make_float3(0, 1, 0);
126 d_transformPoint(m, s0);
127 d_transformPoint(m, s1);
128 d_transformPoint(m, s2);
129 normal = cross(s1 - s0, s2 - s0);
130 } else if (primitive_type[hit_position] == 1) { // hit triangle
131 float3 v0 = make_float3(0, 0, 0);
132 d_transformPoint(m, v0);
133 float3 v1 = make_float3(0, 1, 0);
134 d_transformPoint(m, v1);
135 float3 v2 = make_float3(1, 1, 0);
136 d_transformPoint(m, v2);
137 normal = cross(v1 - v0, v2 - v0);
138 } else if (primitive_type[hit_position] == 2) { // hit disk
139 float3 v0 = make_float3(0, 0, 0);
140 d_transformPoint(m, v0);
141 float3 v1 = make_float3(1, 0, 0);
142 d_transformPoint(m, v1);
143 float3 v2 = make_float3(0, 1, 0);
144 d_transformPoint(m, v2);
145 normal = cross(v1 - v0, v2 - v0);
146 } else if (primitive_type[hit_position] == 4) { // hit voxel
147 float3 vmin = make_float3(-0.5, -0.5, -0.5);
148 d_transformPoint(m, vmin);
149 float3 vmax = make_float3(0.5, 0.5, 0.5);
150 d_transformPoint(m, vmax);
151 }
152 normal = normalize(normal);
153
154 bool face = dot(normal, ray.direction) < 0;
155
156 int b = -1;
157 for (int b_global = 0; b_global < Nbands_global; b_global++) {
158
159 if (band_launch_flag[b_global] == 0) {
160 continue;
161 }
162 b++;
163
164 // Use BufferIndexer for radiation buffers: [primitive][band]
165 // NOTE: b should match the band's index in the original band_labels array
166 // This is correct as long as band_launch_flag isn't modified after launch
167 size_t ind_origin = rad_indexer(origin_position, b);
168 size_t ind_hit = rad_indexer(hit_position, b);
169
170 double strength;
171 if (face || primitive_type[hit_position] == 4) {
172 strength = radiation_out_top[ind_hit] * prd.strength;
173 } else {
174 strength = radiation_out_bottom[ind_hit] * prd.strength;
175 }
176
177 if (strength == 0) {
178 continue;
179 }
180
181 // Use BufferIndexer for material properties: [source][primitive][band]
182 size_t radprop_ind_global = mat_indexer(prd.source_ID, origin_position, b_global);
183 float t_rho = rho[radprop_ind_global];
184 float t_tau = tau[radprop_ind_global];
185
186 // Check if ray was launched from voxel (type 4)
187 if (primitive_type[origin_position] == 4) { // ray was launched from voxel
188
189 // float kappa = t_rho; //just a reminder that rho is actually the absorption coefficient
190 // float sigma_s = t_tau; //just a reminder that tau is actually the scattering coefficient
191 // float beta = kappa + sigma_s;
192 //
193 // // absorption
194 // atomicAdd(&radiation_in[ind_origin], strength * exp(-beta * 0.5 * prd.area) * kappa / beta);
195 //
196 // // scattering
197 // atomicAdd(&scatter_buff_top[ind_origin], strength * exp(-beta * 0.5 * prd.area) * sigma_s / beta);
198
199 } else { // ray was NOT launched from voxel
200
201 // absorption - calculate with defensive check for energy conservation violations
202 float absorption_factor = 1.f - t_rho - t_tau;
203 float contribution = strength * absorption_factor;
204
205
206#ifndef NDEBUG
207 if (absorption_factor < -1e-5f) {
208 printf("ERROR: Negative absorption! rho=%.6f, tau=%.6f, origin_UUID=%u\n", t_rho, t_tau, origin_UUID);
209 absorption_factor = 0.f;
210 }
211#endif
212 atomicAdd(&radiation_in[ind_origin], contribution);
213
214 if ((t_rho > 0 || t_tau > 0) && strength > 0) {
215 if (prd.face) { // reflection from top, transmission from bottom
216 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_rho); // reflection
217 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_tau); // transmission
218 } else { // reflection from bottom, transmission from top
219 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_rho); // reflection
220 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_tau); // transmission
221 }
222 }
223 if (Ncameras > 0) {
224 // Use BufferIndexer for camera material: [source][primitive][band][camera]
225 size_t indc = cam_mat_indexer(prd.source_ID, origin_position, b_global, camera_ID);
226 float t_rho_cam = rho_cam[indc];
227 float t_tau_cam = tau_cam[indc];
228 if ((t_rho_cam > 0 || t_tau_cam > 0) && strength > 0) {
229 if (prd.face) { // reflection from top, transmission from bottom
230 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_rho_cam); // reflection
231 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_tau_cam); // transmission
232 } else { // reflection from bottom, transmission from top
233 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_rho_cam); // reflection
234 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_tau_cam); // transmission
235 }
236 }
237 // Note: Don't accumulate scattered radiation to radiation_specular
238 // Specular should only reflect DIRECT source radiation (accumulated in miss_direct)
239 }
240 }
241
242 // if( primitive_type[UUID] == 4 ){ //if we hit a voxel, reduce strength and launch another ray
243 // 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);
244 // PerRayData prd_transmit = prd;
245 // float beta = rho[UUID]+tau[UUID];
246 // prd_transmit.strength = prd.strength*(1.f-exp(-beta*0.5*prd.area));
247 // rtTrace( top_object, ray_transmit, prd_transmit);
248 // }
249 }
250 }
251}
252
253RT_PROGRAM void closest_hit_camera() {
254
255 // Convert UUID to array position
256 uint hit_position = primitive_positions[UUID];
257
258 // Bounds check: skip if position is invalid
259 if (hit_position == UINT_MAX) {
260 return;
261 }
262
263 // For cameras, origin_UUID is actually the pixel index (not a primitive UUID!)
264 uint pixel_index = prd.origin_UUID;
265 size_t Npixels = camera_resolution_full.x * camera_resolution_full.y;
266
267 // Create indexers
268 RadiationBufferIndexer rad_indexer(Npixels, Nbands_launch); // Use Npixels for camera radiation buffer
269 SourceFluxIndexer source_flux_indexer(Nsources, Nbands_launch);
270 SpecularRadiationIndexer spec_indexer(Nsources, Ncameras, Nprimitives, Nbands_launch);
271
272 if ((periodic_flag.x == 1 || periodic_flag.y == 1) && primitive_type[hit_position] == 5) { // periodic boundary condition
273
274 prd.hit_periodic_boundary = true;
275
276 float3 ray_origin = ray.origin + t_hit * ray.direction;
277
278 float eps = 1e-5;
279
280 float2 xbounds = make_float2(bbox_vertices[make_uint2(0, 0)].x, bbox_vertices[make_uint2(1, 1)].x);
281 float2 ybounds = make_float2(bbox_vertices[make_uint2(0, 0)].y, bbox_vertices[make_uint2(1, 1)].y);
282
283 float width_x = xbounds.y - xbounds.x;
284 float width_y = ybounds.y - ybounds.x;
285
286 prd.periodic_hit = ray_origin;
287 if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.x) <= eps) { //-x facing boundary
288 prd.periodic_hit.x += +width_x - eps;
289 } else if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.y) <= eps) { //+x facing boundary
290 prd.periodic_hit.x += -width_x + eps;
291 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.x) <= eps) { //-y facing boundary
292 prd.periodic_hit.y += +width_y - eps;
293 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.y) <= eps) { //+y facing boundary
294 prd.periodic_hit.y += -width_y + eps;
295 }
296
297 } else {
298
299 // 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)
300
301 // find out if we hit top or bottom surface per each ray
302 float3 normal;
303
304 float m[16];
305 for (uint i = 0; i < 16; i++) {
306 m[i] = transform_matrix[optix::make_uint2(i, hit_position)];
307 }
308
309 if (primitive_type[hit_position] == 0 || primitive_type[hit_position] == 3) { // hit patch or tile
310 float3 s0 = make_float3(0, 0, 0);
311 float3 s1 = make_float3(1, 0, 0);
312 float3 s2 = make_float3(0, 1, 0);
313 d_transformPoint(m, s0);
314 d_transformPoint(m, s1);
315 d_transformPoint(m, s2);
316 normal = cross(s1 - s0, s2 - s0);
317 } else if (primitive_type[hit_position] == 1) { // hit triangle
318 float3 v0 = make_float3(0, 0, 0);
319 d_transformPoint(m, v0);
320 float3 v1 = make_float3(0, 1, 0);
321 d_transformPoint(m, v1);
322 float3 v2 = make_float3(1, 1, 0);
323 d_transformPoint(m, v2);
324 normal = cross(v1 - v0, v2 - v0);
325 } else if (primitive_type[hit_position] == 2) { // hit disk
326 float3 v0 = make_float3(0, 0, 0);
327 d_transformPoint(m, v0);
328 float3 v1 = make_float3(1, 0, 0);
329 d_transformPoint(m, v1);
330 float3 v2 = make_float3(0, 1, 0);
331 d_transformPoint(m, v2);
332 normal = cross(v1 - v0, v2 - v0);
333 } else if (primitive_type[hit_position] == 4) { // hit voxel
334 float3 vmin = make_float3(-0.5, -0.5, -0.5);
335 d_transformPoint(m, vmin);
336 float3 vmax = make_float3(0.5, 0.5, 0.5);
337 d_transformPoint(m, vmax);
338 }
339 normal = normalize(normal);
340
341 bool face = dot(normal, ray.direction) < 0;
342
343
344 float3 camera_normal = d_rotatePoint(make_float3(0, 0, 1), -0.5 * M_PI + camera_direction.x, 0.5f * M_PI - camera_direction.y);
345
346 double strength;
347 for (size_t b = 0; b < Nbands_launch; b++) {
348
349 // Check if any light sources are between camera and hit point
350 float source_radiance = 0.0f;
351 for (uint s = 0; s < Nsources; s++) {
352 float flux = source_fluxes[s * Nbands_launch + b];
353 if (flux <= 0.0f)
354 continue;
355
356 uint source_type = source_types[s];
357
358 if (source_type == 1) {
359 // SPHERE
360 float radius = source_widths[s].x * 0.5f;
361 float3 oc = ray.origin - source_positions[s];
362 float b = dot(oc, ray.direction);
363 float c = dot(oc, oc) - radius * radius;
364 float discriminant = b * b - c;
365
366 if (discriminant >= 0.0f) {
367 float t_sphere = -b - sqrtf(discriminant);
368 if (t_sphere > 0.0f && t_sphere < t_hit) {
369 float area = 4.0f * M_PI * radius * radius;
370 source_radiance += (flux / area) / M_PI;
371 }
372 }
373 } else if (source_type == 3) {
374 // RECTANGLE
375 float transform[16];
376 d_makeTransformMatrix(source_rotations[s], transform);
377 float3 normal = make_float3(transform[2], transform[6], transform[10]);
378
379 float denom = dot(ray.direction, normal);
380 if (denom < -1e-6f) {
381 float3 oc = source_positions[s] - ray.origin;
382 float t_rect = dot(oc, normal) / denom;
383
384 if (t_rect > 0.0f && t_rect < t_hit) {
385 float3 hit_point = ray.origin + t_rect * ray.direction;
386 float3 local_hit = hit_point - source_positions[s];
387 float inv_transform[16];
388 d_invertMatrix(transform, inv_transform);
389 d_transformPoint(inv_transform, local_hit);
390
391 if (fabsf(local_hit.x) <= source_widths[s].x * 0.5f && fabsf(local_hit.y) <= source_widths[s].y * 0.5f) {
392 float area = source_widths[s].x * source_widths[s].y;
393 float cos_angle = -denom;
394 source_radiance += (flux / area) * cos_angle / M_PI;
395 }
396 }
397 }
398 } else if (source_type == 4) {
399 // DISK
400 float transform[16];
401 d_makeTransformMatrix(source_rotations[s], transform);
402 float3 normal = make_float3(transform[2], transform[6], transform[10]);
403
404 float denom = dot(ray.direction, normal);
405 if (denom < -1e-6f) {
406 float3 oc = source_positions[s] - ray.origin;
407 float t_disk = dot(oc, normal) / denom;
408
409 if (t_disk > 0.0f && t_disk < t_hit) {
410 float3 hit_point = ray.origin + t_disk * ray.direction;
411 float3 offset = hit_point - source_positions[s];
412 float dist_sq = dot(offset, offset);
413
414 float radius = source_widths[s].x;
415 if (dist_sq <= radius * radius) {
416 float area = M_PI * radius * radius;
417 float cos_angle = -denom;
418 source_radiance += (flux / area) * cos_angle / M_PI;
419 }
420 }
421 }
422 }
423 }
424
425 // Use BufferIndexer: [primitive][band]
426 size_t ind_hit = rad_indexer(hit_position, b);
427
428 if (face || primitive_type[hit_position] == 4) {
429 strength = radiation_out_top[ind_hit] * prd.strength;
430 } else {
431 strength = radiation_out_bottom[ind_hit] * prd.strength;
432 }
433
434 if (source_radiance > 0.0f) {
435 strength += source_radiance * prd.strength;
436 }
437
438 // specular reflection
439 // Only compute specular on iteration 0 to prevent accumulation across scattering iterations.
440 // radiation_specular contains per-source, camera-weighted incident radiation
441
442 double strength_spec = 0;
443 if (specular_reflection_enabled > 0 && specular_exponent[hit_position] > 0.f && scattering_iteration == 0) {
444
445 // For each source, compute specular contribution
446 for (int rr = 0; rr < Nsources; rr++) {
447
448 // Get camera-weighted incident radiation from this source
449 // Already has source color and camera response weighting applied
450 // Use BufferIndexer: [source][camera][primitive][band]
451 size_t ind_specular = spec_indexer(rr, camera_ID, hit_position, b);
452 float spec = radiation_specular[ind_specular];
453
454 // Apply default 0.25 scaling factor (typical Fresnel reflectance for dielectrics is ~4%,
455 // but this accounts for specular lobe concentration and typical surface roughness)
456 spec *= 0.25f;
457
458 if (spec > 0) {
459 // Determine light direction based on source type
460 float3 light_direction;
461 if (source_types[rr] == 0 || source_types[rr] == 2) {
462 // Collimated or sunsphere: parallel rays from source direction
463 light_direction = normalize(source_positions[rr]);
464 } else {
465 // Sphere, disk, or rectangle: direction from hit point to source center
466 float3 hit_point = ray.origin + t_hit * ray.direction;
467 light_direction = normalize(source_positions[rr] - hit_point);
468 }
469
470 // Blinn-Phong specular direction (half-vector)
471 float3 specular_direction = normalize(light_direction - ray.direction);
472
473 float exponent = specular_exponent[hit_position];
474 double scale_coefficient = 1.0;
475 if (specular_reflection_enabled == 2) { // if we are using the scale coefficient
476 scale_coefficient = specular_scale[hit_position];
477 }
478
479 strength_spec += spec * scale_coefficient * pow(max(0.f, dot(specular_direction, normal)), exponent) * (exponent + 2.f) /
480 (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.
481 }
482 }
483 }
484
485 // absorption
486 // For cameras, use pixel index directly (no UUID lookup needed)
487 // Use BufferIndexer: [pixel][band]
488 size_t ind_camera = rad_indexer(pixel_index, b);
489
490 atomicAdd(&radiation_in_camera[ind_camera],
491 (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.
492 }
493 }
494}
495
496RT_PROGRAM void closest_hit_pixel_label() {
497
498 uint origin_UUID = prd.origin_UUID;
499
500 uint hit_position = primitive_positions[UUID];
501
502 if ((periodic_flag.x == 1 || periodic_flag.y == 1) && primitive_type[hit_position] == 5) { // periodic boundary condition
503
504 prd.hit_periodic_boundary = true;
505
506 float3 ray_origin = ray.origin + t_hit * ray.direction;
507
508 float eps = 1e-5;
509
510 float2 xbounds = make_float2(bbox_vertices[make_uint2(0, 0)].x, bbox_vertices[make_uint2(1, 1)].x);
511 float2 ybounds = make_float2(bbox_vertices[make_uint2(0, 0)].y, bbox_vertices[make_uint2(1, 1)].y);
512
513 float width_x = xbounds.y - xbounds.x;
514 float width_y = ybounds.y - ybounds.x;
515
516 prd.periodic_hit = ray_origin;
517 if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.x) <= eps) { //-x facing boundary
518 prd.periodic_hit.x += +width_x - eps;
519 } else if (periodic_flag.x == 1 && fabs(ray_origin.x - xbounds.y) <= eps) { //+x facing boundary
520 prd.periodic_hit.x += -width_x + eps;
521 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.x) <= eps) { //-y facing boundary
522 prd.periodic_hit.y += +width_y - eps;
523 } else if (periodic_flag.y == 1 && fabs(ray_origin.y - ybounds.y) <= eps) { //+y facing boundary
524 prd.periodic_hit.y += -width_y + eps;
525 }
526
527 } else {
528
529 // 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)
530 // Note: We are reserving a value of 0 for the sky, so we will store UUID+1
531 camera_pixel_label[origin_UUID] = UUID + 1;
532
533 float depth = prd.strength + t_hit;
534 float3 camera_direction3 = d_rotatePoint(make_float3(1, 0, 0), -0.5 * M_PI + camera_direction.x, 0.5f * M_PI - camera_direction.y);
535 camera_pixel_depth[origin_UUID] = abs(dot(camera_direction3, ray.direction)) * depth;
536 }
537}
538
539RT_PROGRAM void miss_direct() {
540
541 // Convert UUID to array position
542 uint origin_position = primitive_positions[prd.origin_UUID];
543
544 // Create indexers
545 RadiationBufferIndexer rad_indexer(Nprimitives, Nbands_launch);
546 MaterialPropertyIndexer mat_indexer(Nsources, Nprimitives, Nbands_global);
547 SourceFluxIndexer source_flux_indexer(Nsources, Nbands_launch);
548 CameraMaterialIndexer cam_mat_indexer(Nsources, Nprimitives, Nbands_global, Ncameras);
549 SourceCameraFluxIndexer source_cam_flux_indexer(Nsources, Nbands_launch, Ncameras);
550 SpecularRadiationIndexer spec_indexer(Nsources, Ncameras, Nprimitives, Nbands_launch);
551
552 int b = -1;
553 for (int b_global = 0; b_global < Nbands_global; b_global++) {
554
555 if (band_launch_flag[b_global] == 0) {
556 continue;
557 }
558 b++;
559
560 // Use BufferIndexer: [primitive][band]
561 size_t ind_origin = rad_indexer(origin_position, b);
562
563 // Use BufferIndexer: [source][primitive][band]
564 size_t radprop_ind_global = mat_indexer(prd.source_ID, origin_position, b_global);
565 float t_rho = rho[radprop_ind_global];
566 float t_tau = tau[radprop_ind_global];
567
568 // Use BufferIndexer: [source][band]
569 size_t flux_idx = source_flux_indexer(prd.source_ID, b);
570 float source_flux = source_fluxes[flux_idx];
571 double strength = prd.strength * source_flux;
572 float absorption = strength * (1.f - t_rho - t_tau);
573
574 // absorption
575 atomicAdd(&radiation_in[ind_origin], absorption);
576
577 if (t_rho > 0 || t_tau > 0) {
578 if (prd.face) { // reflection from top, transmission from bottom
579 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_rho); // reflection
580 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_tau); // transmission
581 } else { // reflection from bottom, transmission from top
582 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_rho); // reflection
583 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_tau); // transmission
584 }
585 }
586 if (Ncameras > 0) {
587 // Use BufferIndexer: [source][primitive][band][camera]
588 size_t indc = cam_mat_indexer(prd.source_ID, origin_position, b_global, camera_ID);
589 float t_rho_cam = rho_cam[indc];
590 float t_tau_cam = tau_cam[indc];
591 if ((t_rho_cam > 0 || t_tau_cam > 0) && strength > 0) {
592 if (prd.face) { // reflection from top, transmission from bottom
593 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_rho_cam); // reflection
594 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_tau_cam); // transmission
595 } else { // reflection from bottom, transmission from top
596 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_rho_cam); // reflection
597 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_tau_cam); // transmission
598 }
599 }
600 // Accumulate incident radiation for specular (per source, camera-weighted)
601 // Apply camera spectral response weighting: ∫(source × camera) / ∫(source)
602 if (strength > 0) {
603 // Use BufferIndexer: [source][band][camera]
604 size_t weight_ind = source_cam_flux_indexer(prd.source_ID, b, camera_ID);
605 float camera_weight = source_fluxes_cam[weight_ind];
606 // Use BufferIndexer: [source][camera][primitive][band] (note different order!)
607 size_t ind_specular = spec_indexer(prd.source_ID, camera_ID, origin_position, b);
608 atomicFloatAdd(&radiation_specular[ind_specular], strength * camera_weight);
609 }
610 }
611 }
612}
613
614// Unified device function to evaluate diffuse angular distribution
615// Supports three modes with automatic priority-based selection:
616// Priority 1: Power-law (Harrison & Coombes) if K > 0
617// Priority 2: Prague sky model if params.w > 0 (valid normalization)
618// Priority 3: Isotropic (uniform) otherwise
619__device__ float evaluateDiffuseAngularDistribution(const float3 &ray_dir, const float3 &peak_dir, float power_law_K, float power_law_norm, const float4 &prague_params) {
620
621 // Priority 1: Power-law (if K > 0)
622 if (power_law_K > 0.0f) {
623 float psi = acos_safe(dot(peak_dir, ray_dir));
624 psi = fmaxf(psi, M_PI / 180.0f); // Avoid singularity at 1 degree
625 return powf(psi, -power_law_K) * power_law_norm;
626 }
627
628 // Priority 2: Prague (if params.w > 0, indicating valid normalization)
629 if (prague_params.w > 0.0f) {
630 // Angular distance from sun (degrees)
631 float gamma = acos_safe(dot(ray_dir, peak_dir)) * 180.0f / float(M_PI);
632
633 // Zenith angle
634 float cos_theta = fmaxf(ray_dir.z, 0.0f);
635
636 // Circumsolar + horizon brightening
637 // params: (circ_strength, circ_width, horizon_brightness, normalization)
638 float pattern = (1.0f + prague_params.x * expf(-gamma / prague_params.y)) * (1.0f + (prague_params.z - 1.0f) * (1.0f - cos_theta));
639
640 // Multiply by π to account for cosine-weighted sampling PDF (cos×sin/π)
641 // This ensures correct Monte Carlo integration for Prague angular distribution
642 return pattern * prague_params.w * M_PI;
643 }
644
645 // Priority 3: Isotropic
646 // For isotropic diffuse with cosine-weighted sampling (PDF = cos×sin/π):
647 // The π from the PDF denominator must appear in the Monte Carlo weight
648 return 1.0f;
649}
650
651RT_PROGRAM void miss_diffuse() {
652
653 // Convert UUID to array position
654 uint origin_position = primitive_positions[prd.origin_UUID];
655
656 // Create indexers
657 RadiationBufferIndexer rad_indexer(Nprimitives, Nbands_launch);
658 MaterialPropertyIndexer mat_indexer(Nsources, Nprimitives, Nbands_global);
659 CameraMaterialIndexer cam_mat_indexer(Nsources, Nprimitives, Nbands_global, Ncameras);
660
661 int b = -1;
662 for (size_t b_global = 0; b_global < Nbands_global; b_global++) {
663
664 if (band_launch_flag[b_global] == 0) {
665 continue;
666 }
667 b++;
668
669 if (diffuse_flux[b] > 0.f) {
670
671 // Use BufferIndexer: [primitive][band]
672 size_t ind_origin = rad_indexer(origin_position, b);
673
674 // Use BufferIndexer: [source][primitive][band]
675 size_t radprop_ind_global = mat_indexer(prd.source_ID, origin_position, b_global);
676 float t_rho = rho[radprop_ind_global];
677 float t_tau = tau[radprop_ind_global];
678
679 // Check if ray was launched from voxel (type 4)
680 if (primitive_type[origin_position] == 4) { // ray was launched from voxel
681
682 float kappa = t_rho; // just a reminder that rho is actually the absorption coefficient
683 float sigma_s = t_tau; // just a reminder that tau is actually the scattering coefficient
684 float beta = kappa + sigma_s;
685
686 // absorption
687 atomicAdd(&radiation_in[ind_origin], diffuse_flux[b] * prd.strength * kappa / beta);
688
689 // scattering
690 atomicAdd(&scatter_buff_top[ind_origin], diffuse_flux[b] * prd.strength * sigma_s / beta);
691
692 } else { // ray was NOT launched from voxel
693
694 // Use unified distribution function (supports power-law, Prague, and isotropic modes)
695 float fd = evaluateDiffuseAngularDistribution(ray.direction, diffuse_peak_dir[b], diffuse_extinction[b], diffuse_dist_norm[b], sky_radiance_params[b]);
696
697 float strength = fd * diffuse_flux[b] * prd.strength;
698
699 // absorption
700 atomicAdd(&radiation_in[ind_origin], strength * (1.f - t_rho - t_tau));
701
702 if (t_rho > 0 || t_tau > 0) {
703 if (prd.face) { // reflection from top, transmission from bottom
704 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_rho); // reflection
705 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_tau); // transmission
706 } else { // reflection from bottom, transmission from top
707 atomicFloatAdd(&scatter_buff_bottom[ind_origin], strength * t_rho); // reflection
708 atomicFloatAdd(&scatter_buff_top[ind_origin], strength * t_tau); // transmission
709 }
710 }
711 if (Ncameras > 0) {
712 // Use BufferIndexer: [source][primitive][band][camera]
713 size_t indc = cam_mat_indexer(prd.source_ID, origin_position, b_global, camera_ID);
714 float t_rho_cam = rho_cam[indc];
715 float t_tau_cam = tau_cam[indc];
716 if ((t_rho_cam > 0 || t_tau_cam > 0) && prd.strength > 0) {
717 if (prd.face) { // reflection from top, transmission from bottom
718 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_rho_cam); // reflection
719 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_tau_cam); // transmission
720 } else { // reflection from bottom, transmission from top
721 atomicFloatAdd(&scatter_buff_bottom_cam[ind_origin], strength * t_rho_cam); // reflection
722 atomicFloatAdd(&scatter_buff_top_cam[ind_origin], strength * t_tau_cam); // transmission
723 }
724 }
725 // Note: Don't accumulate diffuse sky radiation to radiation_specular
726 // Specular should only reflect DIRECT source radiation (accumulated in miss_direct)
727 }
728 }
729 }
730 }
731}
732
733RT_PROGRAM void miss_camera() {
734
735 // For cameras, origin_UUID is actually the pixel index (not a primitive UUID!)
736 uint pixel_index = prd.origin_UUID;
737 size_t Npixels = camera_resolution_full.x * camera_resolution_full.y;
738
739 // Create indexer
740 RadiationBufferIndexer rad_indexer(Npixels, Nbands_launch); // Use Npixels for camera radiation buffer
741
742 for (size_t b = 0; b < Nbands_launch; b++) {
743
744 float radiance = 0.0f;
745
746 // Check all light sources
747 for (uint s = 0; s < Nsources; s++) {
748 float flux = source_fluxes[s * Nbands_launch + b];
749 if (flux <= 0.0f)
750 continue;
751
752 uint source_type = source_types[s];
753
754 if (source_type == 0 || source_type == 2) {
755 // COLLIMATED or SUN_SPHERE
756 float cos_sun_angle = dot(ray.direction, sun_direction);
757 if (cos_sun_angle >= solar_disk_cos_angle && solar_disk_radiance[b] > 0.0f) {
758 radiance += solar_disk_radiance[b];
759 }
760 } else if (source_type == 1) {
761 // SPHERE
762 float radius = source_widths[s].x * 0.5f;
763 if (d_raySphereIntersect(ray.origin, ray.direction, source_positions[s], radius)) {
764 float area = 4.0f * M_PI * radius * radius;
765 radiance += (flux / area) / M_PI;
766 }
767 } else if (source_type == 3) {
768 // RECTANGLE
769 float cos_angle;
770 if (d_rayRectangleIntersect(ray.origin, ray.direction, source_positions[s], source_widths[s].x, source_widths[s].y, source_rotations[s], cos_angle)) {
771 float area = source_widths[s].x * source_widths[s].y;
772 radiance += (flux / area) * cos_angle / M_PI;
773 }
774 } else if (source_type == 4) {
775 // DISK
776 float cos_angle;
777 float radius = source_widths[s].x;
778 if (d_rayDiskIntersect(ray.origin, ray.direction, source_positions[s], radius, source_rotations[s], cos_angle)) {
779 float area = M_PI * radius * radius;
780 radiance += (flux / area) * cos_angle / M_PI;
781 }
782 }
783 }
784
785 // Fallback to sky radiance if no sources visible
786 if (radiance <= 0.0f && camera_sky_radiance[b] > 0.f) {
787 // Evaluate directional sky radiance using unified distribution function
788 // camera_sky_radiance[b] contains the base zenith sky radiance (W/m²/sr) from Prague model
789 // For camera, power-law is disabled (K=0, norm=1), so Prague params are used
790 float angular_weight = evaluateDiffuseAngularDistribution(ray.direction, sun_direction,
791 0.0f, // No power-law for camera
792 1.0f,
793 sky_radiance_params[b]); // Prague params
794
795 radiance = camera_sky_radiance[b] * angular_weight;
796 }
797
798 if (radiance > 0.0f) {
799 // Accumulate radiance directly (same as surface hits accumulate radiation_out)
800 // Units: W/m²/sr
801 // Monte Carlo averaging: prd.strength = 1/N_rays
802 // Use BufferIndexer: [pixel][band]
803 size_t ind_camera = rad_indexer(pixel_index, b);
804 atomicAdd(&radiation_in_camera[ind_camera], radiance * prd.strength);
805 }
806 }
807}
808
809RT_PROGRAM void miss_pixel_label() {
810
811 camera_pixel_depth[prd.origin_UUID] = -1;
812}