1fn is_occluded(pos: vec3f, normal: vec3f, light_dir: vec3f, light_distance: f32) -> bool {
2 var shadow_ray: Ray;
3 shadow_ray.origin = offset_ray(pos, normal);
4 shadow_ray.direction = light_dir;
5 var shadow_hit = trace_any(shadow_ray, light_distance);
6 return shadow_hit;
7}
8
9fn trace_any(ray: Ray, t_max: f32) -> bool {
10 var node_idx_stack: array<u32, 64>;
11 var stack_ptr: i32 = 0;
12 var current_node_idx: u32 = 0;
13
14 while (true) {
15 let node = node_tree.nodes[current_node_idx];
16
17 let primitive_count = u32(node.primitive_count);
18 let child_or_prim_idx = u32(node.left_child);
19 if (primitive_count == 0u) {
20 // internal node
21 let left_child_idx = child_or_prim_idx;
22 let right_child_idx = child_or_prim_idx + 1u;
23
24 // use t_max for pruning
25 let hit1 = any_hit_aabb(ray, node_tree.nodes[left_child_idx].min_corner, node_tree.nodes[left_child_idx].max_corner, t_max);
26 let hit2 = any_hit_aabb(ray, node_tree.nodes[right_child_idx].min_corner, node_tree.nodes[right_child_idx].max_corner, t_max);
27
28 var near_child_idx = left_child_idx;
29 var far_child_idx = right_child_idx;
30 var dist1_hit = hit1;
31 var dist2_hit = hit2;
32
33 if (!hit1 && hit2) {
34 near_child_idx = right_child_idx;
35 far_child_idx = left_child_idx;
36 dist1_hit = hit2;
37 dist2_hit = hit1;
38 }
39
40 if (dist1_hit) {
41 current_node_idx = near_child_idx;
42 if (dist2_hit) {
43 if (stack_ptr >= 64) {
44 break;
45 }
46 // overflow
47 node_idx_stack[stack_ptr] = far_child_idx;
48 stack_ptr += 1;
49 }
50 continue;
51 // descend into near child
52 }
53 // neither child is relevant, fall through to pop
54
55 }
56 else {
57 // leaf node
58 for (var i = 0u; i < primitive_count; i += 1u) {
59 let prim_index = tri_lut.primitive_indices[child_or_prim_idx + i];
60 let triangle = objects.triangles[i32(prim_index)];
61
62 // any_hit_triangle returns true if hit within range
63 if (any_hit_triangle(ray, triangle, 0.001, t_max)) {
64 return true;
65 // found an occlusion, exit immediately
66 }
67 }
68 // finished leaf without finding occlusion, fall through to pop
69 }
70
71 // pop from stack or break if empty
72 if (stack_ptr == 0) {
73 break;
74 // traversal finished without finding occlusion
75 }
76 else {
77 stack_ptr -= 1;
78 current_node_idx = node_idx_stack[stack_ptr];
79 }
80 }
81 // kill sunlight 0.0
82 let floor_z = 0.0;
83 let denom = ray.direction.z;
84 if (abs(denom) > 1e-6) {
85 let t = (floor_z - ray.origin.z) / denom;
86 if (t > 0.001 && t < t_max) {
87 return true;
88 // hit floor within range
89 }
90 }
91 return false;
92 // no occlusion found
93}
94
95fn any_hit_aabb(ray: Ray, aabb_min: vec3f, aabb_max: vec3f, t_max: f32) -> bool {
96 var inverse_dir: vec3<f32> = vec3(1.0) / ray.direction;
97 var tmin = (aabb_min - ray.origin) * inverse_dir;
98 var tmax = (aabb_max - ray.origin) * inverse_dir;
99 var t1 = min(tmin, tmax);
100 var t2 = max(tmin, tmax);
101 var t_near = max(max(t1.x, t1.y), t1.z);
102 var t_far = min(min(t2.x, t2.y), t2.z);
103 return t_near <= t_far && t_far >= 0.001 && t_near <= t_max;
104}
105
106// lazy, just clean this up to only use hit_triangle and move all shading data post hit out
107fn any_hit_triangle(ray: Ray, tri: Triangle, dist_min: f32, dist_max: f32) -> bool {
108 let edge1 = tri.corner_b - tri.corner_a;
109 let edge2 = tri.corner_c - tri.corner_a;
110
111 let pvec = cross(ray.direction, edge2);
112 let determinant = dot(edge1, pvec);
113
114 // reject nearly parallel rays.
115 if abs(determinant) < EPSILON {
116 return false;
117 }
118
119 let inv_det = 1.0 / determinant;
120 let tvec = ray.origin - tri.corner_a;
121
122 // compute barycentric coordinate u.
123 let u = dot(tvec, pvec) * inv_det;
124 if (u < 0.0 || u > 1.0) {
125 return false;
126 }
127
128 // compute barycentric coordinate v.
129 let qvec = cross(tvec, edge1);
130 let v = dot(ray.direction, qvec) * inv_det;
131 if (v < 0.0 || (u + v) > 1.0) {
132 return false;
133 }
134
135 // calculate ray parameter (distance).
136 let dist = dot(edge2, qvec) * inv_det;
137 return dist > dist_min && dist < dist_max;
138}