From 2494a30aefcb710c36a3c62148a11fab9917c251 Mon Sep 17 00:00:00 2001 From: Matthew Gordon Date: Sat, 5 Sep 2020 22:45:43 -0400 Subject: [PATCH] Cast rays for single light wavelengths instead of RGB values Colours are wrong because Spectrum class isn't implemented. Also there's a lot of other cleanup that needs to be done. --- src/accumulation_buffer.rs | 2 +- src/camera.rs | 65 ++++++++++++++++++++-------- src/colour/mod.rs | 6 +++ src/colour/photon.rs | 16 +++++++ src/colour/spectrum.rs | 24 ++++++++++ src/image.rs | 26 ++++++++++- src/integrators.rs | 55 +++++++++++++---------- src/main.rs | 54 +++++++++++------------ src/materials/lambertian_material.rs | 17 +++++--- src/materials/mod.rs | 9 +--- src/materials/phong_material.rs | 30 +++++++------ src/materials/reflective_material.rs | 28 +++++++----- 12 files changed, 223 insertions(+), 109 deletions(-) create mode 100644 src/colour/spectrum.rs diff --git a/src/accumulation_buffer.rs b/src/accumulation_buffer.rs index 26a974a..9d75dec 100644 --- a/src/accumulation_buffer.rs +++ b/src/accumulation_buffer.rs @@ -2,7 +2,7 @@ use crate::colour::{ColourXyz, Photon}; use crate::image::{ImageRgbU8, ToneMapper}; use crate::util::{Array2D, Tile}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct AccumulationBuffer { colour_buffer: Array2D, weight_buffer: Array2D, diff --git a/src/camera.rs b/src/camera.rs index c5c6592..741c34b 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,7 +1,10 @@ use crate::math::Vec3; -use super::colour::{ColourRgbF, NamedColour}; -use super::image::ImageRgbF; +use super::accumulation_buffer::AccumulationBuffer; +use super::colour::{ + ColourRgbF, NamedColour, Photon, Spectrum, LONGEST_VISIBLE_WAVELENGTH, + SHORTEST_VISIBLE_WAVELENGTH, +}; use super::integrators::{DirectionalLight, Integrator, WhittedIntegrator}; use super::raycasting::Ray; use super::sampler::Sampler; @@ -90,42 +93,70 @@ const RECURSION_LIMIT: u16 = 32; /// // display and/or save tile_image /// } /// ``` -pub fn partial_render_scene(scene: &Scene, tile: Tile, height: usize, width: usize) -> ImageRgbF { - let mut output_image_tile = ImageRgbF::new(tile.width(), tile.height()); +pub fn partial_render_scene( + scene: &Scene, + tile: Tile, + height: usize, + width: usize, +) -> AccumulationBuffer { + let mut output_image_tile = AccumulationBuffer::new(tile.width(), tile.height()); let image_sampler = ImageSampler::new(width, height, scene.camera_location); let ambient_intensity = 0.0; let directional_intensity1 = 7.0; let directional_intensity2 = 3.0; let directional_intensity3 = 2.0; let integrator = WhittedIntegrator { - ambient_light: ColourRgbF::from_named(NamedColour::White) * ambient_intensity, + ambient_light: Spectrum::from_linear_rgb( + &(ColourRgbF::from_named(NamedColour::White) * ambient_intensity), + ), lights: vec![ DirectionalLight { direction: Vec3::new(1.0, 1.0, -1.0).normalize(), - colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity1, + spectrum: Spectrum::from_linear_rgb( + &(ColourRgbF::from_named(NamedColour::White) * directional_intensity1), + ), }, DirectionalLight { direction: Vec3::new(-0.5, 2.0, -0.5).normalize(), - colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity2, + spectrum: Spectrum::from_linear_rgb( + &(ColourRgbF::from_named(NamedColour::White) * directional_intensity2), + ), }, DirectionalLight { direction: Vec3::new(-3.0, 0.1, -0.5).normalize(), - colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity3, + spectrum: Spectrum::from_linear_rgb( + &(ColourRgbF::from_named(NamedColour::White) * directional_intensity3), + ), }, ], }; let sampler = Sampler { scene: &scene }; for column in 0..tile.width() { for row in 0..tile.height() { - let ray = image_sampler.ray_for_pixel(tile.start_row + row, tile.start_column + column); - let hit = sampler.sample(&ray); - let colour = match hit { - None => ColourRgbF::from_named(NamedColour::Black), - Some(intersection_info) => { - integrator.integrate(&sampler, &intersection_info, RECURSION_LIMIT) - } - }; - output_image_tile.set_colour(row, column, colour); + for wavelength_number in 0..8 { + let wavelength_ratio = wavelength_number as f64 / 8.0; + let wavelength = SHORTEST_VISIBLE_WAVELENGTH + + wavelength_ratio * (LONGEST_VISIBLE_WAVELENGTH - SHORTEST_VISIBLE_WAVELENGTH); + let ray = + image_sampler.ray_for_pixel(tile.start_row + row, tile.start_column + column); + let hit = sampler.sample(&ray); + let photon = match hit { + None => Photon { + wavelength: 0.0, + intensity: 0.0, + }, + Some(intersection_info) => integrator.integrate( + &sampler, + &intersection_info, + &Photon { + wavelength, + intensity: 0.0, + }, + RECURSION_LIMIT, + ), + }; + output_image_tile.update_pixel(row, column, &photon, 1.0); + } } } output_image_tile diff --git a/src/colour/mod.rs b/src/colour/mod.rs index 83c9332..27b567d 100644 --- a/src/colour/mod.rs +++ b/src/colour/mod.rs @@ -6,3 +6,9 @@ pub use photon::Photon; pub mod colour_xyz; pub use colour_xyz::ColourXyz; + +pub mod spectrum; +pub use spectrum::Spectrum; + +pub const SHORTEST_VISIBLE_WAVELENGTH: f64 = 380.0; +pub const LONGEST_VISIBLE_WAVELENGTH: f64 = 740.0; diff --git a/src/colour/photon.rs b/src/colour/photon.rs index b2732c3..dc1ed2b 100644 --- a/src/colour/photon.rs +++ b/src/colour/photon.rs @@ -9,3 +9,19 @@ pub struct Photon { /// radiant flux in W, irradiance in W/m^2, or radiance in W/(m^2sr). pub intensity: f64, } + +impl Photon { + pub fn scale_intensity(&self, scale_factor: f64) -> Photon { + Photon { + wavelength: self.wavelength, + intensity: self.intensity * scale_factor, + } + } + + pub fn set_intensity(&self, intensity: f64) -> Photon { + Photon { + wavelength: self.wavelength, + intensity: intensity, + } + } +} diff --git a/src/colour/spectrum.rs b/src/colour/spectrum.rs new file mode 100644 index 0000000..398f2c5 --- /dev/null +++ b/src/colour/spectrum.rs @@ -0,0 +1,24 @@ +use crate::colour::{ColourRgbF, Photon}; + +#[derive(Debug)] +pub struct Spectrum {} + +impl Spectrum { + pub fn from_linear_rgb(colour: &ColourRgbF) -> Spectrum { + Spectrum {} + } + + pub fn intensity_at_wavelength(&self, wavelength: f64) -> f64 { + 1.0 + } + + pub fn scale_photon(&self, photon: &Photon) -> Photon { + let wavelength = photon.wavelength; + photon.scale_intensity(self.intensity_at_wavelength(wavelength)) + } + + pub fn emit_photon(&self, photon: &Photon) -> Photon { + let wavelength = photon.wavelength; + photon.set_intensity(self.intensity_at_wavelength(wavelength)) + } +} diff --git a/src/image.rs b/src/image.rs index 3329046..23f8d5b 100644 --- a/src/image.rs +++ b/src/image.rs @@ -3,9 +3,10 @@ use std::fs::File; use std::io::BufWriter; use std::path::Path; -use crate::colour::{ColourRgbF, ColourRgbU8}; +use crate::colour::{ColourRgbF, ColourRgbU8, ColourXyz}; use crate::util::Array2D; +#[derive(Debug)] pub struct ImageRgbU8 { data: Array2D<[u8; 3]>, } @@ -163,6 +164,29 @@ impl ToneMapper for ClampingToneMapper { } } +impl ToneMapper for ClampingToneMapper { + fn apply_tone_mapping(&self, image_in: &Array2D, image_out: &mut ImageRgbU8) { + assert!(image_in.get_width() == image_out.get_width()); + assert!(image_in.get_height() == image_out.get_height()); + for column in 0..image_in.get_width() { + for row in 0..image_in.get_height() { + let colour = image_in[row][column].to_linear_rgb(); + image_out.set_colour( + row, + column, + ColourRgbU8 { + values: [ + Self::clamp(&colour.red()), + Self::clamp(&colour.green()), + Self::clamp(&colour.blue()), + ], + }, + ); + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/integrators.rs b/src/integrators.rs index 01d9190..a67cde8 100644 --- a/src/integrators.rs +++ b/src/integrators.rs @@ -1,6 +1,6 @@ use crate::math::Vec3; -use super::colour::ColourRgbF; +use super::colour::{Photon, Spectrum}; use super::raycasting::{IntersectionInfo, Ray}; use super::sampler::Sampler; use super::util::algebra_utils::try_change_of_basis_matrix; @@ -10,29 +10,29 @@ pub trait Integrator { &self, sampler: &Sampler, info: &IntersectionInfo, + photon: &Photon, recursion_limit: u16, - ) -> ColourRgbF; + ) -> Photon; } pub struct DirectionalLight { pub direction: Vec3, - pub colour: ColourRgbF, + pub spectrum: Spectrum, } pub struct WhittedIntegrator { - pub ambient_light: ColourRgbF, + pub ambient_light: Spectrum, pub lights: Vec, } -// TODO: Get rid of the magic bias number, which should be calculated base on expected error -// bounds and tangent direction impl Integrator for WhittedIntegrator { fn integrate( &self, sampler: &Sampler, info: &IntersectionInfo, + photon: &Photon, recursion_limit: u16, - ) -> ColourRgbF { + ) -> Photon { let world_to_bsdf_space = try_change_of_basis_matrix(&info.tangent, &info.cotangent, &info.normal) .expect("Normal, tangent and cotangent don't for a valid basis."); @@ -43,14 +43,15 @@ impl Integrator for WhittedIntegrator { .iter() .map(|light| { match sampler.sample(&Ray::new(info.location, light.direction).bias(0.000_000_1)) { - Some(_) => self.ambient_light, - None => { - info.material.bsdf()( - world_to_bsdf_space * info.retro, - world_to_bsdf_space * light.direction, - light.colour, - ) * light.direction.dot(&info.normal).abs() - } + Some(_) => self.ambient_light.emit_photon(&photon), + None => info.material.bsdf()( + &(world_to_bsdf_space * info.retro), + &(world_to_bsdf_space * light.direction), + &light + .spectrum + .emit_photon(&photon) + .scale_intensity(light.direction.dot(&info.normal).abs()), + ), } }) .chain( @@ -64,23 +65,31 @@ impl Integrator for WhittedIntegrator { ) { Some(recursive_hit) => { if recursion_limit > 0 { - info.material.bsdf()( - world_to_bsdf_space * info.retro, - *direction, - self.integrate( + let photon = info.material.bsdf()( + &(world_to_bsdf_space * info.retro), + direction, + &self.integrate( &sampler, &recursive_hit, + &photon, recursion_limit - 1, ), - ) * world_space_direction.dot(&info.normal).abs() + ); + photon.scale_intensity( + world_space_direction.dot(&info.normal).abs(), + ) } else { - ColourRgbF::new(0.0, 0.0, 0.0) + photon.scale_intensity(0.0) } } - None => ColourRgbF::new(0.0, 0.0, 0.0), + None => photon.scale_intensity(0.0), } }), ) - .fold(self.ambient_light, |a, b| a + b) + .fold(photon.clone(), |a, b| { + let mut result = a; + result.intensity += b.intensity; + result + }) } } diff --git a/src/main.rs b/src/main.rs index 33f971e..4be0ee5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,15 +13,16 @@ use std::path::{Path, PathBuf}; use std::sync::{mpsc, Arc}; use std::time::Duration; -use vanrijn::colour::{ColourRgbF, NamedColour}; -use vanrijn::image::{ClampingToneMapper, ImageRgbU8, ToneMapper}; +use vanrijn::accumulation_buffer::AccumulationBuffer; +use vanrijn::colour::{ColourRgbF, NamedColour, Spectrum}; +use vanrijn::image::{ClampingToneMapper, ImageRgbU8}; use vanrijn::materials::{LambertianMaterial, PhongMaterial, ReflectiveMaterial}; use vanrijn::math::Vec3; use vanrijn::mesh::load_obj; use vanrijn::partial_render_scene; use vanrijn::raycasting::{Aggregate, BoundingVolumeHierarchy, Plane, Primitive, Sphere}; use vanrijn::scene::Scene; -use vanrijn::util::{Tile, TileIterator}; +use vanrijn::util::TileIterator; #[derive(Debug)] struct CommandLineParameters { @@ -73,25 +74,16 @@ fn parse_args() -> CommandLineParameters { } } -fn update_texture(tile: &Tile, image: &ImageRgbU8, texture: &mut Texture) { +fn update_texture(image: &ImageRgbU8, texture: &mut Texture) { texture .update( - Rect::new( - tile.start_column as i32, - tile.start_row as i32, - tile.width() as u32, - tile.height() as u32, - ), + Rect::new(0, 0, image.get_width() as u32, image.get_height() as u32), image.get_pixel_data(), (image.get_width() * ImageRgbU8::num_channels()) as usize, ) .expect("Couldn't update texture."); } -fn update_image(tile: &Tile, tile_image: &ImageRgbU8, image: &mut ImageRgbU8) { - image.update(tile.start_row, tile.start_column, tile_image); -} - fn init_canvas( image_width: usize, image_height: usize, @@ -114,7 +106,7 @@ pub fn main() -> Result<(), Box> { let image_width = parameters.width; let image_height = parameters.height; - let mut rendered_image = ImageRgbU8::new(image_width, image_height); + let mut rendered_image = AccumulationBuffer::new(image_width, image_height); let (sdl_context, mut canvas) = init_canvas(image_width, image_height)?; @@ -131,7 +123,7 @@ pub fn main() -> Result<(), Box> { let mut model_object = load_obj( &model_file_path, Arc::new(ReflectiveMaterial { - colour: ColourRgbF::from_named(NamedColour::Yellow), + colour: Spectrum::from_linear_rgb(&ColourRgbF::from_named(NamedColour::Yellow)), diffuse_strength: 0.05, reflection_strength: 0.9, }), @@ -149,7 +141,7 @@ pub fn main() -> Result<(), Box> { Vec3::new(0.0, 1.0, 0.0), -2.0, Arc::new(LambertianMaterial { - colour: ColourRgbF::new(0.55, 0.27, 0.04), + colour: Spectrum::from_linear_rgb(&ColourRgbF::new(0.55, 0.27, 0.04)), diffuse_strength: 0.1, }), )) as Box, @@ -157,7 +149,9 @@ pub fn main() -> Result<(), Box> { Vec3::new(-6.25, -0.5, 1.0), 1.0, Arc::new(LambertianMaterial { - colour: ColourRgbF::from_named(NamedColour::Green), + colour: Spectrum::from_linear_rgb(&ColourRgbF::from_named( + NamedColour::Green, + )), diffuse_strength: 0.1, }), )), @@ -165,7 +159,9 @@ pub fn main() -> Result<(), Box> { Vec3::new(-4.25, -0.5, 2.0), 1.0, Arc::new(ReflectiveMaterial { - colour: ColourRgbF::from_named(NamedColour::Blue), + colour: Spectrum::from_linear_rgb(&ColourRgbF::from_named( + NamedColour::Blue, + )), diffuse_strength: 0.01, reflection_strength: 0.99, }), @@ -174,7 +170,9 @@ pub fn main() -> Result<(), Box> { Vec3::new(-5.0, 1.5, 1.0), 1.0, Arc::new(PhongMaterial { - colour: ColourRgbF::from_named(NamedColour::Red), + colour: Spectrum::from_linear_rgb(&ColourRgbF::from_named( + NamedColour::Red, + )), diffuse_strength: 0.05, smoothness: 100.0, specular_strength: 1.0, @@ -193,7 +191,7 @@ pub fn main() -> Result<(), Box> { let worker_boss = std::thread::spawn(move || { let end_tx = tile_tx.clone(); - TileIterator::new(image_width as usize, image_height as usize, 32) + TileIterator::new(image_width as usize, image_height as usize, 256) .map(move |tile| (tile, tile_tx.clone())) .par_bridge() .try_for_each(|(tile, tx)| { @@ -209,16 +207,16 @@ pub fn main() -> Result<(), Box> { 'running: loop { if let Some(ref tile_rx) = tile_rx { for message in tile_rx.try_iter() { - if let Some((tile, tile_image)) = message { - let mut tile_image_rgbu8 = ImageRgbU8::new(tile.width(), tile.height()); - ClampingToneMapper {} - .apply_tone_mapping(&tile_image.data, &mut tile_image_rgbu8); - update_texture(&tile, &tile_image_rgbu8, &mut rendered_image_texture); - update_image(&tile, &tile_image_rgbu8, &mut rendered_image); + if let Some((tile, tile_accumulation_buffer)) = message { + rendered_image.merge_tile(&tile, &tile_accumulation_buffer); + let rgb_image = rendered_image.to_image_rgb_u8(&ClampingToneMapper {}); + update_texture(&rgb_image, &mut rendered_image_texture); canvas.copy(&rendered_image_texture, None, None).unwrap(); canvas.present(); } else if let Some(image_filename) = parameters.output_file { - rendered_image.write_png(&image_filename)?; + rendered_image + .to_image_rgb_u8(&ClampingToneMapper {}) + .write_png(&image_filename)?; break 'running; } } diff --git a/src/materials/lambertian_material.rs b/src/materials/lambertian_material.rs index 318eb33..bed4d00 100644 --- a/src/materials/lambertian_material.rs +++ b/src/materials/lambertian_material.rs @@ -1,28 +1,31 @@ -use crate::colour::ColourRgbF; +use crate::colour::{Photon, Spectrum}; use crate::math::Vec3; -use super::{Bsdf, Material}; +use super::Material; use std::fmt::Debug; #[derive(Debug)] pub struct LambertianMaterial { - pub colour: ColourRgbF, + pub colour: Spectrum, pub diffuse_strength: f64, } impl LambertianMaterial { pub fn new_dummy() -> LambertianMaterial { LambertianMaterial { - colour: ColourRgbF::new(1.0, 1.0, 1.0), + colour: Spectrum {}, diffuse_strength: 1.0, } } } impl Material for LambertianMaterial { - fn bsdf(&self) -> Bsdf { - let colour = self.colour * self.diffuse_strength; - Box::new(move |_w_o: Vec3, _w_i: Vec3, colour_in: ColourRgbF| colour * colour_in) + fn bsdf<'a>(&'a self) -> Box Photon + 'a> { + Box::new(move |_w_o: &Vec3, _w_i: &Vec3, photon_in: &Photon| { + let mut result = self.colour.scale_photon(photon_in); + result.intensity *= self.diffuse_strength; + result + }) } } diff --git a/src/materials/mod.rs b/src/materials/mod.rs index 301b46d..7c5e727 100644 --- a/src/materials/mod.rs +++ b/src/materials/mod.rs @@ -1,11 +1,9 @@ use crate::math::Vec3; -use super::colour::ColourRgbF; +use super::colour::Photon; use std::fmt::Debug; -type Bsdf = Box ColourRgbF>; - pub mod lambertian_material; pub use lambertian_material::LambertianMaterial; @@ -15,11 +13,8 @@ pub use phong_material::PhongMaterial; pub mod reflective_material; pub use reflective_material::ReflectiveMaterial; -pub mod rgb_sampled_bsdf_material; -pub use rgb_sampled_bsdf_material::RgbSampledBsdfMaterial; - pub trait Material: Debug + Sync + Send { - fn bsdf(&self) -> Bsdf; + fn bsdf<'a>(&'a self) -> Box Photon + 'a>; fn sample(&self, _w_o: &Vec3) -> Vec { vec![] diff --git a/src/materials/phong_material.rs b/src/materials/phong_material.rs index 1ecd89a..6e45b1c 100644 --- a/src/materials/phong_material.rs +++ b/src/materials/phong_material.rs @@ -1,32 +1,36 @@ -use crate::colour::{ColourRgbF, NamedColour}; +use crate::colour::{Photon, Spectrum}; use crate::math::Vec3; use std::fmt::Debug; -use super::{Bsdf, Material}; +use super::Material; #[derive(Debug)] pub struct PhongMaterial { - pub colour: ColourRgbF, + pub colour: Spectrum, pub diffuse_strength: f64, pub specular_strength: f64, pub smoothness: f64, } impl Material for PhongMaterial { - fn bsdf(&self) -> Bsdf { - let smoothness = self.smoothness; - let specular_strength = self.specular_strength; - let colour = self.colour * self.diffuse_strength; - Box::new(move |w_o: Vec3, w_i: Vec3, colour_in: ColourRgbF| { + fn bsdf<'a>(&'a self) -> Box Photon + 'a> { + Box::new(move |w_o: &Vec3, w_i: &Vec3, photon_in: &Photon| { if w_i.z() < 0.0 || w_o.z() < 0.0 { - ColourRgbF::from_vec3(&Vec3::zeros()) + Photon { + wavelength: photon_in.wavelength, + intensity: 0.0, + } } else { let reflection_vector = Vec3::new(-w_i.x(), -w_i.y(), w_i.z()); - colour * colour_in - + ColourRgbF::from_named(NamedColour::White) - * w_o.dot(&reflection_vector).abs().powf(smoothness) - * (specular_strength / w_i.dot(&Vec3::unit_z())) + let intensity = self.colour.scale_photon(photon_in).intensity + * self.diffuse_strength + + w_o.dot(&reflection_vector).abs().powf(self.smoothness) + * (self.specular_strength / w_i.dot(&Vec3::unit_z())); + Photon { + wavelength: photon_in.wavelength, + intensity, + } } }) } diff --git a/src/materials/reflective_material.rs b/src/materials/reflective_material.rs index b84c21d..a280a00 100644 --- a/src/materials/reflective_material.rs +++ b/src/materials/reflective_material.rs @@ -1,36 +1,40 @@ -use crate::colour::ColourRgbF; +use crate::colour::{Photon, Spectrum}; use crate::math::Vec3; use std::fmt::Debug; -use super::{Bsdf, Material}; +use super::Material; #[derive(Debug)] pub struct ReflectiveMaterial { - pub colour: ColourRgbF, + pub colour: Spectrum, pub diffuse_strength: f64, pub reflection_strength: f64, } impl Material for ReflectiveMaterial { - fn bsdf(&self) -> Bsdf { - let diffuse_colour_factor = self.colour * self.diffuse_strength; - let reflection_strength = self.reflection_strength; - Box::new(move |w_o: Vec3, w_i: Vec3, colour_in: ColourRgbF| { + fn bsdf<'a>(&'a self) -> Box Photon + 'a> { + Box::new(move |w_o: &Vec3, w_i: &Vec3, photon_in: &Photon| { if w_i.z() <= 0.0 || w_o.z() <= 0.0 { - ColourRgbF::new(0.0, 0.0, 0.0) + Photon { + wavelength: photon_in.wavelength, + intensity: 0.0, + } } else { let reflection_vector = Vec3::new(-w_o.x(), -w_o.y(), w_o.z()); - let reflection_colour = colour_in * reflection_strength; - let diffuse_colour = diffuse_colour_factor * colour_in; + let mut photon_out = self.colour.scale_photon(photon_in); + photon_out.intensity *= self.diffuse_strength; let sigma = 0.05; let two = 2.0; // These are normalized vectors, but sometimes rounding errors cause the // dot product to be slightly above 1 or below 0. The call to clamp // ensures the values stay within the domain of acos, let theta = w_i.dot(&reflection_vector).clamp(0.0, 1.0).abs().acos(); - let reflection_factor = (-(theta.powf(two)) / (two * sigma * sigma)).exp(); - reflection_colour * reflection_factor + diffuse_colour * (1.0 - reflection_factor) + let reflection_factor = + self.reflection_strength * (-(theta.powf(two)) / (two * sigma * sigma)).exp(); + photon_out.intensity = + photon_out.intensity * (1.0 - reflection_factor) + reflection_factor; + photon_out } }) }