vanrijn/src/accumulation_buffer.rs

329 lines
12 KiB
Rust

use crate::colour::{ColourXyz, Photon};
use crate::image::{ImageRgbU8, ToneMapper};
use crate::util::{Array2D, Tile};
#[derive(Clone, Debug)]
pub struct AccumulationBuffer {
colour_buffer: Array2D<ColourXyz>,
colour_sum_buffer: Array2D<ColourXyz>,
colour_bias_buffer: Array2D<ColourXyz>,
weight_buffer: Array2D<f64>,
weight_bias_buffer: Array2D<f64>,
}
impl AccumulationBuffer {
pub fn new(height: usize, width: usize) -> AccumulationBuffer {
let colour_buffer = Array2D::new(width, height);
let colour_sum_buffer = Array2D::new(width, height);
let colour_bias_buffer = Array2D::new(width, height);
let weight_buffer = Array2D::new(width, height);
let weight_bias_buffer = Array2D::new(width, height);
AccumulationBuffer {
colour_buffer,
colour_sum_buffer,
colour_bias_buffer,
weight_buffer,
weight_bias_buffer,
}
}
pub fn width(&self) -> usize {
self.colour_buffer.get_width()
}
pub fn height(&self) -> usize {
self.colour_buffer.get_height()
}
pub fn to_image_rgb_u8<Op: ToneMapper<ColourXyz>>(&self, tone_mapper: &Op) -> ImageRgbU8 {
let mut result = ImageRgbU8::new(self.width(), self.height());
tone_mapper.apply_tone_mapping(&self.colour_buffer, &mut result);
result
}
pub fn update_pixel(&mut self, row: usize, column: usize, photon: &Photon, weight: f64) {
let buffer_colour = &mut self.colour_buffer[row][column];
let buffer_colour_sum = &mut self.colour_sum_buffer[row][column];
let buffer_colour_bias = &mut self.colour_bias_buffer[row][column];
let buffer_weight = &mut self.weight_buffer[row][column];
let buffer_weight_bias = &mut self.weight_bias_buffer[row][column];
let photon_colour = ColourXyz::from_photon(photon);
let weight_sum_y = weight - *buffer_weight_bias;
let weight_sum_t = *buffer_weight + weight_sum_y;
*buffer_weight_bias = (weight_sum_t - *buffer_weight) - weight_sum_y;
*buffer_weight = weight_sum_t;
let colour_sum_y = photon_colour.values * weight - buffer_colour_bias.values;
let colour_sum_t = buffer_colour_sum.values + colour_sum_y;
buffer_colour_bias.values = (colour_sum_t - buffer_colour_sum.values) - colour_sum_y;
buffer_colour_sum.values = colour_sum_t;
buffer_colour.values = buffer_colour_sum.values / *buffer_weight;
}
pub fn merge_tile(&mut self, tile: &Tile, src: &AccumulationBuffer) {
assert!(tile.width() == src.width());
assert!(tile.height() == src.height());
for i in 0..tile.height() {
for j in 0..tile.width() {
let dst_colour = &mut self.colour_buffer[tile.start_row + i][tile.start_column + j];
let dst_weight = &mut self.weight_buffer[tile.start_row + i][tile.start_column + j];
*dst_colour = blend(
dst_colour,
*dst_weight,
&src.colour_buffer[i][j],
src.weight_buffer[i][j],
);
*dst_weight += src.weight_buffer[i][j];
}
}
}
}
fn blend(colour1: &ColourXyz, weight1: f64, colour2: &ColourXyz, weight2: f64) -> ColourXyz {
ColourXyz {
values: (colour1.values * weight1 + colour2.values * weight2) * (1.0 / (weight1 + weight2)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn has_expected_width() {
let target = AccumulationBuffer::new(16, 12);
assert!(target.width() == 16);
}
#[test]
fn has_expected_height() {
let target = AccumulationBuffer::new(16, 12);
assert!(target.height() == 12);
}
#[test]
fn update_pixel_does_not_panic_inside_bounds() {
let mut target = AccumulationBuffer::new(16, 12);
for i in 0..12 {
for j in 0..16 {
target.update_pixel(i, j, &Default::default(), 1.0);
}
}
}
#[test]
#[should_panic]
fn update_pixel_panics_when_row_to_large() {
let mut target = AccumulationBuffer::new(16, 12);
target.update_pixel(12, 0, &Default::default(), 1.0);
}
#[test]
#[should_panic]
fn update_pixel_panics_when_column_to_large() {
let mut target = AccumulationBuffer::new(16, 12);
target.update_pixel(0, 16, &Default::default(), 1.0);
}
#[test]
fn first_update_sets_expected_value() {
let mut target = AccumulationBuffer::new(16, 12);
let photon = Photon {
wavelength: 589.0,
intensity: 1.5,
};
let row = 4;
let column = 5;
let weight = 0.8;
target.update_pixel(row, column, &photon, weight);
assert!(target.colour_buffer[row][column] == ColourXyz::from_photon(&photon));
assert!(target.weight_buffer[row][column] == weight);
}
#[test]
fn first_update_only_sets_expected_value() {
let mut target = AccumulationBuffer::new(16, 12);
let original = target.clone();
let photon = Photon {
wavelength: 589.0,
intensity: 1.5,
};
let set_row = 4;
let set_column = 5;
target.update_pixel(set_row, set_column, &photon, 0.8);
for i in 0..12 {
for j in 0..16 {
if i != set_row && j != set_column {
assert!(target.colour_buffer[i][j] == original.colour_buffer[i][j]);
assert!(target.weight_buffer[i][j] == original.weight_buffer[i][j]);
}
}
}
}
#[test]
fn second_update_blends_colours() {
let mut target = AccumulationBuffer::new(16, 12);
let photon1 = Photon {
wavelength: 589.0,
intensity: 0.5,
};
let photon2 = Photon {
wavelength: 656.0,
intensity: 1.5,
};
let colour1 = ColourXyz::from_photon(&photon1);
let colour2 = ColourXyz::from_photon(&photon2);
let expected_x = (colour1.x() + colour2.x()) / 2.0;
let expected_y = (colour1.y() + colour2.y()) / 2.0;
let expected_z = (colour1.z() + colour2.z()) / 2.0;
let row = 4;
let column = 5;
target.update_pixel(row, column, &photon1, 1.0);
target.update_pixel(row, column, &photon2, 1.0);
assert!(target.colour_buffer[row][column].x() == expected_x);
assert!(target.colour_buffer[row][column].y() == expected_y);
assert!(target.colour_buffer[row][column].z() == expected_z);
}
#[test]
fn second_update_blends_colours_proportionally() {
let mut target = AccumulationBuffer::new(16, 12);
let photon1 = Photon {
wavelength: 589.0,
intensity: 0.5,
};
let photon2 = Photon {
wavelength: 656.0,
intensity: 1.5,
};
let colour1 = ColourXyz::from_photon(&photon1);
let colour2 = ColourXyz::from_photon(&photon2);
let weight1 = 0.75;
let weight2 = 1.25;
let expected_x = (colour1.x() * weight1 + colour2.x() * weight2) / (weight1 + weight2);
let expected_y = (colour1.y() * weight1 + colour2.y() * weight2) / (weight1 + weight2);
let expected_z = (colour1.z() * weight1 + colour2.z() * weight2) / (weight1 + weight2);
let row = 4;
let column = 5;
target.update_pixel(row, column, &photon1, weight1);
target.update_pixel(row, column, &photon2, weight2);
assert!(target.colour_buffer[row][column].x() == expected_x);
assert!(target.colour_buffer[row][column].y() == expected_y);
assert!(target.colour_buffer[row][column].z() == expected_z);
}
#[test]
fn third_update_blends_colours_proportionally() {
let mut target = AccumulationBuffer::new(16, 12);
let photon1 = Photon {
wavelength: 589.0,
intensity: 0.5,
};
let photon2 = Photon {
wavelength: 656.0,
intensity: 1.5,
};
let photon3 = Photon {
wavelength: 393.0,
intensity: 1.2,
};
let colour1 = ColourXyz::from_photon(&photon1);
let colour2 = ColourXyz::from_photon(&photon2);
let colour3 = ColourXyz::from_photon(&photon3);
let weight1 = 0.75;
let weight2 = 1.25;
let weight3 = 0.5;
let expected_x = (colour1.x() * weight1 + colour2.x() * weight2 + colour3.x() * weight3)
/ (weight1 + weight2 + weight3);
let expected_y = (colour1.y() * weight1 + colour2.y() * weight2 + colour3.y() * weight3)
/ (weight1 + weight2 + weight3);
let expected_z = (colour1.z() * weight1 + colour2.z() * weight2 + colour3.z() * weight3)
/ (weight1 + weight2 + weight3);
let row = 4;
let column = 5;
target.update_pixel(row, column, &photon1, weight1);
target.update_pixel(row, column, &photon2, weight2);
target.update_pixel(row, column, &photon3, weight3);
assert!(target.colour_buffer[row][column].x() == expected_x);
assert!(target.colour_buffer[row][column].y() == expected_y);
assert!(target.colour_buffer[row][column].z() == expected_z);
}
#[test]
fn merge_tile_produces_same_results_as_applying_photons_directly() {
let mut single_buffer = AccumulationBuffer::new(16, 12);
let mut large_buffer = AccumulationBuffer::new(16, 12);
let mut small_buffer = AccumulationBuffer::new(4, 5);
let tile = Tile {
start_column: 3,
end_column: 7,
start_row: 4,
end_row: 9,
};
for i in 0..12 {
for j in 0..16 {
let wavelength = 350.0 + (i * j) as f64;
let intensity = 1.0;
let weight = 0.2 + i as f64 * 0.02 + j as f64 * 0.3;
single_buffer.update_pixel(
i,
j,
&Photon {
wavelength,
intensity,
},
weight,
);
large_buffer.update_pixel(
i,
j,
&Photon {
wavelength,
intensity,
},
weight,
);
}
}
for i in 0..5 {
for j in 0..4 {
let wavelength = 700.0 - (i * j) as f64;
let intensity = 1.0;
let weight = 0.2 + i as f64 * 0.02 + j as f64 * 0.3;
small_buffer.update_pixel(
i,
j,
&Photon {
wavelength,
intensity,
},
weight,
);
single_buffer.update_pixel(
tile.start_row + i,
tile.start_column + j,
&Photon {
wavelength,
intensity,
},
weight,
);
}
}
large_buffer.merge_tile(&tile, &small_buffer);
for i in 0..12 {
for j in 0..16 {
assert!(
(large_buffer.colour_buffer[i][j].values
- single_buffer.colour_buffer[i][j].values)
.norm()
< 0.0000000001
);
assert!(large_buffer.weight_buffer[i][j] == single_buffer.weight_buffer[i][j]);
}
}
}
}