pbrt_r3/core/lightdistrib/
spatial.rs1use super::lightdistrib::*;
2use crate::core::base::*;
3use crate::core::geometry::*;
4use crate::core::interaction::*;
5use crate::core::light::*;
6use crate::core::lowdiscrepancy::*;
7use crate::core::medium::*;
8use crate::core::sampling::*;
9use crate::core::scene::*;
10use crate::core::stats::*;
11use std::sync::Arc;
13use std::sync::Mutex;
14
15thread_local!(static N_CREATED: StatCounter = StatCounter::new("SpatialLightDistribution/Distributions created"));
16thread_local!(static N_PROBES_PER_LOOKUP: StatIntDistribution = StatIntDistribution::new("SpatialLightDistribution/Hash probes per lookup"));
17
18#[derive(Debug, Clone)]
19struct HashEntry {
20 packed_pos: u64,
21 distribution: Arc<Distribution1D>,
22}
23
24pub struct SpatialLightDistribution {
29 world_bound: Bounds3f,
30 lights: Vec<Arc<dyn Light>>,
31 voxels: [u32; 3],
32 hash_table: Vec<Mutex<Option<HashEntry>>>,
33}
34
35impl SpatialLightDistribution {
36 pub fn new(scene: &Scene, max_voxels: u32) -> Self {
37 let mut voxels = [1, 1, 1];
38
39 let b = scene.world_bound();
40 let diag = b.diagonal();
41 let bmax = diag[b.maximum_extent()];
42 for i in 0..3 {
43 voxels[i] =
44 (Float::ceil(diag[i] / bmax * max_voxels as Float) as u32).clamp(1, max_voxels);
45
46 assert!(voxels[i] < (1 << 20));
50 }
51
52 let hash_table_size = (4 * voxels[0] * voxels[1] * voxels[2]) as usize;
53 let mut hash_table = Vec::new();
54 for _ in 0..hash_table_size {
55 hash_table.push(Mutex::new(None));
56 }
57 SpatialLightDistribution {
58 world_bound: b,
59 lights: scene.lights.clone(),
60 voxels,
61 hash_table,
62 }
63 }
64
65 pub fn make_hash(&self, packed_pos: u64) -> u64 {
66 let hash_table_size = self.hash_table.len() as u64;
67
68 let mut hash: u64 = packed_pos;
75 hash ^= hash.wrapping_shr(31); hash = hash.wrapping_mul(0x7fb5d329728ea185); hash ^= hash.wrapping_shr(27); hash = hash.wrapping_mul(0x81dadef4bc2dd44d); hash ^= hash.wrapping_shr(33); hash %= hash_table_size;
81 return hash;
82 }
83
84 pub fn get_hash_key(&self, p: &Point3f) -> (u64, u64, [u32; 3]) {
85 let bounds = self.world_bound.clone();
88 let offset = bounds.offset(p);
89 let mut offset = [offset[0], offset[1], offset[2]];
90 for i in 0..3 {
91 offset[i] = offset[i].clamp(0.0, 1.0);
92 }
93 let mut pi: [u32; 3] = [0; 3];
94 for i in 0..3 {
95 let voxels_i = self.voxels[i];
99 pi[i] = u32::clamp(
100 (offset[i] * voxels_i as Float) as u32,
101 0,
102 (voxels_i - 1) as u32,
103 );
104 }
105 let packed_pos = ((pi[0] as u64).wrapping_shl(40)) as u64
108 | ((pi[1] as u64).wrapping_shl(20)) as u64
109 | (pi[2]) as u64;
110 return (packed_pos, self.make_hash(packed_pos), pi);
111 }
112
113 fn compute_distribution(&self, pi: &[u32; 3]) -> Arc<Distribution1D> {
114 N_CREATED.with(|c| c.inc());
115
116 let world_bound = self.world_bound.clone();
119 let voxels = self.voxels.as_ref();
120 let p0 = Point3f::new(
121 pi[0] as Float / voxels[0] as Float,
122 pi[1] as Float / voxels[1] as Float,
123 pi[2] as Float / voxels[2] as Float,
124 );
125 let p1 = Point3f::new(
126 (pi[0] + 1) as Float / voxels[0] as Float,
127 (pi[1] + 1) as Float / voxels[1] as Float,
128 (pi[2] + 1) as Float / voxels[2] as Float,
129 );
130 let pp0 = world_bound.lerp(&p0);
131 let pp1 = world_bound.lerp(&p1);
132 let voxel_bounds = Bounds3f::new(&pp0, &pp1);
133
134 let lights = &self.lights;
141 let n_samples: usize = 128;
142 let lsz = lights.len();
143 let mut light_contrib = vec![0.0; lsz];
144 for i in 0..n_samples {
145 let t = Point3f::new(
146 radical_inverse(0, i as u64),
147 radical_inverse(1, i as u64),
148 radical_inverse(2, i as u64),
149 );
150 let po = voxel_bounds.lerp(&t);
151
152 let intr = Interaction::from(BaseInteraction {
154 p: po, time: 0.0,
156 p_error: Vector3f::zero(), n: Normal3f::zero(), wo: Vector3f::new(1.0, 0.0, 0.0), medium_interface: MediumInterface::new(),
160 });
161
162 let u = Point2f::new(radical_inverse(3, i as u64), radical_inverse(4, i as u64));
165 for j in 0..lsz {
166 let light = lights[j].as_ref();
167 if let Some((li, _wi, pdf, _vis)) = light.sample_li(&intr, &u) {
168 if pdf > 0.0 {
169 light_contrib[j] += li.y() / pdf;
174 }
175 }
176 }
177 }
178
179 let sum_contrib: Float = light_contrib.iter().sum();
185 let avg_contrib = sum_contrib / ((n_samples * lsz) as Float);
186 let min_contrib = if avg_contrib > 0.0 {
187 0.001 * avg_contrib
188 } else {
189 1.0
190 };
191 for i in 0..lsz {
192 light_contrib[i] = Float::max(min_contrib, light_contrib[i]);
193 }
194 return Arc::new(Distribution1D::new(&light_contrib));
196 }
197}
198
199impl LightDistribution for SpatialLightDistribution {
200 fn lookup(&self, p: &Point3f) -> Arc<Distribution1D> {
201 let (packed_pos, mut hash, pi) = self.get_hash_key(p);
202 assert!((hash as usize) < self.hash_table.len());
203
204 let mut step = 1;
208 let mut n_probes = 0;
209 loop {
210 n_probes += 1;
211 let mut hash_entry = self.hash_table[hash as usize].lock().unwrap();
212 if let Some(entry) = hash_entry.as_ref() {
214 let entry_packed_pos = entry.packed_pos;
215 if entry_packed_pos == packed_pos {
216 return entry.distribution.clone();
219 } else {
220 hash += step * step;
224 let hash_table_size = self.hash_table.len() as u64;
225 if hash >= hash_table_size {
226 hash %= hash_table_size;
227 }
228 step += 1;
229 }
230 } else {
231 let distrib = self.compute_distribution(&pi);
236 *hash_entry = Some(HashEntry {
237 packed_pos,
238 distribution: distrib.clone(),
239 });
240
241 N_PROBES_PER_LOOKUP.with(|c| c.add(n_probes));
242 return distrib;
243 }
244 }
245 }
246}
247
248unsafe impl Sync for SpatialLightDistribution {}
249unsafe impl Send for SpatialLightDistribution {}