diff --git a/ray-tracing-core/src/light.rs b/ray-tracing-core/src/light.rs index a804669..9201fc0 100644 --- a/ray-tracing-core/src/light.rs +++ b/ray-tracing-core/src/light.rs @@ -5,7 +5,7 @@ pub trait Light: Send + Sync + Debug { fn emit(&self, w_in: Dir3, rng: &mut R) -> Color; } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct AreaLight { pub(crate) color: Color, } diff --git a/ray-tracing-material-visualizer/src/bin/iridescent.rs b/ray-tracing-material-visualizer/src/bin/iridescent.rs new file mode 100644 index 0000000..3bc8047 --- /dev/null +++ b/ray-tracing-material-visualizer/src/bin/iridescent.rs @@ -0,0 +1,102 @@ +use core::f32; + +use plotters::{prelude::*, style::full_palette::GREEN}; +use rand::{rngs::SmallRng, Rng, SeedableRng}; +use ray_tracing_material::iridescent::Iridescent; +fn main() -> Result<(), Box> { + let root = BitMapBackend::new("iridescent_sweep.png", (800, 600)).into_drawing_area(); + + root.fill(&WHITE)?; + let mut chart = ChartBuilder::on(&root) + .margin(5) + .x_label_area_size(30) + .y_label_area_size(30) + .build_cartesian_2d(0f32..f32::consts::FRAC_PI_2, 0f32..1f32)?; + + chart.configure_mesh().draw()?; + + let (pixel_range_x, pixel_range_y) = chart.plotting_area().get_pixel_range(); + let buckets = pixel_range_x.end - pixel_range_x.start; + let height_per_pixel = 1.0 / (pixel_range_y.end - pixel_range_y.start) as f32; + let samples = 10000; + + let m = Iridescent::new(0.5 * 244.0, 0.5 * 244.0, 1.0, 1.5, 20); + + let mut data = Vec::new(); + + let mut rng = SmallRng::seed_from_u64(0); + + let plotting_area = chart.plotting_area(); + for i in 0..buckets { + let mut s = ray_tracing_core::color::Color::black(); + let mut s_wavelength = ray_tracing_core::color::Color::black(); + + for _ in 0..samples { + let theta1 = rng.gen_range( + (f32::consts::FRAC_PI_2 * (i as f32 / buckets as f32)) + ..=(f32::consts::FRAC_PI_2 * ((i + 1) as f32 / buckets as f32)), + ); + s += m.monte_carlo_reflectance(theta1, &mut rng); + s_wavelength += ray_tracing_core::color::Color::new( + m.averaged_reflectance(595.0, theta1), + m.averaged_reflectance(556.0, theta1), + m.averaged_reflectance(442.0, theta1), + ); + } + + let c = s / samples as f32; + + // data.push(c); + data.push(s_wavelength / samples as f32); + // dbg!(c); + for j in 0..10 { + plotting_area + .draw_pixel( + ( + f32::consts::FRAC_PI_2 * (i as f32 / buckets as f32), + 1.0 - (height_per_pixel * (j as f32)), + ), + &RGBColor( + (c.r() * 256.0) as u8, + (c.g() * 256.0) as u8, + (c.b() * 256.0) as u8, + ), + ) + .unwrap(); + } + } + + chart.draw_series(LineSeries::new( + data.iter().enumerate().map(|(i, c)| { + ( + (f32::consts::FRAC_PI_2 * (i as f32 / buckets as f32)), + c.r(), + ) + }), + &RED, + ))?; + + chart.draw_series(LineSeries::new( + data.iter().enumerate().map(|(i, c)| { + ( + (f32::consts::FRAC_PI_2 * (i as f32 / buckets as f32)), + c.g(), + ) + }), + &GREEN, + ))?; + + chart.draw_series(LineSeries::new( + data.iter().enumerate().map(|(i, c)| { + ( + (f32::consts::FRAC_PI_2 * (i as f32 / buckets as f32)), + c.b(), + ) + }), + &BLUE, + ))?; + + root.present()?; + + Ok(()) +} diff --git a/ray-tracing-material/src/iridescent.rs b/ray-tracing-material/src/iridescent.rs index a29f2cb..7519486 100644 --- a/ray-tracing-material/src/iridescent.rs +++ b/ray-tracing-material/src/iridescent.rs @@ -2,7 +2,7 @@ use core::f32; use ray_tracing_core::prelude::*; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Iridescent { d1: f32, d2: f32, @@ -49,12 +49,12 @@ impl Iridescent { let imag = (num_imag * denom_real - num_real * denom_imag) / (denom_real * denom_real + denom_imag * denom_imag); - if !real.is_normal() || !imag.is_normal() { - dbg!( - l, theta1, theta2, s_polaized, r12, phi, num_real, num_imag, denom_real, - denom_imag, real, imag - ); - } + // if !real.is_normal() || !imag.is_normal() { + // dbg!( + // l, theta1, theta2, s_polaized, r12, phi, num_real, num_imag, denom_real, + // denom_imag, real, imag + // ); + // } real * real + imag * imag } @@ -87,34 +87,34 @@ impl Iridescent { let local_c_squred = self.abs_C_squared(l, theta1, theta2, s_polaized); - if !local_c_squred.is_normal() || !local_cos_k_delta.is_normal() { - dbg!(( - l, - theta1, - theta2, - local_cos_k_delta, - local_c_squred, - s_polaized - )); - } + // if !local_c_squred.is_normal() || !local_cos_k_delta.is_normal() { + // dbg!(( + // l, + // theta1, + // theta2, + // local_cos_k_delta, + // local_c_squred, + // s_polaized + // )); + // } if local_cos_k_delta.abs() < 1.0 { let k_delta = f32::acos(local_cos_k_delta); let u = f32::sin(k_delta) / f32::sin(self.n * k_delta); - if !k_delta.is_normal() || !u.is_normal() { - dbg!(( - l, - theta1, - theta2, - local_cos_k_delta, - local_c_squred, - s_polaized, - k_delta, - u, - )); - } + // if !k_delta.is_normal() || !u.is_normal() { + // dbg!(( + // l, + // theta1, + // theta2, + // local_cos_k_delta, + // local_c_squred, + // s_polaized, + // k_delta, + // u, + // )); + // } local_c_squred / (local_c_squred + u * u) } else if local_cos_k_delta.abs() > 1.0 { @@ -124,40 +124,40 @@ impl Iridescent { let u = f32::sinh(imk_delta) / f32::sinh(self.n * imk_delta); - if !imk_delta.is_normal() || !u.is_normal() { - dbg!(( - l, - theta1, - theta2, - local_cos_k_delta, - local_c_squred, - s_polaized, - imk_delta, - u - )); - } + // if !imk_delta.is_normal() || !u.is_normal() { + // dbg!(( + // l, + // theta1, + // theta2, + // local_cos_k_delta, + // local_c_squred, + // s_polaized, + // imk_delta, + // u + // )); + // } local_c_squred / (local_c_squred + u * u) } else { let u = 1.0 / self.n; - if !u.is_normal() { - dbg!(( - l, - theta1, - theta2, - local_cos_k_delta, - local_c_squred, - s_polaized, - u - )); - } + // if !u.is_normal() { + // dbg!(( + // l, + // theta1, + // theta2, + // local_cos_k_delta, + // local_c_squred, + // s_polaized, + // u + // )); + // } local_c_squred / (local_c_squred + u * u) } } - fn averaged_reflectance(&self, l: f32, theta1: f32) -> f32 { + pub fn averaged_reflectance(&self, l: f32, theta1: f32) -> f32 { 0.5 * (self.reflectance(l, theta1, true) + self.reflectance(l, theta1, false)) } @@ -183,25 +183,25 @@ impl Iridescent { ) } - fn monte_carlo_reflectance(&self, theta1: f32, rng: &mut R) -> Color { + pub fn monte_carlo_reflectance(&self, theta1: f32, rng: &mut R) -> Color { let range = 400.0..800.0; - let size = range.end - range.start; + // let size = range.end - range.start; let l = rng.gen_range(range); let r = self.averaged_reflectance(l, theta1); - if !r.is_normal() { - dbg!((theta1, l, r)); - } + // if !r.is_normal() { + // dbg!((theta1, l, r)); + // } let color = Self::color_matching_function(l); - if !color.r().is_normal() || !color.g().is_normal() || !color.b().is_normal() { - dbg!(color); - } + // if !color.r().is_normal() || !color.g().is_normal() || !color.b().is_normal() { + // dbg!(color); + // } - color * r + color * r * (1.0 / 0.27) } } diff --git a/ray-tracing-scene/src/basic_scene.rs b/ray-tracing-scene/src/basic_scene.rs index f2ee4f6..4b764d7 100644 --- a/ray-tracing-scene/src/basic_scene.rs +++ b/ray-tracing-scene/src/basic_scene.rs @@ -1,26 +1,32 @@ +use ray_tracing_core::light::AreaLight; use ray_tracing_core::prelude::*; use ray_tracing_core::scene::{Intersection, Scene}; use ray_tracing_material::default::DefaultMaterial; -pub struct BasicScene { +#[derive(Clone)] +pub struct BasicScene { pub(crate) spheres: Vec<(Pos3, Float)>, + area_light: AreaLight, + material: M, } -impl BasicScene { - pub fn new() -> Self { +impl BasicScene { + pub fn new(material: M) -> Self { Self { - spheres: vec![(Pos3::zero(), 1.0), (Pos3::new(0.0, 0.0, 2.5), 2.0)], + spheres: vec![(Pos3::zero(), 1.0), (Pos3::new(0.0, -4.5, 0.0), 4.0)], + area_light: AreaLight::new(Color::white()), + material, } } } -impl Default for BasicScene { +impl Default for BasicScene { fn default() -> Self { - Self::new() + Self::new(DefaultMaterial {}) } } -impl Scene for BasicScene { +impl> Scene for BasicScene { fn intersect(&self, ray: Ray, min: Float, max: Float) -> Option> { let mut intersection: Option> = None; @@ -35,7 +41,7 @@ impl Scene for BasicScene { let int = Intersection::new( d, ((ray.start() + d * ray.dir()) - c).normalize(), - Some(&DefaultMaterial {}), + Some(&self.material), None, 0.0, ); @@ -51,7 +57,13 @@ impl Scene for BasicScene { } } - intersection + Some(intersection.unwrap_or(Intersection::new( + Float::INFINITY, + -ray.dir(), + None, + Some(&self.area_light), + 0.0, + ))) } fn sample_light( diff --git a/ray-tracing-scene/src/examples/mod.rs b/ray-tracing-scene/src/examples/mod.rs index 3c4fea3..2371ac9 100644 --- a/ray-tracing-scene/src/examples/mod.rs +++ b/ray-tracing-scene/src/examples/mod.rs @@ -28,7 +28,7 @@ pub fn example_scenes() -> HashMap<&'static str, Box() -> HashMap<&'static str, Box { + scene: OnceCell>, + material: Cell>, +} + +impl SphereScene { + pub fn new(material: M) -> Self { + Self { + scene: OnceCell::new(), + material: Cell::new(Some(material)), + } + } +} + +impl + Clone + 'static> ExampleScene for SphereScene { + fn get_scene(&self) -> Box + Sync> { + let s = self + .scene + .get_or_init(|| BasicScene::new(self.material.take().unwrap())); + + Box::new(s.clone()) + } + + fn get_camera_pos(&self) -> Pos3 { + Pos3::new(5.0, 1.0, 0.0) + } + + fn get_camera_look_at(&self) -> Pos3 { + Pos3::new(0.0, 0.0, 0.0) + } + + fn get_camera_up(&self) -> Dir3 { + Dir3::up() + } + + fn get_horizontal_fov(&self) -> Float { + Float::to_radians(90.0) + } +}