diff --git a/benches/default_benchmark.rs b/benches/default_benchmark.rs index 3a3cd51..e07b473 100644 --- a/benches/default_benchmark.rs +++ b/benches/default_benchmark.rs @@ -96,6 +96,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { sky_color: Color::new(0.5, 0.5, 0.5), pixel_radius: 2.0, bvh_split_method: Some(BVHSplitMethod::Mid), + gamma_correction: 2.2, }; let config_sah: Config = Config { @@ -108,6 +109,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { sky_color: Color::new(0.5, 0.5, 0.5), pixel_radius: 2.0, bvh_split_method: Some(BVHSplitMethod::SAH), + gamma_correction: 2.2, }; let mut r_mid = RenderIntegrator::new(json_scene.clone(), config_mid); diff --git a/input/config.json b/input/config.json index f907705..82e58d6 100644 --- a/input/config.json +++ b/input/config.json @@ -1,9 +1,9 @@ { "img_width": 600.0, "img_height": 600.0, - "sample_batch_size": 16, - "max_sample_batches": 12, - "min_sample_batches": 2, + "sample_batch_size": 64, + "max_sample_batches": 120, + "min_sample_batches": 10, "max_depth": 6, "sky_color": { "r": 0.3, @@ -11,5 +11,6 @@ "b": 0.9 }, "pixel_radius": 2.0, - "bvh_split_method": "SAH" + "bvh_split_method": "SAH", + "gamma_correction": 2.2 } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 79b670f..89dab32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,6 +60,7 @@ mod tests { sky_color: Color::new(0.5, 0.5, 0.5), pixel_radius: 2.0, bvh_split_method: Some(BVHSplitMethod::Mid), + gamma_correction: 2.2, }; let json_camera: JSONCamera = JSONCamera { camera_center: Vec3::new(278., 278., -800.), diff --git a/src/render/camera.rs b/src/render/camera.rs index 77ff888..5377849 100644 --- a/src/render/camera.rs +++ b/src/render/camera.rs @@ -151,6 +151,7 @@ mod tests { sky_color: Color::new(3.0 / 255.0, 165.0 / 255.0, 252.0 / 255.0), pixel_radius: 2.0, bvh_split_method: Some(crate::bvh::BVHSplitMethod::Mid), + gamma_correction: 2.2, }; // TODO diff --git a/src/render/color.rs b/src/render/color.rs index 43fcb01..496fee0 100644 --- a/src/render/color.rs +++ b/src/render/color.rs @@ -64,7 +64,8 @@ impl Color { #[inline(always)] pub fn illuminance(&self) -> f64 { - 0.2126 * self.r + 0.7152 * self.g+ 0.0722 * self.b + let c = self.clamp(); + 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b } } diff --git a/src/render/config.rs b/src/render/config.rs index ae8a4b9..38e56ba 100644 --- a/src/render/config.rs +++ b/src/render/config.rs @@ -16,6 +16,7 @@ pub struct Config { pub sky_color: Color, pub pixel_radius: f64, pub bvh_split_method: Option, + pub gamma_correction: f64, } impl Default for Config { @@ -40,6 +41,7 @@ impl Default for Config { sky_color, bvh_split_method: Some(bvh_split_method), pixel_radius: 2.0, + gamma_correction: 2.2, } } } diff --git a/src/render/integrator.rs b/src/render/integrator.rs index 1af40c8..2b00e52 100644 --- a/src/render/integrator.rs +++ b/src/render/integrator.rs @@ -24,7 +24,7 @@ use crate::{ use sobol_burley::sample; -use super::filter::{Filter, MitchellNetravali}; +use super::{filter::{Filter, MitchellNetravali}, Stats}; #[derive(Debug, Clone)] pub struct RenderIntegrator { @@ -267,18 +267,14 @@ impl RenderIntegrator { for (x, band_item) in band.iter_mut().enumerate() { // start with black color - let mut color: Color = Color::new(0.0, 0.0, 0.0); - //let mut previous_color: Color = Color::new(0.0, 0.0, 0.0); let mut actual_samples: usize = 0; - let mut sum_sample_weight: f64 = 0.0; // sets a pixel to follow and print detailed logs let _follow_coords: [usize; 2] = [40, 120]; let pixel_num: usize = x * y; - let mut colors: Vec = Vec::with_capacity(config.max_sample_batches); - let mut weights: Vec = Vec::with_capacity(config.max_sample_batches); + let mut stats = Stats::new(max_samples); 'batch: for b in 0..config.max_sample_batches { let mut batch_color: Color = Color::new(0.0, 0.0, 0.0); @@ -319,35 +315,19 @@ impl RenderIntegrator { actual_samples += 1; } - colors.push(batch_color); - weights.push(batch_sum_sample_weight); - - - - //color += batch_color; - //sum_sample_weight += batch_sum_sample_weight; - - + stats.push(batch_color, batch_sum_sample_weight); if b > config.min_sample_batches { - if (true) - { + //log::info!("var {}, check {}", stats.interval(), 0.05 * stats.mean().illuminance()); + + if stats.interval() < 0.05 * stats.mean().illuminance() { break 'batch; } } } - let sum_color: Color = colors.iter() - .zip(weights.iter()) - .map(|(c, w)| *c * *w) - .sum(); - - let sum_weights: f64 = weights.iter().sum(); - // set pixel color, but first divide by the number of samples to get the average and return - *band_item = (sum_color / sum_weights) - .clamp() - .linear_to_gamma(2.5); + *band_item = (stats.mean()).clamp().linear_to_gamma(config.gamma_correction); let factor: f64 = (actual_samples - config.min_sample_batches * config.sample_batch_size) as f64 diff --git a/src/render/stats.rs b/src/render/stats.rs new file mode 100644 index 0000000..b6ee527 --- /dev/null +++ b/src/render/stats.rs @@ -0,0 +1,52 @@ +use super::Color; + +pub struct Stats { + colors: Vec, + weights: Vec +} + +impl Stats { + pub fn new(capacity: usize) -> Self { + Stats { + colors: Vec::with_capacity(capacity), + weights: Vec::with_capacity(capacity), + } + } + + pub fn push(&mut self, color: Color, weight: f64) { + self.colors.push(color); + self.weights.push(weight); + } + + pub fn mean(&self) -> Color { + let color: Color = self.colors.iter() + .zip(self.weights.iter()) + .map(|(c, w)| *c * *w) + .sum(); + + let weight: f64 = self.weights.iter().sum(); + + color / weight + } + + pub fn variance(&self) -> f64 { + if self.colors.len() == 1 { + 0.0 + } else { + let mean: f64 = self.mean().illuminance(); + let nominator: f64 = self.weights.iter() + .zip(self.colors.iter()) + .map(|(w, c)| *w * (c.illuminance() - mean).powf(2.0)) + .sum(); + + let sum_of_weights: f64 = self.weights.iter().sum(); + let denominator: f64 = (sum_of_weights * (self.colors.len() - 1) as f64) / (self.colors.len() as f64); + + nominator / denominator + } + } + + pub fn interval(&self) -> f64 { + 1.96 * (self.variance().sqrt() / (self.colors.len() as f64).sqrt()) + } +}