From df36594ed8c8303a391955e78a675f4ed3116634 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 26 Oct 2022 00:47:09 +1100 Subject: [PATCH 01/39] Added NoHit sampling - Importance sampling added for NoHit - MIS code modified to sample NoHit - CDF1D and CDF2D structs added - f64 feature added to force f64 use - PDF generation from texture added - NoHit trait changed to for PDF query and sampling - gui featured added to avoid bundling vulkano if not needed - Changed permute function in perlin noise to use constant - Added new tests for sky sampling and distributions - Added checking on chi2_probability function - Fixed issue where df = 0 due to pooling under certain conditions - Added from_spherical function to Vec3 - Changed classic scene to use z up --- frontend/Cargo.toml | 14 +- frontend/src/generate.rs | 18 +- frontend/src/gui.rs | 4 +- frontend/src/macros.rs | 9 +- frontend/src/main.rs | 226 +++++++------- implementations/Cargo.toml | 5 +- implementations/src/lib.rs | 1 + .../src/materials/trowbridge_reitz.rs | 6 +- implementations/src/samplers/mod.rs | 64 +++- implementations/src/textures/mod.rs | 42 ++- implementations/src/utility/distribution.rs | 151 ++++++++++ implementations/src/utility/mod.rs | 1 + implementations/tests/bxdf.rs | 77 ----- implementations/tests/common/mod.rs | 94 ++++-- implementations/tests/sampling.rs | 285 ++++++++++++++++++ rt_core/Cargo.toml | 5 +- rt_core/src/ray.rs | 148 +++++++-- rt_core/src/sampler.rs | 9 + rt_core/src/vec.rs | 10 + 19 files changed, 892 insertions(+), 277 deletions(-) create mode 100644 implementations/src/utility/distribution.rs delete mode 100644 implementations/tests/bxdf.rs create mode 100644 implementations/tests/sampling.rs diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 817eda1..43b89e1 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -11,9 +11,13 @@ image = "0.23.14" implementations = {path = "../implementations"} rand = { version = "0.8.3", features = [ "small_rng" ] } rand_seeder = "0.2.2" -rt_core = {path = "../rt_core"} -vulkano = "0.28.0" -vulkano-win = "0.28.0" -vulkano-shaders = "0.28.0" +rt_core = { path = "../rt_core" } +vulkano = { version = "0.28.0", optional = true } +vulkano-win = { version = "0.28.0", optional = true } +vulkano-shaders = { version = "0.28.0", optional = true } wavefront_obj = "10.0.0" -winit = "0.26.1" \ No newline at end of file +winit = { version = "0.26.1", optional = true } + +[features] +f64 = ["implementations/f64", "rt_core/f64"] +gui = ["dep:vulkano", "dep:vulkano-win", "dep:vulkano-shaders", "dep:winit"] \ No newline at end of file diff --git a/frontend/src/generate.rs b/frontend/src/generate.rs index dece113..3d44a94 100644 --- a/frontend/src/generate.rs +++ b/frontend/src/generate.rs @@ -23,13 +23,13 @@ pub fn get_seed(length: usize) -> String { pub fn classic(bvh_type: SplitType, aspect_ratio: Float, seed: Option) -> SceneType { let mut primitives = Vec::new(); - let ground = sphere!(0, -1000, 0, 1000, &diffuse!(0.5, 0.5, 0.5, 0.5)); + let ground = sphere!(0, 0, -1000, 1000, &diffuse!(0.5, 0.5, 0.5, 0.5)); - let sphere_one = sphere!(0, 1, 0, 1, &refract!(&solid_colour!(colour!(1)), 1.5)); + let sphere_one = sphere!(0, 0, 1, 1, &refract!(&solid_colour!(colour!(1)), 1.5)); - let sphere_two = sphere!(-4, 1, 0, 1, &diffuse!(0.4, 0.2, 0.1, 0.5)); + let sphere_two = sphere!(-4, 0, 1, 1, &diffuse!(0.4, 0.2, 0.1, 0.5)); - let sphere_three = sphere!(4, 1, 0, 1, &reflect!(&solid_colour!(0.7, 0.6, 0.5), 0)); + let sphere_three = sphere!(4, 0, 1, 1, &reflect!(&solid_colour!(0.7, 0.6, 0.5), 0)); primitives.push(ground); primitives.push(sphere_one); @@ -47,11 +47,11 @@ pub fn classic(bvh_type: SplitType, aspect_ratio: Float, seed: Option) - for b in -11..11 { let center = position!( a as Float + 0.9 * rng.gen::(), - 0.2, - b as Float + 0.9 * rng.gen::() + b as Float + 0.9 * rng.gen::(), + 0.2 ); - if (center - position!(4.0, 0.2, 0.0)).mag() > 0.9 { + if (center - position!(4.0, 0.0, 0.2)).mag() > 0.9 { let choose_material: Float = rng.gen(); let colour = colour!(rng.gen::(), rng.gen::(), rng.gen::()); @@ -76,9 +76,9 @@ pub fn classic(bvh_type: SplitType, aspect_ratio: Float, seed: Option) - let sky = sky!(&texture_lerp!(colour!(0.5, 0.7, 1), colour!(1))); let camera = camera!( - position!(13, 2, -3), + position!(13, -3, 2), position!(0, 0, 0), - position!(0, 1, 0), + position!(0, 0, 1), 29, aspect_ratio, 0.1, diff --git a/frontend/src/gui.rs b/frontend/src/gui.rs index bc505c0..0d8473d 100644 --- a/frontend/src/gui.rs +++ b/frontend/src/gui.rs @@ -140,8 +140,8 @@ impl Gui { mod cs { vulkano_shaders::shader! { - ty: "compute", - src: + ty: "compute", + src: "#version 460 layout(local_size_x = 32, local_size_y = 32) in; diff --git a/frontend/src/macros.rs b/frontend/src/macros.rs index 3482c97..d3c2f85 100644 --- a/frontend/src/macros.rs +++ b/frontend/src/macros.rs @@ -782,10 +782,15 @@ macro_rules! random_sampler { #[macro_export] macro_rules! sky { () => { - std::sync::Arc::new(implementations::Sky::new(None)) + std::sync::Arc::new(implementations::Sky::new( + &std::sync::Arc::new(implementations::AllTextures::SolidColour( + implementations::SolidColour::new(rt_core::Vec3::zero()), + )), + (100, 100), + )) }; ($sky_texture:expr) => { - std::sync::Arc::new(implementations::Sky::new(Some($sky_texture))) + std::sync::Arc::new(implementations::Sky::new($sky_texture, (100, 100))) }; } diff --git a/frontend/src/main.rs b/frontend/src/main.rs index ccd1e3f..1c5c7ff 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,16 +1,13 @@ use crate::{ - gui::{Gui, RenderEvent}, parameters::line_break, utility::{get_progress_output, print_final_statistics, print_render_start, save_u8_to_image}, }; +#[cfg(feature = "gui")] +use gui::{Gui, RenderEvent}; + use rt_core::{Float, SamplerProgress}; -use std::{ - env, - sync::{ - atomic::{AtomicBool, AtomicU64, Ordering}, - Arc, - }, -}; +use std::env; +#[cfg(feature = "gui")] use vulkano::{ buffer::CpuAccessibleBuffer, command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, @@ -20,17 +17,23 @@ use vulkano::{ sync::{self, GpuFuture}, Version, }; +#[cfg(feature = "gui")] use winit::event_loop::EventLoopProxy; -mod generate; +#[cfg(feature = "gui")] mod gui; +#[cfg(feature = "gui")] +mod rendering; + +mod generate; mod load_model; mod macros; mod parameters; -mod rendering; + mod scene; mod utility; +#[cfg(feature = "gui")] struct Data { queue: Arc, device: Arc, @@ -45,6 +48,7 @@ struct Data { event_proxy: EventLoopProxy, } +#[cfg(feature = "gui")] impl Data { pub fn new( queue: Arc, @@ -118,126 +122,130 @@ fn main() { .map(|val| (val.sqrt() * 255.999) as u8) .collect(); - match filename { - Some(filename) => { - save_u8_to_image( - render_options.width, - render_options.height, - output, - filename, - false, - ); - } - None => {} + if let Some(filename) = filename { + save_u8_to_image( + render_options.width, + render_options.height, + output, + filename, + false, + ); } } else { - let required_extensions = vulkano_win::required_extensions(); - let instance = Instance::new(None, Version::V1_5, &required_extensions, None).unwrap(); - let gui = Gui::new( - &instance, - render_options.width as u32, - render_options.height as u32, - ); - - let event_loop_proxy: Option> = - gui.event_loop.as_ref().map(|el| el.create_proxy()); - let iter = [0.0f32, 0.0, 0.0, 0.0] - .repeat((render_options.width * render_options.height) as usize) - .into_iter(); - let buffer = CpuAccessibleBuffer::from_iter( - gui.device.clone(), - vulkano::buffer::BufferUsage::all(), - true, - iter, - ) - .unwrap(); - - let samples = Arc::new(AtomicU64::new(0)); - let ray_count = Arc::new(AtomicU64::new(0)); + #[cfg(feature = "gui")] + { + let required_extensions = vulkano_win::required_extensions(); + let instance = + Instance::new(None, Version::V1_5, &required_extensions, None).unwrap(); + let gui = Gui::new( + &instance, + render_options.width as u32, + render_options.height as u32, + ); - let command_buffers = create_command_buffers( - gui.device.clone(), - gui.queue.clone(), - buffer.clone(), - gui.cpu_rendering.cpu_swapchain.clone(), - ); + let event_loop_proxy: Option> = + gui.event_loop.as_ref().map(|el| el.create_proxy()); + let iter = [0.0f32, 0.0, 0.0, 0.0] + .repeat((render_options.width * render_options.height) as usize) + .into_iter(); + let buffer = CpuAccessibleBuffer::from_iter( + gui.device.clone(), + vulkano::buffer::BufferUsage::all(), + true, + iter, + ) + .unwrap(); + + let samples = Arc::new(AtomicU64::new(0)); + let ray_count = Arc::new(AtomicU64::new(0)); + + let command_buffers = create_command_buffers( + gui.device.clone(), + gui.queue.clone(), + buffer.clone(), + gui.cpu_rendering.cpu_swapchain.clone(), + ); - let mut data = Data::new( - gui.queue.clone(), - gui.device.clone(), - gui.cpu_rendering.to_sc.clone(), - gui.cpu_rendering.from_sc.clone(), - command_buffers, - buffer.clone(), - gui.cpu_rendering.copy_to_first.clone(), - samples.clone(), - render_options.samples_per_pixel, - ray_count.clone(), - event_loop_proxy.unwrap(), - ); + let mut data = Data::new( + gui.queue.clone(), + gui.device.clone(), + gui.cpu_rendering.to_sc.clone(), + gui.cpu_rendering.from_sc.clone(), + command_buffers, + buffer.clone(), + gui.cpu_rendering.copy_to_first.clone(), + samples.clone(), + render_options.samples_per_pixel, + ray_count.clone(), + event_loop_proxy.unwrap(), + ); - let image_copy_finished = data.to_sc.clone(); + let image_copy_finished = data.to_sc.clone(); - let start = print_render_start(render_options.width, render_options.height, None); + let start = print_render_start(render_options.width, render_options.height, None); - let render_canceled = Arc::new(AtomicBool::new(true)); + let render_canceled = Arc::new(AtomicBool::new(true)); - let moved_render_canceled = render_canceled.clone(); - let moved_filename = filename.clone(); + let moved_render_canceled = render_canceled.clone(); + let moved_filename = filename.clone(); - std::thread::spawn(move || { - let ray_count = data.rays_shot.clone(); - let samples = data.samples.clone(); - let buffer = data.buffer.clone(); - let to_sc = data.to_sc.clone(); + std::thread::spawn(move || { + let ray_count = data.rays_shot.clone(); + let samples = data.samples.clone(); + let buffer = data.buffer.clone(); + let to_sc = data.to_sc.clone(); - scene.generate_image_threaded( - render_options, - Some(( - &mut data, - |data: &mut Data, previous: &SamplerProgress, i: u64| { - sample_update(data, previous, i); - }, - )), - ); + scene.generate_image_threaded( + render_options, + Some(( + &mut data, + |data: &mut Data, previous: &SamplerProgress, i: u64| { + sample_update(data, previous, i); + }, + )), + ); - let ray_count = ray_count.load(Ordering::Relaxed); - let samples = samples.load(Ordering::Relaxed); + let ray_count = ray_count.load(Ordering::Relaxed); + let samples = samples.load(Ordering::Relaxed); - print_final_statistics(start, ray_count, Some(samples)); - line_break(); + print_final_statistics(start, ray_count, Some(samples)); + line_break(); - moved_render_canceled.store(false, Ordering::Relaxed); + moved_render_canceled.store(false, Ordering::Relaxed); - save_file( - moved_filename, - render_options.width, - render_options.height, - &*buffer.read().unwrap(), - to_sc, - ); - }); + save_file( + moved_filename, + render_options.width, + render_options.height, + &*buffer.read().unwrap(), + to_sc, + ); + }); - gui.run(); - if render_canceled.load(Ordering::Relaxed) { - let ray_count = ray_count.load(Ordering::Relaxed); - let samples = samples.load(Ordering::Relaxed); + gui.run(); + if render_canceled.load(Ordering::Relaxed) { + let ray_count = ray_count.load(Ordering::Relaxed); + let samples = samples.load(Ordering::Relaxed); - print_final_statistics(start, ray_count, Some(samples)); - line_break(); + print_final_statistics(start, ray_count, Some(samples)); + line_break(); - save_file( - filename, - render_options.width, - render_options.height, - &*buffer.read().unwrap(), - image_copy_finished, - ); + save_file( + filename, + render_options.width, + render_options.height, + &*buffer.read().unwrap(), + image_copy_finished, + ); + } } + #[cfg(not(feature = "gui"))] + println!("feature: gui not enabled"); } } } +#[cfg(feature = "gui")] fn create_command_buffers( device: Arc, queue: Arc, @@ -270,6 +278,7 @@ fn create_command_buffers( ] } +#[cfg(feature = "gui")] fn sample_update(data: &mut Data, previous: &SamplerProgress, i: u64) { // update infomation about the rays shot and samples completed in the current render data.samples.fetch_add(1, Ordering::Relaxed); @@ -339,6 +348,7 @@ fn sample_update(data: &mut Data, previous: &SamplerProgress, i: u64) { .unwrap(); } +#[cfg(feature = "gui")] fn save_file( filename: Option, width: u64, diff --git a/implementations/Cargo.toml b/implementations/Cargo.toml index 03f5fe1..8660108 100644 --- a/implementations/Cargo.toml +++ b/implementations/Cargo.toml @@ -14,4 +14,7 @@ rt_core = {path = "../rt_core"} [dev-dependencies] chrono = "0.4.19" -statrs = "0.16.0" \ No newline at end of file +statrs = "0.16.0" + +[features] +f64 = ["rt_core/f64"] \ No newline at end of file diff --git a/implementations/src/lib.rs b/implementations/src/lib.rs index 32d9e20..48725d0 100644 --- a/implementations/src/lib.rs +++ b/implementations/src/lib.rs @@ -13,6 +13,7 @@ pub use primitives::*; pub use proc::*; pub use samplers::*; pub use textures::*; +pub use utility::*; pub use primitives::triangle::Triangle; diff --git a/implementations/src/materials/trowbridge_reitz.rs b/implementations/src/materials/trowbridge_reitz.rs index af4da60..0df34f1 100644 --- a/implementations/src/materials/trowbridge_reitz.rs +++ b/implementations/src/materials/trowbridge_reitz.rs @@ -5,7 +5,7 @@ use crate::{ utility::{offset_ray, random_float}, }; use rt_core::{Float, Hit, Ray, Scatter, Vec3}; -use std::{f32::INFINITY, sync::Arc}; +use std::sync::Arc; #[derive(Debug)] pub struct TrowbridgeReitz { @@ -16,10 +16,10 @@ pub struct TrowbridgeReitz { } #[cfg(all(feature = "f64"))] -use std::f64::consts::PI; +use std::f64::{consts::PI, INFINITY}; #[cfg(not(feature = "f64"))] -use std::f32::consts::PI; +use std::f32::{consts::PI, INFINITY}; impl TrowbridgeReitz where diff --git a/implementations/src/samplers/mod.rs b/implementations/src/samplers/mod.rs index 162f4b8..112249e 100644 --- a/implementations/src/samplers/mod.rs +++ b/implementations/src/samplers/mod.rs @@ -1,25 +1,75 @@ use crate::textures::Texture; +use crate::utility::distribution::*; +use crate::{generate_pdf, next_float, random_float}; +use rt_core::Float; +use rt_core::PI; use rt_core::{NoHit, Ray, Vec3}; use std::sync::Arc; pub mod random_sampler; pub struct Sky { - texture: Option>, + texture: Arc, + pub pdf: Vec, + cdf: CDF2D, + sampler_res: (usize, usize), } impl Sky { - pub fn new(texture: Option<&Arc>) -> Self { - let texture = texture.cloned(); - Sky { texture } + pub fn new(texture: &Arc, sampler_res: (usize, usize)) -> Self { + let texture = texture.clone(); + + let pdf = generate_pdf(&*texture, sampler_res); + + let cdf = CDF2D::from_pdf(&pdf, sampler_res.0); + + Sky { + texture, + pdf, + cdf, + sampler_res, + } } } impl NoHit for Sky { fn get_colour(&self, ray: &Ray) -> Vec3 { - match &self.texture { - Some(texture) => texture.colour_value(ray.direction, ray.origin), - None => Vec3::zero(), + self.texture.colour_value(ray.direction, ray.origin) + } + fn pdf(&self, wi: Vec3) -> Float { + let sin_theta = (1.0 - wi.z * wi.z).sqrt(); + if sin_theta <= 0.0 { + return 0.0; + } + let theta = wi.z.acos(); + let mut phi = (wi.y).atan2(wi.x); + + if phi < 0.0 { + phi += 2.0 * PI; } + let u = phi / (2.0 * PI); + let v = theta / PI; + + let u_bin = next_float((u * self.sampler_res.0 as Float).floor()) as usize; + let v_bin = next_float((v * self.sampler_res.1 as Float).floor()) as usize; + + let index = + (v_bin * self.sampler_res.0 + u_bin).min(self.sampler_res.0 * self.sampler_res.1 - 1); + + self.pdf[index] / (2.0 * PI * PI * sin_theta) + } + fn can_sample(&self) -> bool { + true + } + fn sample(&self) -> Vec3 { + let uv = self.cdf.sample(); + + let u = next_float(uv.0 as Float + random_float()) / self.sampler_res.0 as Float; + let v = next_float(uv.1 as Float + random_float()) / self.sampler_res.1 as Float; + + let phi = u * 2.0 * PI; + let theta = v * PI; + + Vec3::from_spherical(theta.sin(), theta.cos(), phi.sin(), phi.cos()) } } diff --git a/implementations/src/textures/mod.rs b/implementations/src/textures/mod.rs index c99151d..a82becf 100644 --- a/implementations/src/textures/mod.rs +++ b/implementations/src/textures/mod.rs @@ -28,6 +28,38 @@ pub struct CheckeredTexture { secondary_colour: Vec3, } +pub fn generate_pdf(texture: &T, sample_res: (usize, usize)) -> Vec { + let mut sum = 0.0; + + let mut pdf = Vec::new(); + + let step = (1.0 / sample_res.0 as Float, 1.0 / sample_res.1 as Float); + for y in 0..sample_res.1 { + for x in 0..sample_res.0 { + let u = (x as Float + 0.5) * step.0; + let v = (y as Float + 0.5) * step.1; + let phi = u * 2.0 * PI; + let theta = v * PI; + let sin_theta = theta.sin(); + let direction = Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, theta.cos()); + let col = texture.colour_value(direction, Vec3::zero()); + sum += 0.2126 * col.x + 0.7152 * col.y + 0.0722 * col.z; + pdf.push(0.2126 * col.x + 0.7152 * col.y + 0.0722 * col.z); + } + } + + let average = sum * step.0 * step.1; + + let pdf: Vec = pdf + .into_iter() + .map(|v| (v / average) / (sample_res.0 * sample_res.1) as Float) + .collect(); + + let sum: Float = pdf.iter().sum(); + + pdf.into_iter().map(|v| v / sum).collect() +} + impl CheckeredTexture { pub fn new(primary_colour: Vec3, secondary_colour: Vec3) -> Self { CheckeredTexture { @@ -119,12 +151,12 @@ impl Perlin { perm } - fn permute(perm: &mut [u32; 256]) { + fn permute(perm: &mut [u32; PERLIN_RVECS]) { let mut rng = rand::rngs::SmallRng::from_rng(rand::thread_rng()).unwrap(); - for i in (1..256).rev() { + for i in (1..PERLIN_RVECS).rev() { let target = rng.gen_range(0..i); - perm[0..256].swap(i, target); + perm[0..PERLIN_RVECS].swap(i, target); } } @@ -214,7 +246,7 @@ impl ImageTexture { let image = img.to_rgb32f(); for col in image.into_raw().chunks(3) { data.push(Vec3::new( - *col.get(0).unwrap() as Float, + *col.first().unwrap() as Float, *col.get(1).unwrap() as Float, *col.get(2).unwrap() as Float, )); @@ -258,7 +290,7 @@ impl Lerp { impl Texture for Lerp { fn colour_value(&self, direction: Vec3, _: Vec3) -> Vec3 { - let t = direction.y * 0.5 + 0.5; + let t = direction.z * 0.5 + 0.5; self.colour_one * t + self.colour_two * (1.0 - t) } fn requires_uv(&self) -> bool { diff --git a/implementations/src/utility/distribution.rs b/implementations/src/utility/distribution.rs new file mode 100644 index 0000000..44bd40d --- /dev/null +++ b/implementations/src/utility/distribution.rs @@ -0,0 +1,151 @@ +use rt_core::Float; + +use super::random_float; + +pub struct CDF1D { + pub intervals: Vec, +} + +impl CDF1D { + pub fn from_pdf(pdf: Vec) -> Self { + if pdf.is_empty() { + panic!("Empty pdf passed to CDF1D::from_pdf!"); + } + + let mut intervals = vec![0.0]; + for val in pdf.iter() { + let len = intervals.len(); + intervals.push((intervals[len - 1] + val).min(1.0)); + } + intervals[pdf.len()] = 1.0; + Self { intervals } + } + pub fn sample(&self) -> usize { + let num = random_float(); + + let mut low = 0; + let mut high = self.intervals.len() - 1; + + let mut i = (low + high) / 2; + let mut above = num >= self.intervals[i]; + let mut below = self.intervals[i + 1] > num; + + while !(above && below) { + if above { + low = i; + } else { + high = i; + } + i = (low + high) / 2; + above = num >= self.intervals[i]; + below = self.intervals[i + 1] > num; + } + + i + } +} + +pub struct CDF2D { + pub y_cdf: CDF1D, + x_cdfs: Vec, +} + +impl CDF2D { + pub fn from_pdf(pdf: &[Float], width: usize) -> Self { + if pdf.len() % width != 0 { + panic!("Invalid width passed to CDF2D"); + } + let height = pdf.len() / width; + let mut row_sums: Vec = Vec::new(); + let mut average = 0.0; + for y in 0..height { + let mut row_sum = 0.0; + for x in 0..width { + let i = width * y + x; + average += pdf[i]; + row_sum += pdf[i]; + } + row_sums.push(row_sum); + } + + let y_pdf: Vec = row_sums.iter().map(|v| v / average).collect(); + + // renormalise due to floating point error + let sum: Float = y_pdf.iter().sum(); + let y_pdf: Vec = y_pdf.into_iter().map(|v| v / sum).collect(); + + let y_cdf = CDF1D::from_pdf(y_pdf); + + assert!(pdf.len() / width == (y_cdf.intervals.len() - 1)); + + let x_cdfs: Vec = pdf + .chunks_exact(width) + .zip(row_sums) + .map(|(chunk, row_sum)| CDF1D::from_pdf(chunk.iter().map(|v| v / row_sum).collect())) + .collect(); + + Self { y_cdf, x_cdfs } + } + pub fn sample(&self) -> (usize, usize) { + let v = self.y_cdf.sample(); + let u = self.x_cdfs[v].sample(); + (u, v) + } +} + +#[test] +fn cdf1d_sampling() { + let pdf = [0.1, 0.5, 0.3, 0.1]; + + let cdf = CDF1D::from_pdf(pdf.to_vec()); + + let samples = 1_000_000; + let mut bins = [0, 0, 0, 0]; + + for _ in 0..samples { + match cdf.sample() { + 0 => { + bins[0] += 1; + } + 1 => { + bins[1] += 1; + } + 2 => { + bins[2] += 1; + } + 3 => { + bins[3] += 1; + } + _ => unreachable!(), + } + } + + bins.into_iter() + .zip(pdf) + .for_each(|(v, p)| assert!((v as Float / samples as Float - p).abs() < 0.01)); +} + +#[test] +fn cdf2d_sampling() { + use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; + + let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + let pdf: Vec = (0..10000).map(|_| rng.gen_range(0..1000)).collect(); + let sum: usize = pdf.iter().sum(); + let pdf: Vec = pdf.into_iter().map(|v| v as Float / sum as Float).collect(); + + let cdf = CDF2D::from_pdf(&pdf, 100); + + let mut bins = [0; 10000]; + + let samples = 1_000_000; + + for _ in 0..samples { + let sample = cdf.sample(); + bins[sample.0 + sample.1 * 100] += 1; + + bins.into_iter() + .zip(pdf.clone()) + .for_each(|(v, p)| assert!((v as Float / samples as Float - p).abs() < 0.01)); + } +} diff --git a/implementations/src/utility/mod.rs b/implementations/src/utility/mod.rs index 021d476..167c38a 100644 --- a/implementations/src/utility/mod.rs +++ b/implementations/src/utility/mod.rs @@ -2,6 +2,7 @@ use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; use rt_core::{Float, Vec3, PI}; pub mod coord; +pub mod distribution; pub fn check_side(normal: &mut Vec3, ray_direction: &Vec3) -> bool { if normal.dot(*ray_direction) > 0.0 { diff --git a/implementations/tests/bxdf.rs b/implementations/tests/bxdf.rs deleted file mode 100644 index 309a81f..0000000 --- a/implementations/tests/bxdf.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::common::*; -use rt_core::*; - -mod common; - -fn test_bxdf(bxdf: B, bxdf_name: String) -> bool -where - B: Scatter, -{ - let hit = Hit { - t: 0.0, - error: Vec3::zero(), - point: Vec3::zero(), - uv: None, - normal: Vec3::new(0.0, 0.0, 1.0), - out: true, - }; - - let sample = |wo: Vec3| { - let mut ray = Ray::new(Vec3::zero(), wo, 0.0); - bxdf.scatter_ray(&mut ray, &hit); - ray.direction - }; - - let pdf = |wo: Vec3, wi: Vec3| bxdf.scattering_pdf(&hit, wo, wi); - for i in 0..CHI_TESTS { - let wo = generate_wo(); - let freq_table = samped_frequency_distribution(&sample, wo, THETA_RES, PHI_RES, SAMPLES); - let expected_freq_table: Vec = - integrate_frequency_table(&pdf, wo, THETA_RES, PHI_RES) - .into_iter() - .map(|x| x * SAMPLES as Float) - .collect(); - if i == 0 { - dump_tables( - wo, - &freq_table - .iter() - .map(|&v| v as Float) - .collect::>(), - &expected_freq_table, - THETA_RES, - PHI_RES, - &bxdf_name, - ); - } - let (df, chi_squared) = chi_squared(freq_table, expected_freq_table, SAMPLES); - let p = chi2_probability(df as f64, chi_squared as f64); - let threshold = 1.0 - (1.0 - CHI2_THRESHOLD).powf(1.0 / CHI_TESTS as Float); - if p < threshold as f64 || p.is_infinite() { - return false; - } - } - true -} - -#[test] -fn trowbridge_reitz() { - let bxdf = implementations::trowbridge_reitz::TrowbridgeReitz::new( - &std::sync::Arc::new(implementations::SolidColour::new(Vec3::new(0.0, 0.0, 0.0))), - 0.3, - 3.2 * Vec3::one(), - 1.0, - ); - - assert!(test_bxdf(bxdf, "ggx".to_string())) -} - -#[test] -fn lambertian() { - let bxdf = implementations::lambertian::Lambertian::new( - &std::sync::Arc::new(implementations::SolidColour::new(Vec3::new(0.0, 0.0, 0.0))), - 0.3, - ); - - assert!(test_bxdf(bxdf, "lambertain".to_string())) -} diff --git a/implementations/tests/common/mod.rs b/implementations/tests/common/mod.rs index a641942..249ba2b 100644 --- a/implementations/tests/common/mod.rs +++ b/implementations/tests/common/mod.rs @@ -3,6 +3,7 @@ use rayon::prelude::*; use rt_core::{vec::*, Float}; use statrs::function::gamma::*; use std::{ + cmp::Ordering::*, f64::{consts::*, INFINITY}, {fs::File, io::Write}, }; @@ -17,34 +18,26 @@ pub const CHI_TESTS: usize = 1; use int::*; -fn chi_squared_term(a: Float, b: Float) -> Float { - if a < (SAMPLES / 100000) as Float && b == 0.0 { - return 0.0; - } - let val = a - b; - val * val / b -} - pub fn to_vec(sin_theta: Float, cos_theta: Float, phi: Float) -> Vec3 { Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) } pub fn chi2_probability(dof: f64, distance: f64) -> f64 { - assert!( - (gamma_lr(dof * 0.5, distance * 0.5) + gamma_ur(dof * 0.5, distance * 0.5) - 1.0).abs() - < 0.0001 - ); - gamma_ur(dof * 0.5, distance * 0.5) + match distance.partial_cmp(&0.0).unwrap() { + Less => panic!("distance < 0.0"), + Equal => 1.0, + Greater => gamma_ur(dof * 0.5, distance * 0.5), + } } pub fn integrate_frequency_table( pdf: &F, wo: Vec3, - theta_res: usize, phi_res: usize, + theta_res: usize, ) -> Vec where - F: Fn(Vec3, Vec3) -> Float, + F: Fn(Vec3, Vec3) -> Float + Sync, { let theta_step = PI as Float / theta_res as Float; let phi_step = TAU as Float / phi_res as Float; @@ -65,10 +58,12 @@ where ) }; - let mut vec = Vec::new(); - for theta_i in 0..theta_res { - for phi_i in 0..phi_res { - let a = adaptive_simpsons( + (0..(theta_res * phi_res)) + .into_par_iter() + .map(|i| { + let phi_i = i % phi_res; + let theta_i = i / phi_res; + adaptive_simpsons( |phi| { pdf( phi, @@ -78,18 +73,48 @@ where }, phi_i as Float * phi_step, (phi_i + 1) as Float * phi_step, - ); - vec.push(a); + ) + }) + .collect() +} + +pub fn samped_frequency_distribution_uv( + function: &F, + u_res: usize, + v_res: usize, + sample_count: usize, +) -> Vec +where + F: Fn() -> (usize, usize) + std::marker::Sync, +{ + let mut freq = vec![vec![0.0; u_res * v_res]; 16]; + + freq.par_iter_mut().for_each(|x| { + for _ in 0..(sample_count / 16) { + let sample = function(); + x[sample.0 + sample.1 * u_res] += 1.0; } + }); + + freq.into_iter() + .fold(vec![0.0; u_res * v_res], |mut sum, val| { + sum.iter_mut().zip(val).for_each(|(s, v)| *s += v); + sum + }) + + /*for _ in 0..sample_count { + let sample = function(); + freq[sample.0 + sample.1 * u_res] += 1.0; } - vec + + freq*/ } pub fn samped_frequency_distribution( function: &F, wo: Vec3, - theta_res: usize, phi_res: usize, + theta_res: usize, sample_count: usize, ) -> Vec where @@ -155,6 +180,11 @@ pub fn chi_squared( if actual > (samples / 100_000) as Float { chi_squared += INFINITY as Float; } + } else if expected_pooled > 5.0 { + // prevent df = 0 when all values are less than 5 + let diff = actual_pooled - expected_pooled; + chi_squared += diff * diff / expected_pooled; + df += 1; } else if expected < 5.0 || (expected_pooled > 0.0 && expected_pooled < 5.0) { expected_pooled += expected; actual_pooled += actual; @@ -180,20 +210,28 @@ pub fn dump_tables( wo: Vec3, freq_table: &[Float], expected_freq_table: &[Float], - theta_res: usize, - phi_res: usize, + x: usize, + y: usize, bxdf_name: &str, ) { + fn chi_squared_term(a: Float, b: Float) -> Float { + if a < (SAMPLES / 100000) as Float && b == 0.0 { + return 0.0; + } + let val = a - b; + val * val / b + } + let enumerate = |file: &mut File, func: fn(Float, Float) -> Float| { - (0..theta_res * phi_res).for_each(|index| { + (0..(x * y)).for_each(|index| { file.write_all( format!("{}", func(freq_table[index], expected_freq_table[index])).as_bytes(), ) .unwrap(); - if index % phi_res + 1 != phi_res { + if index % x + 1 != x { file.write_all(b", ").unwrap(); - } else if index / phi_res + 1 != theta_res { + } else if index / x + 1 != y { file.write_all(b"; ").unwrap(); } }); diff --git a/implementations/tests/sampling.rs b/implementations/tests/sampling.rs new file mode 100644 index 0000000..79853e8 --- /dev/null +++ b/implementations/tests/sampling.rs @@ -0,0 +1,285 @@ +use crate::common::*; +use implementations::distribution::CDF2D; + +use rt_core::*; + +mod common; + +fn test_bxdf(bxdf: B, bxdf_name: String) -> bool +where + B: Scatter + Sync, +{ + let hit = Hit { + t: 0.0, + error: Vec3::zero(), + point: Vec3::zero(), + uv: None, + normal: Vec3::new(0.0, 0.0, 1.0), + out: true, + }; + + let sample = |wo: Vec3| { + let mut ray = Ray::new(Vec3::zero(), wo, 0.0); + bxdf.scatter_ray(&mut ray, &hit); + ray.direction + }; + + let pdf = |wo: Vec3, wi: Vec3| bxdf.scattering_pdf(&hit, wo, wi); + for i in 0..CHI_TESTS { + let wo = generate_wo(); + let freq_table = samped_frequency_distribution(&sample, wo, PHI_RES, THETA_RES, SAMPLES); + let expected_freq_table: Vec = + integrate_frequency_table(&pdf, wo, PHI_RES, THETA_RES) + .into_iter() + .map(|x| x * SAMPLES as Float) + .collect(); + if i == 0 { + dump_tables( + wo, + &freq_table + .iter() + .map(|&v| v as Float) + .collect::>(), + &expected_freq_table, + PHI_RES, + THETA_RES, + &bxdf_name, + ); + } + let (df, chi_squared) = chi_squared(freq_table, expected_freq_table, SAMPLES); + let p = chi2_probability(df as f64, chi_squared as f64); + let threshold = 1.0 - (1.0 - CHI2_THRESHOLD).powf(1.0 / CHI_TESTS as Float); + if p < threshold as f64 || p.is_infinite() { + return false; + } + } + true +} + +fn generate_random_pdf(length: usize) -> Vec { + let mut vec = Vec::with_capacity(length); + + for _ in 0..length { + vec.push(random_float()); + } + let sum: Float = vec.iter().sum(); + vec.into_iter().map(|v| v / sum).collect() +} + +#[test] +fn trowbridge_reitz() { + let bxdf = implementations::trowbridge_reitz::TrowbridgeReitz::new( + &std::sync::Arc::new(implementations::SolidColour::new(Vec3::new(0.0, 0.0, 0.0))), + 0.3, + 3.2 * Vec3::one(), + 1.0, + ); + + assert!(test_bxdf(bxdf, "trowbridge_reitz".to_string())) +} + +#[test] +#[ignore] +fn lambertian() { + let bxdf = implementations::lambertian::Lambertian::new( + &std::sync::Arc::new(implementations::SolidColour::new(Vec3::new(0.0, 0.0, 0.0))), + 0.3, + ); + + assert!(test_bxdf(bxdf, "lambertain".to_string())) +} + +#[test] +fn discrete_2d_pdf() { + let pdf = implementations::generate_pdf( + &*std::sync::Arc::new(implementations::AllTextures::Lerp( + implementations::Lerp::new(Vec3::zero(), Vec3::one()), + )), + (50, 50), + ); + assert!(pdf.into_iter().sum::() - 1.0 < 0.01); +} + +#[test] +fn discrete_2d() { + const SAMPLE_WIDTH: usize = 50; + const SAMPLE_HEIGHT: usize = 50; + + let pdf = generate_random_pdf(SAMPLE_WIDTH * SAMPLE_HEIGHT); + + let cdf = implementations::distribution::CDF2D::from_pdf(&pdf, SAMPLE_WIDTH); + + let samples = 10_000_000; + + let sample = || cdf.sample(); + + let expected_freq_table: Vec = pdf.into_iter().map(|v| v * samples as Float).collect(); + + for i in 0..CHI_TESTS { + let freq_table = + samped_frequency_distribution_uv(&sample, SAMPLE_WIDTH, SAMPLE_HEIGHT, samples); + + if i == 0 { + dump_tables( + Vec3::zero(), + &freq_table + .iter() + .map(|&v| v as Float) + .collect::>(), + &expected_freq_table, + SAMPLE_WIDTH, + SAMPLE_HEIGHT, + "2DCDF", + ); + } + let (df, chi_squared) = chi_squared(freq_table, expected_freq_table.clone(), samples); + let p = chi2_probability(df as f64, chi_squared as f64); + let threshold = 1.0 - (1.0 - CHI2_THRESHOLD).powf(1.0 / CHI_TESTS as Float); + if p < threshold as f64 || p.is_infinite() { + panic!("p: {p}"); + } + } +} + +#[test] +fn transform_test() { + const SAMPLE_WIDTH: usize = 50; + const SAMPLE_HEIGHT: usize = 50; + + let tex = std::sync::Arc::new(implementations::AllTextures::Lerp( + implementations::Lerp::new(Vec3::zero(), Vec3::one()), + )); + + let sky = implementations::Sky::new(&tex, (SAMPLE_WIDTH, SAMPLE_HEIGHT)); + + let expected_pdf = sky.pdf.clone(); + + let pdf = (0..(SAMPLE_HEIGHT * SAMPLE_WIDTH)) + .into_iter() + .map(|i| (i % SAMPLE_WIDTH, i / SAMPLE_WIDTH)) + .map(|(u, v)| { + let phi = ((u as Float + 0.5) / SAMPLE_WIDTH as Float) * 2.0 * PI; + let theta = ((v as Float + 0.5) / SAMPLE_HEIGHT as Float) * PI; + + let vec = Vec3::from_spherical(theta.sin(), theta.cos(), phi.sin(), phi.cos()); + + sky.pdf(vec) * 2.0 * PI * PI * theta.sin() + }); + + let samples = 1_000_000; + + let pdf: Vec = pdf.map(|v| v * samples as Float).collect(); + let expected_pdf: Vec = expected_pdf + .into_iter() + .map(|v| v * samples as Float) + .collect(); + + dump_tables( + Vec3::zero(), + &pdf, + &expected_pdf, + SAMPLE_WIDTH, + SAMPLE_HEIGHT, + "transform UV", + ); + let (df, chi_squared) = chi_squared(pdf, expected_pdf, SAMPLE_HEIGHT * SAMPLE_WIDTH); + let p = chi2_probability(df as f64, chi_squared as f64); + let threshold = CHI2_THRESHOLD; + if p < threshold as f64 || p.is_infinite() { + panic!("p: {p}"); + } +} + +#[test] +fn sky_sampling_uv() { + const SAMPLE_WIDTH: usize = 50; + const SAMPLE_HEIGHT: usize = 50; + + let tex = std::sync::Arc::new(implementations::AllTextures::Lerp( + implementations::Lerp::new(Vec3::zero(), Vec3::one()), + )); + + let sky = implementations::Sky::new(&tex, (50, 50)); + + let pdf = sky.pdf; + + let cdf = CDF2D::from_pdf(&pdf, SAMPLE_WIDTH); + + let samples = 10_000_000; + + let sample = || cdf.sample(); + + let expected_freq_table: Vec = pdf.into_iter().map(|v| v * samples as Float).collect(); + + for i in 0..CHI_TESTS { + let freq_table = + samped_frequency_distribution_uv(&sample, SAMPLE_WIDTH, SAMPLE_HEIGHT, samples); + + if i == 0 { + dump_tables( + Vec3::zero(), + &freq_table + .iter() + .map(|&v| v as Float) + .collect::>(), + &expected_freq_table, + SAMPLE_WIDTH, + SAMPLE_HEIGHT, + "Sky Sampling UV", + ); + } + let (df, chi_squared) = chi_squared(freq_table, expected_freq_table.clone(), samples); + let p = chi2_probability(df as f64, chi_squared as f64); + let threshold = 1.0 - (1.0 - CHI2_THRESHOLD).powf(1.0 / CHI_TESTS as Float); + if p < threshold as f64 || p.is_infinite() { + panic!("p: {p}"); + } + } +} + +#[test] +pub fn sky_sampling() { + let tex = std::sync::Arc::new(implementations::AllTextures::Lerp( + implementations::Lerp::new(Vec3::zero(), Vec3::one()), + )); + + const SAMPLE_HEIGHT: usize = 50; + const SAMPLE_WIDTH: usize = 100; + + let sky = implementations::Sky::new(&tex, (SAMPLE_WIDTH, SAMPLE_HEIGHT)); + + let samples = 1_000_000; + + let sample = |_wo: Vec3| sky.sample(); + + let pdf = |_: Vec3, wi: Vec3| sky.pdf(wi) * (SAMPLE_WIDTH * SAMPLE_HEIGHT) as Float; + + let wo = generate_wo(); + let freq_table = samped_frequency_distribution(&sample, wo, PHI_RES, THETA_RES, samples); + + let expected_freq_table: Vec = integrate_frequency_table(&pdf, wo, PHI_RES, THETA_RES) + .into_iter() + .map(|x| x * samples as Float) + .collect(); + + dump_tables( + wo, + &freq_table + .iter() + .map(|&v| v as Float) + .collect::>(), + &expected_freq_table, + PHI_RES, + THETA_RES, + "Sky Sampling", + ); + + let (df, chi_squared) = chi_squared(freq_table, expected_freq_table, samples); + let p = chi2_probability(df as f64, chi_squared as f64); + let threshold = 1.0 - (1.0 - CHI2_THRESHOLD).powf(1.0 / CHI_TESTS as Float); + if p < threshold as f64 { + panic!("Failed to reach pass threshold {p} < {threshold}"); + } else if p.is_infinite() { + panic!("Failed to reach pass threshold p = inf"); + } +} diff --git a/rt_core/Cargo.toml b/rt_core/Cargo.toml index 835bd6f..c523f84 100644 --- a/rt_core/Cargo.toml +++ b/rt_core/Cargo.toml @@ -6,4 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rand = { version = "0.8.3", features = [ "small_rng" ] } \ No newline at end of file +rand = { version = "0.8.3", features = [ "small_rng" ] } + +[features] +f64 = [] \ No newline at end of file diff --git a/rt_core/src/ray.rs b/rt_core/src/ray.rs index 6d6d235..20aeb22 100644 --- a/rt_core/src/ray.rs +++ b/rt_core/src/ray.rs @@ -57,47 +57,84 @@ impl Ray { self.origin + self.direction * t } - fn sample_light, P: Primitive, M: Scatter>( + fn sample_light, P: Primitive, M: Scatter, S: NoHit>( bvh: &A, hit: &Hit, + sky: &S, mat: &M, wo: Vec3, ) -> Vec3 { - let light_index = match bvh - .get_samplable() - .choose(&mut SmallRng::from_rng(thread_rng()).unwrap()) - { - Some(&index) => index, - None => return Vec3::zero(), - }; + let sample_lights = |num_lights: usize| { + let light_index = match bvh + .get_samplable() + .choose(&mut SmallRng::from_rng(thread_rng()).unwrap()) + { + Some(&index) => index, + None => return Vec3::zero(), + }; - let samplable = bvh.get_object(light_index).unwrap(); + let samplable = bvh.get_object(light_index).unwrap(); - let sampled_wi = samplable.sample_visible_from_point(hit.point); + let sampled_wi = samplable.sample_visible_from_point(hit.point); - if let Some(sampled_si) = bvh.check_hit_index( - &Ray::new(hit.point + 0.0001 * hit.normal, sampled_wi, 0.0), - light_index, - ) { - let sampled_hit = &sampled_si.hit; + if let Some(sampled_si) = bvh.check_hit_index( + &Ray::new(hit.point + 0.0001 * hit.normal, sampled_wi, 0.0), + light_index, + ) { + let sampled_hit = &sampled_si.hit; - let sampled_pdf = samplable.scattering_pdf(hit.point, sampled_wi, sampled_hit); + let sampled_pdf = samplable.scattering_pdf(hit.point, sampled_wi, sampled_hit); - if sampled_pdf > 0.0 { - let li = sampled_si.material.get_emission(sampled_hit, sampled_wi); + if sampled_pdf > 0.0 { + let li = sampled_si.material.get_emission(sampled_hit, sampled_wi); - let f = mat.eval(hit, wo, sampled_wi); + let f = mat.eval(hit, wo, sampled_wi); - let num_lights = bvh.get_samplable().len() as Float; + let num_lights = 2.0 * num_lights as Float; - let scattering_pdf = mat.scattering_pdf(hit, wo, sampled_wi); + let scattering_pdf = mat.scattering_pdf(hit, wo, sampled_wi); - let weight = power_heuristic(sampled_pdf, scattering_pdf); + let weight = power_heuristic(sampled_pdf, scattering_pdf); - return li * f * num_lights * weight / sampled_pdf; + li * f * num_lights * weight / sampled_pdf + } else { + Vec3::zero() + } + } else { + Vec3::zero() } + }; + + if sky.can_sample() { + if SmallRng::from_rng(thread_rng()).unwrap().gen() { + let sampled_wi = sky.sample(); + + if bvh + .check_hit(&Ray::new(hit.point + 0.0001 * hit.normal, sampled_wi, 0.0)) + .is_none() + { + let sampled_pdf = sky.pdf(sampled_wi); + + if sampled_pdf > 0.0 { + let ray = Ray::new(Vec3::zero(), sampled_wi, 0.0); + let li = sky.get_colour(&ray); + + let f = mat.eval(hit, wo, sampled_wi); + + let scattering_pdf = mat.scattering_pdf(hit, wo, sampled_wi); + + let weight = power_heuristic(sampled_pdf, scattering_pdf); + + return li * f * 2.0 * weight / sampled_pdf; + } + } + Vec3::zero() + } else { + sample_lights(bvh.get_samplable().len()) + } + } else { + panic!() } - Vec3::zero() } fn sample_light_mis, P: Primitive, M: Scatter>( @@ -149,6 +186,54 @@ impl Ray { output } + fn sample_sky_mis, P: Primitive, M: Scatter, S: NoHit>( + bvh: &A, + hit: &Hit, + mat: &M, + wo: Vec3, + sky: &S, + wi: Vec3, + ) -> Vec3 { + let mut output = Vec3::zero(); + + let sampled_wi = sky.sample(); + + if bvh + .check_hit(&Ray::new(hit.point + 0.0001 * hit.normal, sampled_wi, 0.0)) + .is_none() + { + let sampled_pdf = sky.pdf(sampled_wi); + + if sampled_pdf > 0.0 { + let ray = Ray::new(Vec3::zero(), sampled_wi, 0.0); + let li = sky.get_colour(&ray); + + let f = mat.eval(hit, wo, sampled_wi); + + let scattering_pdf = mat.scattering_pdf(hit, wo, sampled_wi); + + let weight = power_heuristic(sampled_pdf, scattering_pdf); + + output += li * f * weight / sampled_pdf; + } + } + + let scattering_pdf = mat.scattering_pdf(hit, wo, wi); + + let ray = Ray::new(Vec3::zero(), wi, 0.0); + let li = sky.get_colour(&ray); + + let sampling_pdf = sky.pdf(wi); + + let weight = power_heuristic(scattering_pdf, sampling_pdf); + + let fp = mat.eval_over_scattering_pdf(hit, wo, wi); + + output += li * fp * weight; + + output + } + pub fn get_colour, P: Primitive, M: Scatter, S: NoHit>( ray: &mut Ray, sky: &S, @@ -186,7 +271,7 @@ impl Ray { ray_count += 1; throughput *= mat.eval_over_scattering_pdf(&hit, wo, wi); } else { - output += throughput * Self::sample_light(bvh, &hit, &mat, wo); + output += throughput * Self::sample_light(bvh, &hit, sky, &mat, wo); ray_count += 1; throughput *= mat.eval_over_scattering_pdf(&hit, wo, wi); } @@ -197,13 +282,20 @@ impl Ray { } else { if mat.is_delta() { throughput *= mat.eval(&hit, wo, wi); + output += throughput * sky.get_colour(ray); + } else if sky.can_sample() { + output += + throughput * Self::sample_sky_mis(bvh, &hit, &mat, wo, sky, wi); + ray_count += 1; + throughput *= mat.eval_over_scattering_pdf(&hit, wo, wi); } else { - output += throughput * Self::sample_light(bvh, &hit, &mat, wo); + output += throughput * Self::sample_light(bvh, &hit, sky, &mat, wo); ray_count += 1; throughput *= mat.eval_over_scattering_pdf(&hit, wo, wi); + + output += throughput * sky.get_colour(ray); } - output += throughput * sky.get_colour(ray); ray_count += depth as u64; break; } @@ -268,8 +360,6 @@ impl Ray { if !mat.is_delta() { throughput *= mat.eval_over_scattering_pdf(hit, wo, ray.direction); - //mat.eval(hit, wo, ray.direction) - // / mat.scattering_pdf(hit, wo, ray.direction); } else { throughput *= mat.eval(hit, wo, ray.direction); } diff --git a/rt_core/src/sampler.rs b/rt_core/src/sampler.rs index 3e67281..99dede9 100644 --- a/rt_core/src/sampler.rs +++ b/rt_core/src/sampler.rs @@ -64,4 +64,13 @@ pub trait Camera { pub trait NoHit { fn get_colour(&self, ray: &Ray) -> Vec3; + fn pdf(&self, _: Vec3) -> Float { + unimplemented!() + } + fn can_sample(&self) -> bool { + false + } + fn sample(&self) -> Vec3 { + unimplemented!() + } } diff --git a/rt_core/src/vec.rs b/rt_core/src/vec.rs index e910269..d71b3d0 100644 --- a/rt_core/src/vec.rs +++ b/rt_core/src/vec.rs @@ -136,6 +136,16 @@ impl Vec3 { Vec3::new(0.0, 0.0, 0.0) } + #[inline] + pub fn from_spherical( + sin_theta: Float, + cos_theta: Float, + sin_phi: Float, + cos_phi: Float, + ) -> Self { + Vec3::new(sin_theta * cos_phi, sin_theta * sin_phi, cos_theta) + } + #[inline] pub fn dot(&self, other: Self) -> Float { self.x * other.x + self.y * other.y + self.z * other.z From 3b966ab68876242f5ed111397559e6dc82e5eab7 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Fri, 28 Oct 2022 04:19:56 +1100 Subject: [PATCH 02/39] Fixed imports in frontend/main.rs --- frontend/src/main.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 1c5c7ff..8e78d36 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -3,22 +3,26 @@ use crate::{ utility::{get_progress_output, print_final_statistics, print_render_start, save_u8_to_image}, }; #[cfg(feature = "gui")] -use gui::{Gui, RenderEvent}; +use { + gui::{Gui, RenderEvent}, + std::sync::{ + atomic::{AtomicBool, AtomicU64, Ordering}, + Arc, + }, + vulkano::{ + buffer::CpuAccessibleBuffer, + command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, + device::{Device, Queue}, + image::StorageImage, + instance::Instance, + sync::{self, GpuFuture}, + Version, + }, + winit::event_loop::EventLoopProxy, +}; use rt_core::{Float, SamplerProgress}; use std::env; -#[cfg(feature = "gui")] -use vulkano::{ - buffer::CpuAccessibleBuffer, - command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, - device::{Device, Queue}, - image::StorageImage, - instance::Instance, - sync::{self, GpuFuture}, - Version, -}; -#[cfg(feature = "gui")] -use winit::event_loop::EventLoopProxy; #[cfg(feature = "gui")] mod gui; From ea78ba8d4dc27e854d9ebc195a584f7f4b4cf2b5 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 11 Jan 2023 17:44:01 +1100 Subject: [PATCH 03/39] Initial statistics rework --- implementations/Cargo.toml | 3 +- implementations/src/lib.rs | 9 - .../src/materials/trowbridge_reitz.rs | 47 +-- implementations/src/samplers/mod.rs | 53 ++-- implementations/src/textures/mod.rs | 22 +- implementations/src/utility/distribution.rs | 151 --------- implementations/src/utility/mod.rs | 1 - implementations/statistics/Cargo.toml | 22 ++ implementations/statistics/src/bxdfs.rs | 86 +++++ implementations/statistics/src/chi_squared.rs | 70 ++++ .../statistics/src/distributions.rs | 300 ++++++++++++++++++ implementations/statistics/src/integrators.rs | 76 +++++ implementations/statistics/src/lib.rs | 65 ++++ .../statistics/src/spherical_sampling.rs | 166 ++++++++++ implementations/tests/common/int.rs | 50 --- implementations/tests/common/mod.rs | 66 +--- implementations/tests/sampling.rs | 278 +--------------- rt_core/src/lib.rs | 25 +- 18 files changed, 863 insertions(+), 627 deletions(-) delete mode 100644 implementations/src/utility/distribution.rs create mode 100644 implementations/statistics/Cargo.toml create mode 100644 implementations/statistics/src/bxdfs.rs create mode 100644 implementations/statistics/src/chi_squared.rs create mode 100644 implementations/statistics/src/distributions.rs create mode 100644 implementations/statistics/src/integrators.rs create mode 100644 implementations/statistics/src/lib.rs create mode 100644 implementations/statistics/src/spherical_sampling.rs delete mode 100644 implementations/tests/common/int.rs diff --git a/implementations/Cargo.toml b/implementations/Cargo.toml index 8660108..f23bf1f 100644 --- a/implementations/Cargo.toml +++ b/implementations/Cargo.toml @@ -7,10 +7,11 @@ edition = "2021" [dependencies] image = "0.24.3" -proc = {path = "./proc"} +proc = { path = "./proc" } rand = { version = "0.8.3", features = [ "small_rng" ] } rayon = "1.5.1" rt_core = {path = "../rt_core"} +statistics = { path = "./statistics" } [dev-dependencies] chrono = "0.4.19" diff --git a/implementations/src/lib.rs b/implementations/src/lib.rs index 48725d0..8caf315 100644 --- a/implementations/src/lib.rs +++ b/implementations/src/lib.rs @@ -16,12 +16,3 @@ pub use textures::*; pub use utility::*; pub use primitives::triangle::Triangle; - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} diff --git a/implementations/src/materials/trowbridge_reitz.rs b/implementations/src/materials/trowbridge_reitz.rs index 0df34f1..b3ce0b7 100644 --- a/implementations/src/materials/trowbridge_reitz.rs +++ b/implementations/src/materials/trowbridge_reitz.rs @@ -1,10 +1,10 @@ -use crate::utility::coord::Coordinate; use crate::{ materials::refract, textures::Texture, - utility::{offset_ray, random_float}, + utility::{coord::Coordinate, offset_ray}, }; -use rt_core::{Float, Hit, Ray, Scatter, Vec3}; +use rand::{rngs::SmallRng, thread_rng, SeedableRng}; +use rt_core::*; use std::sync::Arc; #[derive(Debug)] @@ -15,12 +15,6 @@ pub struct TrowbridgeReitz { pub metallic: Float, } -#[cfg(all(feature = "f64"))] -use std::f64::{consts::PI, INFINITY}; - -#[cfg(not(feature = "f64"))] -use std::f32::{consts::PI, INFINITY}; - impl TrowbridgeReitz where T: Texture, @@ -57,30 +51,13 @@ where ((1.0 + self.alpha * self.alpha * tan_sq).sqrt() - 1.0) * 0.5 } - fn distribution_ggx(&self, hit: &Hit, h: Vec3) -> Float { + fn microfacet_ndf_ggx(&self, hit: &Hit, h: Vec3) -> Float { let noh = hit.normal.dot(h); let alpha_sq = self.alpha * self.alpha; let noh_sq = noh * noh; let den = noh_sq * (alpha_sq - 1.0) + 1.0; alpha_sq / (PI * den * den) } - - fn sample_h(&self, hit: &Hit, _: Vec3) -> Vec3 { - let coord = Coordinate::new_from_z(hit.normal); - - let r1 = random_float(); - let r2 = random_float(); - let cos_theta = ((1.0 - r1) / (r1 * (self.alpha * self.alpha - 1.0) + 1.0)).sqrt(); - let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); - let phi_s = (2.0 * PI * r2).max(0.0).min(2.0 * PI); - - let mut h = - Vec3::new(phi_s.cos() * sin_theta, phi_s.sin() * sin_theta, cos_theta).normalised(); - - coord.vec_to_coordinate(&mut h); - - h - } } impl Scatter for TrowbridgeReitz @@ -88,9 +65,14 @@ where T: Texture, { fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { - let wo = -ray.direction; + let coord = Coordinate::new_from_z(hit.normal); + + let mut h = statistics::bxdfs::trowbridge_reitz::sample_h( + self.alpha, + &mut SmallRng::from_rng(thread_rng()).unwrap(), + ); - let h = self.sample_h(hit, wo); + coord.vec_to_coordinate(&mut h); let direction = ray.direction.reflected(h); @@ -101,8 +83,7 @@ where } fn scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Float { let wo = -wo; - let h = (wo + wi).normalised(); - let a = self.distribution_ggx(hit, h) * h.dot(hit.normal).abs() / (4.0 * wo.dot(h)); + let a = statistics::bxdfs::trowbridge_reitz::pdf_outgoing(self.alpha, wo, wi, hit.normal); if a == 0.0 { INFINITY } else { @@ -119,7 +100,7 @@ where let spec_component = self.fresnel(hit, wo, wi, h) * self.geometry_ggx(h, wo, wi) - * self.distribution_ggx(hit, h) + * self.microfacet_ndf_ggx(hit, h) / (4.0 * wo.dot(hit.normal) * wi.dot(hit.normal)); spec_component * hit.normal.dot(wi).abs() @@ -132,7 +113,7 @@ where return Vec3::zero(); } - self.distribution_ggx(hit, h); + self.microfacet_ndf_ggx(hit, h); self.fresnel(hit, wo, wi, h) * self.geometry_ggx(h, wo, wi) / self.geometry_partial_ggx(h, wo) diff --git a/implementations/src/samplers/mod.rs b/implementations/src/samplers/mod.rs index 112249e..0ec2f71 100644 --- a/implementations/src/samplers/mod.rs +++ b/implementations/src/samplers/mod.rs @@ -1,17 +1,14 @@ -use crate::textures::Texture; -use crate::utility::distribution::*; -use crate::{generate_pdf, next_float, random_float}; -use rt_core::Float; -use rt_core::PI; -use rt_core::{NoHit, Ray, Vec3}; +use crate::{generate_values, next_float, random_float, textures::Texture}; +use rand::{rngs::SmallRng, thread_rng, SeedableRng}; +use rt_core::*; +use statistics::distributions::*; use std::sync::Arc; pub mod random_sampler; pub struct Sky { texture: Arc, - pub pdf: Vec, - cdf: CDF2D, + pub distribution: Distribution2D, sampler_res: (usize, usize), } @@ -19,14 +16,13 @@ impl Sky { pub fn new(texture: &Arc, sampler_res: (usize, usize)) -> Self { let texture = texture.clone(); - let pdf = generate_pdf(&*texture, sampler_res); + let values = generate_values(&*texture, sampler_res); - let cdf = CDF2D::from_pdf(&pdf, sampler_res.0); + let distribution = Distribution2D::new(&values, sampler_res.0); Sky { texture, - pdf, - cdf, + distribution, sampler_res, } } @@ -49,20 +45,16 @@ impl NoHit for Sky { } let u = phi / (2.0 * PI); let v = theta / PI; - - let u_bin = next_float((u * self.sampler_res.0 as Float).floor()) as usize; - let v_bin = next_float((v * self.sampler_res.1 as Float).floor()) as usize; - - let index = - (v_bin * self.sampler_res.0 + u_bin).min(self.sampler_res.0 * self.sampler_res.1 - 1); - - self.pdf[index] / (2.0 * PI * PI * sin_theta) + self.sampler_res.0 as Float * self.sampler_res.1 as Float * self.distribution.pdf(u, v) + / (sin_theta * TAU * PI) } fn can_sample(&self) -> bool { true } fn sample(&self) -> Vec3 { - let uv = self.cdf.sample(); + let uv = self + .distribution + .sample(&mut SmallRng::from_rng(thread_rng()).unwrap()); let u = next_float(uv.0 as Float + random_float()) / self.sampler_res.0 as Float; let v = next_float(uv.1 as Float + random_float()) / self.sampler_res.1 as Float; @@ -73,3 +65,22 @@ impl NoHit for Sky { Vec3::from_spherical(theta.sin(), theta.cos(), phi.sin(), phi.cos()) } } + +#[cfg(test)] +mod tests { + use crate::*; + use rand::rngs::ThreadRng; + use rt_core::*; + use statistics::spherical_sampling::test_spherical_pdf; + + #[test] + fn sky_sampling() { + let tex = std::sync::Arc::new(AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one()))); + + let sky = Sky::new(&tex, (60, 30)); + + let pdf = |outgoing: Vec3| sky.pdf(outgoing); + let sample = |_: &mut ThreadRng| sky.sample(); + test_spherical_pdf("lerp sky sampling", &pdf, &sample, false); + } +} diff --git a/implementations/src/textures/mod.rs b/implementations/src/textures/mod.rs index a82becf..bec76ca 100644 --- a/implementations/src/textures/mod.rs +++ b/implementations/src/textures/mod.rs @@ -1,7 +1,7 @@ use image::{io::Reader, GenericImageView}; use proc::Texture; use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; -use rt_core::{Float, Vec2, Vec3, PI}; +use rt_core::*; const PERLIN_RVECS: usize = 256; @@ -28,10 +28,8 @@ pub struct CheckeredTexture { secondary_colour: Vec3, } -pub fn generate_pdf(texture: &T, sample_res: (usize, usize)) -> Vec { - let mut sum = 0.0; - - let mut pdf = Vec::new(); +pub fn generate_values(texture: &T, sample_res: (usize, usize)) -> Vec { + let mut values = Vec::new(); let step = (1.0 / sample_res.0 as Float, 1.0 / sample_res.1 as Float); for y in 0..sample_res.1 { @@ -43,21 +41,11 @@ pub fn generate_pdf(texture: &T, sample_res: (usize, usize)) -> Vec< let sin_theta = theta.sin(); let direction = Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, theta.cos()); let col = texture.colour_value(direction, Vec3::zero()); - sum += 0.2126 * col.x + 0.7152 * col.y + 0.0722 * col.z; - pdf.push(0.2126 * col.x + 0.7152 * col.y + 0.0722 * col.z); + values.push((0.2126 * col.x + 0.7152 * col.y + 0.0722 * col.z) * sin_theta); } } - let average = sum * step.0 * step.1; - - let pdf: Vec = pdf - .into_iter() - .map(|v| (v / average) / (sample_res.0 * sample_res.1) as Float) - .collect(); - - let sum: Float = pdf.iter().sum(); - - pdf.into_iter().map(|v| v / sum).collect() + values } impl CheckeredTexture { diff --git a/implementations/src/utility/distribution.rs b/implementations/src/utility/distribution.rs deleted file mode 100644 index 44bd40d..0000000 --- a/implementations/src/utility/distribution.rs +++ /dev/null @@ -1,151 +0,0 @@ -use rt_core::Float; - -use super::random_float; - -pub struct CDF1D { - pub intervals: Vec, -} - -impl CDF1D { - pub fn from_pdf(pdf: Vec) -> Self { - if pdf.is_empty() { - panic!("Empty pdf passed to CDF1D::from_pdf!"); - } - - let mut intervals = vec![0.0]; - for val in pdf.iter() { - let len = intervals.len(); - intervals.push((intervals[len - 1] + val).min(1.0)); - } - intervals[pdf.len()] = 1.0; - Self { intervals } - } - pub fn sample(&self) -> usize { - let num = random_float(); - - let mut low = 0; - let mut high = self.intervals.len() - 1; - - let mut i = (low + high) / 2; - let mut above = num >= self.intervals[i]; - let mut below = self.intervals[i + 1] > num; - - while !(above && below) { - if above { - low = i; - } else { - high = i; - } - i = (low + high) / 2; - above = num >= self.intervals[i]; - below = self.intervals[i + 1] > num; - } - - i - } -} - -pub struct CDF2D { - pub y_cdf: CDF1D, - x_cdfs: Vec, -} - -impl CDF2D { - pub fn from_pdf(pdf: &[Float], width: usize) -> Self { - if pdf.len() % width != 0 { - panic!("Invalid width passed to CDF2D"); - } - let height = pdf.len() / width; - let mut row_sums: Vec = Vec::new(); - let mut average = 0.0; - for y in 0..height { - let mut row_sum = 0.0; - for x in 0..width { - let i = width * y + x; - average += pdf[i]; - row_sum += pdf[i]; - } - row_sums.push(row_sum); - } - - let y_pdf: Vec = row_sums.iter().map(|v| v / average).collect(); - - // renormalise due to floating point error - let sum: Float = y_pdf.iter().sum(); - let y_pdf: Vec = y_pdf.into_iter().map(|v| v / sum).collect(); - - let y_cdf = CDF1D::from_pdf(y_pdf); - - assert!(pdf.len() / width == (y_cdf.intervals.len() - 1)); - - let x_cdfs: Vec = pdf - .chunks_exact(width) - .zip(row_sums) - .map(|(chunk, row_sum)| CDF1D::from_pdf(chunk.iter().map(|v| v / row_sum).collect())) - .collect(); - - Self { y_cdf, x_cdfs } - } - pub fn sample(&self) -> (usize, usize) { - let v = self.y_cdf.sample(); - let u = self.x_cdfs[v].sample(); - (u, v) - } -} - -#[test] -fn cdf1d_sampling() { - let pdf = [0.1, 0.5, 0.3, 0.1]; - - let cdf = CDF1D::from_pdf(pdf.to_vec()); - - let samples = 1_000_000; - let mut bins = [0, 0, 0, 0]; - - for _ in 0..samples { - match cdf.sample() { - 0 => { - bins[0] += 1; - } - 1 => { - bins[1] += 1; - } - 2 => { - bins[2] += 1; - } - 3 => { - bins[3] += 1; - } - _ => unreachable!(), - } - } - - bins.into_iter() - .zip(pdf) - .for_each(|(v, p)| assert!((v as Float / samples as Float - p).abs() < 0.01)); -} - -#[test] -fn cdf2d_sampling() { - use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; - - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let pdf: Vec = (0..10000).map(|_| rng.gen_range(0..1000)).collect(); - let sum: usize = pdf.iter().sum(); - let pdf: Vec = pdf.into_iter().map(|v| v as Float / sum as Float).collect(); - - let cdf = CDF2D::from_pdf(&pdf, 100); - - let mut bins = [0; 10000]; - - let samples = 1_000_000; - - for _ in 0..samples { - let sample = cdf.sample(); - bins[sample.0 + sample.1 * 100] += 1; - - bins.into_iter() - .zip(pdf.clone()) - .for_each(|(v, p)| assert!((v as Float / samples as Float - p).abs() < 0.01)); - } -} diff --git a/implementations/src/utility/mod.rs b/implementations/src/utility/mod.rs index 167c38a..021d476 100644 --- a/implementations/src/utility/mod.rs +++ b/implementations/src/utility/mod.rs @@ -2,7 +2,6 @@ use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; use rt_core::{Float, Vec3, PI}; pub mod coord; -pub mod distribution; pub fn check_side(normal: &mut Vec3, ray_direction: &Vec3) -> bool { if normal.dot(*ray_direction) > 0.0 { diff --git a/implementations/statistics/Cargo.toml b/implementations/statistics/Cargo.toml new file mode 100644 index 0000000..42e387d --- /dev/null +++ b/implementations/statistics/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "statistics" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = "0.8.3" +num_cpus = "1.15" +statrs = "0.16.0" +rayon = "1.5.1" +rt_core = { path = "../../rt_core" } + +[dev-dependencies] +image = "0.24.5" +num_cpus = "1.15" +statrs = "0.16.0" +rand_core = "0.6.0" +rayon = "1.5.1" +rand = { version = "0.8.3", features = [ "small_rng" ] } + +[features] +f64 = ["rt_core/f64"] \ No newline at end of file diff --git a/implementations/statistics/src/bxdfs.rs b/implementations/statistics/src/bxdfs.rs new file mode 100644 index 0000000..666cdae --- /dev/null +++ b/implementations/statistics/src/bxdfs.rs @@ -0,0 +1,86 @@ +pub mod trowbridge_reitz { + use crate::*; + use rand::Rng; + pub fn sample_h(alpha: Float, rng: &mut R) -> Vec3 { + let r1: Float = rng.gen(); + let r2: Float = rng.gen(); + let cos_theta = ((1.0 - r1) / (r1 * (alpha * alpha - 1.0) + 1.0)).sqrt(); + let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); + let phi_s = (TAU * r2).max(0.0).min(TAU); + + Vec3::new(phi_s.cos() * sin_theta, phi_s.sin() * sin_theta, cos_theta).normalised() + } + + pub fn reference_d(h: Vec3, alpha: Float) -> Float { + if h.z <= 0.0 { + return 0.0; + } + + let a_sq = alpha * alpha; + let cos_theta = h.z; + let cos_theta_sq = cos_theta * cos_theta; + let sin_theta = (1.0 - cos_theta_sq).sqrt(); + let tan_theta = sin_theta / cos_theta; + let tmp = a_sq + tan_theta * tan_theta; + + a_sq / (PI * cos_theta_sq * cos_theta_sq * tmp * tmp) + } + + pub fn d(alpha: Float, cos_theta: Float) -> Float { + if cos_theta <= 0.0 { + return 0.0; + } + let a_sq = alpha * alpha; + let tmp = cos_theta * cos_theta * (a_sq - 1.0) + 1.0; + a_sq / (PI * tmp * tmp) + } + + pub fn pdf_h(h: Vec3, alpha: Float) -> Float { + // technically the paper has an .abs() but it isn't needed since h.z < 0 => D(m) = 0 + d(alpha, h.z) * h.z + } + + pub fn sample(alpha: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + let h = sample_h(alpha, rng); + 2.0 * incoming.dot(h) * h - incoming + } + + pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { + let h = (incoming + outgoing).normalised(); + let cos_theta = normal.dot(h); + let d = { + // Why no positive charactaristic function? + let a_sq = alpha * alpha; + let tmp = cos_theta * cos_theta * (a_sq - 1.0) + 1.0; + a_sq / (PI * tmp * tmp) + }; + d * cos_theta.abs() / (4.0 * outgoing.dot(h).abs()) + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::{spherical_sampling::*, *}; + use rand::{rngs::ThreadRng, thread_rng, Rng}; + + #[test] + fn trowbridge_reitz_h() { + let alpha: Float = thread_rng().gen(); + let pdf = |outgoing: Vec3| trowbridge_reitz::pdf_h(outgoing, alpha); + let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample_h(alpha, rng); + test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); + } + + #[test] + fn trowbridge_reitz() { + let mut rng = thread_rng(); + let incoming: Vec3 = generate_wi(&mut rng); + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| { + trowbridge_reitz::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) + }; + let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); + test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); + } +} diff --git a/implementations/statistics/src/chi_squared.rs b/implementations/statistics/src/chi_squared.rs new file mode 100644 index 0000000..9f5d0dd --- /dev/null +++ b/implementations/statistics/src/chi_squared.rs @@ -0,0 +1,70 @@ +use crate::*; +use rayon::prelude::*; +use statrs::function::gamma::gamma_ur; + +use std::cmp::Ordering::*; +pub fn chi2_probability(dof: f64, distance: f64) -> f64 { + match distance.partial_cmp(&0.0).unwrap() { + Less => panic!("distance < 0.0"), + Equal => 1.0, + Greater => gamma_ur(dof * 0.5, distance * 0.5), + } +} + +pub fn chi_squared( + freq_table: &[Float], + expected_freq_table: &[Float], + samples: usize, +) -> (usize, Float) { + assert_eq!(freq_table.len(), expected_freq_table.len()); + + let mut values = expected_freq_table + .into_par_iter() + .zip(freq_table.into_par_iter()) + .collect::>(); + + values.sort_by(|a, b| a.0.partial_cmp(b.0).unwrap()); + + let mut df = 0; + + let mut expected_pooled = 0.0; + let mut actual_pooled = 0.0; + + let mut chi_squared = 0.0; + + for (expected, actual) in values { + if *expected == 0.0 { + if *actual > (samples / 100_000) as Float { + chi_squared += INFINITY as Float; + } + } else if expected_pooled > 5.0 { + // prevent df = 0 when all values are less than 5 + let diff = actual_pooled - expected_pooled; + chi_squared += diff * diff / expected_pooled; + df += 1; + } else if *expected < 5.0 || (expected_pooled > 0.0 && expected_pooled < 5.0) { + expected_pooled += expected; + actual_pooled += actual; + } else { + let diff = actual - expected; + chi_squared += diff * diff / expected; + df += 1; + } + } + + if actual_pooled > 0.0 || expected_pooled > 0.0 { + let diff = actual_pooled - expected_pooled; + chi_squared += diff * diff / expected_pooled; + df += 1; + } + + df -= 1; + + (df, chi_squared) +} + +#[cfg(test)] +pub mod test {} + +#[cfg(test)] +pub use test::*; diff --git a/implementations/statistics/src/distributions.rs b/implementations/statistics/src/distributions.rs new file mode 100644 index 0000000..903534c --- /dev/null +++ b/implementations/statistics/src/distributions.rs @@ -0,0 +1,300 @@ +use crate::Float; +use rand::Rng; + +#[derive(Debug, Clone, PartialEq)] +pub struct Distribution1D { + pub pdf: Vec, + pub cdf: Vec, +} + +impl Distribution1D { + pub fn new(values: &[Float]) -> Self { + if values.is_empty() { + panic!("Empty pdf passed to Distribution1D::from_pdf!"); + } + + let n = values.len(); + + let mut intervals = vec![0.0]; + + for i in 1..=n { + let last = intervals[i - 1]; + intervals.push(last + values[i - 1] as Float); + } + + let c = intervals[n]; + for (_, value) in intervals.iter_mut().enumerate() { + if c != 0.0 { + *value /= c as Float; + } + } + + let mut pdf = Vec::new(); + let mut last = 0.0; + for value in &intervals[1..] { + pdf.push(value - last); + last = *value; + } + + Self { + pdf, + cdf: intervals, + } + } + + pub fn sample_naive(&self, rng: &mut R) -> usize { + let threshold = rng.gen(); + + self.cdf.iter().position(|v| v >= &threshold).unwrap() - 1 + } + pub fn sample(&self, rng: &mut R) -> usize { + let num = rng.gen(); + + let pred = |i| self.cdf[i] <= num; + + { + let mut first = 0; + let mut len = self.cdf.len(); + while len > 0 { + let half = len >> 1; + let middle = first + half; + + if pred(middle) { + first = middle + 1; + len -= half + 1; + } else { + len = half; + } + } + (first - 1).clamp(0, self.cdf.len() - 2) + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Distribution2D { + pub x_distributions: Vec, + pub y_distribution: Distribution1D, + pub dim: (usize, usize), +} + +impl Distribution2D { + pub fn new(values: &[Float], width: usize) -> Self { + assert_eq!(values.len() % width, 0); + let mut y_values = Vec::new(); + let mut x_distributions = Vec::new(); + for vec_x in values.chunks_exact(width) { + x_distributions.push(Distribution1D::new(vec_x)); + let row_sum: Float = vec_x.iter().sum(); + y_values.push(row_sum); + } + let y_distribution = Distribution1D::new(&y_values); + + Self { + x_distributions, + y_distribution, + dim: (width, values.len() / width), + } + } + pub fn sample(&self, rng: &mut R) -> (usize, usize) { + let v = self.y_distribution.sample(rng); + let u = self.x_distributions[v].sample(rng); + (u, v) + } + pub fn pdf(&self, u: Float, v: Float) -> Float { + let u = ((self.dim.0 as Float * u) as usize).clamp(0, self.dim.0 - 1); + let v = ((self.dim.1 as Float * v) as usize).clamp(0, self.dim.1 - 1); + + self.y_distribution.pdf[v] * self.x_distributions[v].pdf[u] + } + pub fn dim(&self) -> (usize, usize) { + self.dim + } +} + +#[cfg(test)] +mod tests { + use crate::{chi_squared::*, distributions::*, utility::*}; + use rand::thread_rng; + use rayon::prelude::*; + + macro_rules! random_1d { + ($len:expr) => {{ + const SAMPLES: usize = 100_000; + const SAMPLE_LEN: usize = 100; + const NUMBER_TESTS: usize = 10; + const CHI_SQUARED_THRESHOLD: Float = 0.01; + const BATCH_EXPONENT: usize = 6; + const BATCHES: usize = 2usize.pow(BATCH_EXPONENT as u32); + + let mut rng = thread_rng(); + + let values: Vec = (0..SAMPLE_LEN) + .into_iter() + .map(|_| rng.gen_range(0.0..100.0)) + .collect(); + + let cdf = Distribution1D::new(&values); + + let expected_values: Vec = cdf.pdf.iter().map(|v| v * SAMPLES as Float).collect(); + + let func = |samples| { + const SAMPLE_LEN_MINUS_ONE: usize = SAMPLE_LEN - 1; + let mut rng = thread_rng(); + let mut sampled_values = vec![0u64; SAMPLE_LEN]; + for _ in 0..samples { + match cdf.sample(&mut rng) { + index @ 0..=SAMPLE_LEN_MINUS_ONE => { + sampled_values[index] += 1; + } + _ => unreachable!(), + } + } + sampled_values + }; + + // Šidák correction + let threshold = 1.0 - (1.0 - CHI_SQUARED_THRESHOLD).powf(1.0 / NUMBER_TESTS as Float); + + for i in 0..NUMBER_TESTS { + let sampled_vecs: Vec> = (0..BATCHES) + .map(|_| { + distribute_samples_over_threads(SAMPLES as u64, &func) + .into_iter() + .map(|v| v as Float) + .collect() + }) + .collect(); + let sampled_values: Vec = (0..SAMPLE_LEN) + .into_par_iter() + .map(|i| { + recursively_binary_average((0..BATCHES).map(|j| sampled_vecs[j][i]).collect()) + }) + .collect(); + + let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); + + let p_value = chi2_probability(df as f64, chi_squared as f64); + if p_value < threshold as f64 { + panic!("LERP: recieved p value of {p_value} with {SAMPLES} samples on test {i}/{NUMBER_TESTS}") + } + }} + }; + } + + #[test] + fn random_1d_small() { + random_1d!(50) + } + + #[test] + fn random_1d_medium() { + random_1d!(500) + } + #[test] + fn random_1d_large() { + random_1d!(5000) + } + + macro_rules! random_2d { + ($x:expr, $y:expr) => { { + const SAMPLES: usize = 100_000; + const X_RES_MINUS_ONE: usize = $x - 1; + const Y_RES_MINUS_ONE: usize = $y - 1; + const SAMPLES_RES: (usize, usize) = (X_RES_MINUS_ONE + 1, Y_RES_MINUS_ONE + 1); + const SAMPLE_LEN: usize = SAMPLES_RES.0 * SAMPLES_RES.1; + const NUMBER_TESTS: usize = 10; + const CHI_SQUARED_THRESHOLD: Float = 0.01; + const BATCH_EXPONENT: usize = 6; + const BATCHES: usize = 2usize.pow(BATCH_EXPONENT as u32); + + //use rand_core::SeedableRng; + + //let mut rng = SmallRng::seed_from_u64(321321); + let mut rng = thread_rng(); + + let values: Vec = (0..SAMPLE_LEN) + .into_iter() + .map(|_| rng.gen_range(0.0..100.0)) + .collect(); + + let dist = Distribution2D::new(&values, SAMPLES_RES.0); + + let mut expected_values: Vec = Vec::new(); + for y in 0..SAMPLES_RES.1 { + for x in 0..SAMPLES_RES.0 { + expected_values.push( + dist.y_distribution.pdf[y] * dist.x_distributions[y].pdf[x] * SAMPLES as Float, + ); + } + } + + // dist.pdf.iter().map(|v| v * SAMPLES as Float).collect(); + + let func = |samples| { + const SAMPLE_LEN_MINUS_ONE: usize = SAMPLE_LEN - 1; + let mut rng = thread_rng(); + let mut sampled_values = vec![0u64; SAMPLE_LEN]; + for _ in 0..samples { + match dist.sample(&mut rng) { + indices @ (0..=X_RES_MINUS_ONE, 0..=Y_RES_MINUS_ONE) + if indices.0 + (X_RES_MINUS_ONE + 1) * indices.1 < SAMPLE_LEN => + { + sampled_values[indices.0 + indices.1 * (X_RES_MINUS_ONE + 1)] += 1; + } + _ => unreachable!(), + } + } + sampled_values + }; + + // Šidák correction + let threshold = 1.0 - (1.0 - CHI_SQUARED_THRESHOLD).powf(1.0 / NUMBER_TESTS as Float); + + for i in 0..NUMBER_TESTS { + let sampled_vecs: Vec> = (0..BATCHES) + .into_iter() + .map(|_| { + distribute_samples_over_threads(SAMPLES as u64, &func) + .into_iter() + .map(|v| v as Float) + .collect() + }) + .collect(); + let sampled_values: Vec = (0..SAMPLE_LEN) + .into_par_iter() + .map(|i| { + recursively_binary_average( + (0..BATCHES) + .into_iter() + .map(|j| sampled_vecs[j][i]) + .collect(), + ) + }) + .collect(); + + let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); + + let p_value = chi2_probability(df as f64, chi_squared as f64); + if p_value < threshold as f64 { + panic!("LERP: recieved p value of {p_value} with {SAMPLES} samples on test {i}/{NUMBER_TESTS}") + } + } + }}; + } + + #[test] + fn random_2d_small() { + random_2d!(3, 3) + } + + #[test] + fn random_2d_medium() { + random_2d!(30, 60) + } + + #[test] + fn random_2d_large() { + random_2d!(800, 1200) + } +} diff --git a/implementations/statistics/src/integrators.rs b/implementations/statistics/src/integrators.rs new file mode 100644 index 0000000..dd4424f --- /dev/null +++ b/implementations/statistics/src/integrators.rs @@ -0,0 +1,76 @@ +use crate::{Float, Vec3}; +const MAX_DEPTH: usize = 6; +const EPSILON: Float = 0.000001; + +pub fn integrate_solid_angle( + pdf: &F, + theta_start: Float, + theta_end: Float, + phi_start: Float, + phi_end: Float, +) -> Float +where + F: Fn(Vec3) -> Float, +{ + let pdf = |phi: Float, a, b| { + adaptive_simpsons( + |theta| { + pdf(Vec3::new( + phi.cos() * theta.sin(), + phi.sin() * theta.sin(), + theta.cos(), + )) * theta.sin() + }, + a, + b, + ) + }; + + adaptive_simpsons(|phi| pdf(phi, theta_start, theta_end), phi_start, phi_end) +} + +pub fn adaptive_simpsons(function: F, a: Float, b: Float) -> Float +where + F: Fn(Float) -> Float, +{ + fn aux( + function: &F, + a: Float, + b: Float, + c: Float, + fa: Float, + fb: Float, + fc: Float, + i: Float, + epsilon: Float, + depth: usize, + ) -> Float + where + F: Fn(Float) -> Float, + { + let d = 0.5 * (a + b); + let e = 0.5 * (b + c); + let fd = function(d); + let fe = function(e); + + let h = c - a; + let i0 = (1.0 / 12.0) * h * (fa + 4.0 * fd + fb); + let i1 = (1.0 / 12.0) * h * (fb + 4.0 * fe + fc); + let ip = i0 + i1; + + if depth >= MAX_DEPTH || (ip - i).abs() < 15.0 * epsilon { + return ip + (1.0 / 15.0) * (ip - i); + } + + aux(function, a, d, b, fa, fd, fb, i0, 0.5 * epsilon, depth + 1) + + aux(function, b, e, c, fb, fe, fc, i1, 0.5 * epsilon, depth + 1) + } + let c = b; + let b = 0.5 * (a + b); + + let fa = function(a); + let fb = function(b); + let fc = function(c); + let i = (c - a) * (1.0 / 6.0) * (fa + 4.0 * fb + fc); + aux(&function, a, b, c, fa, fb, fc, i, EPSILON, 0) +} diff --git a/implementations/statistics/src/lib.rs b/implementations/statistics/src/lib.rs new file mode 100644 index 0000000..ae604c6 --- /dev/null +++ b/implementations/statistics/src/lib.rs @@ -0,0 +1,65 @@ +pub use rt_core::*; + +pub mod bxdfs; +pub mod chi_squared; +pub mod distributions; +pub mod integrators; +pub mod spherical_sampling; + +pub mod utility { + + use rayon::prelude::*; + use std::ops::Add; + + pub fn distribute_samples_over_threads(samples: u64, f: &F) -> Vec + where + T: Add + Send, + F: Fn(u64) -> Vec + Sync, + Vec: FromIterator<::Output>, + { + let thread_count = num_cpus::get(); + let mut samples_per_thread = vec![samples / thread_count as u64; thread_count]; + let diff = ((samples / thread_count as u64) * thread_count as u64) - samples; + let last = samples_per_thread.len() - 1; + samples_per_thread[last] += diff; + + samples_per_thread + .into_par_iter() + .map(f) + .reduce_with(|a, b| { + a.into_iter() + .zip(b.into_iter()) + .map(|(a, b)| a + b) + .collect() + }) + .unwrap() + } + + use super::Float; + + pub fn recursively_binary_average(mut values: Vec) -> Float { + let mut len = values.len(); + if len & (len - 1) != 0 && len != 0 { + panic!("values.len() is not a power of 2"); + } + while len != 1 { + len /= 2; + + let (a, b) = values.split_at(len); + + values = a + .iter() + .zip(b.iter()) + .map(|(&a, &b)| 0.5 * (a + b)) + .collect(); + } + + values[0] + } + + #[cfg(test)] + #[test] + fn binary_average() { + assert!((recursively_binary_average(vec![1.0, 3.0, 7.0, 1.0]) - 3.0).abs() < 0.0000001); + } +} diff --git a/implementations/statistics/src/spherical_sampling.rs b/implementations/statistics/src/spherical_sampling.rs new file mode 100644 index 0000000..5e04ae5 --- /dev/null +++ b/implementations/statistics/src/spherical_sampling.rs @@ -0,0 +1,166 @@ +use crate::chi_squared::chi2_probability; +use crate::chi_squared::chi_squared; +use crate::integrators::integrate_solid_angle; +use crate::utility::distribute_samples_over_threads; +use crate::utility::recursively_binary_average; +use crate::*; +use crate::{Float, Vec3, PI}; +use rand::rngs::ThreadRng; +use rand::thread_rng; +use rand::Rng; +use rayon::prelude::*; + +pub fn cosine_hemisphere_sampling(rng: &mut R) -> Vec3 { + let cos_theta = (1.0 - rng.gen::()).sqrt(); + let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); + let phi = 2.0 * PI * rng.gen::(); + Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) +} + +pub fn cosine_hemisphere_pdf(wo: Vec3) -> Float { + wo.z.max(0.0) / PI +} + +pub fn specular_sampling(n: Float, rng: &mut R) -> Vec3 { + let a = rng.gen::().powf(1.0 / (n + 1.0)); + let term = (1.0 - a * a).sqrt(); + let phi = 2.0 * PI * rng.gen::(); + Vec3::new(term * phi.cos(), term * phi.sin(), a) +} + +pub fn random_unit_vector(rng: &mut R) -> Vec3 { + let (mut x, mut y, mut z) = (1.0, 1.0, 1.0); + while x * x + y * y + z * z > 1.0 { + x = rng.gen_range(-1.0..1.0); + y = rng.gen_range(-1.0..1.0); + z = rng.gen_range(-1.0..1.0); + } + + Vec3::new(x, y, z).normalised() +} + +pub fn test_spherical_pdf(name: &str, pdf: &P, sample: &S, hemisphere: bool) +where + P: Fn(Vec3) -> Float, + S: Fn(&mut ThreadRng) -> Vec3 + Send + Sync, +{ + const THETA_RES: usize = 80; + const PHI_RES: usize = 160; + const SAMPLES: usize = 100_000; + const SAMPLE_LEN: usize = THETA_RES * PHI_RES; + const NUMBER_TESTS: usize = 10; + const CHI_SQUARED_THRESHOLD: Float = 0.01; + const BATCH_EXPONENT: usize = 6; + const BATCHES: usize = 2usize.pow(BATCH_EXPONENT as u32); + + let mut expected_values = Vec::new(); + + let theta_step = if hemisphere { FRAC_PI_2 } else { PI } / THETA_RES as Float; + let phi_step = TAU / PHI_RES as Float; + for phi_i in 0..PHI_RES { + for theta_i in 0..THETA_RES { + let theta_start = theta_i as Float * theta_step; + let phi_start = phi_i as Float * phi_step; + expected_values.push(integrate_solid_angle( + pdf, + theta_start, + theta_start + theta_step, + phi_start, + phi_start + phi_step, + )); + } + } + + let pdf_sum = expected_values.iter().sum::(); + if (pdf_sum - 1.0).abs() > 0.001 { + panic!("reference pdf doesn't integrate to 1: {pdf_sum}"); + } + + let expected_values: Vec = expected_values + .into_iter() + .map(|v| v * SAMPLES as Float) + .collect(); + + let func = |samples| { + const SAMPLE_LEN_MINUS_ONE: usize = SAMPLE_LEN - 1; + let mut rng = thread_rng(); + let mut sampled_values = vec![0u64; SAMPLE_LEN]; + for _ in 0..samples { + let wo = sample(&mut rng); + + let sin_theta = (1.0 - wo.z * wo.z).sqrt(); + if sin_theta < 0.0 { + panic!("sin_theta ({sin_theta}) < 0.0"); + } + let theta = wo.z.acos(); + let mut phi = (wo.y).atan2(wo.x); + if phi < 0.0 { + phi += 2.0 * PI; + } + let theta_i = theta / theta_step; + let phi_i = phi / phi_step; + let index = (phi_i as usize * THETA_RES + theta_i as usize).min(SAMPLE_LEN_MINUS_ONE); + + sampled_values[index] += 1; + } + sampled_values + }; + + let threshold = 1.0 - (1.0 - CHI_SQUARED_THRESHOLD).powf(1.0 / NUMBER_TESTS as Float); + + for i in 0..NUMBER_TESTS { + let sampled_vecs: Vec> = (0..BATCHES) + .map(|_| { + distribute_samples_over_threads(SAMPLES as u64, &func) + .into_iter() + .map(|v| v as Float) + .collect() + }) + .collect(); + let sampled_values: Vec = (0..SAMPLE_LEN) + .into_par_iter() + .map(|i| recursively_binary_average((0..BATCHES).map(|j| sampled_vecs[j][i]).collect())) + .collect(); + + let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); + + let p_value = chi2_probability(df as f64, chi_squared as f64); + if p_value < threshold as f64 { + panic!("{name}: recieved p value of {p_value} with {SAMPLES} samples averaged over {BATCHES} batches on test {i}/{NUMBER_TESTS}") + } + } +} + +#[cfg(test)] +pub mod test { + use crate::{ + chi_squared::*, integrators::*, spherical_sampling::*, utility::*, Float, Vec3, PI, TAU, + }; + use rand::{rngs::ThreadRng, thread_rng, Rng}; + use rayon::prelude::*; + use rt_core::FRAC_PI_2; + + pub fn to_vec(sin_theta: Float, cos_theta: Float, phi: Float) -> Vec3 { + Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) + } + + pub fn generate_wi(rng: &mut R) -> Vec3 { + let cos_theta: Float = rng.gen(); + let phi = TAU as Float * rng.gen::(); + + to_vec((1.0 - cos_theta * cos_theta).sqrt(), cos_theta, phi) + } + + #[test] + fn cosine_hemisphere() { + test_spherical_pdf( + "cosine hemisphere sampling", + &cosine_hemisphere_pdf, + &cosine_hemisphere_sampling, + true, + ); + } +} + +#[cfg(test)] +pub use test::*; diff --git a/implementations/tests/common/int.rs b/implementations/tests/common/int.rs deleted file mode 100644 index 5e114ee..0000000 --- a/implementations/tests/common/int.rs +++ /dev/null @@ -1,50 +0,0 @@ -use rt_core::Float; - -const MAX_DEPTH: usize = 6; -const EPSILON: Float = 0.000001; - -pub fn adaptive_simpsons(function: F, a: Float, b: Float) -> Float -where - F: Fn(Float) -> Float, -{ - fn aux( - function: &F, - a: Float, - b: Float, - c: Float, - fa: Float, - fb: Float, - fc: Float, - i: Float, - epsilon: Float, - depth: usize, - ) -> Float - where - F: Fn(Float) -> Float, - { - let d = 0.5 * (a + b); - let e = 0.5 * (b + c); - let fd = function(d); - let fe = function(e); - - let h = c - a; - let i0 = (1.0 / 12.0) * h * (fa + 4.0 * fd + fb); - let i1 = (1.0 / 12.0) * h * (fb + 4.0 * fe + fc); - let ip = i0 + i1; - - if depth >= MAX_DEPTH || (ip - i).abs() < 15.0 * epsilon { - return ip + (1.0 / 15.0) * (ip - i); - } - - aux(function, a, d, b, fa, fd, fb, i0, 0.5 * epsilon, depth + 1) - + aux(function, b, e, c, fb, fe, fc, i1, 0.5 * epsilon, depth + 1) - } - let c = b; - let b = 0.5 * (a + b); - - let fa = function(a); - let fb = function(b); - let fc = function(c); - let i = (c - a) * (1.0 / 6.0) * (fa + 4.0 * fb + fc); - aux(&function, a, b, c, fa, fb, fc, i, EPSILON, 0) -} diff --git a/implementations/tests/common/mod.rs b/implementations/tests/common/mod.rs index 249ba2b..2b4752a 100644 --- a/implementations/tests/common/mod.rs +++ b/implementations/tests/common/mod.rs @@ -1,23 +1,20 @@ use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; use rayon::prelude::*; use rt_core::{vec::*, Float}; +use statistics::integrators::adaptive_simpsons; use statrs::function::gamma::*; use std::{ cmp::Ordering::*, - f64::{consts::*, INFINITY}, + f64::consts::*, {fs::File, io::Write}, }; -pub mod int; - pub const SAMPLES: usize = 10_000_000; pub const THETA_RES: usize = 80; pub const PHI_RES: usize = 2 * THETA_RES; pub const CHI2_THRESHOLD: Float = 0.01; pub const CHI_TESTS: usize = 1; -use int::*; - pub fn to_vec(sin_theta: Float, cos_theta: Float, phi: Float) -> Vec3 { Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) } @@ -101,13 +98,6 @@ where sum.iter_mut().zip(val).for_each(|(s, v)| *s += v); sum }) - - /*for _ in 0..sample_count { - let sample = function(); - freq[sample.0 + sample.1 * u_res] += 1.0; - } - - freq*/ } pub fn samped_frequency_distribution( @@ -154,58 +144,6 @@ pub fn generate_wo() -> Vec3 { to_vec((1.0 - cos_theta * cos_theta).sqrt(), cos_theta, phi) } -pub fn chi_squared( - freq_table: Vec, - expected_freq_table: Vec, - samples: usize, -) -> (usize, Float) { - assert_eq!(freq_table.len(), expected_freq_table.len()); - - let mut values = expected_freq_table - .into_par_iter() - .zip(freq_table.into_par_iter()) - .collect::>(); - - values.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); - - let mut df = 0; - - let mut expected_pooled = 0.0; - let mut actual_pooled = 0.0; - - let mut chi_squared = 0.0; - - for (expected, actual) in values { - if expected == 0.0 { - if actual > (samples / 100_000) as Float { - chi_squared += INFINITY as Float; - } - } else if expected_pooled > 5.0 { - // prevent df = 0 when all values are less than 5 - let diff = actual_pooled - expected_pooled; - chi_squared += diff * diff / expected_pooled; - df += 1; - } else if expected < 5.0 || (expected_pooled > 0.0 && expected_pooled < 5.0) { - expected_pooled += expected; - actual_pooled += actual; - } else { - let diff = actual - expected; - chi_squared += diff * diff / expected; - df += 1; - } - } - - if actual_pooled > 0.0 || expected_pooled > 0.0 { - let diff = actual_pooled - expected_pooled; - chi_squared += diff * diff / expected_pooled; - df += 1; - } - - df -= 1; - - (df, chi_squared) -} - pub fn dump_tables( wo: Vec3, freq_table: &[Float], diff --git a/implementations/tests/sampling.rs b/implementations/tests/sampling.rs index 79853e8..635a00a 100644 --- a/implementations/tests/sampling.rs +++ b/implementations/tests/sampling.rs @@ -1,149 +1,12 @@ -use crate::common::*; -use implementations::distribution::CDF2D; - +use rand::rngs::ThreadRng; use rt_core::*; +use statistics::spherical_sampling::test_spherical_pdf; mod common; -fn test_bxdf(bxdf: B, bxdf_name: String) -> bool -where - B: Scatter + Sync, -{ - let hit = Hit { - t: 0.0, - error: Vec3::zero(), - point: Vec3::zero(), - uv: None, - normal: Vec3::new(0.0, 0.0, 1.0), - out: true, - }; - - let sample = |wo: Vec3| { - let mut ray = Ray::new(Vec3::zero(), wo, 0.0); - bxdf.scatter_ray(&mut ray, &hit); - ray.direction - }; - - let pdf = |wo: Vec3, wi: Vec3| bxdf.scattering_pdf(&hit, wo, wi); - for i in 0..CHI_TESTS { - let wo = generate_wo(); - let freq_table = samped_frequency_distribution(&sample, wo, PHI_RES, THETA_RES, SAMPLES); - let expected_freq_table: Vec = - integrate_frequency_table(&pdf, wo, PHI_RES, THETA_RES) - .into_iter() - .map(|x| x * SAMPLES as Float) - .collect(); - if i == 0 { - dump_tables( - wo, - &freq_table - .iter() - .map(|&v| v as Float) - .collect::>(), - &expected_freq_table, - PHI_RES, - THETA_RES, - &bxdf_name, - ); - } - let (df, chi_squared) = chi_squared(freq_table, expected_freq_table, SAMPLES); - let p = chi2_probability(df as f64, chi_squared as f64); - let threshold = 1.0 - (1.0 - CHI2_THRESHOLD).powf(1.0 / CHI_TESTS as Float); - if p < threshold as f64 || p.is_infinite() { - return false; - } - } - true -} - -fn generate_random_pdf(length: usize) -> Vec { - let mut vec = Vec::with_capacity(length); - - for _ in 0..length { - vec.push(random_float()); - } - let sum: Float = vec.iter().sum(); - vec.into_iter().map(|v| v / sum).collect() -} - -#[test] -fn trowbridge_reitz() { - let bxdf = implementations::trowbridge_reitz::TrowbridgeReitz::new( - &std::sync::Arc::new(implementations::SolidColour::new(Vec3::new(0.0, 0.0, 0.0))), - 0.3, - 3.2 * Vec3::one(), - 1.0, - ); - - assert!(test_bxdf(bxdf, "trowbridge_reitz".to_string())) -} - #[test] -#[ignore] -fn lambertian() { - let bxdf = implementations::lambertian::Lambertian::new( - &std::sync::Arc::new(implementations::SolidColour::new(Vec3::new(0.0, 0.0, 0.0))), - 0.3, - ); - - assert!(test_bxdf(bxdf, "lambertain".to_string())) -} - -#[test] -fn discrete_2d_pdf() { - let pdf = implementations::generate_pdf( - &*std::sync::Arc::new(implementations::AllTextures::Lerp( - implementations::Lerp::new(Vec3::zero(), Vec3::one()), - )), - (50, 50), - ); - assert!(pdf.into_iter().sum::() - 1.0 < 0.01); -} - -#[test] -fn discrete_2d() { - const SAMPLE_WIDTH: usize = 50; - const SAMPLE_HEIGHT: usize = 50; - - let pdf = generate_random_pdf(SAMPLE_WIDTH * SAMPLE_HEIGHT); - - let cdf = implementations::distribution::CDF2D::from_pdf(&pdf, SAMPLE_WIDTH); - - let samples = 10_000_000; - - let sample = || cdf.sample(); - - let expected_freq_table: Vec = pdf.into_iter().map(|v| v * samples as Float).collect(); - - for i in 0..CHI_TESTS { - let freq_table = - samped_frequency_distribution_uv(&sample, SAMPLE_WIDTH, SAMPLE_HEIGHT, samples); - - if i == 0 { - dump_tables( - Vec3::zero(), - &freq_table - .iter() - .map(|&v| v as Float) - .collect::>(), - &expected_freq_table, - SAMPLE_WIDTH, - SAMPLE_HEIGHT, - "2DCDF", - ); - } - let (df, chi_squared) = chi_squared(freq_table, expected_freq_table.clone(), samples); - let p = chi2_probability(df as f64, chi_squared as f64); - let threshold = 1.0 - (1.0 - CHI2_THRESHOLD).powf(1.0 / CHI_TESTS as Float); - if p < threshold as f64 || p.is_infinite() { - panic!("p: {p}"); - } - } -} - -#[test] -fn transform_test() { - const SAMPLE_WIDTH: usize = 50; +fn sky_sampling() { + const SAMPLE_WIDTH: usize = 30; const SAMPLE_HEIGHT: usize = 50; let tex = std::sync::Arc::new(implementations::AllTextures::Lerp( @@ -152,134 +15,7 @@ fn transform_test() { let sky = implementations::Sky::new(&tex, (SAMPLE_WIDTH, SAMPLE_HEIGHT)); - let expected_pdf = sky.pdf.clone(); - - let pdf = (0..(SAMPLE_HEIGHT * SAMPLE_WIDTH)) - .into_iter() - .map(|i| (i % SAMPLE_WIDTH, i / SAMPLE_WIDTH)) - .map(|(u, v)| { - let phi = ((u as Float + 0.5) / SAMPLE_WIDTH as Float) * 2.0 * PI; - let theta = ((v as Float + 0.5) / SAMPLE_HEIGHT as Float) * PI; - - let vec = Vec3::from_spherical(theta.sin(), theta.cos(), phi.sin(), phi.cos()); - - sky.pdf(vec) * 2.0 * PI * PI * theta.sin() - }); - - let samples = 1_000_000; - - let pdf: Vec = pdf.map(|v| v * samples as Float).collect(); - let expected_pdf: Vec = expected_pdf - .into_iter() - .map(|v| v * samples as Float) - .collect(); - - dump_tables( - Vec3::zero(), - &pdf, - &expected_pdf, - SAMPLE_WIDTH, - SAMPLE_HEIGHT, - "transform UV", - ); - let (df, chi_squared) = chi_squared(pdf, expected_pdf, SAMPLE_HEIGHT * SAMPLE_WIDTH); - let p = chi2_probability(df as f64, chi_squared as f64); - let threshold = CHI2_THRESHOLD; - if p < threshold as f64 || p.is_infinite() { - panic!("p: {p}"); - } -} - -#[test] -fn sky_sampling_uv() { - const SAMPLE_WIDTH: usize = 50; - const SAMPLE_HEIGHT: usize = 50; - - let tex = std::sync::Arc::new(implementations::AllTextures::Lerp( - implementations::Lerp::new(Vec3::zero(), Vec3::one()), - )); - - let sky = implementations::Sky::new(&tex, (50, 50)); - - let pdf = sky.pdf; - - let cdf = CDF2D::from_pdf(&pdf, SAMPLE_WIDTH); - - let samples = 10_000_000; - - let sample = || cdf.sample(); - - let expected_freq_table: Vec = pdf.into_iter().map(|v| v * samples as Float).collect(); - - for i in 0..CHI_TESTS { - let freq_table = - samped_frequency_distribution_uv(&sample, SAMPLE_WIDTH, SAMPLE_HEIGHT, samples); - - if i == 0 { - dump_tables( - Vec3::zero(), - &freq_table - .iter() - .map(|&v| v as Float) - .collect::>(), - &expected_freq_table, - SAMPLE_WIDTH, - SAMPLE_HEIGHT, - "Sky Sampling UV", - ); - } - let (df, chi_squared) = chi_squared(freq_table, expected_freq_table.clone(), samples); - let p = chi2_probability(df as f64, chi_squared as f64); - let threshold = 1.0 - (1.0 - CHI2_THRESHOLD).powf(1.0 / CHI_TESTS as Float); - if p < threshold as f64 || p.is_infinite() { - panic!("p: {p}"); - } - } -} - -#[test] -pub fn sky_sampling() { - let tex = std::sync::Arc::new(implementations::AllTextures::Lerp( - implementations::Lerp::new(Vec3::zero(), Vec3::one()), - )); - - const SAMPLE_HEIGHT: usize = 50; - const SAMPLE_WIDTH: usize = 100; - - let sky = implementations::Sky::new(&tex, (SAMPLE_WIDTH, SAMPLE_HEIGHT)); - - let samples = 1_000_000; - - let sample = |_wo: Vec3| sky.sample(); - - let pdf = |_: Vec3, wi: Vec3| sky.pdf(wi) * (SAMPLE_WIDTH * SAMPLE_HEIGHT) as Float; - - let wo = generate_wo(); - let freq_table = samped_frequency_distribution(&sample, wo, PHI_RES, THETA_RES, samples); - - let expected_freq_table: Vec = integrate_frequency_table(&pdf, wo, PHI_RES, THETA_RES) - .into_iter() - .map(|x| x * samples as Float) - .collect(); - - dump_tables( - wo, - &freq_table - .iter() - .map(|&v| v as Float) - .collect::>(), - &expected_freq_table, - PHI_RES, - THETA_RES, - "Sky Sampling", - ); - - let (df, chi_squared) = chi_squared(freq_table, expected_freq_table, samples); - let p = chi2_probability(df as f64, chi_squared as f64); - let threshold = 1.0 - (1.0 - CHI2_THRESHOLD).powf(1.0 / CHI_TESTS as Float); - if p < threshold as f64 { - panic!("Failed to reach pass threshold {p} < {threshold}"); - } else if p.is_infinite() { - panic!("Failed to reach pass threshold p = inf"); - } + let pdf = |outgoing: Vec3| sky.pdf(outgoing); + let sample = |_: &mut ThreadRng| sky.sample(); + test_spherical_pdf("lerp sky sampling", &pdf, &sample, false); } diff --git a/rt_core/src/lib.rs b/rt_core/src/lib.rs index 845fe88..f981836 100644 --- a/rt_core/src/lib.rs +++ b/rt_core/src/lib.rs @@ -13,18 +13,25 @@ pub use sampler::*; pub use vec::*; #[cfg(all(feature = "f64"))] -pub type Float = f64; -#[cfg(all(feature = "f64"))] -pub use std::f64::consts::PI; -#[cfg(all(feature = "f64"))] -pub const EPSILON: Float = 5.58E-8; +pub mod f64_stuff { + pub type Float = f64; + pub use std::f64::consts::*; + pub use std::f64::*; + pub const EPSILON: Float = 5.58E-8; +} #[cfg(not(feature = "f64"))] -pub type Float = f32; -#[cfg(not(feature = "f64"))] -pub use std::f32::consts::PI; +pub mod f32_stuff { + pub type Float = f32; + pub use std::f32::consts::*; + pub use std::f32::*; + pub const EPSILON: Float = 3.0E-4; +} + #[cfg(not(feature = "f64"))] -pub const EPSILON: Float = 3.0E-4; +pub use f32_stuff::*; +#[cfg(all(feature = "f64"))] +pub use f64_stuff::*; #[inline] pub fn power_heuristic(pdf_a: Float, pdf_b: Float) -> Float { From 4d69578781f7b62ab59416a1db6e096d2928e017 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Sat, 14 Jan 2023 16:08:20 +1100 Subject: [PATCH 04/39] Reimplemented MIS --- frontend/src/generate.rs | 66 +++- frontend/src/parameters.rs | 7 +- implementations/src/samplers/mod.rs | 16 +- implementations/statistics/src/bxdfs.rs | 24 +- .../statistics/src/distributions.rs | 2 +- implementations/statistics/src/integrators.rs | 2 +- implementations/statistics/src/lib.rs | 15 +- .../statistics/src/spherical_sampling.rs | 14 +- implementations/tests/sampling.rs | 278 +++++++++++++++ rt_core/src/ray.rs | 333 ++++++------------ 10 files changed, 509 insertions(+), 248 deletions(-) diff --git a/frontend/src/generate.rs b/frontend/src/generate.rs index 3d44a94..3331553 100644 --- a/frontend/src/generate.rs +++ b/frontend/src/generate.rs @@ -1,6 +1,7 @@ use crate::{scene::Scene, utility::create_bvh_with_info, *}; use implementations::{ random_sampler::RandomSampler, split::SplitType, AllMaterials, AllPrimitives, AllTextures, Bvh, + Lambertian, }; use rand::{distributions::Alphanumeric, rngs::SmallRng, thread_rng, Rng, SeedableRng}; use rand_seeder::Seeder; @@ -40,7 +41,7 @@ pub fn classic(bvh_type: SplitType, aspect_ratio: Float, seed: Option) - None => get_seed(32), }; - println!("\tseed: {}", seed); + println!("\tseed: {seed}"); let mut rng: SmallRng = Seeder::from(seed).make_rng(); for a in -11..11 { @@ -90,6 +91,69 @@ pub fn classic(bvh_type: SplitType, aspect_ratio: Float, seed: Option) - scene!(camera, sky, random_sampler!(), bvh) } +pub fn bxdf_testing(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { + let mut primitives = Vec::new(); + + let testing_mat = &std::sync::Arc::new(implementations::AllMaterials::Lambertian( + Lambertian::new(&solid_colour!(0.93, 0.62, 0.54), 0.5), + )); + + primitives.extend(aarect!( + -500.0, + -500.0, + 500.0, + 500.0, + -40.0, + &axis!(Z), + &diffuse!(0.5, 0.5, 0.5, 0.5) + )); + + let glowy = sphere!( + 0, + -100.5, + 0, + 50, + &emit!(&solid_colour!(colour!(0, 1, 0)), 5.5) + ); + + let glowy_two = sphere!( + 0, + 0.0, + 300, + 50, + &emit!(&solid_colour!(colour!(1, 1, 1)), 10.5) + ); + + let materials = vec![(testing_mat.clone(), "default")]; + + primitives.extend(crate::load_model::load_model_with_materials( + "../res/dragon.obj", + &materials, + )); + + primitives.push(glowy); + primitives.push(glowy_two); + + let camera = camera!( + position!(700, -700, 700), + position!(0, 0, 0), + position!(0, 0, 1), + 34, + aspect_ratio, + 0, + 10 + ); + + let bvh = create_bvh_with_info(primitives, bvh_type); + + scene!( + camera, + sky!(&image!("../res/skymaps/lilienstein.webp")), + random_sampler!(), + bvh + ) +} + pub fn furnace(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { let mut primitives = Vec::new(); diff --git a/frontend/src/parameters.rs b/frontend/src/parameters.rs index ec9f34b..8e7c1fa 100644 --- a/frontend/src/parameters.rs +++ b/frontend/src/parameters.rs @@ -231,7 +231,7 @@ fn get_info(args: &[String], index: usize) { println!("Motion Blur: No"); } _ => { - println!("{} is not a valid scene index!", string); + println!("{string} is not a valid scene index!"); println!("Please specify a valid for scene!"); println!("Do -L or--list to view scenes or do -H or --help for more information."); process::exit(0); @@ -270,8 +270,11 @@ fn get_scene( "coffee" => { scene!(coffee, bvh_type, aspect_ratio) } + "bxdf_testing" => { + scene!(bxdf_testing, bvh_type, aspect_ratio) + } _ => { - println!("{} is not a valid scene index!", string); + println!("{string} is not a valid scene index!"); println!("Please specify a valid for scene!"); println!("Do -L or--list to view scenes or do -H or --help for more information."); process::exit(0); diff --git a/implementations/src/samplers/mod.rs b/implementations/src/samplers/mod.rs index 0ec2f71..869c370 100644 --- a/implementations/src/samplers/mod.rs +++ b/implementations/src/samplers/mod.rs @@ -8,7 +8,7 @@ pub mod random_sampler; pub struct Sky { texture: Arc, - pub distribution: Distribution2D, + pub distribution: Option, sampler_res: (usize, usize), } @@ -18,7 +18,11 @@ impl Sky { let values = generate_values(&*texture, sampler_res); - let distribution = Distribution2D::new(&values, sampler_res.0); + let distribution = if sampler_res.0 | sampler_res.1 != 0 { + Some(Distribution2D::new(&values, sampler_res.0)) + } else { + None + }; Sky { texture, @@ -45,15 +49,19 @@ impl NoHit for Sky { } let u = phi / (2.0 * PI); let v = theta / PI; - self.sampler_res.0 as Float * self.sampler_res.1 as Float * self.distribution.pdf(u, v) + self.sampler_res.0 as Float + * self.sampler_res.1 as Float + * self.distribution.as_ref().unwrap().pdf(u, v) / (sin_theta * TAU * PI) } fn can_sample(&self) -> bool { - true + self.sampler_res.0 | self.sampler_res.1 != 0 } fn sample(&self) -> Vec3 { let uv = self .distribution + .as_ref() + .unwrap() .sample(&mut SmallRng::from_rng(thread_rng()).unwrap()); let u = next_float(uv.0 as Float + random_float()) / self.sampler_res.0 as Float; diff --git a/implementations/statistics/src/bxdfs.rs b/implementations/statistics/src/bxdfs.rs index 666cdae..82d6c4a 100644 --- a/implementations/statistics/src/bxdfs.rs +++ b/implementations/statistics/src/bxdfs.rs @@ -7,7 +7,6 @@ pub mod trowbridge_reitz { let cos_theta = ((1.0 - r1) / (r1 * (alpha * alpha - 1.0) + 1.0)).sqrt(); let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); let phi_s = (TAU * r2).max(0.0).min(TAU); - Vec3::new(phi_s.cos() * sin_theta, phi_s.sin() * sin_theta, cos_theta).normalised() } @@ -46,15 +45,20 @@ pub mod trowbridge_reitz { } pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { - let h = (incoming + outgoing).normalised(); + let mut h = (incoming + outgoing).normalised(); + if h.dot(normal) < 0.0 { + h = -(incoming + outgoing).normalised(); + } let cos_theta = normal.dot(h); let d = { - // Why no positive charactaristic function? + if cos_theta <= 0.0 { + return 0.0; + } let a_sq = alpha * alpha; let tmp = cos_theta * cos_theta * (a_sq - 1.0) + 1.0; a_sq / (PI * tmp * tmp) }; - d * cos_theta.abs() / (4.0 * outgoing.dot(h).abs()) + d * h.dot(normal).abs() / (4.0 * outgoing.dot(h).abs()) } } @@ -83,4 +87,16 @@ pub mod test { let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); } + + /*#[test] + fn some_test() { + let mut rng = thread_rng(); + let incoming: Vec3 = generate_wi(&mut rng); + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| { + + }; + let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); + test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); + }*/ } diff --git a/implementations/statistics/src/distributions.rs b/implementations/statistics/src/distributions.rs index 903534c..5d22998 100644 --- a/implementations/statistics/src/distributions.rs +++ b/implementations/statistics/src/distributions.rs @@ -80,7 +80,7 @@ pub struct Distribution2D { impl Distribution2D { pub fn new(values: &[Float], width: usize) -> Self { - assert_eq!(values.len() % width, 0); + assert!(values.len() % width == 0 && !values.is_empty()); let mut y_values = Vec::new(); let mut x_distributions = Vec::new(); for vec_x in values.chunks_exact(width) { diff --git a/implementations/statistics/src/integrators.rs b/implementations/statistics/src/integrators.rs index dd4424f..d1dc9f4 100644 --- a/implementations/statistics/src/integrators.rs +++ b/implementations/statistics/src/integrators.rs @@ -19,7 +19,7 @@ where phi.cos() * theta.sin(), phi.sin() * theta.sin(), theta.cos(), - )) * theta.sin() + )) * theta.sin().abs() }, a, b, diff --git a/implementations/statistics/src/lib.rs b/implementations/statistics/src/lib.rs index ae604c6..da14110 100644 --- a/implementations/statistics/src/lib.rs +++ b/implementations/statistics/src/lib.rs @@ -9,7 +9,7 @@ pub mod spherical_sampling; pub mod utility { use rayon::prelude::*; - use std::ops::Add; + use std::ops::*; pub fn distribute_samples_over_threads(samples: u64, f: &F) -> Vec where @@ -37,7 +37,12 @@ pub mod utility { use super::Float; - pub fn recursively_binary_average(mut values: Vec) -> Float { + pub fn recursively_binary_average(mut values: Vec) -> T + where + ::Output: Mul, + T: Copy, + Vec: FromIterator<<::Output as Mul>::Output>, + { let mut len = values.len(); if len & (len - 1) != 0 && len != 0 { panic!("values.len() is not a power of 2"); @@ -50,7 +55,7 @@ pub mod utility { values = a .iter() .zip(b.iter()) - .map(|(&a, &b)| 0.5 * (a + b)) + .map(|(&a, &b)| (a + b) * 0.5) .collect(); } @@ -60,6 +65,8 @@ pub mod utility { #[cfg(test)] #[test] fn binary_average() { - assert!((recursively_binary_average(vec![1.0, 3.0, 7.0, 1.0]) - 3.0).abs() < 0.0000001); + assert!( + (recursively_binary_average::(vec![1.0, 3.0, 7.0, 1.0]) - 3.0).abs() < 0.0000001 + ); } } diff --git a/implementations/statistics/src/spherical_sampling.rs b/implementations/statistics/src/spherical_sampling.rs index 5e04ae5..2a18405 100644 --- a/implementations/statistics/src/spherical_sampling.rs +++ b/implementations/statistics/src/spherical_sampling.rs @@ -119,7 +119,11 @@ where .collect(); let sampled_values: Vec = (0..SAMPLE_LEN) .into_par_iter() - .map(|i| recursively_binary_average((0..BATCHES).map(|j| sampled_vecs[j][i]).collect())) + .map(|i| { + recursively_binary_average::( + (0..BATCHES).map(|j| sampled_vecs[j][i]).collect(), + ) + }) .collect(); let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); @@ -133,12 +137,8 @@ where #[cfg(test)] pub mod test { - use crate::{ - chi_squared::*, integrators::*, spherical_sampling::*, utility::*, Float, Vec3, PI, TAU, - }; - use rand::{rngs::ThreadRng, thread_rng, Rng}; - use rayon::prelude::*; - use rt_core::FRAC_PI_2; + use crate::{spherical_sampling::*, Float, Vec3, TAU}; + use rand::Rng; pub fn to_vec(sin_theta: Float, cos_theta: Float, phi: Float) -> Vec3 { Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) diff --git a/implementations/tests/sampling.rs b/implementations/tests/sampling.rs index 635a00a..7cfe2c5 100644 --- a/implementations/tests/sampling.rs +++ b/implementations/tests/sampling.rs @@ -19,3 +19,281 @@ fn sky_sampling() { let sample = |_: &mut ThreadRng| sky.sample(); test_spherical_pdf("lerp sky sampling", &pdf, &sample, false); } + +use implementations::sphere::Sphere; +use implementations::*; +use rayon::prelude::{IntoParallelIterator, ParallelIterator}; + +use std::sync::Arc; + +type MaterialType = AllMaterials; +type PrimitiveType = AllPrimitives; +type BvhType = Bvh; + +pub fn furnace_test(sampler_res: (usize, usize)) -> (Sky, BvhType) { + let light_tex = AllTextures::SolidColour(SolidColour::new(Vec3::new(1.0, 1.0, 1.0))); + let light_mat = Arc::new(AllMaterials::Emit(Emit::new(&Arc::new(light_tex), 1.0))); + + let mat = AllTextures::SolidColour(SolidColour::new(Vec3::new(0.5, 0.5, 0.5))); + + let mat = Arc::new(AllMaterials::Lambertian(Lambertian::new( + &Arc::new(mat), + 0.5, + ))); + + let hidden_light_tex = AllTextures::SolidColour(SolidColour::new(Vec3::new(1.0, 0.0, 1.0))); + let hidden_light_mat = Arc::new(AllMaterials::Emit(Emit::new( + &Arc::new(hidden_light_tex), + 15.0, + ))); + + let primitives = vec![ + AllPrimitives::Sphere(Sphere::new(Vec3::zero(), 0.5, &mat)), + AllPrimitives::Sphere(Sphere::new(Vec3::new(0.0, 0.0, 0.0), 1000.0, &light_mat)), + AllPrimitives::Sphere(Sphere::new( + Vec3::new(0.0, 0.0, -5.0), + 0.45, + &hidden_light_mat, + )), // hidden light from above + ]; + + let sky_tex = AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::new(0.5, 1.0, 0.2))); + + let sky = implementations::Sky::new(&std::sync::Arc::new(sky_tex), sampler_res); + + (sky, Bvh::new(primitives, split::SplitType::Sah)) +} + +pub fn get_test_scene(m: MaterialType, sampler_res: (usize, usize)) -> (Sky, BvhType) { + let light_tex = AllTextures::SolidColour(SolidColour::new(Vec3::new(1.0, 0.0, 0.5))); + let _light_mat = Arc::new(AllMaterials::Emit(Emit::new(&Arc::new(light_tex), 5.0))); + + let mat = Arc::new(m); + + let primitives = vec![ + AllPrimitives::Triangle(Triangle::new( + [ + Vec3::new(-0.5, -0.5, 0.0), + Vec3::new(0.0, 0.5, 0.0), + Vec3::new(0.5, -0.5, 0.0), + ], + [Vec3::new(0.0, 0.0, 1.0); 3], + &mat, + )), + //AllPrimitives::Sphere(Sphere::new(Vec3::new(0.0, 3.0, 1.0), 0.3, &light_mat)), + //AllPrimitives::Sphere(Sphere::new(Vec3::new(0.0, 2.9, 0.9), 0.2, &mat)), + //AllPrimitives::Sphere(Sphere::new(Vec3::new(0.0, 0.0, 0.0), 1000.0, &light_mat)), + ]; + + //let sky_tex = AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one())); + let sky_tex = AllTextures::SolidColour(implementations::SolidColour::new(rt_core::Vec3::new( + 1.0, 1.0, 1.0, //5.0, 0.0, 2.5, + ))); //rt_core::Vec3::zero())); + let sky = implementations::Sky::new(&std::sync::Arc::new(sky_tex), sampler_res); //Arc::new(sky_tex), sampler_res); + + (sky, Bvh::new(primitives, split::SplitType::Sah)) +} + +pub fn bxdf_testing(sampler_res: (usize, usize)) -> (Sky, BvhType) { + let mut primitives = Vec::new(); + + let triangle = |point_one, point_two, point_three, material| { + let normal = { + let a: Vec3 = point_two - point_one; + let b = point_three - point_one; + a.cross(b) + } + .normalised(); + + AllPrimitives::Triangle(Triangle::new( + [point_one, point_two, point_three], + [normal; 3], + material, + )) + }; + + let aarect = |point_one: &Vec2, point_two: &Vec2, axis_value: Float, axis: &Axis, material| { + let point_three = + Axis::point_from_2d(&Vec2::new(point_one.x, point_two.y), axis, axis_value); + let point_four = + Axis::point_from_2d(&Vec2::new(point_two.x, point_one.y), axis, axis_value); + let point_one = Axis::point_from_2d(point_one, axis, axis_value); + let point_two = Axis::point_from_2d(point_two, axis, axis_value); + vec![ + triangle(point_one, point_two, point_three, material), + triangle(point_one, point_two, point_four, material), + ] + }; + + let sphere = + |position, radius, material| AllPrimitives::Sphere(Sphere::new(position, radius, material)); + + let grey_tex = AllTextures::SolidColour(SolidColour::new(Vec3::new(0.5, 0.5, 0.5))); + + let diffuse = AllMaterials::Lambertian(Lambertian::new(&Arc::new(grey_tex), 0.5)); + + primitives.extend(aarect( + &Vec2::new(-500.0, -500.0), + &Vec2::new(500.0, 500.0), + -40.0, + &Axis::Z, + &Arc::new(diffuse), + )); + + let mat = Arc::new(AllMaterials::Emit(Emit::new( + &Arc::new(AllTextures::SolidColour(SolidColour::new(Vec3::new( + 0.0, 1.0, 0.0, + )))), + 5.5, + ))); + + let glowy = sphere(Vec3::new(0.0, -100.5, 0.0), 50.0, &mat); + + let glowy_two = sphere( + Vec3::new(0.0, 0.0, 300.0), + 50.0, + &Arc::new(AllMaterials::Emit(Emit::new( + &Arc::new(AllTextures::SolidColour(SolidColour::new(Vec3::new( + 1.0, 1.0, 1.0, + )))), + 10.5, + ))), + ); + + //let materials = vec![(testing_mat.clone(), "default")]; + + /*primitives.extend(crate::load_model::load_model_with_materials( + "../res/dragon.obj", + &materials, + ));*/ + + primitives.push(glowy); + primitives.push(glowy_two); + + let image = |filepath| Arc::new(AllTextures::ImageTexture(ImageTexture::new(filepath))); + + let bvh = Bvh::new(primitives, split::SplitType::Sah); //create_bvh_with_info(primitives, bvh_type); + + ( + Sky::new(&image("../res/skymaps/lilienstein.webp"), sampler_res), + bvh, + ) +} + +#[test] +fn mis() { + let ray = Ray::new(Vec3::new(0.0, 0.0, 3.0), Vec3::new(0.0, 0.0, -1.0), 0.0); + + let (sky, bvh) = bxdf_testing((0, 0)); + + let samples = 2u64.pow(23); //5_000_000; + let mut ref_vals = Vec::new(); + for _ in 0..samples { + ref_vals.push(Ray::get_colour_naive(&mut ray.clone(), &sky, &bvh).0) + } + + let ref_val = statistics::utility::recursively_binary_average(ref_vals); + + println!("naive_sampling: {ref_val}"); + + let mut vals = Vec::new(); + for _ in 0..samples { + vals.push(Ray::get_colour(&mut ray.clone(), &sky, &bvh).0) + } + + let val = statistics::utility::recursively_binary_average(vals); + + println!("mis_sampling: {val}"); + + assert!((ref_val - val).mag() < 0.001) +} + +#[test] +fn mis_sky_sampling() { + let ray = Ray::new(Vec3::new(0.0, 0.0, 3.0), Vec3::new(0.0, 0.0, -1.0), 0.0); + + let (sky, bvh) = bxdf_testing((20, 10)); + + let samples = 5_000_000; + + let ref_val = (0..samples) + .into_par_iter() + .map(|_| Ray::get_colour_naive(&mut ray.clone(), &sky, &bvh).0) + .reduce_with(std::ops::Add::add) + .unwrap() + / samples as Float; + + let mut sum = Vec3::zero(); + for _ in 0..500 { + sum += (0..100_000) + .into_par_iter() + .map(|_| Ray::get_colour(&mut ray.clone(), &sky, &bvh).0) + .reduce_with(std::ops::Add::add) + .unwrap() / 100_000.0 as Float; + } + let val = sum / 500.0; + + println!("sky_sampling: {val}"); + + assert!((ref_val - val).mag() < 0.001) +} + +#[test] +fn naive_furnace_test() { + let ray = Ray::new(Vec3::new(0.0, 0.0, 3.0), Vec3::new(0.0, 0.0, -1.0), 0.0); + + let (sky, bvh) = furnace_test((0, 0)); + + let samples = 5_000_000; + + let ref_val = Vec3::new(0.25, 0.25, 0.25); + + let val = (0..samples) + .into_par_iter() + .map(|_| Ray::get_colour_naive(&mut ray.clone(), &sky, &bvh).0) + .reduce_with(std::ops::Add::add) + .unwrap() + / samples as Float; + + assert!((ref_val - val).mag() < 0.001) +} + +#[test] +fn mis_furnace_test() { + let ray = Ray::new(Vec3::new(0.0, 0.0, 3.0), Vec3::new(0.0, 0.0, -1.0), 0.0); + + let (sky, bvh) = furnace_test((0, 0)); + + let samples = 5_000_000; + + let ref_val = Vec3::new(0.25, 0.25, 0.25); + + let val = (0..samples) + .into_par_iter() + .map(|_| Ray::get_colour(&mut ray.clone(), &sky, &bvh).0) + .reduce_with(std::ops::Add::add) + .unwrap() + / samples as Float; + + assert!((ref_val - val).mag() < 0.001) +} + +#[test] +fn mis_sky_sampling_furnace_test() { + let ray = Ray::new(Vec3::new(0.0, 0.0, 3.0), Vec3::new(0.0, 0.0, -1.0), 0.0); + + let (sky, bvh) = furnace_test((10, 10)); + + let samples = 5_000_000; + + let ref_val = Vec3::new(0.25, 0.25, 0.25); + + let val = (0..samples) + .into_par_iter() + .map(|_| Ray::get_colour(&mut ray.clone(), &sky, &bvh).0) + .reduce_with(std::ops::Add::add) + .unwrap() + / samples as Float; + + assert!((ref_val - val).mag() < 0.001) +} diff --git a/rt_core/src/ray.rs b/rt_core/src/ray.rs index 20aeb22..eafb99b 100644 --- a/rt_core/src/ray.rs +++ b/rt_core/src/ray.rs @@ -1,7 +1,4 @@ -use crate::{ - power_heuristic, AccelerationStructure, Float, Hit, NoHit, Primitive, Scatter, - SurfaceIntersection, Vec3, -}; +use crate::{power_heuristic, AccelerationStructure, Float, Hit, NoHit, Primitive, Scatter, Vec3}; use rand::{prelude::SliceRandom, rngs::SmallRng, thread_rng, Rng, SeedableRng}; const RUSSIAN_ROULETTE_THRESHOLD: u32 = 3; @@ -9,6 +6,7 @@ const MAX_DEPTH: u32 = 50; pub type Colour = Vec3; +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Ray { pub origin: Vec3, pub direction: Vec3, @@ -57,181 +55,66 @@ impl Ray { self.origin + self.direction * t } - fn sample_light, P: Primitive, M: Scatter, S: NoHit>( + fn sample_lights_test, P: Primitive, M: Scatter, S: NoHit>( bvh: &A, hit: &Hit, - sky: &S, - mat: &M, - wo: Vec3, - ) -> Vec3 { - let sample_lights = |num_lights: usize| { - let light_index = match bvh - .get_samplable() - .choose(&mut SmallRng::from_rng(thread_rng()).unwrap()) - { - Some(&index) => index, - None => return Vec3::zero(), - }; - - let samplable = bvh.get_object(light_index).unwrap(); - - let sampled_wi = samplable.sample_visible_from_point(hit.point); - - if let Some(sampled_si) = bvh.check_hit_index( - &Ray::new(hit.point + 0.0001 * hit.normal, sampled_wi, 0.0), - light_index, - ) { - let sampled_hit = &sampled_si.hit; - - let sampled_pdf = samplable.scattering_pdf(hit.point, sampled_wi, sampled_hit); - - if sampled_pdf > 0.0 { - let li = sampled_si.material.get_emission(sampled_hit, sampled_wi); - - let f = mat.eval(hit, wo, sampled_wi); - - let num_lights = 2.0 * num_lights as Float; - - let scattering_pdf = mat.scattering_pdf(hit, wo, sampled_wi); - - let weight = power_heuristic(sampled_pdf, scattering_pdf); - - li * f * num_lights * weight / sampled_pdf - } else { - Vec3::zero() - } - } else { - Vec3::zero() - } + _sky: &S, + _mat: &M, + _wo: Vec3, + ) -> Option<(Vec3, Vec3, Float)> { + //l_pos, le, l_pdf + let light_index = match bvh + .get_samplable() + .choose(&mut SmallRng::from_rng(thread_rng()).unwrap()) + { + Some(&index) => index, + None => return None, }; - if sky.can_sample() { - if SmallRng::from_rng(thread_rng()).unwrap().gen() { - let sampled_wi = sky.sample(); - - if bvh - .check_hit(&Ray::new(hit.point + 0.0001 * hit.normal, sampled_wi, 0.0)) - .is_none() - { - let sampled_pdf = sky.pdf(sampled_wi); - - if sampled_pdf > 0.0 { - let ray = Ray::new(Vec3::zero(), sampled_wi, 0.0); - let li = sky.get_colour(&ray); - - let f = mat.eval(hit, wo, sampled_wi); - - let scattering_pdf = mat.scattering_pdf(hit, wo, sampled_wi); - - let weight = power_heuristic(sampled_pdf, scattering_pdf); - - return li * f * 2.0 * weight / sampled_pdf; - } - } - Vec3::zero() - } else { - sample_lights(bvh.get_samplable().len()) - } - } else { - panic!() - } - } - - fn sample_light_mis, P: Primitive, M: Scatter>( - bvh: &A, - hit: &Hit, - mat: &M, - wo: Vec3, - index: usize, - wi: Vec3, - new_si: &SurfaceIntersection, - ) -> Vec3 { - let mut output = Vec3::zero(); - let samplable = bvh.get_object(index).unwrap(); + let samplable = bvh.get_object(light_index).unwrap(); let sampled_wi = samplable.sample_visible_from_point(hit.point); if let Some(sampled_si) = bvh.check_hit_index( &Ray::new(hit.point + 0.0001 * hit.normal, sampled_wi, 0.0), - index, + light_index, ) { let sampled_hit = &sampled_si.hit; let sampled_pdf = samplable.scattering_pdf(hit.point, sampled_wi, sampled_hit); if sampled_pdf > 0.0 { - let li = new_si.material.get_emission(sampled_hit, sampled_wi); - - let f = mat.eval(hit, wo, sampled_wi); + let li = sampled_si.material.get_emission(sampled_hit, sampled_wi); - let scattering_pdf = mat.scattering_pdf(hit, wo, sampled_wi); + let num_lights = bvh.get_samplable().len() as Float; - let weight = power_heuristic(sampled_pdf, scattering_pdf); - - output += li * f * weight / sampled_pdf; + Some((sampled_si.hit.point, li, sampled_pdf / num_lights)) + } else { + None } + } else { + None } - - let scattering_pdf = mat.scattering_pdf(hit, wo, wi); - let li = new_si.material.get_emission(&new_si.hit, wi); - - let sampling_pdf = samplable.scattering_pdf(hit.point, wi, &new_si.hit); - - let weight = power_heuristic(scattering_pdf, sampling_pdf); - - let fp = mat.eval_over_scattering_pdf(hit, wo, wi); - - output += li * fp * weight; - - output } - fn sample_sky_mis, P: Primitive, M: Scatter, S: NoHit>( - bvh: &A, + fn sample_material_test< + A: AccelerationStructure, + P: Primitive, + M: Scatter, + S: NoHit, + >( + _bvh: &A, hit: &Hit, + _sky: &S, mat: &M, - wo: Vec3, - sky: &S, - wi: Vec3, - ) -> Vec3 { - let mut output = Vec3::zero(); - - let sampled_wi = sky.sample(); - - if bvh - .check_hit(&Ray::new(hit.point + 0.0001 * hit.normal, sampled_wi, 0.0)) - .is_none() - { - let sampled_pdf = sky.pdf(sampled_wi); - - if sampled_pdf > 0.0 { - let ray = Ray::new(Vec3::zero(), sampled_wi, 0.0); - let li = sky.get_colour(&ray); - - let f = mat.eval(hit, wo, sampled_wi); - - let scattering_pdf = mat.scattering_pdf(hit, wo, sampled_wi); - - let weight = power_heuristic(sampled_pdf, scattering_pdf); - - output += li * f * weight / sampled_pdf; - } + ray: &mut Ray, + ) -> Option<(Vec3, Float)> { + let wo = ray.direction; + if mat.scatter_ray(ray, hit) { + None + } else { + Some((ray.direction, mat.scattering_pdf(hit, wo, ray.direction))) } - - let scattering_pdf = mat.scattering_pdf(hit, wo, wi); - - let ray = Ray::new(Vec3::zero(), wi, 0.0); - let li = sky.get_colour(&ray); - - let sampling_pdf = sky.pdf(wi); - - let weight = power_heuristic(scattering_pdf, sampling_pdf); - - let fp = mat.eval_over_scattering_pdf(hit, wo, wi); - - output += li * fp * weight; - - output } pub fn get_colour, P: Primitive, M: Scatter, S: NoHit>( @@ -239,84 +122,86 @@ impl Ray { sky: &S, bvh: &A, ) -> (Colour, u64) { - let mut output; - let mut throughput = Colour::one(); - let mut ray_count = 1; + let (mut throughput, mut output) = (Colour::one(), Colour::zero()); + let mut wo; + let mut hit; + let mut mat; + let mut ray_count = 0; + // depth 0 if let Some((surface_intersection, _index)) = bvh.check_hit(ray) { - let (mut hit, mut mat) = (surface_intersection.hit, surface_intersection.material); - let mut wo = ray.direction; - output = mat.get_emission(&hit, wo); + (hit, mat) = (surface_intersection.hit, surface_intersection.material); - let mut exit = mat.scatter_ray(ray, &hit); + wo = ray.direction; - if !exit { - for depth in 1..MAX_DEPTH { - if exit { - ray_count += depth as u64; - break; - } + let emission = mat.get_emission(&hit, wo); - let wi = ray.direction; - - if let Some((new_si, new_index)) = bvh.check_hit(ray) { - if mat.is_delta() { - throughput *= mat.eval(&hit, wo, wi); - output += throughput * new_si.material.get_emission(&hit, wo); - } else if bvh.get_samplable().contains(&new_index) { - output += throughput - * Self::sample_light_mis( - bvh, &hit, &mat, wo, new_index, wi, &new_si, - ); - ray_count += 1; - throughput *= mat.eval_over_scattering_pdf(&hit, wo, wi); - } else { - output += throughput * Self::sample_light(bvh, &hit, sky, &mat, wo); - ray_count += 1; - throughput *= mat.eval_over_scattering_pdf(&hit, wo, wi); - } - - mat = new_si.material; - hit = new_si.hit; - wo = wi; - } else { - if mat.is_delta() { - throughput *= mat.eval(&hit, wo, wi); - output += throughput * sky.get_colour(ray); - } else if sky.can_sample() { - output += - throughput * Self::sample_sky_mis(bvh, &hit, &mat, wo, sky, wi); - ray_count += 1; - throughput *= mat.eval_over_scattering_pdf(&hit, wo, wi); - } else { - output += throughput * Self::sample_light(bvh, &hit, sky, &mat, wo); - ray_count += 1; - throughput *= mat.eval_over_scattering_pdf(&hit, wo, wi); - - output += throughput * sky.get_colour(ray); - } - - ray_count += depth as u64; - break; - } + let exit = mat.scatter_ray(ray, &hit); - if depth > RUSSIAN_ROULETTE_THRESHOLD { - let p = throughput.component_max(); - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - if rng.gen::() > p { - ray_count += depth as u64; - break; - } - throughput /= p; - } + output += emission; - exit = mat.scatter_ray(ray, &hit); - } - } else { - output = mat.get_emission(&hit, wo); + if exit { + return (output, ray_count); } } else { - output = sky.get_colour(ray); + output += sky.get_colour(ray); + return (output, ray_count); + } + + let mut depth = 1; + while depth < MAX_DEPTH { + // light sampling + if let Some((l_pos, le, l_pdf)) = Ray::sample_lights_test(bvh, &hit, sky, &mat, wo) { + let l_wi = (l_pos - hit.point).normalised(); + let m_pdf = mat.scattering_pdf(&hit, wo, l_wi); + let mis_weight = power_heuristic(l_pdf, m_pdf); + + output += throughput * mat.eval(&hit, wo, l_wi) * mis_weight * le / l_pdf; + } + ray_count += 1; + + // material sample and bounce + let (m_wi, m_pdf) = match Ray::sample_material_test(bvh, &hit, sky, &mat, ray) { + Some((m_wi, m_pdf)) => (m_wi, m_pdf), + None => break, + }; + + throughput *= mat.eval(&hit, wo, m_wi) / mat.scattering_pdf(&hit, wo, m_wi); + + let (surface_intersection, index) = match bvh.check_hit(ray) { + Some((surface_intersection, index)) => (surface_intersection, index), + None => { + output += throughput * sky.get_colour(ray); + break; + } // no sky sampling support yet + }; + + if surface_intersection.material.get_emission(&hit, wo) != Vec3::zero() { + let light_pdf = if bvh.get_samplable().contains(&index) { + bvh.get_object(index).unwrap().scattering_pdf( + hit.point, + m_wi, + &surface_intersection.hit, + ) / bvh.get_samplable().len() as Float + } else { + 0.0 + }; + let mis_weight = power_heuristic(m_pdf, light_pdf); + let le = surface_intersection.material.get_emission(&hit, wo); + output += throughput * mis_weight * le; + if surface_intersection.material.is_light() { + break; + } + } + + hit = surface_intersection.hit; + mat = surface_intersection.material; + wo = m_wi; + + depth += 1; + } + if output.contains_nan() || !output.is_finite() { + return (Vec3::zero(), ray_count); } (output, ray_count) } From 0d1aa19fab5df17ec9f28781b383a625ae3f3b43 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Sun, 15 Jan 2023 21:14:15 +1100 Subject: [PATCH 05/39] dependency refactor --- frontend/Cargo.toml | 8 +- frontend/scene/Cargo.toml | 16 + frontend/{src/scene.rs => scene/src/lib.rs} | 2 +- frontend/src/generate.rs | 8 +- frontend/src/gui.rs | 4 +- frontend/src/load_model.rs | 4 +- frontend/src/macros.rs | 286 +++++++++++------- frontend/src/main.rs | 4 +- frontend/src/parameters.rs | 18 +- frontend/src/utility.rs | 41 ++- implementations/Cargo.toml | 3 +- implementations/src/acceleration/aabb.rs | 3 +- implementations/src/acceleration/mod.rs | 2 +- implementations/src/acceleration/split.rs | 3 +- implementations/src/cameras/mod.rs | 3 +- implementations/src/lib.rs | 2 + implementations/src/materials/emissive.rs | 3 +- implementations/src/materials/lambertian.rs | 2 +- implementations/src/materials/mod.rs | 2 +- implementations/src/materials/phong.rs | 2 +- implementations/src/materials/reflect.rs | 2 +- implementations/src/materials/refract.rs | 2 +- .../src/materials/trowbridge_reitz.rs | 2 +- implementations/src/primitives/mod.rs | 2 +- implementations/src/primitives/sphere.rs | 2 +- implementations/src/primitives/triangle.rs | 26 +- implementations/src/samplers/mod.rs | 3 +- .../src/samplers/random_sampler.rs | 20 +- implementations/src/textures/mod.rs | 2 +- implementations/src/utility/coord.rs | 2 +- implementations/src/utility/mod.rs | 2 +- implementations/statistics/src/lib.rs | 4 +- implementations/tests/common/mod.rs | 214 ------------- implementations/tests/sampling.rs | 2 - 34 files changed, 287 insertions(+), 414 deletions(-) create mode 100644 frontend/scene/Cargo.toml rename frontend/{src/scene.rs => scene/src/lib.rs} (91%) delete mode 100644 implementations/tests/common/mod.rs diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 43b89e1..a584ca0 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -8,16 +8,16 @@ edition = "2021" [dependencies] chrono = "0.4.19" image = "0.23.14" -implementations = {path = "../implementations"} +implementations = { path = "../implementations" } rand = { version = "0.8.3", features = [ "small_rng" ] } rand_seeder = "0.2.2" -rt_core = { path = "../rt_core" } +scene = { path = "./scene" } vulkano = { version = "0.28.0", optional = true } -vulkano-win = { version = "0.28.0", optional = true } vulkano-shaders = { version = "0.28.0", optional = true } +vulkano-win = { version = "0.28.0", optional = true } wavefront_obj = "10.0.0" winit = { version = "0.26.1", optional = true } [features] -f64 = ["implementations/f64", "rt_core/f64"] +f64 = ["implementations/f64"] gui = ["dep:vulkano", "dep:vulkano-win", "dep:vulkano-shaders", "dep:winit"] \ No newline at end of file diff --git a/frontend/scene/Cargo.toml b/frontend/scene/Cargo.toml new file mode 100644 index 0000000..2f943a6 --- /dev/null +++ b/frontend/scene/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "scene" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +image = "0.23.14" +implementations = {path = "../../implementations"} +rt_core = { path = "../../rt_core" } +toml = "0.5.10" +wavefront_obj = "10.0.0" + +[features] +f64 = ["implementations/f64", "rt_core/f64"] \ No newline at end of file diff --git a/frontend/src/scene.rs b/frontend/scene/src/lib.rs similarity index 91% rename from frontend/src/scene.rs rename to frontend/scene/src/lib.rs index 35d5793..ae4ba74 100644 --- a/frontend/src/scene.rs +++ b/frontend/scene/src/lib.rs @@ -1,5 +1,5 @@ use implementations::{SimpleCamera, Sky, Texture}; -use rt_core::{AccelerationStructure, Primitive, RenderOptions, Sampler, SamplerProgress, Scatter}; +use rt_core::*; use std::{marker::PhantomData, sync::Arc}; pub struct Scene< diff --git a/frontend/src/generate.rs b/frontend/src/generate.rs index 3331553..8625854 100644 --- a/frontend/src/generate.rs +++ b/frontend/src/generate.rs @@ -1,11 +1,11 @@ -use crate::{scene::Scene, utility::create_bvh_with_info, *}; +use crate::{utility::create_bvh_with_info, *}; use implementations::{ - random_sampler::RandomSampler, split::SplitType, AllMaterials, AllPrimitives, AllTextures, Bvh, - Lambertian, + random_sampler::RandomSampler, rt_core::Float, split::SplitType, AllMaterials, AllPrimitives, + AllTextures, Bvh, Lambertian, }; use rand::{distributions::Alphanumeric, rngs::SmallRng, thread_rng, Rng, SeedableRng}; use rand_seeder::Seeder; -use rt_core::Float; +use scene::*; type MaterialType = AllMaterials; type PrimitiveType = AllPrimitives; diff --git a/frontend/src/gui.rs b/frontend/src/gui.rs index 0d8473d..20871ac 100644 --- a/frontend/src/gui.rs +++ b/frontend/src/gui.rs @@ -139,9 +139,7 @@ impl Gui { let cpu_rendering = CpuRendering::new(&physical_device, device.clone(), width, height); mod cs { - vulkano_shaders::shader! { - ty: "compute", - src: + vulkano_shaders::shader! {ty: "compute",src: "#version 460 layout(local_size_x = 32, local_size_y = 32) in; diff --git a/frontend/src/load_model.rs b/frontend/src/load_model.rs index 82e8985..4c42078 100644 --- a/frontend/src/load_model.rs +++ b/frontend/src/load_model.rs @@ -1,8 +1,8 @@ use implementations::{ + rt_core::*, triangle::{MeshData, MeshTriangle}, AllMaterials, AllPrimitives, AllTextures, }; -use rt_core::{Float, Vec3}; use std::sync::Arc; pub fn load_model_with_materials( @@ -75,7 +75,7 @@ fn get_material( if let Some(mat) = mat { return mat.0.clone(); } - panic!("{} material not found", name); + panic!("{name} material not found"); } } } diff --git a/frontend/src/macros.rs b/frontend/src/macros.rs index d3c2f85..c36811d 100644 --- a/frontend/src/macros.rs +++ b/frontend/src/macros.rs @@ -4,31 +4,34 @@ #[macro_export] macro_rules! position { ($x:expr, $y:expr, $z:expr) => { - rt_core::vec::Vec3::new( - $x as rt_core::Float, - $y as rt_core::Float, - $z as rt_core::Float, + implementations::rt_core::vec::Vec3::new( + $x as implementations::rt_core::Float, + $y as implementations::rt_core::Float, + $z as implementations::rt_core::Float, ) }; ($x:expr, $y:expr) => { - rt_core::vec::Vec2::new($x as rt_core::Float, $y as rt_core::Float) + implementations::rt_core::vec::Vec2::new( + $x as implementations::rt_core::Float, + $y as implementations::rt_core::Float, + ) }; } #[macro_export] macro_rules! colour { ($r:expr,$g:expr,$b:expr) => { - rt_core::Vec3::new( - $r as rt_core::Float, - $g as rt_core::Float, - $b as rt_core::Float, + implementations::rt_core::Vec3::new( + $r as implementations::rt_core::Float, + $g as implementations::rt_core::Float, + $b as implementations::rt_core::Float, ) }; ($value:expr) => { - rt_core::Vec3::new( - $value as rt_core::Float, - $value as rt_core::Float, - $value as rt_core::Float, + implementations::rt_core::Vec3::new( + $value as implementations::rt_core::Float, + $value as implementations::rt_core::Float, + $value as implementations::rt_core::Float, ) }; } @@ -36,17 +39,17 @@ macro_rules! colour { #[macro_export] macro_rules! rotation { ($x:expr, $y:expr, $z:expr, D) => { - rt_core::vec::Vec3::new( - $x as rt_core::Float * rt_core::PI / 180.0, - $y as rt_core::Float * rt_core::PI / 180.0, - $z as rt_core::Float * rt_core::PI / 180.0, + implementations::rt_core::vec::Vec3::new( + $x as implementations::rt_core::Float * implementations::rt_core::PI / 180.0, + $y as implementations::rt_core::Float * implementations::rt_core::PI / 180.0, + $z as implementations::rt_core::Float * implementations::rt_core::PI / 180.0, ) }; ($x:expr, $y:expr, $z:expr, R) => { - rt_core::vec::Vec3::new( - $x as rt_core::Float, - $y as rt_core::Float, - $z as rt_core::Float, + implementations::rt_core::vec::Vec3::new( + $x as implementations::rt_core::Float, + $y as implementations::rt_core::Float, + $z as implementations::rt_core::Float, ) }; } @@ -94,12 +97,15 @@ macro_rules! image { macro_rules! checkered { ($colour_one:expr, $colour_two:expr) => { std::sync::Arc::new(implementations::AllTextures::CheckeredTexture( - rt_core::CheckeredTexture::new($colour_one, $colour_two), + implementations::rt_core::CheckeredTexture::new($colour_one, $colour_two), )) }; ($r1:expr, $g1:expr, $b1:expr, $r2:expr, $g2:expr, $b2:expr) => { std::sync::Arc::new(implementations::AllTextures::CheckeredTexture( - rt_core::CheckeredTexture::new(colour!($r1, $g1, $b1), colour!($r2, $g2, $b2)), + implementations::rt_core::CheckeredTexture::new( + colour!($r1, $g1, $b1), + colour!($r2, $g2, $b2), + ), )) }; } @@ -112,10 +118,9 @@ macro_rules! texture_lerp { )) }; ($r1:expr, $g1:expr, $b1:expr, $r2:expr, $g2:expr, $b2:expr) => { - std::sync::Arc::new(implementations::AllTextures::Lerp(rt_core::Lerp::new( - colour!($r1, $g1, $b1), - colour!($r2, $g2, $b2), - ))) + std::sync::Arc::new(implementations::AllTextures::Lerp( + implementations::rt_core::Lerp::new(colour!($r1, $g1, $b1), colour!($r2, $g2, $b2)), + )) }; } @@ -123,7 +128,7 @@ macro_rules! texture_lerp { macro_rules! perlin { () => { std::sync::Arc::new(implementations::AllTextures::Perlin(Box::new( - rt_core::Perlin::new(), + implementations::rt_core::Perlin::new(), ))) }; } @@ -139,13 +144,16 @@ macro_rules! diffuse { &std::sync::Arc::new(implementations::AllTextures::SolidColour( implementations::SolidColour::new(colour!($r, $g, $b)), )), - $absorption as rt_core::Float, + $absorption as implementations::rt_core::Float, ), )) }; ($texture:expr,$absorption:expr) => { std::sync::Arc::new(implementations::AllMaterials::Lambertian( - implementations::Lambertian::new($texture, $absorption as rt_core::Float), + implementations::Lambertian::new( + $texture, + $absorption as implementations::rt_core::Float, + ), )) }; } @@ -154,17 +162,17 @@ macro_rules! diffuse { macro_rules! reflect { ($r:expr,$g:expr,$b:expr, $fuzz:expr) => { std::sync::Arc::new(implementations::AllMaterials::Reflect( - rt_core::Reflect::new( + implementations::rt_core::Reflect::new( &Arc::new(implementations::AllTextures::SolidColour( - rt_core::SolidColour::new(colour!($r, $g, $b)), + implementations::rt_core::SolidColour::new(colour!($r, $g, $b)), )), - $fuzz as rt_core::Float, + $fuzz as implementations::rt_core::Float, ), )); }; ($texture:expr,$fuzz:expr) => { std::sync::Arc::new(implementations::AllMaterials::Reflect( - implementations::Reflect::new($texture, $fuzz as rt_core::Float), + implementations::Reflect::new($texture, $fuzz as implementations::rt_core::Float), )) }; } @@ -173,17 +181,17 @@ macro_rules! reflect { macro_rules! refract { ($r:expr,$g:expr,$b:expr, $eta:expr) => { std::sync::Arc::new(implementations::AllMaterials::Refract( - rt_core::Refract::new( + implementations::rt_core::Refract::new( &std::sync::Arc::new(implementations::AllTextures::SolidColour( - rt_core::SolidColour::new(colour!($r, $g, $b)), + implementations::rt_core::SolidColour::new(colour!($r, $g, $b)), )), - $eta as rt_core::Float, + $eta as implementations::rt_core::Float, ), )) }; ($texture:expr,$eta:expr) => { std::sync::Arc::new(implementations::AllMaterials::Refract( - implementations::Refract::new($texture, $eta as rt_core::Float), + implementations::Refract::new($texture, $eta as implementations::rt_core::Float), )) }; } @@ -191,14 +199,16 @@ macro_rules! refract { #[macro_export] macro_rules! emit { ($r:expr,$g:expr,$b:expr, $strength:expr) => { - std::sync::Arc::new(implementations::AllMaterials::Emit(rt_core::Emit::new( - &std::sync::Arc::new(Texture::SolidColour(SolidColour::new(colour!($r, $g, $b)))), - $strength as rt_core::Float, - ))); + std::sync::Arc::new(implementations::AllMaterials::Emit( + implementations::rt_core::Emit::new( + &std::sync::Arc::new(Texture::SolidColour(SolidColour::new(colour!($r, $g, $b)))), + $strength as implementations::rt_core::Float, + ), + )); }; ($texture:expr,$strength:expr) => { std::sync::Arc::new(implementations::AllMaterials::Emit( - implementations::Emit::new($texture, $strength as rt_core::Float), + implementations::Emit::new($texture, $strength as implementations::rt_core::Float), )) }; } @@ -218,7 +228,7 @@ macro_rules! sphere { ($position:expr, $radius:expr, $material:expr) => { implementations::AllPrimitives::Sphere(implementations::sphere::Sphere::new( $position, - $radius as rt_core::Float, + $radius as implementations::rt_core::Float, $material, )) }; @@ -228,12 +238,12 @@ macro_rules! sphere { macro_rules! aarect { ($point_one:expr, $point_two:expr, $axis_value:expr, $axis:expr, $material:expr) => {{ let point_three = implementations::Axis::point_from_2d( - rt_core::Vec2::new($point_one.x, $point_two.y), + implementations::rt_core::Vec2::new($point_one.x, $point_two.y), $axis, $axis_value, ); let point_four = implementations::Axis::point_from_2d( - rt_core::Vec2::new($point_two.x, $point_one.y), + implementations::rt_core::Vec2::new($point_two.x, $point_one.y), $axis, $axis_value, ); @@ -246,14 +256,26 @@ macro_rules! aarect { vec }}; ($x1:expr, $y1:expr, $x2:expr, $y2:expr, $axis_value:expr, $axis:expr, $material:expr, $cp:expr) => {{ - let point_three = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x1, $y2), $axis, $axis_value); - let point_four = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x2, $y1), $axis, $axis_value); - let point_one = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x1, $y1), $axis, $axis_value); - let point_two = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x2, $y2), $axis, $axis_value); + let point_three = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x1, $y2), + $axis, + $axis_value, + ); + let point_four = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x2, $y1), + $axis, + $axis_value, + ); + let point_one = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x1, $y1), + $axis, + $axis_value, + ); + let point_two = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x2, $y2), + $axis, + $axis_value, + ); let mut vec = Vec::new(); let nr = 0.5 * (point_one + point_two) - $cp; vec.push(triangle!(point_one, point_two, point_three, $material, nr)); @@ -262,14 +284,26 @@ macro_rules! aarect { vec }}; ($x1:expr, $y1:expr, $x2:expr, $y2:expr, $axis_value:expr, $axis:expr, $material:expr) => {{ - let point_three = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x1, $y2), $axis, $axis_value); - let point_four = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x2, $y1), $axis, $axis_value); - let point_one = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x1, $y1), $axis, $axis_value); - let point_two = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x2, $y2), $axis, $axis_value); + let point_three = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x1, $y2), + $axis, + $axis_value, + ); + let point_four = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x2, $y1), + $axis, + $axis_value, + ); + let point_one = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x1, $y1), + $axis, + $axis_value, + ); + let point_two = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x2, $y2), + $axis, + $axis_value, + ); let mut vec = Vec::new(); vec.push(triangle!(point_one, point_two, point_three, $material)); @@ -284,20 +318,28 @@ macro_rules! rect { ($point_one:expr, $point_two:expr, $axis_value:expr, $axis:expr, $rotation:expr, $material:expr) => {{ let center_point = 0.5 * ($point_one + $point_two); let point_three = implementations::Axis::point_from_2d( - rt_core::Vec2::new($point_one.x, $point_two.y), + implementations::rt_core::Vec2::new($point_one.x, $point_two.y), $axis, $axis_value, ); let point_four = implementations::Axis::point_from_2d( - rt_core::Vec2::new($point_two.x, $point_one.y), + implementations::rt_core::Vec2::new($point_two.x, $point_one.y), $axis, $axis_value, ); let point_one = implementations::Axis::point_from_2d($point_one, $axis, $axis_value); let point_two = implementations::Axis::point_from_2d($point_two, $axis, $axis_value); - let sin_rot = rt_core::Vec3::new($rotation.x.sin(), $rotation.y.sin(), $rotation.z.sin()); - let cos_rot = rt_core::Vec3::new($rotation.x.cos(), $rotation.y.cos(), $rotation.z.cos()); + let sin_rot = implementations::rt_core::Vec3::new( + $rotation.x.sin(), + $rotation.y.sin(), + $rotation.z.sin(), + ); + let cos_rot = implementations::rt_core::Vec3::new( + $rotation.x.cos(), + $rotation.y.cos(), + $rotation.z.cos(), + ); let point_one = $crate::utility::rotate_around_point(point_one, center_point, sin_rot, cos_rot); @@ -315,17 +357,37 @@ macro_rules! rect { vec }}; ($x1:expr, $y1:expr, $x2:expr, $y2:expr, $axis_value:expr, $axis:expr, $rotation:expr, $material:expr) => {{ - let mut point_three = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x1, $y2), $axis, $axis_value); - let mut point_four = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x2, $y1), $axis, $axis_value); - let mut point_one = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x1, $y1), $axis, $axis_value); - let mut point_two = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x2, $y2), $axis, $axis_value); - - let sin_rot = rt_core::Vec3::new($rotation.x.sin(), $rotation.y.sin(), $rotation.z.sin()); - let cos_rot = rt_core::Vec3::new($rotation.x.cos(), $rotation.y.cos(), $rotation.z.cos()); + let mut point_three = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x1, $y2), + $axis, + $axis_value, + ); + let mut point_four = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x2, $y1), + $axis, + $axis_value, + ); + let mut point_one = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x1, $y1), + $axis, + $axis_value, + ); + let mut point_two = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x2, $y2), + $axis, + $axis_value, + ); + + let sin_rot = implementations::rt_core::Vec3::new( + $rotation.x.sin(), + $rotation.y.sin(), + $rotation.z.sin(), + ); + let cos_rot = implementations::rt_core::Vec3::new( + $rotation.x.cos(), + $rotation.y.cos(), + $rotation.z.cos(), + ); $crate::utility::rotate_around_point(&mut point_one, center_point, sin_rot, cos_rot); @@ -342,14 +404,26 @@ macro_rules! rect { vec }}; ($x1:expr, $y1:expr, $x2:expr, $y2:expr, $axis_value:expr, $axis:expr, $center_point:expr, $sin_rot:expr, $cos_rot:expr, $material:expr) => {{ - let mut point_three = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x1, $y2), $axis, $axis_value); - let mut point_four = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x2, $y1), $axis, $axis_value); - let mut point_one = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x1, $y1), $axis, $axis_value); - let mut point_two = - implementations::Axis::point_from_2d(&rt_core::Vec2::new($x2, $y2), $axis, $axis_value); + let mut point_three = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x1, $y2), + $axis, + $axis_value, + ); + let mut point_four = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x2, $y1), + $axis, + $axis_value, + ); + let mut point_one = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x1, $y1), + $axis, + $axis_value, + ); + let mut point_two = implementations::Axis::point_from_2d( + &implementations::rt_core::Vec2::new($x2, $y2), + $axis, + $axis_value, + ); $crate::utility::rotate_around_point(&mut point_one, $center_point, $sin_rot, $cos_rot); @@ -437,7 +511,7 @@ macro_rules! aacuboid { }}; ($x1:expr, $y1:expr, $z1:expr, $x2:expr, $y2:expr, $z2:expr, $material:expr) => {{ let mut vec = Vec::new(); - let center_point = 0.5 * (rt_core::Vec3::new($x1, $y1, $z1) + rt_core::Vec3::new($x2, $y2, $z2)); + let center_point = 0.5 * (implementations::rt_core::Vec3::new($x1, $y1, $z1) + implementations::rt_core::Vec3::new($x2, $y2, $z2)); vec.extend(aarect!( $x1, $y1, @@ -589,11 +663,19 @@ macro_rules! cuboid { vec }}; ($x1:expr, $y1:expr, $z1:expr, $x2:expr, $y2:expr, $z2:expr, $rotation:expr, $material:expr) => {{ - let point_one = rt_core::Vec3::new($x1, $y1, $z1); - let point_two = rt_core::Vec3::new($x2, $y2, $z2); + let point_one = implementations::rt_core::Vec3::new($x1, $y1, $z1); + let point_two = implementations::rt_core::Vec3::new($x2, $y2, $z2); let center_point = 0.5 * (point_one + point_two); - let sin_rot = rt_core::Vec3::new($rotation.x.sin(), $rotation.y.sin(), $rotation.z.sin()); - let cos_rot = rt_core::Vec3::new($rotation.x.cos(), $rotation.y.cos(), $rotation.z.cos()); + let sin_rot = implementations::rt_core::Vec3::new( + $rotation.x.sin(), + $rotation.y.sin(), + $rotation.z.sin(), + ); + let cos_rot = implementations::rt_core::Vec3::new( + $rotation.x.cos(), + $rotation.y.cos(), + $rotation.z.cos(), + ); let mut vec = Vec::new(); vec.extend(rect!( @@ -746,11 +828,13 @@ macro_rules! triangle { } .normalized(); - rt_core::AllPrimitives::Triangle(rt_core::Triangle::new_from_arc( - [point_one, point_two, point_three], - [normal; 3], - $material, - )) + implementations::rt_core::AllPrimitives::Triangle( + implementations::rt_core::Triangle::new_from_arc( + [point_one, point_two, point_three], + [normal; 3], + $material, + ), + ) }}; } @@ -764,10 +848,10 @@ macro_rules! camera { $origin, $lookat, $vup, - $fov as rt_core::Float, - $aspect_ratio as rt_core::Float, - $aperture as rt_core::Float, - $focus_dist as rt_core::Float, + $fov as implementations::rt_core::Float, + $aspect_ratio as implementations::rt_core::Float, + $aperture as implementations::rt_core::Float, + $focus_dist as implementations::rt_core::Float, )) }; } @@ -784,7 +868,7 @@ macro_rules! sky { () => { std::sync::Arc::new(implementations::Sky::new( &std::sync::Arc::new(implementations::AllTextures::SolidColour( - implementations::SolidColour::new(rt_core::Vec3::zero()), + implementations::SolidColour::new(implementations::rt_core::Vec3::zero()), )), (100, 100), )) @@ -804,6 +888,6 @@ macro_rules! bvh { #[macro_export] macro_rules! scene { ($camera:expr, $sky:expr, $sampler:expr, $bvh:expr) => { - $crate::scene::Scene::new($camera, $sky, $sampler, $bvh) + scene::Scene::new($camera, $sky, $sampler, $bvh) }; } diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 8e78d36..8580ecb 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -21,7 +21,7 @@ use { winit::event_loop::EventLoopProxy, }; -use rt_core::{Float, SamplerProgress}; +use implementations::rt_core::{Float, SamplerProgress}; use std::env; #[cfg(feature = "gui")] @@ -33,8 +33,6 @@ mod generate; mod load_model; mod macros; mod parameters; - -mod scene; mod utility; #[cfg(feature = "gui")] diff --git a/frontend/src/parameters.rs b/frontend/src/parameters.rs index 8e7c1fa..43d448f 100644 --- a/frontend/src/parameters.rs +++ b/frontend/src/parameters.rs @@ -1,7 +1,9 @@ use crate::generate::SceneType; use chrono::Local; -use implementations::split::SplitType; -use rt_core::{Float, RenderMethod, RenderOptions}; +use implementations::{ + rt_core::{Float, RenderMethod, RenderOptions}, + split::SplitType, +}; use std::process; pub struct Parameters { @@ -299,8 +301,8 @@ fn get_render_method(args: &[String], index: usize) -> Option { Some(string) => match &string.to_ascii_lowercase()[..] { "naive" => RenderMethod::Naive, "mis" => RenderMethod::MIS, - _val => { - println!("Unsupported render method: {}", _val); + render_method => { + println!("Unsupported render method: {render_method}"); println!("Do -H or --help for more information."); process::exit(0); } @@ -350,8 +352,8 @@ fn get_bvh_type(args: &[String], index: usize) -> SplitType { "equal" => SplitType::EqualCounts, "middle" => SplitType::Middle, "sah" => SplitType::Sah, - _ => { - println!("{} is not a valid value for BVH type!", string); + bvh_type => { + println!("{bvh_type} is not a valid value for BVH type!"); println!("Please specify a valid value for BVH type!"); println!("Do -H or --help for more information."); process::exit(0); @@ -378,7 +380,7 @@ fn get_samples(args: &[String], index: usize) -> u64 { _ => parsed, }, Err(_) => { - println!("{} is not a valid value for samples!", string); + println!("{string} is not a valid value for samples!"); println!("Please specify a valid value for height!"); println!("Do -H or --help for more information."); process::exit(0); @@ -405,7 +407,7 @@ fn get_dimension(args: &[String], index: usize) -> u64 { _ => parsed, }, Err(_) => { - println!("{} is not a valid value for height!", string); + println!("{string} is not a valid value for height!"); println!("Please specify a valid value for height!"); println!("Do -H or --help for more information."); process::exit(0); diff --git a/frontend/src/utility.rs b/frontend/src/utility.rs index cdbc1dc..48f6c2d 100644 --- a/frontend/src/utility.rs +++ b/frontend/src/utility.rs @@ -1,6 +1,5 @@ use chrono::Local; -use implementations::{aabb::AABound, split::SplitType, Axis, Bvh}; -use rt_core::{Float, Primitive, Scatter, Vec3}; +use implementations::{aabb::AABound, rt_core::*, split::SplitType, Axis, Bvh}; use std::{ io::{stdout, Write}, process, @@ -32,29 +31,29 @@ pub fn get_readable_duration(duration: Duration) -> String { let days_string = match days { 0 => "".to_string(), - 1 => format!("{} day, ", days), - _ => format!("{} days, ", days), + 1 => format!("{days} day, "), + _ => format!("{days} days, "), }; let hours = (duration.as_secs() - days * 86400) / 3600; let hours_string = match hours { 0 => "".to_string(), - 1 => format!("{} hour, ", hours), - _ => format!("{} hours, ", hours), + 1 => format!("{hours} hour, "), + _ => format!("{hours} hours, "), }; let minutes = (duration.as_secs() - days * 86400 - hours * 3600) / 60; let minutes_string = match minutes { 0 => "".to_string(), - 1 => format!("{} minute, ", minutes), - _ => format!("{} minutes, ", minutes), + 1 => format!("{minutes} minute, "), + _ => format!("{minutes} minutes, "), }; let seconds = duration.as_secs() % 60; let seconds_string = match seconds { 0 => "~0 seconds".to_string(), - 1 => format!("{} second", seconds), - _ => format!("{} seconds", seconds), + 1 => format!("{seconds} second"), + _ => format!("{seconds} seconds"), }; days_string + &hours_string + &minutes_string + &seconds_string } @@ -81,7 +80,7 @@ pub fn create_bvh_with_info + AABound, M: Scatter>( pub fn get_progress_output(samples_completed: u64, total_samples: u64) { progress_bar(samples_completed as f64 / total_samples as f64); - print!(" ({}/{}) samples", samples_completed, total_samples); + print!(" ({samples_completed}/{total_samples}) samples"); stdout().flush().unwrap(); } @@ -111,15 +110,13 @@ pub fn save_u8_to_image(width: u64, height: u64, image: Vec, filename: Strin .unwrap(); } "ppm" => { - let mut data = format!("P3\n{} {}\n255\n", width, height) - .as_bytes() - .to_owned(); + let mut data = format!("P3\n{width} {height}\n255\n").as_bytes().to_owned(); image.iter().enumerate().for_each(|(i, &v)| { if i % 3 == 0 { - data.extend_from_slice(format!("{}\n", v).as_bytes()) + data.extend_from_slice(format!("{v}\n").as_bytes()) } else { - data.extend_from_slice(format!("{} ", v).as_bytes()) + data.extend_from_slice(format!("{v} ").as_bytes()) } }); @@ -141,9 +138,9 @@ pub fn print_final_statistics(start: Instant, ray_count: u64, samples: Option println!("\tSamples: {}", samples), + Some(samples) => println!("\tSamples: {samples}"), None => { println!() } @@ -158,10 +155,10 @@ pub fn print_final_statistics(start: Instant, ray_count: u64, samples: Option) -> Instant { let time = Local::now(); println!("{} - Render started", time.format("%X")); - println!("\tWidth: {}", width); - println!("\tHeight: {}", height); + println!("\tWidth: {width}"); + println!("\tHeight: {height}"); match samples { - Some(samples) => println!("\tSamples per pixel: {}\n", samples), + Some(samples) => println!("\tSamples per pixel: {samples}\n"), None => println!(), } Instant::now() @@ -206,7 +203,7 @@ pub fn rotate_around_axis(point: &mut Vec3, axis: Axis, sin: Float, cos: Float) mod tests { use super::*; - use rt_core::PI; + use implementations::rt_core::PI; #[test] fn rotation() { diff --git a/implementations/Cargo.toml b/implementations/Cargo.toml index f23bf1f..fc7a297 100644 --- a/implementations/Cargo.toml +++ b/implementations/Cargo.toml @@ -10,7 +10,6 @@ image = "0.24.3" proc = { path = "./proc" } rand = { version = "0.8.3", features = [ "small_rng" ] } rayon = "1.5.1" -rt_core = {path = "../rt_core"} statistics = { path = "./statistics" } [dev-dependencies] @@ -18,4 +17,4 @@ chrono = "0.4.19" statrs = "0.16.0" [features] -f64 = ["rt_core/f64"] \ No newline at end of file +f64 = ["statistics/f64"] \ No newline at end of file diff --git a/implementations/src/acceleration/aabb.rs b/implementations/src/acceleration/aabb.rs index 1791967..366bf3a 100644 --- a/implementations/src/acceleration/aabb.rs +++ b/implementations/src/acceleration/aabb.rs @@ -1,5 +1,4 @@ -use crate::utility::gamma; -use rt_core::{Float, Ray, Vec3}; +use crate::{rt_core::*, utility::gamma}; pub trait AABound { fn get_aabb(&self) -> AABB; diff --git a/implementations/src/acceleration/mod.rs b/implementations/src/acceleration/mod.rs index 38b4f8e..3fa8a3f 100644 --- a/implementations/src/acceleration/mod.rs +++ b/implementations/src/acceleration/mod.rs @@ -1,10 +1,10 @@ use crate::{ aabb::{AABound, AABB}, acceleration::split::{Split, SplitType}, + rt_core::*, utility::sort_by_indices, Axis, }; -use rt_core::{AccelerationStructure, Primitive, Ray, Scatter, SurfaceIntersection, Vec3}; use std::{collections::VecDeque, marker::PhantomData}; #[cfg(all(feature = "f64"))] diff --git a/implementations/src/acceleration/split.rs b/implementations/src/acceleration/split.rs index 7e443a9..371c124 100644 --- a/implementations/src/acceleration/split.rs +++ b/implementations/src/acceleration/split.rs @@ -1,5 +1,4 @@ -use crate::{aabb::AABB, acceleration::PrimitiveInfo, Axis}; -use rt_core::Float; +use crate::{aabb::AABB, acceleration::PrimitiveInfo, rt_core::*, Axis}; const NUM_BUCKETS: usize = 12; const MAX_IN_NODE: usize = 255; diff --git a/implementations/src/cameras/mod.rs b/implementations/src/cameras/mod.rs index b1a5c56..72eb998 100644 --- a/implementations/src/cameras/mod.rs +++ b/implementations/src/cameras/mod.rs @@ -1,5 +1,4 @@ -use crate::utility::random_float; -use rt_core::{Camera, Float, Ray, Vec3}; +use crate::{rt_core::*, utility::random_float}; pub struct SimpleCamera { pub viewport_width: Float, diff --git a/implementations/src/lib.rs b/implementations/src/lib.rs index 8caf315..7fa0ed7 100644 --- a/implementations/src/lib.rs +++ b/implementations/src/lib.rs @@ -6,6 +6,8 @@ mod samplers; mod textures; mod utility; +pub use statistics::rt_core; + pub use acceleration::*; pub use cameras::*; pub use materials::*; diff --git a/implementations/src/materials/emissive.rs b/implementations/src/materials/emissive.rs index 58569de..1561df8 100644 --- a/implementations/src/materials/emissive.rs +++ b/implementations/src/materials/emissive.rs @@ -1,5 +1,4 @@ -use crate::{textures::Texture, utility::offset_ray}; -use rt_core::{Float, Hit, Ray, Scatter, Vec3}; +use crate::{rt_core::*, textures::Texture, utility::offset_ray}; use std::sync::Arc; #[derive(Debug)] diff --git a/implementations/src/materials/lambertian.rs b/implementations/src/materials/lambertian.rs index 3984488..02eca84 100644 --- a/implementations/src/materials/lambertian.rs +++ b/implementations/src/materials/lambertian.rs @@ -1,8 +1,8 @@ use crate::{ + rt_core::*, textures::Texture, utility::{coord::Coordinate, cosine_hemisphere_sampling, near_zero, offset_ray}, }; -use rt_core::{Float, Hit, Ray, Scatter, Vec3}; use std::sync::Arc; #[derive(Debug)] diff --git a/implementations/src/materials/mod.rs b/implementations/src/materials/mod.rs index 2839859..cebd2bb 100644 --- a/implementations/src/materials/mod.rs +++ b/implementations/src/materials/mod.rs @@ -1,5 +1,5 @@ +use crate::rt_core::{Float, Hit, Ray, Scatter, Vec3}; use proc::Scatter; -use rt_core::{Float, Hit, Ray, Scatter, Vec3}; pub mod emissive; pub mod lambertian; diff --git a/implementations/src/materials/phong.rs b/implementations/src/materials/phong.rs index 10c3183..add27e0 100644 --- a/implementations/src/materials/phong.rs +++ b/implementations/src/materials/phong.rs @@ -1,8 +1,8 @@ use crate::{ + rt_core::*, textures::Texture, utility::{random_float, specular_sampling, coord::Coordinate, cosine_hemisphere_sampling, near_zero, offset_ray}, }; -use rt_core::{Float, Hit, Ray, Scatter, Vec3, PI}; use std::sync::Arc; #[derive(Debug)] diff --git a/implementations/src/materials/reflect.rs b/implementations/src/materials/reflect.rs index b939426..839b122 100644 --- a/implementations/src/materials/reflect.rs +++ b/implementations/src/materials/reflect.rs @@ -1,8 +1,8 @@ use crate::{ + rt_core::*, textures::Texture, utility::{offset_ray, random_unit_vector}, }; -use rt_core::{Float, Hit, Ray, Scatter, Vec3}; use std::sync::Arc; #[derive(Debug)] diff --git a/implementations/src/materials/refract.rs b/implementations/src/materials/refract.rs index daecb08..39228dd 100644 --- a/implementations/src/materials/refract.rs +++ b/implementations/src/materials/refract.rs @@ -1,9 +1,9 @@ use crate::{ materials::reflect::Reflect, + rt_core::*, textures::Texture, utility::{offset_ray, random_float}, }; -use rt_core::{Float, Hit, Ray, Scatter, Vec3}; use std::sync::Arc; #[derive(Debug)] diff --git a/implementations/src/materials/trowbridge_reitz.rs b/implementations/src/materials/trowbridge_reitz.rs index b3ce0b7..0a6907f 100644 --- a/implementations/src/materials/trowbridge_reitz.rs +++ b/implementations/src/materials/trowbridge_reitz.rs @@ -1,10 +1,10 @@ use crate::{ materials::refract, + rt_core::*, textures::Texture, utility::{coord::Coordinate, offset_ray}, }; use rand::{rngs::SmallRng, thread_rng, SeedableRng}; -use rt_core::*; use std::sync::Arc; #[derive(Debug)] diff --git a/implementations/src/primitives/mod.rs b/implementations/src/primitives/mod.rs index 0abc039..d1249e8 100644 --- a/implementations/src/primitives/mod.rs +++ b/implementations/src/primitives/mod.rs @@ -4,9 +4,9 @@ use crate::{ sphere::Sphere, triangle::{MeshTriangle, Triangle}, }, + rt_core::*, }; use proc::Primitive; -use rt_core::{Float, Hit, Primitive, Ray, Scatter, SurfaceIntersection, Vec2, Vec3}; pub mod sphere; pub mod triangle; diff --git a/implementations/src/primitives/sphere.rs b/implementations/src/primitives/sphere.rs index 1e1be9c..5221489 100644 --- a/implementations/src/primitives/sphere.rs +++ b/implementations/src/primitives/sphere.rs @@ -1,8 +1,8 @@ use crate::{ aabb::{AABound, AABB}, + rt_core::*, utility::{coord::Coordinate, random_float}, }; -use rt_core::{Float, Hit, Primitive, Ray, Scatter, SurfaceIntersection, Vec2, Vec3, EPSILON, PI}; use std::sync::Arc; #[derive(Debug)] diff --git a/implementations/src/primitives/triangle.rs b/implementations/src/primitives/triangle.rs index 431d0cd..235e5ee 100644 --- a/implementations/src/primitives/triangle.rs +++ b/implementations/src/primitives/triangle.rs @@ -1,10 +1,10 @@ use crate::{ aabb::{AABound, AABB}, primitives::Axis, + rt_core::*, utility::{check_side, gamma}, }; use rand::{thread_rng, Rng}; -use rt_core::{Float, Hit, Primitive, Ray, Scatter, SurfaceIntersection, Vec2, Vec3}; use std::sync::Arc; #[derive(Clone, Debug)] @@ -92,10 +92,10 @@ where M: Scatter, { fn get_point(&self, index: usize) -> Vec3 { - (*self.mesh).vertices[self.point_indices[index]] + self.mesh.vertices[self.point_indices[index]] } fn get_normal(&self, index: usize) -> Vec3 { - (*self.mesh).normals[self.normal_indices[index]] + self.mesh.normals[self.normal_indices[index]] } fn get_material(&self) -> &Arc { &self.material @@ -254,11 +254,11 @@ where triangle_intersection(self, ray) } fn area(&self) -> Float { - 0.5 * ((*self.mesh).vertices[self.point_indices[1]] - - (*self.mesh).vertices[self.point_indices[0]]) + 0.5 * (self.mesh.vertices[self.point_indices[1]] + - self.mesh.vertices[self.point_indices[0]]) .cross( - (*self.mesh).vertices[self.point_indices[2]] - - (*self.mesh).vertices[self.point_indices[0]], + self.mesh.vertices[self.point_indices[2]] + - self.mesh.vertices[self.point_indices[0]], ) .mag() } @@ -267,9 +267,9 @@ where let uv = rng.gen::().sqrt(); let uv = (1.0 - uv, uv * rng.gen::().sqrt()); - let point = uv.0 * (*self.mesh).vertices[self.point_indices[0]] - + uv.1 * (*self.mesh).vertices[self.point_indices[1]] - + (1.0 - uv.0 - uv.1) * (*self.mesh).vertices[self.point_indices[2]]; + let point = uv.0 * self.mesh.vertices[self.point_indices[0]] + + uv.1 * self.mesh.vertices[self.point_indices[1]] + + (1.0 - uv.0 - uv.1) * self.mesh.vertices[self.point_indices[2]]; (point - in_point).normalised() } @@ -292,9 +292,9 @@ impl AABound for Triangle { impl AABound for MeshTriangle { fn get_aabb(&self) -> AABB { let points = [ - (*self.mesh).vertices[self.point_indices[0]], - (*self.mesh).vertices[self.point_indices[1]], - (*self.mesh).vertices[self.point_indices[2]], + self.mesh.vertices[self.point_indices[0]], + self.mesh.vertices[self.point_indices[1]], + self.mesh.vertices[self.point_indices[2]], ]; AABB::new( diff --git a/implementations/src/samplers/mod.rs b/implementations/src/samplers/mod.rs index 869c370..8b7d76d 100644 --- a/implementations/src/samplers/mod.rs +++ b/implementations/src/samplers/mod.rs @@ -1,6 +1,5 @@ -use crate::{generate_values, next_float, random_float, textures::Texture}; +use crate::{generate_values, next_float, random_float, rt_core::*, textures::Texture}; use rand::{rngs::SmallRng, thread_rng, SeedableRng}; -use rt_core::*; use statistics::distributions::*; use std::sync::Arc; diff --git a/implementations/src/samplers/random_sampler.rs b/implementations/src/samplers/random_sampler.rs index 5c55e7a..35c629d 100644 --- a/implementations/src/samplers/random_sampler.rs +++ b/implementations/src/samplers/random_sampler.rs @@ -1,9 +1,9 @@ -use rand::Rng; -use rayon::prelude::*; -use rt_core::{ +use crate::rt_core::{ AccelerationStructure, Camera, Float, NoHit, Primitive, Ray, RenderMethod, RenderOptions, Sampler, SamplerProgress, Scatter, }; +use rand::Rng; +use rayon::prelude::*; pub struct RandomSampler; @@ -53,8 +53,8 @@ impl Sampler for RandomSampler { for chunk_pixel_i in 0..(chunk.len() / 3) { let pixel_i = chunk_pixel_i as u64 + pixel_chunk_size * chunk_i as u64; - let x = pixel_i as u64 % render_options.width; - let y = (pixel_i as u64 - x) / render_options.width; + let x = pixel_i % render_options.width; + let y = (pixel_i - x) / render_options.width; let u = (rng.gen_range(0.0..1.0) + x as Float) / render_options.width as Float; let v = 1.0 @@ -82,9 +82,8 @@ impl Sampler for RandomSampler { }); }); if i != 0 { - match presentation_update.as_mut() { - Some((ref mut data, f)) => f(data, previous, i), - None => (), + if let Some((ref mut data, f)) = presentation_update.as_mut() { + f(data, previous, i) }; } } @@ -94,9 +93,8 @@ impl Sampler for RandomSampler { } else { (&accumulator_buffers.1, &mut accumulator_buffers.0) }; - match presentation_update.as_mut() { - Some((ref mut data, f)) => f(data, previous, render_options.samples_per_pixel), - None => (), + if let Some((ref mut data, f)) = presentation_update.as_mut() { + f(data, previous, render_options.samples_per_pixel) } } } diff --git a/implementations/src/textures/mod.rs b/implementations/src/textures/mod.rs index bec76ca..c8570d6 100644 --- a/implementations/src/textures/mod.rs +++ b/implementations/src/textures/mod.rs @@ -1,7 +1,7 @@ +use crate::rt_core::*; use image::{io::Reader, GenericImageView}; use proc::Texture; use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; -use rt_core::*; const PERLIN_RVECS: usize = 256; diff --git a/implementations/src/utility/coord.rs b/implementations/src/utility/coord.rs index 6663540..740786f 100644 --- a/implementations/src/utility/coord.rs +++ b/implementations/src/utility/coord.rs @@ -1,4 +1,4 @@ -use rt_core::Vec3; +use crate::rt_core::Vec3; pub struct Coordinate { pub x: Vec3, diff --git a/implementations/src/utility/mod.rs b/implementations/src/utility/mod.rs index 021d476..12842f1 100644 --- a/implementations/src/utility/mod.rs +++ b/implementations/src/utility/mod.rs @@ -1,5 +1,5 @@ +use crate::rt_core::{Float, Vec3, PI}; use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; -use rt_core::{Float, Vec3, PI}; pub mod coord; diff --git a/implementations/statistics/src/lib.rs b/implementations/statistics/src/lib.rs index da14110..66d3cf4 100644 --- a/implementations/statistics/src/lib.rs +++ b/implementations/statistics/src/lib.rs @@ -1,4 +1,4 @@ -pub use rt_core::*; +pub use rt_core::{self, *}; pub mod bxdfs; pub mod chi_squared; @@ -35,7 +35,7 @@ pub mod utility { .unwrap() } - use super::Float; + use rt_core::Float; pub fn recursively_binary_average(mut values: Vec) -> T where diff --git a/implementations/tests/common/mod.rs b/implementations/tests/common/mod.rs deleted file mode 100644 index 2b4752a..0000000 --- a/implementations/tests/common/mod.rs +++ /dev/null @@ -1,214 +0,0 @@ -use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; -use rayon::prelude::*; -use rt_core::{vec::*, Float}; -use statistics::integrators::adaptive_simpsons; -use statrs::function::gamma::*; -use std::{ - cmp::Ordering::*, - f64::consts::*, - {fs::File, io::Write}, -}; - -pub const SAMPLES: usize = 10_000_000; -pub const THETA_RES: usize = 80; -pub const PHI_RES: usize = 2 * THETA_RES; -pub const CHI2_THRESHOLD: Float = 0.01; -pub const CHI_TESTS: usize = 1; - -pub fn to_vec(sin_theta: Float, cos_theta: Float, phi: Float) -> Vec3 { - Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) -} - -pub fn chi2_probability(dof: f64, distance: f64) -> f64 { - match distance.partial_cmp(&0.0).unwrap() { - Less => panic!("distance < 0.0"), - Equal => 1.0, - Greater => gamma_ur(dof * 0.5, distance * 0.5), - } -} - -pub fn integrate_frequency_table( - pdf: &F, - wo: Vec3, - phi_res: usize, - theta_res: usize, -) -> Vec -where - F: Fn(Vec3, Vec3) -> Float + Sync, -{ - let theta_step = PI as Float / theta_res as Float; - let phi_step = TAU as Float / phi_res as Float; - let pdf = |phi: Float, a, b| { - adaptive_simpsons( - |theta| { - pdf( - wo, - Vec3::new( - phi.cos() * theta.sin(), - phi.sin() * theta.sin(), - theta.cos(), - ), - ) * theta.sin() - }, - a, - b, - ) - }; - - (0..(theta_res * phi_res)) - .into_par_iter() - .map(|i| { - let phi_i = i % phi_res; - let theta_i = i / phi_res; - adaptive_simpsons( - |phi| { - pdf( - phi, - theta_i as Float * theta_step, - (theta_i + 1) as Float * theta_step, - ) - }, - phi_i as Float * phi_step, - (phi_i + 1) as Float * phi_step, - ) - }) - .collect() -} - -pub fn samped_frequency_distribution_uv( - function: &F, - u_res: usize, - v_res: usize, - sample_count: usize, -) -> Vec -where - F: Fn() -> (usize, usize) + std::marker::Sync, -{ - let mut freq = vec![vec![0.0; u_res * v_res]; 16]; - - freq.par_iter_mut().for_each(|x| { - for _ in 0..(sample_count / 16) { - let sample = function(); - x[sample.0 + sample.1 * u_res] += 1.0; - } - }); - - freq.into_iter() - .fold(vec![0.0; u_res * v_res], |mut sum, val| { - sum.iter_mut().zip(val).for_each(|(s, v)| *s += v); - sum - }) -} - -pub fn samped_frequency_distribution( - function: &F, - wo: Vec3, - phi_res: usize, - theta_res: usize, - sample_count: usize, -) -> Vec -where - F: Fn(Vec3) -> Vec3, -{ - let mut freq = vec![0.0; theta_res * phi_res]; - let theta_step = PI as Float / theta_res as Float; - let phi_step = TAU as Float / phi_res as Float; - - for _ in 0..sample_count { - let sampled = function(wo); - let theta = sampled.z.acos(); - let mut phi = (sampled.y).atan2(sampled.x); - - if phi < 0.0 { - phi += TAU as Float; - } - - let theta_bin = ((theta / theta_step) as usize).max(0).min(theta_res - 1); - let phi_bin = ((phi / phi_step) as usize).max(0).min(phi_res - 1); - - freq[theta_bin * phi_res + phi_bin] += 1.0; - } - - freq -} - -pub fn random_float() -> Float { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - rng.gen() -} - -pub fn generate_wo() -> Vec3 { - let cos_theta = random_float(); - let phi = TAU as Float * random_float(); - - to_vec((1.0 - cos_theta * cos_theta).sqrt(), cos_theta, phi) -} - -pub fn dump_tables( - wo: Vec3, - freq_table: &[Float], - expected_freq_table: &[Float], - x: usize, - y: usize, - bxdf_name: &str, -) { - fn chi_squared_term(a: Float, b: Float) -> Float { - if a < (SAMPLES / 100000) as Float && b == 0.0 { - return 0.0; - } - let val = a - b; - val * val / b - } - - let enumerate = |file: &mut File, func: fn(Float, Float) -> Float| { - (0..(x * y)).for_each(|index| { - file.write_all( - format!("{}", func(freq_table[index], expected_freq_table[index])).as_bytes(), - ) - .unwrap(); - - if index % x + 1 != x { - file.write_all(b", ").unwrap(); - } else if index / x + 1 != y { - file.write_all(b"; ").unwrap(); - } - }); - }; - - let time = chrono::Local::now(); - - let mut file = File::create(format!( - "chi_test_{bxdf_name}@{}.m", - time.format("%Y-%m-%d:%H:%M") - )) - .unwrap(); - - file.write_all(format!("% wo = {wo}\nfrequencies = [ ").as_bytes()) - .unwrap(); - enumerate(&mut file, |o, _| o); - - file.write_all(b" ];\nexpected_frequencies = [ ").unwrap(); - enumerate(&mut file, |_, e| e); - - file.write_all(b" ];\nchi_terms = [ ").unwrap(); - enumerate(&mut file, chi_squared_term); - - file.write_all( - b" ]; -colormap(jet); -clf; -subplot(3,1,1); -imagesc([0, 360], [0, 180], frequencies); -axis equal; -title('observed frequencies'); -subplot(3,1,2); -imagesc([0, 360], [0, 180], chi_terms); -axis equal; -title('chi terms'); -subplot(3,1,3); -imagesc([0, 360], [0, 180], expected_frequencies); -axis equal; -title('expected frequencies');", - ) - .unwrap(); -} diff --git a/implementations/tests/sampling.rs b/implementations/tests/sampling.rs index 7cfe2c5..342b944 100644 --- a/implementations/tests/sampling.rs +++ b/implementations/tests/sampling.rs @@ -2,8 +2,6 @@ use rand::rngs::ThreadRng; use rt_core::*; use statistics::spherical_sampling::test_spherical_pdf; -mod common; - #[test] fn sky_sampling() { const SAMPLE_WIDTH: usize = 30; From 3bcbf4e1d73099980dcea1ae1315ee4d54f67cb9 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Sat, 21 Jan 2023 15:15:31 +1100 Subject: [PATCH 06/39] Added initial scene loading code --- frontend/Cargo.toml | 3 +- frontend/scene/Cargo.toml | 4 +- frontend/scene/src/lib.rs | 4 + frontend/scene/src/parse/camera.rs | 101 ++++++++++++ frontend/scene/src/parse/materials.rs | 119 +++++++++++++ frontend/scene/src/parse/mod.rs | 220 +++++++++++++++++++++++++ frontend/scene/src/parse/primitives.rs | 131 +++++++++++++++ frontend/scene/src/parse/sky.rs | 99 +++++++++++ frontend/scene/src/parse/textures.rs | 126 ++++++++++++++ frontend/src/generate.rs | 6 +- frontend/src/load_model.rs | 2 +- frontend/src/main.rs | 2 +- frontend/src/parameters.rs | 4 +- frontend/src/utility.rs | 7 +- implementations/src/cameras/mod.rs | 1 + implementations/src/samplers/mod.rs | 1 + implementations/src/textures/mod.rs | 20 ++- implementations/tests/sampling.rs | 2 +- rt_core/src/vec.rs | 12 ++ 19 files changed, 842 insertions(+), 22 deletions(-) create mode 100644 frontend/scene/src/parse/camera.rs create mode 100644 frontend/scene/src/parse/materials.rs create mode 100644 frontend/scene/src/parse/mod.rs create mode 100644 frontend/scene/src/parse/primitives.rs create mode 100644 frontend/scene/src/parse/sky.rs create mode 100644 frontend/scene/src/parse/textures.rs diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index a584ca0..6f83b69 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" [dependencies] chrono = "0.4.19" image = "0.23.14" -implementations = { path = "../implementations" } rand = { version = "0.8.3", features = [ "small_rng" ] } rand_seeder = "0.2.2" scene = { path = "./scene" } @@ -19,5 +18,5 @@ wavefront_obj = "10.0.0" winit = { version = "0.26.1", optional = true } [features] -f64 = ["implementations/f64"] +f64 = ["scene/f64"] gui = ["dep:vulkano", "dep:vulkano-win", "dep:vulkano-shaders", "dep:winit"] \ No newline at end of file diff --git a/frontend/scene/Cargo.toml b/frontend/scene/Cargo.toml index 2f943a6..1e93988 100644 --- a/frontend/scene/Cargo.toml +++ b/frontend/scene/Cargo.toml @@ -7,10 +7,10 @@ edition = "2021" [dependencies] image = "0.23.14" +serde = { version = "1.0", features = ["derive"] } implementations = {path = "../../implementations"} -rt_core = { path = "../../rt_core" } toml = "0.5.10" wavefront_obj = "10.0.0" [features] -f64 = ["implementations/f64", "rt_core/f64"] \ No newline at end of file +f64 = ["implementations/f64"] \ No newline at end of file diff --git a/frontend/scene/src/lib.rs b/frontend/scene/src/lib.rs index ae4ba74..0300554 100644 --- a/frontend/scene/src/lib.rs +++ b/frontend/scene/src/lib.rs @@ -1,3 +1,7 @@ +pub mod parse; + +pub use implementations::{self, rt_core}; + use implementations::{SimpleCamera, Sky, Texture}; use rt_core::*; use std::{marker::PhantomData, sync::Arc}; diff --git a/frontend/scene/src/parse/camera.rs b/frontend/scene/src/parse/camera.rs new file mode 100644 index 0000000..8e936c1 --- /dev/null +++ b/frontend/scene/src/parse/camera.rs @@ -0,0 +1,101 @@ +use crate::parse::*; + +use serde::Deserialize; + +use toml::Value; + +use crate::*; + +pub fn parse_cameras(data: Value, camera_names: &[String]) -> Result, Error> { + parse_items::<(), CameraLoad, SimpleCamera>(data, camera_names, &()) +} + +#[derive(Debug)] +pub enum CameraLoadError { + MissingField, + InvalidType, +} + +#[derive(Deserialize, Debug)] +pub struct CameraLoad { + definition: Option, + #[serde(rename = "type")] + camera_type: Option, + origin: Option<[Float; 3]>, + lookat: Option<[Float; 3]>, + vup: Option<[Float; 3]>, + fov: Option, + aspect_ratio: Option, + aperture: Option, + focus_dist: Option, +} + +impl Load for CameraLoad { + type LoadType = (); + fn derive_from(&mut self, other: Self) { + if self.camera_type.is_none() { + self.camera_type = other.camera_type; + } + if self.origin.is_none() { + self.origin = other.origin; + } + if self.lookat.is_none() { + self.lookat = other.lookat; + } + if self.vup.is_none() { + self.vup = other.vup; + } + if self.fov.is_none() { + self.fov = other.fov; + } + if self.aspect_ratio.is_none() { + self.aspect_ratio = other.aspect_ratio; + } + if self.aperture.is_none() { + self.aperture = other.aperture; + } + if self.focus_dist.is_none() { + self.focus_dist = other.focus_dist; + } + } + fn get_definition(&self) -> Option { + self.definition.clone() + } + fn get_plural<'a>() -> &'a str { + "cameras" + } +} + +try_into!( + CameraLoad, + SimpleCamera, + CameraLoadError, + SimpleCamera::new( + origin.into(), + lookat.into(), + vup.into(), + fov, + aspect_ratio, + aperture, + focus_dist, + ), + origin, + lookat, + vup, + fov, + aspect_ratio, + aperture, + focus_dist +); + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse::tests::EXAMPLE_TOML; + + #[test] + pub fn parse_texture() { + let value = EXAMPLE_TOML.parse::().unwrap(); + let _ = parse_cameras(value, &["camera_one".to_string()]).unwrap(); + } +} diff --git a/frontend/scene/src/parse/materials.rs b/frontend/scene/src/parse/materials.rs new file mode 100644 index 0000000..44991e5 --- /dev/null +++ b/frontend/scene/src/parse/materials.rs @@ -0,0 +1,119 @@ +use crate::{parse::*, *}; +use implementations::{AllMaterials, AllTextures, Lambertian}; +use serde::Deserialize; +use std::{collections::HashMap, sync::Arc}; + +use toml::Value; + +use super::parse_items; + +pub fn parse_materials( + data: Value, + material_names: &[String], + textures_map: &HashMap>, +) -> Result>, Error> { + parse_items::>, MaterialLoad, AllMaterials>( + data, + material_names, + textures_map, + ) +} + +#[derive(Deserialize, Debug)] +pub struct MaterialLoad { + definition: Option, + #[serde(rename = "type")] + mat_type: Option, + texture: Option, + #[serde(skip)] + texture_instance: Option>, + absorbtion: Option, +} + +impl Load for MaterialLoad { + type LoadType = HashMap>; + fn derive_from(&mut self, other: Self) { + if self.mat_type.is_none() { + self.mat_type = other.mat_type; + } + if self.texture.is_none() { + self.texture = other.texture; + } + if self.absorbtion.is_none() { + self.absorbtion = other.absorbtion; + } + } + fn load(&mut self, textures_map: &Self::LoadType) -> Result<(), Error> { + let tex_name = match &self.texture { + Some(tex_name) => tex_name, + None => return Err(MaterialLoadError::MissingField.into()), + }; + + match textures_map.get(tex_name) { + Some(tex) => self.texture_instance = Some(tex.clone()), + None => return Err(MaterialLoadError::TextureNotFound.into()), + }; + + Ok(()) + } + fn get_definition(&self) -> Option { + self.definition.clone() + } + fn get_plural<'a>() -> &'a str { + "materials" + } +} + +#[derive(Debug)] +pub enum MaterialLoadError { + MissingField, + InvalidType, + TextureNotFound, +} + +type Lambert = Lambertian; + +try_into!( + MaterialLoad, + Lambert, + MaterialLoadError, + Lambertian::new(&texture_instance, absorbtion), + texture_instance, + absorbtion +); + +impl TryInto> for MaterialLoad { + type Error = MaterialLoadError; + + fn try_into(self) -> Result, Self::Error> { + match self.mat_type.clone() { + Some(m_type) => match &m_type[..] { + "lambertian" => Ok(AllMaterials::Lambertian(self.try_into()?)), + _ => Err(MaterialLoadError::InvalidType), + }, + _ => Err(MaterialLoadError::MissingField), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse::tests::EXAMPLE_TOML; + use crate::parse::textures::parse_textures; + + #[test] + pub fn parse_material() { + let value = EXAMPLE_TOML.parse::().unwrap(); + let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; + let textures = parse_textures(value.clone(), &texture_names).unwrap(); + + let textures: HashMap> = HashMap::from_iter( + texture_names + .into_iter() + .zip(textures.into_iter().map(Arc::new)), + ); + + let _ = parse_materials(value, &["material_one".to_string()], &textures).unwrap(); + } +} diff --git a/frontend/scene/src/parse/mod.rs b/frontend/scene/src/parse/mod.rs new file mode 100644 index 0000000..df7de4e --- /dev/null +++ b/frontend/scene/src/parse/mod.rs @@ -0,0 +1,220 @@ +use crate::parse::{ + camera::CameraLoadError, materials::MaterialLoadError, primitives::PrimitiveLoadError, + sky::SkyLoadError, textures::TextureLoadError, +}; +use std::io; +use std::path::Path; +use toml::Value; + +pub mod camera; +pub mod materials; +pub mod primitives; +pub mod sky; +pub mod textures; + +#[macro_export] +macro_rules! try_into { + ($type:ident, $into_type:ident, $error:ident, $constructor:expr, $( $x:ident ), *) => { + impl TryInto<$into_type> for $type { + type Error = $error; + + fn try_into(self) -> Result<$into_type, Self::Error> { + $( + let $x = match self.$x { + Some(v) => v, + None => return Err($error::MissingField), + }; + )* + + Ok($constructor) + } + } + }; +} + +pub trait Load { + type LoadType; + fn derive_from(&mut self, other: Self); + fn load(&mut self, _: &Self::LoadType) -> Result<(), Error> { + Ok(()) + } + fn get_definition(&self) -> Option; + fn get_plural<'a>() -> &'a str; +} + +#[derive(Debug)] +pub enum Error { + IO(io::Error), + Parse(toml::de::Error), + TextureLoad(TextureLoadError), + MaterialLoad(MaterialLoadError), + PrimitiveLoad(PrimitiveLoadError), + SkyLoad(SkyLoadError), + CameraLoad(CameraLoadError), + NoValue(String), + WrongType(String), +} + +macro_rules! from { + ($error:path, $variant:ident) => { + impl From<$error> for Error { + fn from(error: $error) -> Self { + Error::$variant(error) + } + } + }; +} + +from!(io::Error, IO); +from!(toml::de::Error, Parse); +from!(TextureLoadError, TextureLoad); +from!(MaterialLoadError, MaterialLoad); +from!(PrimitiveLoadError, PrimitiveLoad); +from!(SkyLoadError, SkyLoad); +from!(CameraLoadError, CameraLoad); + +pub fn parse_value_from_file(filepath: &Path) -> Result { + let data = std::fs::read_to_string(filepath)?; + + Ok(data.parse::()?) +} + +pub fn parse_items<'de, O, L, T>(data: Value, names: &[String], other: &O) -> Result, Error> +where + L: Load + serde::Deserialize<'de> + Load + TryInto, + Error: From<>::Error>, +{ + let mut loaded_names: Vec = Vec::new(); + let value = match data.get(L::get_plural()) { + Some(value) => value, + None => return Err(Error::NoValue(L::get_plural().to_string())), + }; + + match &value { + Value::Array(vec_values) => { + for value in vec_values { + let name = value.clone().try_into()?; + loaded_names.push(name); + } + } + _ => return Err(Error::WrongType(L::get_plural().to_string())), + }; + + for name in names.iter() { + if !loaded_names.contains(name) { + return Err(Error::NoValue(name.to_owned())); + } + } + + let mut loads = Vec::new(); + for string_name in names.iter() { + let value = match data.get(string_name) { + Some(value) => value, + None => return Err(Error::NoValue(string_name.clone())), + }; + match value { + val @ Value::Table(_) => { + loads.push(parse_item::(val.clone(), other)?); + } + _ => return Err(Error::WrongType(string_name.clone())), + } + } + + let items = loads + .into_iter() + .map(|l| l.try_into()) + .collect::, _>>()?; + + Ok(items) +} + +pub fn parse_item<'de, O, L>(data: Value, other: &O) -> Result +where + L: Load + serde::Deserialize<'de> + Load, +{ + let mut load: L = data.try_into()?; + let definition_data: Option = match load.get_definition() { + Some(ref def) => { + let data = std::fs::read_to_string(def)?.parse::()?; + Some(parse_item(data, other)?) + } + _ => None, + }; + + if let Some(data) = definition_data { + load.derive_from(data) + }; + + load.load(other)?; + + Ok(load) +} + +#[cfg(test)] +mod tests { + + pub const EXAMPLE_TOML: &str = r#" +# alternative format (must be up top or they will be a subtable) +texture_two = { type = "image", path = "./textures/rock.png" } +texture_four = { type = "solid_colour", colour = [0.5, 0.5, 0.5] } +texture_three = { definition = "./other.toml" } # can define stuff externally +material_two = { definition = "./other.toml" } + +# these are pub +scenes = ["scene_one"] +materials = ["material_one"] +primitives = ["sphere_one"] +textures = ["texture_four", "texture_one"] +skies = ["sky_one"] +cameras = ["camera_one"] + +# An example scene +[scene_one.materials] +default = "m1" +mat1 = "material_one" +mat2 = "material_two" + +[scene_one.primitives] +sphere_one = { type = "sphere", radius = 0.5, position = [10.5, 1.5, -3.5], mat = "mat2" } + +[camera_one] +type = "simple" +origin = [0.0, -1.0, 1.0] +lookat = [0.0, 0.0, 0.0] +vup = [0.0, 0.0, 1.0] +fov = 36.0 +focus_dist = 10.0 +aperture = 2.0 +aspect_ratio = 1.777777777 + +[material_one] +type = "lambertian" +absorbtion = 0.5 +texture = "texture_one" + +[sky_one] +texture = "texture_one" +sample_res = [ 20, 40 ] + +[texture_one] +type = "lerp" +colour_one = [ 0.0, 0.0, 0.0 ] +colour_two = [ 1.0, 1.0, 1.0 ] + +[sphere_one] +type = "sphere" +radius = 0.5 +position = [0.0, 0.0, 0.5] +material = "material_one" + +[a_triangle] +type = "triangle" +points = [ [0.0, 0.0, 0.0], [0.0, 0.5, 0.0], [0.5, 0.5, 0.0] ] +normals = [ [0.0, 0.0, 1.0] ] +mat = "material_two" + +[some_mesh] +type = "triangle_mesh" +data = "./models/dragon" +mat = "material_two""#; +} diff --git a/frontend/scene/src/parse/primitives.rs b/frontend/scene/src/parse/primitives.rs new file mode 100644 index 0000000..f9ac0aa --- /dev/null +++ b/frontend/scene/src/parse/primitives.rs @@ -0,0 +1,131 @@ +use crate::implementations::{sphere::Sphere, AllMaterials, AllPrimitives, AllTextures}; +use crate::parse::*; +use crate::*; +use serde::Deserialize; +use std::collections::HashMap; +use std::sync::Arc; +use toml::Value; + +type Material = AllMaterials; + +pub fn parse_primitives( + data: Value, + primitive_names: &[String], + materials_map: &HashMap>, +) -> Result>, Error> { + parse_items::< + HashMap>>, + PrimitiveLoad, + AllPrimitives>, + >(data, primitive_names, materials_map) +} + +#[derive(Deserialize, Debug)] +pub struct PrimitiveLoad { + definition: Option, + #[serde(rename = "type")] + primitive_type: Option, + material: Option, + #[serde(skip)] + material_instance: Option>>, + position: Option<[Float; 3]>, + radius: Option, +} + +impl Load for PrimitiveLoad { + type LoadType = HashMap>>; + fn load(&mut self, materials_map: &Self::LoadType) -> Result<(), Error> { + let mat_name = match &self.material { + Some(mat_name) => mat_name, + None => return Err(PrimitiveLoadError::MissingField.into()), + }; + + match materials_map.get(mat_name) { + Some(mat) => self.material_instance = Some(mat.clone()), + None => return Err(PrimitiveLoadError::MaterialNotFound.into()), + } + + Ok(()) + } + fn derive_from(&mut self, other: Self) { + if self.primitive_type.is_none() { + self.primitive_type = other.primitive_type; + } + if self.material.is_none() { + self.material = other.material; + } + if self.radius.is_none() { + self.radius = other.radius; + } + } + fn get_definition(&self) -> Option { + self.definition.clone() + } + fn get_plural<'a>() -> &'a str { + "primitives" + } +} + +#[derive(Debug)] +pub enum PrimitiveLoadError { + MissingField, + InvalidType, + MaterialNotFound, +} + +type SphereInstance = Sphere; + +try_into!( + PrimitiveLoad, + SphereInstance, + PrimitiveLoadError, + Sphere::new(position.into(), radius, &material_instance), + position, + radius, + material_instance +); + +impl TryInto> for PrimitiveLoad { + type Error = PrimitiveLoadError; + + fn try_into(self) -> Result, Self::Error> { + match self.primitive_type.clone() { + Some(m_type) => match &m_type[..] { + "sphere" => Ok(AllPrimitives::Sphere(self.try_into()?)), + _ => Err(PrimitiveLoadError::InvalidType), + }, + _ => Err(PrimitiveLoadError::MissingField), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse::materials::parse_materials; + use crate::parse::tests::EXAMPLE_TOML; + use crate::parse::textures::parse_textures; + + #[test] + pub fn parse_primitive() { + let value = EXAMPLE_TOML.parse::().unwrap(); + let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; + let textures = parse_textures(value.clone(), &texture_names).unwrap(); + + let textures: HashMap> = HashMap::from_iter( + texture_names + .into_iter() + .zip(textures.into_iter().map(Arc::new)), + ); + + let material_names = vec!["material_one".to_string()]; + let materials = parse_materials(value.clone(), &material_names, &textures).unwrap(); + let materials: HashMap> = HashMap::from_iter( + material_names + .into_iter() + .zip(materials.into_iter().map(Arc::new)), + ); + + let _ = parse_primitives(value, &["sphere_one".to_string()], &materials).unwrap(); + } +} diff --git a/frontend/scene/src/parse/sky.rs b/frontend/scene/src/parse/sky.rs new file mode 100644 index 0000000..ac5a9f9 --- /dev/null +++ b/frontend/scene/src/parse/sky.rs @@ -0,0 +1,99 @@ +use crate::parse::*; +use implementations::AllTextures; +use serde::Deserialize; +use std::collections::HashMap; + +use toml::Value; + +use crate::*; + +pub fn parse_skies( + data: Value, + sky_names: &[String], + textures_map: &HashMap>, +) -> Result>, Error> { + parse_items::>, SkyLoad, Sky>( + data, + sky_names, + textures_map, + ) +} + +#[derive(Deserialize, Debug)] +pub struct SkyLoad { + definition: Option, + texture: Option, + #[serde(skip)] + texture_instance: Option>, + sample_res: Option<[usize; 2]>, +} + +impl Load for SkyLoad { + type LoadType = HashMap>; + fn derive_from(&mut self, other: Self) { + if self.texture.is_none() { + self.texture = other.texture; + } + if self.sample_res.is_none() { + self.sample_res = other.sample_res; + } + } + fn load(&mut self, textures_map: &Self::LoadType) -> Result<(), Error> { + let tex_name = match &self.texture { + Some(tex_name) => tex_name, + None => return Err(MaterialLoadError::MissingField.into()), + }; + + match textures_map.get(tex_name) { + Some(tex) => self.texture_instance = Some(tex.clone()), + None => return Err(MaterialLoadError::TextureNotFound.into()), + } + + Ok(()) + } + fn get_definition(&self) -> Option { + self.definition.clone() + } + fn get_plural<'a>() -> &'a str { + "skies" + } +} + +type SkyInstance = Sky; + +try_into!( + SkyLoad, + SkyInstance, + SkyLoadError, + Sky::new(&texture_instance, (sample_res[0], sample_res[1])), + texture_instance, + sample_res +); + +#[derive(Debug)] +pub enum SkyLoadError { + MissingField, + InvalidType, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse::tests::EXAMPLE_TOML; + use crate::parse::textures::parse_textures; + + #[test] + pub fn parse_sky() { + let value = EXAMPLE_TOML.parse::().unwrap(); + let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; + let textures = parse_textures(value.clone(), &texture_names).unwrap(); + + let textures: HashMap> = HashMap::from_iter( + texture_names + .into_iter() + .zip(textures.into_iter().map(Arc::new)), + ); + + let _ = parse_skies(value, &["sky_one".to_string()], &textures).unwrap(); + } +} diff --git a/frontend/scene/src/parse/textures.rs b/frontend/scene/src/parse/textures.rs new file mode 100644 index 0000000..15a6819 --- /dev/null +++ b/frontend/scene/src/parse/textures.rs @@ -0,0 +1,126 @@ +use crate::parse::*; +use implementations::{AllTextures, CheckeredTexture, ImageTexture, Lerp, Perlin, SolidColour}; +use serde::Deserialize; +use std::path::PathBuf; + +use toml::Value; + +use crate::*; + +pub fn parse_textures(data: Value, texture_names: &[String]) -> Result, Error> { + parse_items::<(), TextureLoad, AllTextures>(data, texture_names, &()) +} + +#[derive(Deserialize, Debug)] +pub struct TextureLoad { + definition: Option, + #[serde(rename = "type")] + tex_type: Option, + colour: Option<[Float; 3]>, + colour_one: Option<[Float; 3]>, + colour_two: Option<[Float; 3]>, + path: Option, +} + +#[derive(Debug)] +pub enum TextureLoadError { + MissingField, + InvalidType, +} + +impl Load for TextureLoad { + type LoadType = (); + fn derive_from(&mut self, other: Self) { + if self.tex_type.is_none() { + self.tex_type = other.tex_type; + } + if self.colour.is_none() { + self.colour = other.colour; + } + if self.colour_one.is_none() { + self.colour_one = other.colour_one; + } + if self.colour_two.is_none() { + self.colour_two = other.colour_two; + } + if self.path.is_none() { + self.path = other.path; + } + } + fn get_definition(&self) -> Option { + self.definition.clone() + } + fn get_plural<'a>() -> &'a str { + "textures" + } +} + +try_into!( + TextureLoad, + SolidColour, + TextureLoadError, + SolidColour::new(colour.into()), + colour +); + +try_into!( + TextureLoad, + ImageTexture, + TextureLoadError, + ImageTexture::new(&path), + path +); + +try_into!( + TextureLoad, + CheckeredTexture, + TextureLoadError, + CheckeredTexture::new(colour_one.into(), colour_two.into()), + colour_one, + colour_two +); + +try_into!( + TextureLoad, + Lerp, + TextureLoadError, + Lerp::new(colour_one.into(), colour_two.into()), + colour_one, + colour_two +); + +try_into!(TextureLoad, Perlin, TextureLoadError, Perlin::new(),); + +impl TryInto for TextureLoad { + type Error = TextureLoadError; + + fn try_into(self) -> Result { + match self.tex_type.clone() { + Some(t_type) => match &t_type[..] { + "solid_colour" => Ok(AllTextures::SolidColour(self.try_into()?)), + "lerp" => Ok(AllTextures::Lerp(self.try_into()?)), + "perlin" => Ok(AllTextures::Perlin(Box::new(self.try_into()?))), + "checkered" => Ok(AllTextures::CheckeredTexture(self.try_into()?)), + "image" => Ok(AllTextures::ImageTexture(self.try_into()?)), + _ => Err(TextureLoadError::InvalidType), + }, + _ => Err(TextureLoadError::MissingField), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse::tests::EXAMPLE_TOML; + + #[test] + pub fn parse_texture() { + let value = EXAMPLE_TOML.parse::().unwrap(); + let _ = parse_textures( + value, + &["texture_four".to_string(), "texture_one".to_string()], + ) + .unwrap(); + } +} diff --git a/frontend/src/generate.rs b/frontend/src/generate.rs index 8625854..ddd95cb 100644 --- a/frontend/src/generate.rs +++ b/frontend/src/generate.rs @@ -1,10 +1,10 @@ use crate::{utility::create_bvh_with_info, *}; -use implementations::{ +use rand::{distributions::Alphanumeric, rngs::SmallRng, thread_rng, Rng, SeedableRng}; +use rand_seeder::Seeder; +use scene::implementations::{ random_sampler::RandomSampler, rt_core::Float, split::SplitType, AllMaterials, AllPrimitives, AllTextures, Bvh, Lambertian, }; -use rand::{distributions::Alphanumeric, rngs::SmallRng, thread_rng, Rng, SeedableRng}; -use rand_seeder::Seeder; use scene::*; type MaterialType = AllMaterials; diff --git a/frontend/src/load_model.rs b/frontend/src/load_model.rs index 4c42078..785b891 100644 --- a/frontend/src/load_model.rs +++ b/frontend/src/load_model.rs @@ -1,4 +1,4 @@ -use implementations::{ +use scene::implementations::{ rt_core::*, triangle::{MeshData, MeshTriangle}, AllMaterials, AllPrimitives, AllTextures, diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 8580ecb..4e216bb 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -21,7 +21,7 @@ use { winit::event_loop::EventLoopProxy, }; -use implementations::rt_core::{Float, SamplerProgress}; +use scene::rt_core::{Float, SamplerProgress}; use std::env; #[cfg(feature = "gui")] diff --git a/frontend/src/parameters.rs b/frontend/src/parameters.rs index 43d448f..dec980a 100644 --- a/frontend/src/parameters.rs +++ b/frontend/src/parameters.rs @@ -1,8 +1,8 @@ use crate::generate::SceneType; use chrono::Local; -use implementations::{ +use scene::{ + implementations::split::SplitType, rt_core::{Float, RenderMethod, RenderOptions}, - split::SplitType, }; use std::process; diff --git a/frontend/src/utility.rs b/frontend/src/utility.rs index 48f6c2d..4c0d557 100644 --- a/frontend/src/utility.rs +++ b/frontend/src/utility.rs @@ -1,5 +1,8 @@ use chrono::Local; -use implementations::{aabb::AABound, rt_core::*, split::SplitType, Axis, Bvh}; +use scene::{ + implementations::{aabb::AABound, split::SplitType, Axis, Bvh}, + rt_core::*, +}; use std::{ io::{stdout, Write}, process, @@ -203,7 +206,7 @@ pub fn rotate_around_axis(point: &mut Vec3, axis: Axis, sin: Float, cos: Float) mod tests { use super::*; - use implementations::rt_core::PI; + use scene::implementations::rt_core::PI; #[test] fn rotation() { diff --git a/implementations/src/cameras/mod.rs b/implementations/src/cameras/mod.rs index 72eb998..8ee85bd 100644 --- a/implementations/src/cameras/mod.rs +++ b/implementations/src/cameras/mod.rs @@ -1,5 +1,6 @@ use crate::{rt_core::*, utility::random_float}; +#[derive(Debug)] pub struct SimpleCamera { pub viewport_width: Float, pub viewport_height: Float, diff --git a/implementations/src/samplers/mod.rs b/implementations/src/samplers/mod.rs index 8b7d76d..6e69200 100644 --- a/implementations/src/samplers/mod.rs +++ b/implementations/src/samplers/mod.rs @@ -5,6 +5,7 @@ use std::sync::Arc; pub mod random_sampler; +#[derive(Debug)] pub struct Sky { texture: Arc, pub distribution: Option, diff --git a/implementations/src/textures/mod.rs b/implementations/src/textures/mod.rs index c8570d6..642cbbf 100644 --- a/implementations/src/textures/mod.rs +++ b/implementations/src/textures/mod.rs @@ -2,6 +2,7 @@ use crate::rt_core::*; use image::{io::Reader, GenericImageView}; use proc::Texture; use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; +use std::path::Path; const PERLIN_RVECS: usize = 256; @@ -24,8 +25,8 @@ pub enum AllTextures { #[derive(Debug)] pub struct CheckeredTexture { - primary_colour: Vec3, - secondary_colour: Vec3, + colour_one: Vec3, + colour_two: Vec3, } pub fn generate_values(texture: &T, sample_res: (usize, usize)) -> Vec { @@ -49,10 +50,10 @@ pub fn generate_values(texture: &T, sample_res: (usize, usize)) -> V } impl CheckeredTexture { - pub fn new(primary_colour: Vec3, secondary_colour: Vec3) -> Self { + pub fn new(colour_one: Vec3, colour_two: Vec3) -> Self { CheckeredTexture { - primary_colour, - secondary_colour, + colour_one, + colour_two, } } } @@ -61,9 +62,9 @@ impl Texture for CheckeredTexture { fn colour_value(&self, _: Vec3, point: Vec3) -> Vec3 { let sign = (10.0 * point.x).sin() * (10.0 * point.y).sin() * (10.0 * point.z).sin(); if sign > 0.0 { - self.primary_colour + self.colour_one } else { - self.secondary_colour + self.colour_two } } fn requires_uv(&self) -> bool { @@ -205,7 +206,10 @@ pub struct ImageTexture { } impl ImageTexture { - pub fn new(filepath: &str) -> Self { + pub fn new

(filepath: &P) -> Self + where + P: AsRef, + { // open image and get dimensions let img = match image::open(filepath) { diff --git a/implementations/tests/sampling.rs b/implementations/tests/sampling.rs index 342b944..22423f5 100644 --- a/implementations/tests/sampling.rs +++ b/implementations/tests/sampling.rs @@ -168,7 +168,7 @@ pub fn bxdf_testing(sampler_res: (usize, usize)) -> (Sky, BvhType) primitives.push(glowy); primitives.push(glowy_two); - let image = |filepath| Arc::new(AllTextures::ImageTexture(ImageTexture::new(filepath))); + let image = |filepath| Arc::new(AllTextures::ImageTexture(ImageTexture::new(&filepath))); let bvh = Bvh::new(primitives, split::SplitType::Sah); //create_bvh_with_info(primitives, bvh_type); diff --git a/rt_core/src/vec.rs b/rt_core/src/vec.rs index d71b3d0..d987196 100644 --- a/rt_core/src/vec.rs +++ b/rt_core/src/vec.rs @@ -333,3 +333,15 @@ impl std::fmt::Display for Vec3 { write!(f, "({}, {}, {})", self.x, self.y, self.z) } } + +impl From<[Float; 3]> for Vec3 { + fn from(vec: [Float; 3]) -> Self { + Vec3::new(vec[0], vec[1], vec[2]) + } +} + +impl From<[Float; 2]> for Vec2 { + fn from(vec: [Float; 2]) -> Self { + Vec2::new(vec[0], vec[1]) + } +} From d95b81993734eec426ddbfb218c5b91eba438c74 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Sat, 21 Jan 2023 18:07:54 +1100 Subject: [PATCH 07/39] minor mis fix --- frontend/src/generate.rs | 2 +- rt_core/src/ray.rs | 56 +++++++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/frontend/src/generate.rs b/frontend/src/generate.rs index ddd95cb..c0759de 100644 --- a/frontend/src/generate.rs +++ b/frontend/src/generate.rs @@ -148,7 +148,7 @@ pub fn bxdf_testing(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { scene!( camera, - sky!(&image!("../res/skymaps/lilienstein.webp")), + sky!(&image!(&"../res/skymaps/lilienstein.webp".to_owned())), random_sampler!(), bvh ) diff --git a/rt_core/src/ray.rs b/rt_core/src/ray.rs index eafb99b..f916a28 100644 --- a/rt_core/src/ray.rs +++ b/rt_core/src/ray.rs @@ -136,7 +136,7 @@ impl Ray { let emission = mat.get_emission(&hit, wo); - let exit = mat.scatter_ray(ray, &hit); + let exit = mat.scatter_ray(&mut ray.clone(), &hit); output += emission; @@ -150,15 +150,18 @@ impl Ray { let mut depth = 1; while depth < MAX_DEPTH { - // light sampling - if let Some((l_pos, le, l_pdf)) = Ray::sample_lights_test(bvh, &hit, sky, &mat, wo) { - let l_wi = (l_pos - hit.point).normalised(); - let m_pdf = mat.scattering_pdf(&hit, wo, l_wi); - let mis_weight = power_heuristic(l_pdf, m_pdf); - - output += throughput * mat.eval(&hit, wo, l_wi) * mis_weight * le / l_pdf; + if !mat.is_delta() { + // light sampling + if let Some((l_pos, le, l_pdf)) = Ray::sample_lights_test(bvh, &hit, sky, &mat, wo) + { + let l_wi = (l_pos - hit.point).normalised(); + let m_pdf = mat.scattering_pdf(&hit, wo, l_wi); + let mis_weight = power_heuristic(l_pdf, m_pdf); + + output += throughput * mat.eval(&hit, wo, l_wi) * mis_weight * le / l_pdf; + } + ray_count += 1; } - ray_count += 1; // material sample and bounce let (m_wi, m_pdf) = match Ray::sample_material_test(bvh, &hit, sky, &mat, ray) { @@ -166,7 +169,11 @@ impl Ray { None => break, }; - throughput *= mat.eval(&hit, wo, m_wi) / mat.scattering_pdf(&hit, wo, m_wi); + throughput *= if mat.is_delta() { + mat.eval(&hit, wo, m_wi) + } else { + mat.eval_over_scattering_pdf(&hit, wo, m_wi) + }; let (surface_intersection, index) = match bvh.check_hit(ray) { Some((surface_intersection, index)) => (surface_intersection, index), @@ -177,18 +184,25 @@ impl Ray { }; if surface_intersection.material.get_emission(&hit, wo) != Vec3::zero() { - let light_pdf = if bvh.get_samplable().contains(&index) { - bvh.get_object(index).unwrap().scattering_pdf( - hit.point, - m_wi, - &surface_intersection.hit, - ) / bvh.get_samplable().len() as Float - } else { - 0.0 - }; - let mis_weight = power_heuristic(m_pdf, light_pdf); let le = surface_intersection.material.get_emission(&hit, wo); - output += throughput * mis_weight * le; + + if mat.is_delta() { + output += throughput * le; + } else { + let light_pdf = if bvh.get_samplable().contains(&index) { + bvh.get_object(index).unwrap().scattering_pdf( + hit.point, + m_wi, + &surface_intersection.hit, + ) / bvh.get_samplable().len() as Float + } else { + 0.0 + }; + let mis_weight = power_heuristic(m_pdf, light_pdf); + + output += throughput * mis_weight * le; + } + if surface_intersection.material.is_light() { break; } From dce5e9d38bf4609aa44758f1dff07e4debdee145 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Sun, 22 Jan 2023 15:44:06 +1100 Subject: [PATCH 08/39] Added scene parsing --- .../src/parse/acceleration_structures.rs | 126 ++++++++++++++ frontend/scene/src/parse/camera.rs | 2 +- frontend/scene/src/parse/materials.rs | 8 +- frontend/scene/src/parse/mod.rs | 33 ++-- frontend/scene/src/parse/primitives.rs | 12 +- frontend/scene/src/parse/scenes.rs | 164 ++++++++++++++++++ frontend/scene/src/parse/sky.rs | 8 +- frontend/scene/src/parse/textures.rs | 2 +- 8 files changed, 329 insertions(+), 26 deletions(-) create mode 100644 frontend/scene/src/parse/acceleration_structures.rs create mode 100644 frontend/scene/src/parse/scenes.rs diff --git a/frontend/scene/src/parse/acceleration_structures.rs b/frontend/scene/src/parse/acceleration_structures.rs new file mode 100644 index 0000000..0f6337e --- /dev/null +++ b/frontend/scene/src/parse/acceleration_structures.rs @@ -0,0 +1,126 @@ +use crate::parse::parse_item; +use crate::parse::Error; +use crate::parse::Load; +use implementations::AllMaterials; +use implementations::AllPrimitives; +use implementations::Bvh; +use toml::Value; + +use implementations::split::SplitType; +use implementations::AllTextures; + +use crate::*; +use serde::Deserialize; + +type PrimitiveInstance = AllPrimitives>; +type BvhInstance = Bvh>; + +pub fn parse_accleration_structure( + data: Value, + name: String, + primitives_map: &mut Vec, +) -> Result { + let data = match data.get(name.clone()) { + Some(value) => value, + None => return Err(Error::NoValue(name)), + }; + Ok( + parse_item::, AccelerationStructureLoad>( + data.clone(), + primitives_map, + )? + .try_into()?, + ) +} + +#[derive(Deserialize, Debug)] +pub struct AccelerationStructureLoad { + definition: Option, + #[serde(rename = "type")] + split_type: Option, + #[serde(skip)] + primitive_instances: Vec, +} + +#[derive(Debug)] +pub enum AccelerationStructureLoadError { + MissingField, + InvalidType, + PrimitiveNotFound, +} + +impl Load for AccelerationStructureLoad { + type LoadType = Vec; + fn load(&mut self, primitives_vec: &mut Self::LoadType) -> Result<(), Error> { + self.primitive_instances = primitives_vec.drain(..).collect(); + + Ok(()) + } + fn derive_from(&mut self, other: Self) { + if self.split_type.is_none() { + self.split_type = other.split_type; + } + } + fn get_definition(&self) -> Option { + self.definition.clone() + } + fn get_plural<'a>() -> &'a str { + unreachable!() + } +} + +impl TryInto for AccelerationStructureLoad { + type Error = AccelerationStructureLoadError; + + fn try_into(self) -> Result { + let split_type = match self.split_type { + Some(v) => v, + None => return Err(AccelerationStructureLoadError::MissingField), + }; + + Ok(Bvh::new(self.primitive_instances, { + match &split_type[..] { + "sah" => SplitType::Sah, + "middle" => SplitType::Middle, + "equal_counts" => SplitType::EqualCounts, + _ => return Err(AccelerationStructureLoadError::InvalidType), + } + })) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse::materials::parse_materials; + use crate::parse::primitives::parse_primitives; + use crate::parse::tests::EXAMPLE_TOML; + use crate::parse::textures::parse_textures; + use std::collections::HashMap; + + #[test] + pub fn parse_bvh() { + let value = EXAMPLE_TOML.parse::().unwrap(); + let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; + let textures = parse_textures(value.clone(), &texture_names).unwrap(); + + let mut textures: HashMap> = HashMap::from_iter( + texture_names + .into_iter() + .zip(textures.into_iter().map(Arc::new)), + ); + + let material_names = vec!["material_one".to_string()]; + let materials = parse_materials(value.clone(), &material_names, &mut textures).unwrap(); + let mut materials: HashMap>> = HashMap::from_iter( + material_names + .into_iter() + .zip(materials.into_iter().map(Arc::new)), + ); + + let mut primitives = + parse_primitives(value.clone(), &["sphere_one".to_string()], &mut materials).unwrap(); + + let _ = parse_accleration_structure(value, "bvh_one".to_string(), &mut primitives).unwrap(); + } +} diff --git a/frontend/scene/src/parse/camera.rs b/frontend/scene/src/parse/camera.rs index 8e936c1..9f13dab 100644 --- a/frontend/scene/src/parse/camera.rs +++ b/frontend/scene/src/parse/camera.rs @@ -7,7 +7,7 @@ use toml::Value; use crate::*; pub fn parse_cameras(data: Value, camera_names: &[String]) -> Result, Error> { - parse_items::<(), CameraLoad, SimpleCamera>(data, camera_names, &()) + parse_items::<(), CameraLoad, SimpleCamera>(data, camera_names, &mut ()) } #[derive(Debug)] diff --git a/frontend/scene/src/parse/materials.rs b/frontend/scene/src/parse/materials.rs index 44991e5..015e4c3 100644 --- a/frontend/scene/src/parse/materials.rs +++ b/frontend/scene/src/parse/materials.rs @@ -10,7 +10,7 @@ use super::parse_items; pub fn parse_materials( data: Value, material_names: &[String], - textures_map: &HashMap>, + textures_map: &mut HashMap>, ) -> Result>, Error> { parse_items::>, MaterialLoad, AllMaterials>( data, @@ -43,7 +43,7 @@ impl Load for MaterialLoad { self.absorbtion = other.absorbtion; } } - fn load(&mut self, textures_map: &Self::LoadType) -> Result<(), Error> { + fn load(&mut self, textures_map: &mut Self::LoadType) -> Result<(), Error> { let tex_name = match &self.texture { Some(tex_name) => tex_name, None => return Err(MaterialLoadError::MissingField.into()), @@ -108,12 +108,12 @@ mod tests { let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; let textures = parse_textures(value.clone(), &texture_names).unwrap(); - let textures: HashMap> = HashMap::from_iter( + let mut textures: HashMap> = HashMap::from_iter( texture_names .into_iter() .zip(textures.into_iter().map(Arc::new)), ); - let _ = parse_materials(value, &["material_one".to_string()], &textures).unwrap(); + let _ = parse_materials(value, &["material_one".to_string()], &mut textures).unwrap(); } } diff --git a/frontend/scene/src/parse/mod.rs b/frontend/scene/src/parse/mod.rs index df7de4e..d6c7739 100644 --- a/frontend/scene/src/parse/mod.rs +++ b/frontend/scene/src/parse/mod.rs @@ -1,3 +1,5 @@ +use crate::parse::acceleration_structures::AccelerationStructureLoadError; +use crate::parse::scenes::SceneLoadError; use crate::parse::{ camera::CameraLoadError, materials::MaterialLoadError, primitives::PrimitiveLoadError, sky::SkyLoadError, textures::TextureLoadError, @@ -6,9 +8,11 @@ use std::io; use std::path::Path; use toml::Value; +pub mod acceleration_structures; pub mod camera; pub mod materials; pub mod primitives; +pub mod scenes; pub mod sky; pub mod textures; @@ -35,7 +39,7 @@ macro_rules! try_into { pub trait Load { type LoadType; fn derive_from(&mut self, other: Self); - fn load(&mut self, _: &Self::LoadType) -> Result<(), Error> { + fn load(&mut self, _: &mut Self::LoadType) -> Result<(), Error> { Ok(()) } fn get_definition(&self) -> Option; @@ -51,6 +55,8 @@ pub enum Error { PrimitiveLoad(PrimitiveLoadError), SkyLoad(SkyLoadError), CameraLoad(CameraLoadError), + AccelerationStructureLoad(AccelerationStructureLoadError), + SceneLoad(SceneLoadError), NoValue(String), WrongType(String), } @@ -72,6 +78,8 @@ from!(MaterialLoadError, MaterialLoad); from!(PrimitiveLoadError, PrimitiveLoad); from!(SkyLoadError, SkyLoad); from!(CameraLoadError, CameraLoad); +from!(AccelerationStructureLoadError, AccelerationStructureLoad); +from!(SceneLoadError, SceneLoad); pub fn parse_value_from_file(filepath: &Path) -> Result { let data = std::fs::read_to_string(filepath)?; @@ -79,7 +87,11 @@ pub fn parse_value_from_file(filepath: &Path) -> Result { Ok(data.parse::()?) } -pub fn parse_items<'de, O, L, T>(data: Value, names: &[String], other: &O) -> Result, Error> +pub fn parse_items<'de, O, L, T>( + data: Value, + names: &[String], + other: &mut O, +) -> Result, Error> where L: Load + serde::Deserialize<'de> + Load + TryInto, Error: From<>::Error>, @@ -128,7 +140,7 @@ where Ok(items) } -pub fn parse_item<'de, O, L>(data: Value, other: &O) -> Result +pub fn parse_item<'de, O, L>(data: Value, other: &mut O) -> Result where L: Load + serde::Deserialize<'de> + Load, { @@ -169,13 +181,14 @@ skies = ["sky_one"] cameras = ["camera_one"] # An example scene -[scene_one.materials] -default = "m1" -mat1 = "material_one" -mat2 = "material_two" - -[scene_one.primitives] -sphere_one = { type = "sphere", radius = 0.5, position = [10.5, 1.5, -3.5], mat = "mat2" } +[scene_one] +primitives = ["sphere_one", "a_triangle"] +bvh = "bvh_one" +camera = "camera_one" +sky = "sky_one" + +[bvh_one] +type = "sah" [camera_one] type = "simple" diff --git a/frontend/scene/src/parse/primitives.rs b/frontend/scene/src/parse/primitives.rs index f9ac0aa..31ffe0a 100644 --- a/frontend/scene/src/parse/primitives.rs +++ b/frontend/scene/src/parse/primitives.rs @@ -11,7 +11,7 @@ type Material = AllMaterials; pub fn parse_primitives( data: Value, primitive_names: &[String], - materials_map: &HashMap>, + materials_map: &mut HashMap>, ) -> Result>, Error> { parse_items::< HashMap>>, @@ -34,7 +34,7 @@ pub struct PrimitiveLoad { impl Load for PrimitiveLoad { type LoadType = HashMap>>; - fn load(&mut self, materials_map: &Self::LoadType) -> Result<(), Error> { + fn load(&mut self, materials_map: &mut Self::LoadType) -> Result<(), Error> { let mat_name = match &self.material { Some(mat_name) => mat_name, None => return Err(PrimitiveLoadError::MissingField.into()), @@ -112,20 +112,20 @@ mod tests { let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; let textures = parse_textures(value.clone(), &texture_names).unwrap(); - let textures: HashMap> = HashMap::from_iter( + let mut textures: HashMap> = HashMap::from_iter( texture_names .into_iter() .zip(textures.into_iter().map(Arc::new)), ); let material_names = vec!["material_one".to_string()]; - let materials = parse_materials(value.clone(), &material_names, &textures).unwrap(); - let materials: HashMap> = HashMap::from_iter( + let materials = parse_materials(value.clone(), &material_names, &mut textures).unwrap(); + let mut materials: HashMap> = HashMap::from_iter( material_names .into_iter() .zip(materials.into_iter().map(Arc::new)), ); - let _ = parse_primitives(value, &["sphere_one".to_string()], &materials).unwrap(); + let _ = parse_primitives(value, &["sphere_one".to_string()], &mut materials).unwrap(); } } diff --git a/frontend/scene/src/parse/scenes.rs b/frontend/scene/src/parse/scenes.rs new file mode 100644 index 0000000..6425c70 --- /dev/null +++ b/frontend/scene/src/parse/scenes.rs @@ -0,0 +1,164 @@ +use crate::parse::parse_item; +use crate::parse::Error; +use crate::parse::Load; +use crate::Scene; +use implementations::AllPrimitives; +use implementations::Bvh; +use implementations::SimpleCamera; +use implementations::Sky; +use toml::Value; + +use implementations::AllMaterials; + +use implementations::AllTextures; + +use crate::*; +use implementations::random_sampler::RandomSampler; +use serde::Deserialize; + +type Material = AllMaterials; +type Primitive = AllPrimitives; +type BvhInstance = Bvh>; +type SceneInstance = Scene; +type Passthrough = ( + Option, + Option, + Option>, +); + +pub fn parse_scene( + data: Value, + name: String, + passthrough_data: &mut Passthrough, +) -> Result { + let data = match data.get(name.clone()) { + Some(value) => value, + None => return Err(Error::NoValue(name)), + }; + let load: SceneLoad = parse_item::(data.clone(), passthrough_data)?; + Ok(load.try_into()?) +} + +#[derive(Deserialize)] +pub struct SceneLoad { + definition: Option, + camera: Option, + sky: Option, + bvh: Option, + #[serde(skip)] + bvh_instance: Option>, + #[serde(skip)] + camera_instance: Option, + #[serde(skip)] + sky_instance: Option>, +} + +#[derive(Debug)] +pub enum SceneLoadError { + MissingField, + InvalidType, + BvhNotFound, + CameraNotFound, + SkyNotFound, + PrimitiveNotFound, +} + +impl Load for SceneLoad { + type LoadType = ( + Option, + Option, + Option>, + ); + fn load(&mut self, data: &mut Self::LoadType) -> Result<(), Error> { + self.bvh_instance = data.0.take(); + self.camera_instance = data.1.take(); + self.sky_instance = data.2.take(); + + Ok(()) + } + fn derive_from(&mut self, other: Self) { + if self.camera.is_none() { + self.camera = other.camera; + } + if self.sky.is_none() { + self.sky = other.sky; + } + if self.bvh.is_none() { + self.bvh = other.bvh; + } + } + fn get_definition(&self) -> Option { + self.definition.clone() + } + fn get_plural<'a>() -> &'a str { + "scenes" + } +} + +try_into!( + SceneLoad, + SceneInstance, + SceneLoadError, + Scene::new( + Arc::new(camera_instance), + Arc::new(sky_instance), + Arc::new(RandomSampler {}), + Arc::new(bvh_instance), + ), + bvh_instance, + camera_instance, + sky_instance +); + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse::acceleration_structures::parse_accleration_structure; + use crate::parse::camera::parse_cameras; + use crate::parse::materials::parse_materials; + use crate::parse::primitives::parse_primitives; + use crate::parse::sky::parse_skies; + use crate::parse::tests::EXAMPLE_TOML; + use crate::parse::textures::parse_textures; + use std::collections::HashMap; + + #[test] + pub fn scene() { + let value = EXAMPLE_TOML.parse::().unwrap(); + let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; + let textures = parse_textures(value.clone(), &texture_names).unwrap(); + + let mut textures: HashMap> = HashMap::from_iter( + texture_names + .into_iter() + .zip(textures.into_iter().map(Arc::new)), + ); + + let material_names = vec!["material_one".to_string()]; + let materials = parse_materials(value.clone(), &material_names, &mut textures).unwrap(); + let mut materials: HashMap>> = HashMap::from_iter( + material_names + .into_iter() + .zip(materials.into_iter().map(Arc::new)), + ); + + let mut primitives = + parse_primitives(value.clone(), &["sphere_one".to_string()], &mut materials).unwrap(); + + let bvh = + parse_accleration_structure(value.clone(), "bvh_one".to_string(), &mut primitives) + .unwrap(); + let camera = parse_cameras(value.clone(), &["camera_one".to_string()]) + .unwrap() + .remove(0); + let sky = parse_skies(value.clone(), &["sky_one".to_string()], &mut textures) + .unwrap() + .remove(0); + let _ = parse_scene( + value, + "scene_one".to_string(), + &mut (Some(bvh), Some(camera), Some(sky)), + ) + .unwrap(); + } +} diff --git a/frontend/scene/src/parse/sky.rs b/frontend/scene/src/parse/sky.rs index ac5a9f9..86c7153 100644 --- a/frontend/scene/src/parse/sky.rs +++ b/frontend/scene/src/parse/sky.rs @@ -10,7 +10,7 @@ use crate::*; pub fn parse_skies( data: Value, sky_names: &[String], - textures_map: &HashMap>, + textures_map: &mut HashMap>, ) -> Result>, Error> { parse_items::>, SkyLoad, Sky>( data, @@ -38,7 +38,7 @@ impl Load for SkyLoad { self.sample_res = other.sample_res; } } - fn load(&mut self, textures_map: &Self::LoadType) -> Result<(), Error> { + fn load(&mut self, textures_map: &mut Self::LoadType) -> Result<(), Error> { let tex_name = match &self.texture { Some(tex_name) => tex_name, None => return Err(MaterialLoadError::MissingField.into()), @@ -88,12 +88,12 @@ mod tests { let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; let textures = parse_textures(value.clone(), &texture_names).unwrap(); - let textures: HashMap> = HashMap::from_iter( + let mut textures: HashMap> = HashMap::from_iter( texture_names .into_iter() .zip(textures.into_iter().map(Arc::new)), ); - let _ = parse_skies(value, &["sky_one".to_string()], &textures).unwrap(); + let _ = parse_skies(value, &["sky_one".to_string()], &mut textures).unwrap(); } } diff --git a/frontend/scene/src/parse/textures.rs b/frontend/scene/src/parse/textures.rs index 15a6819..430c781 100644 --- a/frontend/scene/src/parse/textures.rs +++ b/frontend/scene/src/parse/textures.rs @@ -8,7 +8,7 @@ use toml::Value; use crate::*; pub fn parse_textures(data: Value, texture_names: &[String]) -> Result, Error> { - parse_items::<(), TextureLoad, AllTextures>(data, texture_names, &()) + parse_items::<(), TextureLoad, AllTextures>(data, texture_names, &mut ()) } #[derive(Deserialize, Debug)] From e42fbf996a4e0d2c8f1d9d454eba781004a8fabd Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Sun, 22 Jan 2023 16:40:15 +1100 Subject: [PATCH 09/39] Changed generic in Primitive to associated type --- frontend/scene/src/lib.rs | 10 ++-------- frontend/src/utility.rs | 2 +- implementations/proc/src/lib.rs | 5 ++++- implementations/src/acceleration/mod.rs | 10 +++++----- implementations/src/primitives/sphere.rs | 3 ++- implementations/src/primitives/triangle.rs | 6 ++++-- implementations/src/samplers/random_sampler.rs | 2 +- rt_core/src/acceleration.rs | 2 +- rt_core/src/primitive.rs | 6 ++++-- rt_core/src/ray.rs | 18 ++++-------------- rt_core/src/sampler.rs | 2 +- 11 files changed, 29 insertions(+), 37 deletions(-) diff --git a/frontend/scene/src/lib.rs b/frontend/scene/src/lib.rs index 0300554..b84e09c 100644 --- a/frontend/scene/src/lib.rs +++ b/frontend/scene/src/lib.rs @@ -6,13 +6,7 @@ use implementations::{SimpleCamera, Sky, Texture}; use rt_core::*; use std::{marker::PhantomData, sync::Arc}; -pub struct Scene< - P: Primitive, - M: Scatter, - S: Sampler, - A: AccelerationStructure, - T: Texture, -> { +pub struct Scene, T: Texture> { pub acceleration_structure: Arc, pub camera: Arc, pub sampler: Arc, @@ -22,7 +16,7 @@ pub struct Scene< impl Scene where - P: Primitive + Send + Sync + 'static, + P: Primitive + Send + Sync + 'static, M: Scatter + Send + Sync + 'static, S: Sampler, A: AccelerationStructure + Send + Sync, diff --git a/frontend/src/utility.rs b/frontend/src/utility.rs index 4c0d557..51a7960 100644 --- a/frontend/src/utility.rs +++ b/frontend/src/utility.rs @@ -61,7 +61,7 @@ pub fn get_readable_duration(duration: Duration) -> String { days_string + &hours_string + &minutes_string + &seconds_string } -pub fn create_bvh_with_info + AABound, M: Scatter>( +pub fn create_bvh_with_info( primitives: Vec

, bvh_type: SplitType, ) -> Arc> { diff --git a/implementations/proc/src/lib.rs b/implementations/proc/src/lib.rs index 2f4d3fe..ba31130 100644 --- a/implementations/proc/src/lib.rs +++ b/implementations/proc/src/lib.rs @@ -177,7 +177,10 @@ pub fn derive_primitive(tokens: TokenStream) -> TokenStream { }); quote! { - impl #impl_generics Primitive #ty_generics for #enum_name #ty_generics #where_clause {#( #functions_primitive )*} + impl #impl_generics Primitive for #enum_name #ty_generics #where_clause { + type Material = M; + #( #functions_primitive )* + } impl #impl_generics AABound for #enum_name #ty_generics #where_clause { #( #functions_aabound )*} } .into() diff --git a/implementations/src/acceleration/mod.rs b/implementations/src/acceleration/mod.rs index 3fa8a3f..55d3aff 100644 --- a/implementations/src/acceleration/mod.rs +++ b/implementations/src/acceleration/mod.rs @@ -25,7 +25,7 @@ pub struct PrimitiveInfo { } impl PrimitiveInfo { - fn new + AABound, M: Scatter>(index: usize, primitive: &P) -> PrimitiveInfo { + fn new(index: usize, primitive: &P) -> PrimitiveInfo { let aabb = primitive.get_aabb(); let min = aabb.min; let max = aabb.max; @@ -38,7 +38,7 @@ impl PrimitiveInfo { } } -pub struct Bvh, M: Scatter> { +pub struct Bvh { split_type: SplitType, nodes: Vec, pub primitives: Vec

, @@ -48,7 +48,7 @@ pub struct Bvh, M: Scatter> { impl Bvh where - P: Primitive + AABound, + P: Primitive + AABound, M: Scatter, { pub fn new(primitives: Vec

, split_type: SplitType) -> Self { @@ -63,7 +63,7 @@ where .primitives .iter() .enumerate() - .map(|(index, primitive)| PrimitiveInfo::new(index, primitive)) + .map(|(index, primitive)| PrimitiveInfo::new::(index, primitive)) .collect(); bvh.build_bvh(&mut Vec::new(), 0, &mut primitives_info); @@ -179,7 +179,7 @@ where impl AccelerationStructure for Bvh where - P: Primitive, + P: Primitive, M: Scatter, { fn get_intersection_candidates(&self, ray: &Ray) -> Vec<(usize, usize)> { diff --git a/implementations/src/primitives/sphere.rs b/implementations/src/primitives/sphere.rs index 5221489..c6c71fd 100644 --- a/implementations/src/primitives/sphere.rs +++ b/implementations/src/primitives/sphere.rs @@ -26,10 +26,11 @@ where } #[allow(clippy::suspicious_operation_groupings)] -impl Primitive for Sphere +impl Primitive for Sphere where M: Scatter, { + type Material = M; fn get_int(&self, ray: &Ray) -> Option> { let dir = ray.direction; let center = self.center; diff --git a/implementations/src/primitives/triangle.rs b/implementations/src/primitives/triangle.rs index 235e5ee..3083559 100644 --- a/implementations/src/primitives/triangle.rs +++ b/implementations/src/primitives/triangle.rs @@ -215,10 +215,11 @@ pub fn triangle_intersection, M: Scatter>( )) } -impl Primitive for Triangle +impl Primitive for Triangle where M: Scatter, { + type Material = M; fn get_int(&self, ray: &Ray) -> Option> { triangle_intersection(self, ray) } @@ -246,10 +247,11 @@ where } } -impl Primitive for MeshTriangle +impl Primitive for MeshTriangle where M: Scatter, { + type Material = M; fn get_int(&self, ray: &Ray) -> Option> { triangle_intersection(self, ray) } diff --git a/implementations/src/samplers/random_sampler.rs b/implementations/src/samplers/random_sampler.rs index 35c629d..1ed98b0 100644 --- a/implementations/src/samplers/random_sampler.rs +++ b/implementations/src/samplers/random_sampler.rs @@ -17,7 +17,7 @@ impl Sampler for RandomSampler { mut presentation_update: Option<(&mut T, F)>, ) where C: Camera + Send + Sync, - P: Primitive + Sync + Send + 'static, + P: Primitive + Sync + Send + 'static, M: Scatter + Send + Sync + 'static, F: Fn(&mut T, &SamplerProgress, u64), A: AccelerationStructure + Send + Sync, diff --git a/rt_core/src/acceleration.rs b/rt_core/src/acceleration.rs index a6e6d8c..3eb3fef 100644 --- a/rt_core/src/acceleration.rs +++ b/rt_core/src/acceleration.rs @@ -2,7 +2,7 @@ use crate::{Primitive, Ray, Scatter, SurfaceIntersection}; pub trait AccelerationStructure where - P: Primitive, + P: Primitive, M: Scatter, { fn get_intersection_candidates(&self, ray: &Ray) -> Vec<(usize, usize)>; diff --git a/rt_core/src/primitive.rs b/rt_core/src/primitive.rs index 994ce93..b59471b 100644 --- a/rt_core/src/primitive.rs +++ b/rt_core/src/primitive.rs @@ -42,8 +42,10 @@ where } } -pub trait Primitive { - fn get_int(&self, _: &Ray) -> Option>; +pub trait Primitive { + type Material: Scatter; + + fn get_int(&self, _: &Ray) -> Option>; fn does_int(&self, ray: &Ray) -> bool { self.get_int(ray).is_some() } diff --git a/rt_core/src/ray.rs b/rt_core/src/ray.rs index f916a28..536bb19 100644 --- a/rt_core/src/ray.rs +++ b/rt_core/src/ray.rs @@ -55,7 +55,7 @@ impl Ray { self.origin + self.direction * t } - fn sample_lights_test, P: Primitive, M: Scatter, S: NoHit>( + fn sample_lights_test, P: Primitive, M: Scatter, S: NoHit>( bvh: &A, hit: &Hit, _sky: &S, @@ -97,12 +97,7 @@ impl Ray { } } - fn sample_material_test< - A: AccelerationStructure, - P: Primitive, - M: Scatter, - S: NoHit, - >( + fn sample_material_test, P: Primitive, M: Scatter, S: NoHit>( _bvh: &A, hit: &Hit, _sky: &S, @@ -117,7 +112,7 @@ impl Ray { } } - pub fn get_colour, P: Primitive, M: Scatter, S: NoHit>( + pub fn get_colour, P: Primitive, M: Scatter, S: NoHit>( ray: &mut Ray, sky: &S, bvh: &A, @@ -220,12 +215,7 @@ impl Ray { (output, ray_count) } - pub fn get_colour_naive< - A: AccelerationStructure, - P: Primitive, - M: Scatter, - S: NoHit, - >( + pub fn get_colour_naive, P: Primitive, M: Scatter, S: NoHit>( ray: &mut Ray, sky: &S, bvh: &A, diff --git a/rt_core/src/sampler.rs b/rt_core/src/sampler.rs index 99dede9..0d8a289 100644 --- a/rt_core/src/sampler.rs +++ b/rt_core/src/sampler.rs @@ -10,7 +10,7 @@ pub trait Sampler { _update_function: Option<(&mut T, F)>, ) where C: Camera + Send + Sync, - P: Primitive + Sync + Send + 'static, + P: Primitive + Sync + Send + 'static, M: Scatter + Send + Sync + 'static, F: Fn(&mut T, &SamplerProgress, u64), A: AccelerationStructure + Send + Sync, From b0546897e59ca7cb03ffcbf0b4d9839ca2160255 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 25 Jan 2023 22:30:49 +1100 Subject: [PATCH 10/39] Initial GGX VNDF routines added --- implementations/statistics/Cargo.toml | 1 + implementations/statistics/src/bxdfs.rs | 153 ++++++++++++++++-- implementations/statistics/src/chi_squared.rs | 8 +- .../statistics/src/spherical_sampling.rs | 72 ++++++++- 4 files changed, 217 insertions(+), 17 deletions(-) diff --git a/implementations/statistics/Cargo.toml b/implementations/statistics/Cargo.toml index 42e387d..5467767 100644 --- a/implementations/statistics/Cargo.toml +++ b/implementations/statistics/Cargo.toml @@ -9,6 +9,7 @@ num_cpus = "1.15" statrs = "0.16.0" rayon = "1.5.1" rt_core = { path = "../../rt_core" } +image = "0.24.5" [dev-dependencies] image = "0.24.5" diff --git a/implementations/statistics/src/bxdfs.rs b/implementations/statistics/src/bxdfs.rs index 82d6c4a..dcdd0ec 100644 --- a/implementations/statistics/src/bxdfs.rs +++ b/implementations/statistics/src/bxdfs.rs @@ -34,6 +34,15 @@ pub mod trowbridge_reitz { a_sq / (PI * tmp * tmp) } + pub fn alternative_d(alpha: Float, h: Vec3, cos_theta: Float) -> Float { + if cos_theta < 0.0 { + return 0.0; + } + let a_sq = alpha * alpha; + let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; + 1.0 / (PI * a_sq * tmp * tmp) + } + pub fn pdf_h(h: Vec3, alpha: Float) -> Float { // technically the paper has an .abs() but it isn't needed since h.z < 0 => D(m) = 0 d(alpha, h.z) * h.z @@ -50,18 +59,121 @@ pub mod trowbridge_reitz { h = -(incoming + outgoing).normalised(); } let cos_theta = normal.dot(h); - let d = { - if cos_theta <= 0.0 { - return 0.0; - } - let a_sq = alpha * alpha; - let tmp = cos_theta * cos_theta * (a_sq - 1.0) + 1.0; - a_sq / (PI * tmp * tmp) - }; + let d = d(alpha, cos_theta); d * h.dot(normal).abs() / (4.0 * outgoing.dot(h).abs()) } } +pub mod trowbridge_reitz_vndf { + use crate::*; + use rand::Rng; + + // All in local frame + pub fn d_ansiotropic(a_x: Float, a_y: Float, h: Vec3) -> Float { + let tmp = h.x * h.x / (a_x * a_x) + h.y * h.y / (a_y * a_y) + h.z * h.z; + 1.0 / (PI * a_x * a_y * tmp * tmp) + } + + pub fn d_isotropic(a: Float, h: Vec3) -> Float { + let a_sq = a * a; + + let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; + if (1.0 / (PI * a_sq * tmp * tmp)).is_nan() { + println!("{tmp} = ({}^2 + {}^2) / {a_sq} + {}^2", h.x, h.y, h.z); + } + 1.0 / (PI * a_sq * tmp * tmp) + } + + pub fn lambda_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { + let tmp = 1.0 + (a_x * a_x * v.x * v.x + a_y * a_y * v.y * v.y) / (v.z * v.z); + 0.5 * (tmp.sqrt() - 1.0) + } + + pub fn lambda_isotropic(a: Float, v: Vec3) -> Float { + let tmp = 1.0 + (a * a * (v.x * v.x + v.y * v.y)) / (v.z * v.z); + 0.5 * (tmp.sqrt() - 1.0) + } + + pub fn g1_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { + 1.0 / (1.0 + lambda_ansiotropic(a_x, a_y, v)) + } + pub fn g1_isotropic(a: Float, v: Vec3) -> Float { + 1.0 / (1.0 + lambda_isotropic(a, v)) + } + + pub fn vndf_ansiotropic(a_x: Float, a_y: Float, h: Vec3, v: Vec3) -> Float { + if h.z < 0.0 { + return 0.0; + } + g1_ansiotropic(a_x, a_y, v) * v.dot(h).max(0.0) * d_ansiotropic(a_x, a_y, h) / v.z + } + + pub fn vndf_isotropic(a: Float, h: Vec3, v: Vec3) -> Float { + if h.z < 0.0 { + return 0.0; + } + g1_isotropic(a, v) * v.dot(h).max(0.0) * d_isotropic(a, h) / v.z + } + pub fn sample_vndf_ansiotropic(a_x: Float, a_y: Float, v: Vec3, rng: &mut R) -> Vec3 { + // chapter 1 act 1 + // transform from ellipsoid configuration to hemisphere by multipling by the scaling factors on the x and y axies (a_x, a_y) + let v_hemisphere = Vec3::new(a_x * v.x, a_y * v.y, v.z).normalised(); + + // chapter 2 act 1 + + // interlude + let len_sq = v_hemisphere.x * v_hemisphere.x + v_hemisphere.y * v_hemisphere.y; + + let basis_two = if len_sq > 0.0 { + Vec3::new(-v_hemisphere.y, v_hemisphere.x, 0.0) / len_sq.sqrt() + } else { + Vec3::new(1.0, 0.0, 0.0) // len_sq = 0 implies v_hemisphere = Z, so X is a valid orthonormal basis + }; + let basis_three = v_hemisphere.cross(basis_two); // v_hemisphere is first basis + + // chapter 3 act 1 + let r = rng.gen::().sqrt(); + let phi = TAU * rng.gen::(); + let mut t = r * Vec2::new(phi.cos(), phi.sin()); + let s = 0.5 * (1.0 + v_hemisphere.z); + t.y = (1.0 - s) * (1.0 - t.x * t.x).sqrt() + s * t.y; + + // chapter 4 act 1 + let h_hemisphere = t.x * basis_two + + t.y * basis_three + + (1.0 - t.x * t.x - t.y * t.y).max(0.0).sqrt() * v_hemisphere; + + // chapter 5 final act + + // not dividing since microfacet normal is a covector + Vec3::new( + a_x * h_hemisphere.x, + a_y * h_hemisphere.y, + h_hemisphere.z.max(0.0), // avoid numerical errors + ) + .normalised() + } + + pub fn sample_vndf_isotropic(a: Float, v: Vec3, rng: &mut R) -> Vec3 { + sample_vndf_ansiotropic(a, a, v, rng) // cause I'm lazy + } + + pub fn sample_outgoing_isotropic(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + let h = sample_vndf_isotropic(a, incoming, rng); + 2.0 * incoming.dot(h) * h - incoming + } + + pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { + let mut h = (incoming + outgoing).normalised(); + if h.dot(normal) < 0.0 { + h = -(incoming + outgoing).normalised(); + } + let _cos_theta = normal.dot(h); + let vndf = vndf_ansiotropic(alpha, alpha, h, incoming); + vndf / (4.0 * incoming.dot(h)) + } +} + #[cfg(test)] pub mod test { use super::*; @@ -87,16 +199,29 @@ pub mod test { let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); } + #[test] + fn trowbridge_reitz_vndf_h() { + let mut rng = thread_rng(); + let incoming: Vec3 = generate_wi(&mut rng); + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| trowbridge_reitz_vndf::vndf_isotropic(alpha, outgoing, incoming); + let sample = |rng: &mut ThreadRng| { + trowbridge_reitz_vndf::sample_vndf_isotropic(alpha, incoming, rng) + }; + test_spherical_pdf("trowbrige reitz vndf h", &pdf, &sample, false); + } - /*#[test] - fn some_test() { + #[test] + fn trowbridge_reitz_vndf() { let mut rng = thread_rng(); let incoming: Vec3 = generate_wi(&mut rng); let alpha: Float = rng.gen(); let pdf = |outgoing: Vec3| { - + trowbridge_reitz_vndf::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) }; - let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); - test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); - }*/ + let sample = |rng: &mut ThreadRng| { + trowbridge_reitz_vndf::sample_outgoing_isotropic(alpha, incoming, rng) + }; + test_spherical_pdf("trowbrige reitz vndf", &pdf, &sample, false); + } } diff --git a/implementations/statistics/src/chi_squared.rs b/implementations/statistics/src/chi_squared.rs index 9f5d0dd..41e3473 100644 --- a/implementations/statistics/src/chi_squared.rs +++ b/implementations/statistics/src/chi_squared.rs @@ -7,7 +7,13 @@ pub fn chi2_probability(dof: f64, distance: f64) -> f64 { match distance.partial_cmp(&0.0).unwrap() { Less => panic!("distance < 0.0"), Equal => 1.0, - Greater => gamma_ur(dof * 0.5, distance * 0.5), + Greater => { + if distance.is_infinite() { + 0.0 + } else { + gamma_ur(dof * 0.5, distance * 0.5) + } + } } } diff --git a/implementations/statistics/src/spherical_sampling.rs b/implementations/statistics/src/spherical_sampling.rs index 2a18405..0e4d358 100644 --- a/implementations/statistics/src/spherical_sampling.rs +++ b/implementations/statistics/src/spherical_sampling.rs @@ -76,7 +76,7 @@ where panic!("reference pdf doesn't integrate to 1: {pdf_sum}"); } - let expected_values: Vec = expected_values + let mut expected_values: Vec = expected_values .into_iter() .map(|v| v * SAMPLES as Float) .collect(); @@ -117,7 +117,7 @@ where .collect() }) .collect(); - let sampled_values: Vec = (0..SAMPLE_LEN) + let mut sampled_values: Vec = (0..SAMPLE_LEN) .into_par_iter() .map(|i| { recursively_binary_average::( @@ -130,6 +130,74 @@ where let p_value = chi2_probability(df as f64, chi_squared as f64); if p_value < threshold as f64 { + let expected_abs_max = expected_values + .iter() + .max_by(|x, y| x.abs().partial_cmp(&y.abs()).unwrap()) + .unwrap() + .abs(); + + let sampled_abs_max = sampled_values + .iter() + .max_by(|x, y| x.abs().partial_cmp(&y.abs()).unwrap()) + .unwrap() + .abs(); + + let get_colour = |value: Float, max_abs_value: Float| -> Vec3 { + if value > 0.0 { + let t = value / max_abs_value; + + t * Vec3::new(1.0, 0.0, 0.0) + } else if value == 0.0 { + Vec3::new(0.0, 1.0, 0.0) + } else { + Vec3::new(0.0, 0.0, 1.0) + } + }; + + let transpose = |vec: &mut Vec| { + *vec = (0..(PHI_RES * THETA_RES)) + .map(|i| { + let y = i % PHI_RES; + let x = i / PHI_RES; + + vec[y * THETA_RES + x] + }) + .collect::>(); + }; + + transpose(&mut expected_values); + transpose(&mut sampled_values); + + let mut image = expected_values + .into_iter() + .map(|v| get_colour(v, expected_abs_max)) + .collect::>(); + image.extend( + (0..PHI_RES) + .map(|_| Vec3::new(0.12, 0.95, 0.95)) + .collect::>(), + ); + image.extend( + sampled_values + .into_iter() + .map(|v| get_colour(v, sampled_abs_max)) + .collect::>(), + ); + let image = image + .into_iter() + .flat_map(|v| [v.x, v.y, v.z]) + .map(|v| (v * 256.0).clamp(0.0, 255.0) as u8) + .collect::>(); + + image::save_buffer( + format!("{name}_failed_output_test_{i}.png"), + &image, + PHI_RES.try_into().unwrap(), + (THETA_RES * 2 + 1).try_into().unwrap(), + image::ColorType::Rgb8, + ) + .unwrap(); + panic!("{name}: recieved p value of {p_value} with {SAMPLES} samples averaged over {BATCHES} batches on test {i}/{NUMBER_TESTS}") } } From 9f0f7450d60ae8cbac9439194eab4a30e79366b6 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Thu, 26 Jan 2023 23:08:40 +1100 Subject: [PATCH 11/39] Changed AccelerationStructure to use associated traits --- frontend/scene/src/lib.rs | 12 ++++++-- implementations/src/acceleration/mod.rs | 4 ++- .../src/samplers/random_sampler.rs | 2 +- rt_core/src/acceleration.rs | 18 ++++++------ rt_core/src/ray.rs | 28 ++++++++++++++++--- rt_core/src/sampler.rs | 2 +- 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/frontend/scene/src/lib.rs b/frontend/scene/src/lib.rs index b84e09c..9184d4d 100644 --- a/frontend/scene/src/lib.rs +++ b/frontend/scene/src/lib.rs @@ -1,4 +1,4 @@ -pub mod parse; +//pub mod parse; pub use implementations::{self, rt_core}; @@ -6,7 +6,13 @@ use implementations::{SimpleCamera, Sky, Texture}; use rt_core::*; use std::{marker::PhantomData, sync::Arc}; -pub struct Scene, T: Texture> { +pub struct Scene< + P: Primitive, + M: Scatter, + S: Sampler, + A: AccelerationStructure, + T: Texture, +> { pub acceleration_structure: Arc, pub camera: Arc, pub sampler: Arc, @@ -19,7 +25,7 @@ where P: Primitive + Send + Sync + 'static, M: Scatter + Send + Sync + 'static, S: Sampler, - A: AccelerationStructure + Send + Sync, + A: AccelerationStructure + Send + Sync, T: Texture + Send + Sync, { pub fn new( diff --git a/implementations/src/acceleration/mod.rs b/implementations/src/acceleration/mod.rs index 55d3aff..aa04474 100644 --- a/implementations/src/acceleration/mod.rs +++ b/implementations/src/acceleration/mod.rs @@ -177,11 +177,13 @@ where } } -impl AccelerationStructure for Bvh +impl AccelerationStructure for Bvh where P: Primitive, M: Scatter, { + type Object = P; + type Material = M; fn get_intersection_candidates(&self, ray: &Ray) -> Vec<(usize, usize)> { let mut offset_len = Vec::new(); diff --git a/implementations/src/samplers/random_sampler.rs b/implementations/src/samplers/random_sampler.rs index 1ed98b0..8fba416 100644 --- a/implementations/src/samplers/random_sampler.rs +++ b/implementations/src/samplers/random_sampler.rs @@ -20,7 +20,7 @@ impl Sampler for RandomSampler { P: Primitive + Sync + Send + 'static, M: Scatter + Send + Sync + 'static, F: Fn(&mut T, &SamplerProgress, u64), - A: AccelerationStructure + Send + Sync, + A: AccelerationStructure + Send + Sync, S: NoHit + Send + Sync, { let channels = 3; diff --git a/rt_core/src/acceleration.rs b/rt_core/src/acceleration.rs index 3eb3fef..f494b51 100644 --- a/rt_core/src/acceleration.rs +++ b/rt_core/src/acceleration.rs @@ -1,21 +1,23 @@ use crate::{Primitive, Ray, Scatter, SurfaceIntersection}; -pub trait AccelerationStructure -where - P: Primitive, - M: Scatter, -{ +pub trait AccelerationStructure { + type Object: Primitive; + type Material: Scatter; fn get_intersection_candidates(&self, ray: &Ray) -> Vec<(usize, usize)>; - fn check_hit_index(&self, ray: &Ray, object_index: usize) -> Option>; + fn check_hit_index( + &self, + ray: &Ray, + object_index: usize, + ) -> Option>; - fn check_hit(&self, ray: &Ray) -> Option<(SurfaceIntersection, usize)>; + fn check_hit(&self, ray: &Ray) -> Option<(SurfaceIntersection, usize)>; fn get_samplable(&self) -> &[usize] { unimplemented!() } - fn get_object(&self, _index: usize) -> Option<&P> { + fn get_object(&self, _index: usize) -> Option<&Self::Object> { unimplemented!() } } diff --git a/rt_core/src/ray.rs b/rt_core/src/ray.rs index 536bb19..2b1a12b 100644 --- a/rt_core/src/ray.rs +++ b/rt_core/src/ray.rs @@ -55,7 +55,12 @@ impl Ray { self.origin + self.direction * t } - fn sample_lights_test, P: Primitive, M: Scatter, S: NoHit>( + fn sample_lights_test< + A: AccelerationStructure, + P: Primitive, + M: Scatter, + S: NoHit, + >( bvh: &A, hit: &Hit, _sky: &S, @@ -97,7 +102,12 @@ impl Ray { } } - fn sample_material_test, P: Primitive, M: Scatter, S: NoHit>( + fn sample_material_test< + A: AccelerationStructure, + P: Primitive, + M: Scatter, + S: NoHit, + >( _bvh: &A, hit: &Hit, _sky: &S, @@ -112,7 +122,12 @@ impl Ray { } } - pub fn get_colour, P: Primitive, M: Scatter, S: NoHit>( + pub fn get_colour< + A: AccelerationStructure, + P: Primitive, + M: Scatter, + S: NoHit, + >( ray: &mut Ray, sky: &S, bvh: &A, @@ -215,7 +230,12 @@ impl Ray { (output, ray_count) } - pub fn get_colour_naive, P: Primitive, M: Scatter, S: NoHit>( + pub fn get_colour_naive< + A: AccelerationStructure, + P: Primitive, + M: Scatter, + S: NoHit, + >( ray: &mut Ray, sky: &S, bvh: &A, diff --git a/rt_core/src/sampler.rs b/rt_core/src/sampler.rs index 0d8a289..84bbec4 100644 --- a/rt_core/src/sampler.rs +++ b/rt_core/src/sampler.rs @@ -13,7 +13,7 @@ pub trait Sampler { P: Primitive + Sync + Send + 'static, M: Scatter + Send + Sync + 'static, F: Fn(&mut T, &SamplerProgress, u64), - A: AccelerationStructure + Send + Sync, + A: AccelerationStructure + Send + Sync, S: NoHit + Send + Sync; } From 7be4bc2de4e4ee0a5455c5434c4a9faee0de2fcd Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Fri, 27 Jan 2023 16:25:07 +1100 Subject: [PATCH 12/39] Initial framework for rewrite --- frontend/scene/Cargo.toml | 6 +- frontend/scene/src/lib.rs | 163 ++++++++++++++++++ implementations/src/materials/emissive.rs | 2 +- implementations/src/materials/lambertian.rs | 2 +- implementations/src/materials/mod.rs | 2 +- implementations/src/materials/phong.rs | 2 +- implementations/src/materials/reflect.rs | 2 +- implementations/src/materials/refract.rs | 2 +- .../src/materials/trowbridge_reitz.rs | 2 +- implementations/src/textures/mod.rs | 12 +- 10 files changed, 180 insertions(+), 15 deletions(-) diff --git a/frontend/scene/Cargo.toml b/frontend/scene/Cargo.toml index 1e93988..a14d22a 100644 --- a/frontend/scene/Cargo.toml +++ b/frontend/scene/Cargo.toml @@ -7,10 +7,12 @@ edition = "2021" [dependencies] image = "0.23.14" -serde = { version = "1.0", features = ["derive"] } -implementations = {path = "../../implementations"} +serde = { version = "1.0", features = [ "derive" ] } +implementations = { path = "../../implementations" } toml = "0.5.10" wavefront_obj = "10.0.0" +ouroboros = "0.15.5" +bumpalo = "3.12.0" [features] f64 = ["implementations/f64"] \ No newline at end of file diff --git a/frontend/scene/src/lib.rs b/frontend/scene/src/lib.rs index 9184d4d..2393eb0 100644 --- a/frontend/scene/src/lib.rs +++ b/frontend/scene/src/lib.rs @@ -1,6 +1,7 @@ //pub mod parse; pub use implementations::{self, rt_core}; +use std::collections::HashMap; use implementations::{SimpleCamera, Sky, Texture}; use rt_core::*; @@ -56,3 +57,165 @@ where ) } } + +use bumpalo::Bump; +use ouroboros::self_referencing; + +/* +#[self_referencing] +struct ActualScene<'b, A: AccelerationStructure, M: Scatter + 'static> { + bump: Bump, + #[borrows(bump)] + #[covariant] + scene: Scene<'b, 'this, A, M>, +} + +pub struct Scene<'b, 'a, A: AccelerationStructure<'a>, M: Scatter> { + acceleration_structure: Option, + arena: &'a Bump, + material_search: HashMap<&'b str, &'a M>, +} +*/ + +#[self_referencing] +pub struct ActualScene< + 'b, + P: Primitive, + M: Scatter + 'static, + S: Sampler, + A: AccelerationStructure, + T: Texture + 'static, + N: NoHit, +> { + arena: Bump, + #[borrows(arena)] + #[covariant] + scene: NewScene<'this, 'b, P, M, S, A, T, N>, +} + +pub struct NewScene< + 'a, + 'b, + P: Primitive, + M: Scatter, + S: Sampler, + A: AccelerationStructure, + T: Texture, + N: NoHit, +> { + arena: &'a Bump, + core_scene: Option>, + texture_search: HashMap<&'b str, &'a T>, + material_search: HashMap<&'b str, &'a M>, +} + +pub struct CoreScene< + P: Primitive, + M: Scatter, + S: Sampler, + A: AccelerationStructure, + N: NoHit, +> { + acceleration_structure: A, + camera: SimpleCamera, + sampler: S, + sky: N, +} + +use implementations::random_sampler::RandomSampler; +use implementations::sphere::Sphere; + +#[test] +pub fn create_scene() { + type TextureType = AllTextures; + type MaterialType = AllMaterials; + type PrimitiveType = AllPrimitives; + + let mut scene: ActualScene< + '_, + PrimitiveType, + MaterialType, + RandomSampler, + Bvh, + AllTextures, + Sky, + > = ActualSceneBuilder { + arena: Bump::new(), + scene_builder: |bump| NewScene { + arena: bump, + core_scene: None, + texture_search: HashMap::new(), + material_search: HashMap::new(), + }, + } + .build(); + + use implementations::*; + + // add textures + scene.with_scene_mut(|scene| { + let tex = scene + .arena + .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::new( + 0.5, 0.5, 0.5, + )))); + scene.texture_search.insert("Grey", tex); + let tex = scene + .arena + .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::one()))); + scene.texture_search.insert("White", tex); + let tex = scene + .arena + .alloc(AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one()))); + scene.texture_search.insert("black white lerp", tex); + }); + + // add materials + scene.with_scene_mut(|scene| { + let mat = scene.arena.alloc(AllMaterials::Emit(Emit::new( + &Arc::new(scene.texture_search["White"].clone()), // till migration to references + 1.5, + ))); + scene.material_search.insert("Light", mat as &_); + + let mat = scene.arena.alloc(AllMaterials::Lambertian(Lambertian::new( + &Arc::new(scene.texture_search["Grey"].clone()), + 0.5, + ))); + scene.material_search.insert("Diffuse", mat as &_); + }); + + // create primitives and rest of scene + scene.with_scene_mut(|scene| { + let primitives = vec![ + AllPrimitives::Sphere(Sphere::new( + Vec3::new(0.0, 0.0, -1000.0), + 1000.0, + &Arc::new(scene.material_search["Diffuse"].clone()), + )), + AllPrimitives::Sphere(Sphere::new( + Vec3::new(0.0, 0.0, 0.5), + 0.5, + &Arc::new(scene.material_search["Light"].clone()), + )), + ]; + + scene.core_scene = Some(CoreScene { + acceleration_structure: Bvh::new(primitives, split::SplitType::Sah), + camera: SimpleCamera::new( + Vec3::new(-5.0, -3.0, 3.0), + Vec3::new(0.0, 0.0, 0.5), + Vec3::new(0.0, 0.0, 1.0), + 34.0, + 16.0 / 9.0, + 0.0, + 10.0, + ), + sampler: RandomSampler {}, + sky: Sky::new( + &Arc::new(scene.texture_search["black white lerp"].clone()), + (0, 0), + ), + }); + }); +} diff --git a/implementations/src/materials/emissive.rs b/implementations/src/materials/emissive.rs index 1561df8..b3153cb 100644 --- a/implementations/src/materials/emissive.rs +++ b/implementations/src/materials/emissive.rs @@ -1,7 +1,7 @@ use crate::{rt_core::*, textures::Texture, utility::offset_ray}; use std::sync::Arc; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Emit { pub texture: Arc, pub strength: Float, diff --git a/implementations/src/materials/lambertian.rs b/implementations/src/materials/lambertian.rs index 02eca84..f593aca 100644 --- a/implementations/src/materials/lambertian.rs +++ b/implementations/src/materials/lambertian.rs @@ -5,7 +5,7 @@ use crate::{ }; use std::sync::Arc; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Lambertian { pub texture: Arc, pub absorption: Float, diff --git a/implementations/src/materials/mod.rs b/implementations/src/materials/mod.rs index cebd2bb..71c0301 100644 --- a/implementations/src/materials/mod.rs +++ b/implementations/src/materials/mod.rs @@ -16,7 +16,7 @@ pub use crate::{ textures::Texture, }; -#[derive(Scatter, Debug)] +#[derive(Scatter, Debug, Clone)] pub enum AllMaterials { Emit(Emit), Lambertian(Lambertian), diff --git a/implementations/src/materials/phong.rs b/implementations/src/materials/phong.rs index add27e0..5bd201a 100644 --- a/implementations/src/materials/phong.rs +++ b/implementations/src/materials/phong.rs @@ -5,7 +5,7 @@ use crate::{ }; use std::sync::Arc; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Phong { pub ks: Float, pub kd: Float, diff --git a/implementations/src/materials/reflect.rs b/implementations/src/materials/reflect.rs index 839b122..e625b86 100644 --- a/implementations/src/materials/reflect.rs +++ b/implementations/src/materials/reflect.rs @@ -5,7 +5,7 @@ use crate::{ }; use std::sync::Arc; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Reflect { pub texture: Arc, pub fuzz: Float, diff --git a/implementations/src/materials/refract.rs b/implementations/src/materials/refract.rs index 39228dd..a2429cd 100644 --- a/implementations/src/materials/refract.rs +++ b/implementations/src/materials/refract.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::sync::Arc; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Refract { pub texture: Arc, pub eta: Float, diff --git a/implementations/src/materials/trowbridge_reitz.rs b/implementations/src/materials/trowbridge_reitz.rs index 0a6907f..9c49b10 100644 --- a/implementations/src/materials/trowbridge_reitz.rs +++ b/implementations/src/materials/trowbridge_reitz.rs @@ -7,7 +7,7 @@ use crate::{ use rand::{rngs::SmallRng, thread_rng, SeedableRng}; use std::sync::Arc; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TrowbridgeReitz { pub texture: Arc, pub alpha: Float, diff --git a/implementations/src/textures/mod.rs b/implementations/src/textures/mod.rs index 642cbbf..a80ce85 100644 --- a/implementations/src/textures/mod.rs +++ b/implementations/src/textures/mod.rs @@ -14,7 +14,7 @@ pub trait Texture { false } } -#[derive(Texture, Debug)] +#[derive(Texture, Debug, Clone)] pub enum AllTextures { CheckeredTexture(CheckeredTexture), SolidColour(SolidColour), @@ -23,7 +23,7 @@ pub enum AllTextures { Perlin(Box), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CheckeredTexture { colour_one: Vec3, colour_two: Vec3, @@ -72,7 +72,7 @@ impl Texture for CheckeredTexture { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Perlin { ran_vecs: [Vec3; PERLIN_RVECS], perm_x: [u32; PERLIN_RVECS], @@ -179,7 +179,7 @@ impl Texture for Box { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SolidColour { pub colour: Vec3, } @@ -199,7 +199,7 @@ impl Texture for SolidColour { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ImageTexture { pub data: Vec, pub dim: (usize, usize), @@ -265,7 +265,7 @@ impl Texture for ImageTexture { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Lerp { pub colour_one: Vec3, pub colour_two: Vec3, From 416a6e786ed8221f1af1c2195dba137e167d312a Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Fri, 27 Jan 2023 17:00:34 +1100 Subject: [PATCH 13/39] Removed phong and coffee scene --- frontend/scene/src/lib.rs | 192 +++++++++++++------------ frontend/src/generate.rs | 57 -------- frontend/src/parameters.rs | 3 - implementations/src/materials/mod.rs | 4 +- implementations/src/materials/phong.rs | 83 ----------- 5 files changed, 99 insertions(+), 240 deletions(-) delete mode 100644 implementations/src/materials/phong.rs diff --git a/frontend/scene/src/lib.rs b/frontend/scene/src/lib.rs index 2393eb0..4d6c197 100644 --- a/frontend/scene/src/lib.rs +++ b/frontend/scene/src/lib.rs @@ -122,100 +122,104 @@ pub struct CoreScene< sky: N, } -use implementations::random_sampler::RandomSampler; -use implementations::sphere::Sphere; - -#[test] -pub fn create_scene() { - type TextureType = AllTextures; - type MaterialType = AllMaterials; - type PrimitiveType = AllPrimitives; - - let mut scene: ActualScene< - '_, - PrimitiveType, - MaterialType, - RandomSampler, - Bvh, - AllTextures, - Sky, - > = ActualSceneBuilder { - arena: Bump::new(), - scene_builder: |bump| NewScene { - arena: bump, - core_scene: None, - texture_search: HashMap::new(), - material_search: HashMap::new(), - }, - } - .build(); - - use implementations::*; - - // add textures - scene.with_scene_mut(|scene| { - let tex = scene - .arena - .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::new( - 0.5, 0.5, 0.5, - )))); - scene.texture_search.insert("Grey", tex); - let tex = scene - .arena - .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::one()))); - scene.texture_search.insert("White", tex); - let tex = scene - .arena - .alloc(AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one()))); - scene.texture_search.insert("black white lerp", tex); - }); - - // add materials - scene.with_scene_mut(|scene| { - let mat = scene.arena.alloc(AllMaterials::Emit(Emit::new( - &Arc::new(scene.texture_search["White"].clone()), // till migration to references - 1.5, - ))); - scene.material_search.insert("Light", mat as &_); - - let mat = scene.arena.alloc(AllMaterials::Lambertian(Lambertian::new( - &Arc::new(scene.texture_search["Grey"].clone()), - 0.5, - ))); - scene.material_search.insert("Diffuse", mat as &_); - }); - - // create primitives and rest of scene - scene.with_scene_mut(|scene| { - let primitives = vec![ - AllPrimitives::Sphere(Sphere::new( - Vec3::new(0.0, 0.0, -1000.0), - 1000.0, - &Arc::new(scene.material_search["Diffuse"].clone()), - )), - AllPrimitives::Sphere(Sphere::new( - Vec3::new(0.0, 0.0, 0.5), +#[cfg(test)] +mod tests { + use super::*; + use implementations::random_sampler::RandomSampler; + use implementations::sphere::Sphere; + + #[test] + pub fn create_scene() { + type TextureType = AllTextures; + type MaterialType = AllMaterials; + type PrimitiveType = AllPrimitives; + + let mut scene: ActualScene< + '_, + PrimitiveType, + MaterialType, + RandomSampler, + Bvh, + AllTextures, + Sky, + > = ActualSceneBuilder { + arena: Bump::new(), + scene_builder: |bump| NewScene { + arena: bump, + core_scene: None, + texture_search: HashMap::new(), + material_search: HashMap::new(), + }, + } + .build(); + + use implementations::*; + + // add textures + scene.with_scene_mut(|scene| { + let tex = scene + .arena + .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::new( + 0.5, 0.5, 0.5, + )))); + scene.texture_search.insert("Grey", tex); + let tex = scene + .arena + .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::one()))); + scene.texture_search.insert("White", tex); + let tex = scene + .arena + .alloc(AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one()))); + scene.texture_search.insert("black white lerp", tex); + }); + + // add materials + scene.with_scene_mut(|scene| { + let mat = scene.arena.alloc(AllMaterials::Emit(Emit::new( + &Arc::new(scene.texture_search["White"].clone()), // till migration to references + 1.5, + ))); + scene.material_search.insert("Light", mat as &_); + + let mat = scene.arena.alloc(AllMaterials::Lambertian(Lambertian::new( + &Arc::new(scene.texture_search["Grey"].clone()), 0.5, - &Arc::new(scene.material_search["Light"].clone()), - )), - ]; - - scene.core_scene = Some(CoreScene { - acceleration_structure: Bvh::new(primitives, split::SplitType::Sah), - camera: SimpleCamera::new( - Vec3::new(-5.0, -3.0, 3.0), - Vec3::new(0.0, 0.0, 0.5), - Vec3::new(0.0, 0.0, 1.0), - 34.0, - 16.0 / 9.0, - 0.0, - 10.0, - ), - sampler: RandomSampler {}, - sky: Sky::new( - &Arc::new(scene.texture_search["black white lerp"].clone()), - (0, 0), - ), + ))); + scene.material_search.insert("Diffuse", mat as &_); + }); + + // create primitives and rest of scene + scene.with_scene_mut(|scene| { + let primitives = vec![ + AllPrimitives::Sphere(Sphere::new( + Vec3::new(0.0, 0.0, -1000.0), + 1000.0, + &Arc::new(scene.material_search["Diffuse"].clone()), + )), + AllPrimitives::Sphere(Sphere::new( + Vec3::new(0.0, 0.0, 0.5), + 0.5, + &Arc::new(scene.material_search["Light"].clone()), + )), + ]; + + scene.core_scene = Some(CoreScene { + acceleration_structure: Bvh::new(primitives, split::SplitType::Sah), + camera: SimpleCamera::new( + Vec3::new(-5.0, -3.0, 3.0), + Vec3::new(0.0, 0.0, 0.5), + Vec3::new(0.0, 0.0, 1.0), + 34.0, + 16.0 / 9.0, + 0.0, + 10.0, + ), + sampler: RandomSampler {}, + sky: Sky::new( + &Arc::new(scene.texture_search["black white lerp"].clone()), + (0, 0), + ), + }); }); - }); + } } diff --git a/frontend/src/generate.rs b/frontend/src/generate.rs index c0759de..53cc665 100644 --- a/frontend/src/generate.rs +++ b/frontend/src/generate.rs @@ -244,63 +244,6 @@ pub fn overshadowed(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { scene!(camera, sky!(), random_sampler!(), bvh) }*/ -pub fn coffee(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { - let mut primitives = Vec::new(); - - let diffuse = &diffuse!(1, 1, 1, 0.9); - - let orange = &std::sync::Arc::new(implementations::AllMaterials::Phong( - implementations::Phong::new(&solid_colour!(0.592, 0.192, 0.0), 0.2, 0.8, 1250.0), - )); - - let floor = &std::sync::Arc::new(implementations::AllMaterials::Phong( - implementations::Phong::new(&solid_colour!(colour!(1)), 0.1, 0.9, 1250.0), - )); - - let black = &std::sync::Arc::new(implementations::AllMaterials::Phong( - implementations::Phong::new(&solid_colour!(colour!(0.006)), 0.1, 0.9, 1250.0), - )); - - let glass = &refract!(&solid_colour!(colour!(1)), 1.5); - - let left_light = &emit!(&solid_colour!(colour!(1)), 3.0 * 1.0); - let right_light = &emit!(&solid_colour!(colour!(1)), 1.0 * 1.0); - let left_reflection = &emit!(&solid_colour!(colour!(1)), 0.75 * 1.0); - - let metal = &reflect!(&solid_colour!(colour!(1)), 0); - - let materials = vec![ - (diffuse.clone(), "default"), - (orange.clone(), "Plastic_Orange"), - (glass.clone(), "Glass"), - (left_light.clone(), "Left_Light"), - (left_reflection.clone(), "Left_Reflection"), - (right_light.clone(), "Right_Light"), - (floor.clone(), "Floor"), - (black.clone(), "Plastic_Black"), - (metal.clone(), "Metal"), - ]; - primitives.extend(crate::load_model::load_model_with_materials( - "../../../render_scenes/coffee.obj", - &materials, - )); - - let sky = sky!(); - - let camera = camera!( - position!(0, 0.17 * 10.0, (2.0386 - 1.0) * 10.0), - position!(0, 0.150512 * 10.0, 0), - position!(0, 1, 0), - 40, - aspect_ratio, - 0, - 0.0075 * 10.0 - ); - - let bvh = create_bvh_with_info(primitives, bvh_type); - scene!(camera, sky, random_sampler!(), bvh) -} - pub fn cornell(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { let mut primitives = Vec::new(); diff --git a/frontend/src/parameters.rs b/frontend/src/parameters.rs index dec980a..1ae6260 100644 --- a/frontend/src/parameters.rs +++ b/frontend/src/parameters.rs @@ -269,9 +269,6 @@ fn get_scene( "furnace" => { scene!(furnace, bvh_type, aspect_ratio) } - "coffee" => { - scene!(coffee, bvh_type, aspect_ratio) - } "bxdf_testing" => { scene!(bxdf_testing, bvh_type, aspect_ratio) } diff --git a/implementations/src/materials/mod.rs b/implementations/src/materials/mod.rs index 71c0301..6575676 100644 --- a/implementations/src/materials/mod.rs +++ b/implementations/src/materials/mod.rs @@ -3,14 +3,13 @@ use proc::Scatter; pub mod emissive; pub mod lambertian; -pub mod phong; pub mod reflect; pub mod refract; pub mod trowbridge_reitz; pub use crate::{ materials::{ - emissive::Emit, lambertian::Lambertian, phong::Phong, reflect::Reflect, refract::Refract, + emissive::Emit, lambertian::Lambertian, reflect::Reflect, refract::Refract, trowbridge_reitz::TrowbridgeReitz, }, textures::Texture, @@ -20,7 +19,6 @@ pub use crate::{ pub enum AllMaterials { Emit(Emit), Lambertian(Lambertian), - Phong(Phong), TrowbridgeReitz(TrowbridgeReitz), Reflect(Reflect), Refract(Refract), diff --git a/implementations/src/materials/phong.rs b/implementations/src/materials/phong.rs deleted file mode 100644 index 5bd201a..0000000 --- a/implementations/src/materials/phong.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{ - rt_core::*, - textures::Texture, - utility::{random_float, specular_sampling, coord::Coordinate, cosine_hemisphere_sampling, near_zero, offset_ray}, -}; -use std::sync::Arc; - -#[derive(Debug, Clone)] -pub struct Phong { - pub ks: Float, - pub kd: Float, - pub exponent: Float, - pub texture: Arc, -} - -impl Phong -where - T: Texture, -{ - pub fn new(texture: &Arc, kd: Float, ks: Float, exponent: Float) -> Self { - Self { ks, kd, exponent, texture: texture.clone()} - } -} - -impl Scatter for Phong -where - T: Texture, -{ - fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { - let rand = random_float(); - let direction = if rand > self.kd + self.ks { - return true; - } else if rand > self.kd { - // sample diffuse - let coordinate_system = Coordinate::new_from_z(hit.normal); - let mut direction = cosine_hemisphere_sampling(); - coordinate_system.vec_to_coordinate(&mut direction); - - if near_zero(direction) { - direction = hit.normal; - } - direction - } else { - // sample specular - let mut spec_dir = ray.direction; - spec_dir.reflect(hit.normal); - let coordinate_system = Coordinate::new_from_z(spec_dir); - let mut direction = specular_sampling(self.exponent); - coordinate_system.vec_to_coordinate(&mut direction); - - if direction.dot(hit.normal) < 0.0 { - return true; - } - if near_zero(direction) { - direction = spec_dir; - } - direction - }; - let point = offset_ray(hit.point, hit.normal, hit.error, true); - *ray = Ray::new(point, direction, ray.time); - false - } - fn scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Float { - let mut spec_dir = wo; - spec_dir.reflect(hit.normal); - let cos_alpha = spec_dir.dot(wi).max(0.0); - - let diffuse = hit.normal.dot(wi).max(0.0) / PI; - let spec = (self.exponent + 1.0) * cos_alpha.powf(self.exponent) / (2.0 * PI); - self.kd * diffuse + self.ks * spec - } - fn eval(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Vec3 { - let mut spec_dir = wo; - spec_dir.reflect(hit.normal); - let cos_alpha = spec_dir.dot(wi).max(0.0); - - let diff = self.texture.colour_value(wo, hit.point)* hit.normal.dot(wi).max(0.0) - / PI; - let spec = Vec3::one() * (self.exponent + 2.0) * cos_alpha.powf(self.exponent) / (2.0 * PI); - - self.kd * diff + self.ks * spec - } -} \ No newline at end of file From 7f67699a44cb8a1e52ba49b27914b2a264350e4b Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Mon, 30 Jan 2023 08:41:43 +1100 Subject: [PATCH 14/39] Initial scene rework (highly WIP) --- frontend/scene/Cargo.toml | 4 +- frontend/scene/src/lib.rs | 362 +++++++++--------- implementations/Cargo.toml | 2 + implementations/proc/src/lib.rs | 2 +- implementations/src/acceleration/mod.rs | 24 +- implementations/src/materials/emissive.rs | 16 +- implementations/src/materials/lambertian.rs | 13 +- implementations/src/materials/mod.rs | 12 +- implementations/src/materials/reflect.rs | 16 +- implementations/src/materials/refract.rs | 18 +- .../src/materials/trowbridge_reitz.rs | 13 +- implementations/src/primitives/mod.rs | 8 +- implementations/src/primitives/sphere.rs | 15 +- implementations/src/primitives/triangle.rs | 46 +-- implementations/src/samplers/mod.rs | 17 +- rt_core/src/primitive.rs | 11 +- rt_core/src/ray.rs | 5 +- 17 files changed, 290 insertions(+), 294 deletions(-) diff --git a/frontend/scene/Cargo.toml b/frontend/scene/Cargo.toml index a14d22a..e866ead 100644 --- a/frontend/scene/Cargo.toml +++ b/frontend/scene/Cargo.toml @@ -10,9 +10,11 @@ image = "0.23.14" serde = { version = "1.0", features = [ "derive" ] } implementations = { path = "../../implementations" } toml = "0.5.10" +rand = "0.8.5" +rayon = "1.5.1" wavefront_obj = "10.0.0" ouroboros = "0.15.5" -bumpalo = "3.12.0" +bumpalo = {version="3.12.0", features=["collections"]} [features] f64 = ["implementations/f64"] \ No newline at end of file diff --git a/frontend/scene/src/lib.rs b/frontend/scene/src/lib.rs index 4d6c197..a17311b 100644 --- a/frontend/scene/src/lib.rs +++ b/frontend/scene/src/lib.rs @@ -1,225 +1,233 @@ -//pub mod parse; - -pub use implementations::{self, rt_core}; -use std::collections::HashMap; - -use implementations::{SimpleCamera, Sky, Texture}; +pub use implementations::{self, rt_core, SimpleCamera}; +use rand::Rng; +use rayon::prelude::*; use rt_core::*; -use std::{marker::PhantomData, sync::Arc}; - -pub struct Scene< - P: Primitive, - M: Scatter, - S: Sampler, - A: AccelerationStructure, - T: Texture, -> { - pub acceleration_structure: Arc, - pub camera: Arc, - pub sampler: Arc, - pub sky: Arc>, - phantom_data: PhantomData<(M, P)>, -} - -impl Scene -where - P: Primitive + Send + Sync + 'static, - M: Scatter + Send + Sync + 'static, - S: Sampler, - A: AccelerationStructure + Send + Sync, - T: Texture + Send + Sync, -{ - pub fn new( - camera: Arc, - sky: Arc>, - sampler: Arc, - acceleration_structure: Arc, - ) -> Self { - Scene { - acceleration_structure, - camera, - sampler, - sky, - phantom_data: PhantomData, - } - } - pub fn generate_image_threaded( - &self, - render_options: RenderOptions, - presentation_update: Option<(&mut D, impl Fn(&mut D, &SamplerProgress, u64))>, - ) { - self.sampler.sample_image( - render_options, - &*self.camera, - &*self.sky, - &*self.acceleration_structure, - presentation_update, - ) - } -} use bumpalo::Bump; use ouroboros::self_referencing; -/* #[self_referencing] -struct ActualScene<'b, A: AccelerationStructure, M: Scatter + 'static> { - bump: Bump, - #[borrows(bump)] - #[covariant] - scene: Scene<'b, 'this, A, M>, -} - -pub struct Scene<'b, 'a, A: AccelerationStructure<'a>, M: Scatter> { - acceleration_structure: Option, - arena: &'a Bump, - material_search: HashMap<&'b str, &'a M>, -} -*/ - -#[self_referencing] -pub struct ActualScene< - 'b, - P: Primitive, - M: Scatter + 'static, - S: Sampler, - A: AccelerationStructure, - T: Texture + 'static, - N: NoHit, -> { +pub struct SceneHolder { arena: Bump, #[borrows(arena)] #[covariant] - scene: NewScene<'this, 'b, P, M, S, A, T, N>, + scene: Scene<'this, S, A, N>, } -pub struct NewScene< - 'a, - 'b, - P: Primitive, - M: Scatter, - S: Sampler, - A: AccelerationStructure, - T: Texture, - N: NoHit, -> { +pub struct Scene<'a, S: Sampler, A: AccelerationStructure + Send + Sync, N: NoHit + Send + Sync> { arena: &'a Bump, - core_scene: Option>, - texture_search: HashMap<&'b str, &'a T>, - material_search: HashMap<&'b str, &'a M>, + camera: SimpleCamera, + _sampler: S, + core_scene: Option>, } -pub struct CoreScene< - P: Primitive, - M: Scatter, - S: Sampler, - A: AccelerationStructure, - N: NoHit, -> { +pub struct SceneData { acceleration_structure: A, - camera: SimpleCamera, - sampler: S, sky: N, } +impl< + 'a, + S: Sampler + Send + Sync, + A: AccelerationStructure + Send + Sync, + N: NoHit + Send + Sync, + > Scene<'a, S, A, N> +{ + pub fn render( + &self, + render_options: RenderOptions, + mut presentation_update: Option<(&mut T, F)>, + ) where + F: Fn(&mut T, &SamplerProgress, u64), + { + let channels = 3; + let pixel_num = render_options.width * render_options.height; + + let mut accumulator_buffers = ( + SamplerProgress::new(pixel_num, channels), + SamplerProgress::new(pixel_num, channels), + ); + + let pixel_chunk_size = 10000; + let chunk_size = pixel_chunk_size * channels; + + let camera = &self.camera; + let core_scene = self.core_scene.as_ref().unwrap(); + let (sky, acceleration_structure) = (&core_scene.sky, &core_scene.acceleration_structure); + + for i in 0..render_options.samples_per_pixel { + let (previous, current) = if i % 2 == 0 { + (&accumulator_buffers.0, &mut accumulator_buffers.1) + } else { + (&accumulator_buffers.1, &mut accumulator_buffers.0) + }; + + rayon::scope(|s| { + s.spawn(|_| { + current.rays_shot = current + .current_image + .par_chunks_mut(chunk_size as usize) + .enumerate() + .map(|(chunk_i, chunk)| { + let mut rng = rand::thread_rng(); + let mut rays_shot = 0; + for chunk_pixel_i in 0..(chunk.len() / 3) { + let pixel_i = + chunk_pixel_i as u64 + pixel_chunk_size * chunk_i as u64; + let x = pixel_i % render_options.width; + let y = (pixel_i - x) / render_options.width; + let u = (rng.gen_range(0.0..1.0) + x as Float) + / render_options.width as Float; + let v = 1.0 + - (rng.gen_range(0.0..1.0) + y as Float) + / render_options.height as Float; + + let mut ray = camera.get_ray(u, v); // remember to add le DOF + let result = match render_options.render_method { + RenderMethod::Naive => { + Ray::get_colour_naive(&mut ray, sky, acceleration_structure) + } + RenderMethod::MIS => { + Ray::get_colour(&mut ray, sky, acceleration_structure) + } + }; + + chunk[chunk_pixel_i * channels as usize] = result.0.x; + chunk[chunk_pixel_i * channels as usize + 1] = result.0.y; + chunk[chunk_pixel_i * channels as usize + 2] = result.0.z; + rays_shot += result.1; + } + rays_shot + }) + .sum(); + }); + }); + if i != 0 { + if let Some((ref mut data, f)) = presentation_update.as_mut() { + f(data, previous, i) + }; + } + } + + let (previous, _) = if render_options.samples_per_pixel % 2 == 0 { + (&accumulator_buffers.0, &mut accumulator_buffers.1) + } else { + (&accumulator_buffers.1, &mut accumulator_buffers.0) + }; + if let Some((ref mut data, f)) = presentation_update.as_mut() { + f(data, previous, render_options.samples_per_pixel) + } + } +} + #[cfg(test)] mod tests { use super::*; use implementations::random_sampler::RandomSampler; use implementations::sphere::Sphere; + use implementations::*; + use std::collections::HashMap; #[test] pub fn create_scene() { type TextureType = AllTextures; - type MaterialType = AllMaterials; - type PrimitiveType = AllPrimitives; - - let mut scene: ActualScene< - '_, - PrimitiveType, - MaterialType, - RandomSampler, - Bvh, - AllTextures, - Sky, - > = ActualSceneBuilder { + type MaterialType<'a> = AllMaterials<'a, TextureType>; + + let mut texture_search: HashMap<&str, &TextureType> = HashMap::new(); + let mut material_search: HashMap<&str, &MaterialType> = HashMap::new(); + + let mut scene = SceneHolderBuilder { arena: Bump::new(), - scene_builder: |bump| NewScene { + scene_builder: |bump| Scene { arena: bump, core_scene: None, - texture_search: HashMap::new(), - material_search: HashMap::new(), + camera: SimpleCamera::new( + Vec3::new(-5.0, -3.0, 3.0), + Vec3::new(0.0, 0.0, 0.5), + Vec3::new(0.0, 0.0, 1.0), + 34.0, + 16.0 / 9.0, + 0.0, + 10.0, + ), + _sampler: RandomSampler {}, }, } .build(); - use implementations::*; - - // add textures - scene.with_scene_mut(|scene| { - let tex = scene - .arena - .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::new( - 0.5, 0.5, 0.5, - )))); - scene.texture_search.insert("Grey", tex); - let tex = scene - .arena - .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::one()))); - scene.texture_search.insert("White", tex); - let tex = scene - .arena - .alloc(AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one()))); - scene.texture_search.insert("black white lerp", tex); - }); - - // add materials - scene.with_scene_mut(|scene| { - let mat = scene.arena.alloc(AllMaterials::Emit(Emit::new( - &Arc::new(scene.texture_search["White"].clone()), // till migration to references - 1.5, - ))); - scene.material_search.insert("Light", mat as &_); - - let mat = scene.arena.alloc(AllMaterials::Lambertian(Lambertian::new( - &Arc::new(scene.texture_search["Grey"].clone()), - 0.5, - ))); - scene.material_search.insert("Diffuse", mat as &_); - }); + scene.with_mut(|scene| { + // add textures + texture_search.insert( + "Grey", + scene + .arena + .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::new( + 0.5, 0.5, 0.5, + )))), + ); + + texture_search.insert( + "White", + scene + .arena + .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::one()))), + ); + + texture_search.insert( + "black white lerp", + scene + .arena + .alloc(AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one()))), + ); + + // add materials + material_search.insert( + "Light", + scene + .arena + .alloc(AllMaterials::Emit(Emit::new(texture_search["White"], 1.5))), + ); + + material_search.insert( + "Diffuse", + scene.arena.alloc(AllMaterials::Lambertian(Lambertian::new( + texture_search["Grey"], + 0.5, + ))), + ); + let mut primitives = bumpalo::collections::Vec::new_in(scene.arena); - // create primitives and rest of scene - scene.with_scene_mut(|scene| { - let primitives = vec![ + // add primitives + primitives.extend([ AllPrimitives::Sphere(Sphere::new( Vec3::new(0.0, 0.0, -1000.0), 1000.0, - &Arc::new(scene.material_search["Diffuse"].clone()), + material_search["Diffuse"], )), AllPrimitives::Sphere(Sphere::new( Vec3::new(0.0, 0.0, 0.5), 0.5, - &Arc::new(scene.material_search["Light"].clone()), + material_search["Light"], )), - ]; + ]); - scene.core_scene = Some(CoreScene { - acceleration_structure: Bvh::new(primitives, split::SplitType::Sah), - camera: SimpleCamera::new( - Vec3::new(-5.0, -3.0, 3.0), - Vec3::new(0.0, 0.0, 0.5), - Vec3::new(0.0, 0.0, 1.0), - 34.0, - 16.0 / 9.0, - 0.0, - 10.0, - ), - sampler: RandomSampler {}, - sky: Sky::new( - &Arc::new(scene.texture_search["black white lerp"].clone()), - (0, 0), - ), + // construct bvh + let bvh = Bvh::new(primitives, split::SplitType::Sah); + + scene.scene.core_scene = Some(SceneData { + acceleration_structure: bvh, + sky: Sky::new(texture_search["black white lerp"], (0, 0)), }); + + scene.scene.render::<(), fn(&mut _, &_, _)>( + RenderOptions { + samples_per_pixel: 1, + render_method: RenderMethod::MIS, + width: 100, + height: 50, + }, + None, + ); }); } } diff --git a/implementations/Cargo.toml b/implementations/Cargo.toml index fc7a297..e91baf8 100644 --- a/implementations/Cargo.toml +++ b/implementations/Cargo.toml @@ -11,6 +11,8 @@ proc = { path = "./proc" } rand = { version = "0.8.3", features = [ "small_rng" ] } rayon = "1.5.1" statistics = { path = "./statistics" } +bumpalo = {version="3.12.0", features=["collections"]} + [dev-dependencies] chrono = "0.4.19" diff --git a/implementations/proc/src/lib.rs b/implementations/proc/src/lib.rs index ba31130..5a3637e 100644 --- a/implementations/proc/src/lib.rs +++ b/implementations/proc/src/lib.rs @@ -122,7 +122,7 @@ pub fn derive_primitive(tokens: TokenStream) -> TokenStream { let func_names_primitive = [ ( - quote!(get_int(&self, __one: &Ray) -> Option), + quote!(get_int(&self, __one: &Ray) -> Option>), quote!(get_int(__one)), ), ( diff --git a/implementations/src/acceleration/mod.rs b/implementations/src/acceleration/mod.rs index aa04474..9d98238 100644 --- a/implementations/src/acceleration/mod.rs +++ b/implementations/src/acceleration/mod.rs @@ -38,29 +38,31 @@ impl PrimitiveInfo { } } -pub struct Bvh { +pub struct Bvh<'a, P: Primitive, M: Scatter> { split_type: SplitType, nodes: Vec, - pub primitives: Vec

, + pub primitives: &'a [P], pub lights: Vec, phantom: PhantomData, } -impl Bvh +unsafe impl<'a, P: Primitive + AABound + Send, M: Scatter + Send> Sync for Bvh<'a, P, M> {} + +impl<'a, P, M> Bvh<'a, P, M> where P: Primitive + AABound, M: Scatter, { - pub fn new(primitives: Vec

, split_type: SplitType) -> Self { + pub fn new(mut primitives: bumpalo::collections::Vec<'a, P>, split_type: SplitType) -> Self { let mut bvh = Self { split_type, nodes: Vec::new(), - primitives, + primitives: &[], lights: Vec::new(), phantom: PhantomData, }; - let mut primitives_info: Vec = bvh - .primitives + //let prims = + let mut primitives_info: Vec = primitives .iter() .enumerate() .map(|(index, primitive)| PrimitiveInfo::new::(index, primitive)) @@ -69,16 +71,18 @@ where bvh.build_bvh(&mut Vec::new(), 0, &mut primitives_info); sort_by_indices( - &mut bvh.primitives, + &mut primitives, primitives_info.iter().map(|&info| info.index).collect(), ); - for (i, prim) in bvh.primitives.iter().enumerate() { + for (i, prim) in primitives.iter().enumerate() { if prim.material_is_light() { bvh.lights.push(i); } } + bvh.primitives = primitives.into_bump_slice(); + bvh } pub fn number_nodes(&self) -> usize { @@ -177,7 +181,7 @@ where } } -impl AccelerationStructure for Bvh +impl<'a, P, M> AccelerationStructure for Bvh<'a, P, M> where P: Primitive, M: Scatter, diff --git a/implementations/src/materials/emissive.rs b/implementations/src/materials/emissive.rs index b3153cb..61cbf85 100644 --- a/implementations/src/materials/emissive.rs +++ b/implementations/src/materials/emissive.rs @@ -1,25 +1,21 @@ use crate::{rt_core::*, textures::Texture, utility::offset_ray}; -use std::sync::Arc; #[derive(Debug, Clone)] -pub struct Emit { - pub texture: Arc, +pub struct Emit<'a, T: Texture> { + pub texture: &'a T, pub strength: Float, } -impl Emit +impl<'a, T> Emit<'a, T> where T: Texture, { - pub fn new(texture: &Arc, strength: Float) -> Self { - Emit { - texture: texture.clone(), - strength, - } + pub fn new(texture: &'a T, strength: Float) -> Self { + Emit { texture, strength } } } -impl Scatter for Emit +impl<'a, T> Scatter for Emit<'a, T> where T: Texture, { diff --git a/implementations/src/materials/lambertian.rs b/implementations/src/materials/lambertian.rs index f593aca..734949b 100644 --- a/implementations/src/materials/lambertian.rs +++ b/implementations/src/materials/lambertian.rs @@ -3,11 +3,10 @@ use crate::{ textures::Texture, utility::{coord::Coordinate, cosine_hemisphere_sampling, near_zero, offset_ray}, }; -use std::sync::Arc; #[derive(Debug, Clone)] -pub struct Lambertian { - pub texture: Arc, +pub struct Lambertian<'a, T: Texture> { + pub texture: &'a T, pub absorption: Float, } @@ -17,19 +16,19 @@ use std::f64::consts::PI; #[cfg(not(feature = "f64"))] use std::f32::consts::PI; -impl Lambertian +impl<'a, T> Lambertian<'a, T> where T: Texture, { - pub fn new(texture: &Arc, absorption: Float) -> Self { + pub fn new(texture: &'a T, absorption: Float) -> Self { Lambertian { - texture: texture.clone(), + texture, absorption, } } } -impl Scatter for Lambertian +impl<'a, T> Scatter for Lambertian<'a, T> where T: Texture, { diff --git a/implementations/src/materials/mod.rs b/implementations/src/materials/mod.rs index 6575676..18b2831 100644 --- a/implementations/src/materials/mod.rs +++ b/implementations/src/materials/mod.rs @@ -16,10 +16,10 @@ pub use crate::{ }; #[derive(Scatter, Debug, Clone)] -pub enum AllMaterials { - Emit(Emit), - Lambertian(Lambertian), - TrowbridgeReitz(TrowbridgeReitz), - Reflect(Reflect), - Refract(Refract), +pub enum AllMaterials<'a, T: Texture> { + Emit(Emit<'a, T>), + Lambertian(Lambertian<'a, T>), + TrowbridgeReitz(TrowbridgeReitz<'a, T>), + Reflect(Reflect<'a, T>), + Refract(Refract<'a, T>), } diff --git a/implementations/src/materials/reflect.rs b/implementations/src/materials/reflect.rs index e625b86..b9bc3d1 100644 --- a/implementations/src/materials/reflect.rs +++ b/implementations/src/materials/reflect.rs @@ -3,27 +3,23 @@ use crate::{ textures::Texture, utility::{offset_ray, random_unit_vector}, }; -use std::sync::Arc; #[derive(Debug, Clone)] -pub struct Reflect { - pub texture: Arc, +pub struct Reflect<'a, T: Texture> { + pub texture: &'a T, pub fuzz: Float, } -impl Reflect +impl<'a, T> Reflect<'a, T> where T: Texture, { - pub fn new(texture: &Arc, fuzz: Float) -> Self { - Reflect { - texture: texture.clone(), - fuzz, - } + pub fn new(texture: &'a T, fuzz: Float) -> Self { + Reflect { texture, fuzz } } } -impl Scatter for Reflect +impl<'a, T> Scatter for Reflect<'a, T> where T: Texture, { diff --git a/implementations/src/materials/refract.rs b/implementations/src/materials/refract.rs index a2429cd..8056e5c 100644 --- a/implementations/src/materials/refract.rs +++ b/implementations/src/materials/refract.rs @@ -4,27 +4,23 @@ use crate::{ textures::Texture, utility::{offset_ray, random_float}, }; -use std::sync::Arc; #[derive(Debug, Clone)] -pub struct Refract { - pub texture: Arc, +pub struct Refract<'a, T: Texture> { + pub texture: &'a T, pub eta: Float, } -impl Refract +impl<'a, T> Refract<'a, T> where T: Texture, { - pub fn new(texture: &Arc, eta: Float) -> Self { - Refract { - texture: texture.clone(), - eta, - } + pub fn new(texture: &'a T, eta: Float) -> Self { + Refract { texture, eta } } } -impl Scatter for Refract +impl<'a, T> Scatter for Refract<'a, T> where T: Texture, { @@ -41,7 +37,7 @@ where let f0 = (1.0 - eta_fraction) / (1.0 + eta_fraction); let f0 = f0 * f0 * Vec3::one(); if cannot_refract || fresnel(cos_theta, f0).x > random_float() { - let ref_mat = Reflect::new(&self.texture.clone(), 0.0); + let ref_mat = Reflect::new(self.texture, 0.0); return ref_mat.scatter_ray(ray, hit); } diff --git a/implementations/src/materials/trowbridge_reitz.rs b/implementations/src/materials/trowbridge_reitz.rs index 9c49b10..98bda8d 100644 --- a/implementations/src/materials/trowbridge_reitz.rs +++ b/implementations/src/materials/trowbridge_reitz.rs @@ -5,23 +5,22 @@ use crate::{ utility::{coord::Coordinate, offset_ray}, }; use rand::{rngs::SmallRng, thread_rng, SeedableRng}; -use std::sync::Arc; #[derive(Debug, Clone)] -pub struct TrowbridgeReitz { - pub texture: Arc, +pub struct TrowbridgeReitz<'a, T: Texture> { + pub texture: &'a T, pub alpha: Float, pub ior: Vec3, pub metallic: Float, } -impl TrowbridgeReitz +impl<'a, T> TrowbridgeReitz<'a, T> where T: Texture, { - pub fn new(texture: &Arc, roughness: Float, ior: Vec3, metallic: Float) -> Self { + pub fn new(texture: &'a T, roughness: Float, ior: Vec3, metallic: Float) -> Self { Self { - texture: texture.clone(), + texture, alpha: roughness * roughness, ior, metallic, @@ -60,7 +59,7 @@ where } } -impl Scatter for TrowbridgeReitz +impl<'a, T> Scatter for TrowbridgeReitz<'a, T> where T: Texture, { diff --git a/implementations/src/primitives/mod.rs b/implementations/src/primitives/mod.rs index d1249e8..d62ea03 100644 --- a/implementations/src/primitives/mod.rs +++ b/implementations/src/primitives/mod.rs @@ -12,10 +12,10 @@ pub mod sphere; pub mod triangle; #[derive(Primitive, Debug)] -pub enum AllPrimitives { - Sphere(Sphere), - Triangle(Triangle), - MeshTriangle(MeshTriangle), +pub enum AllPrimitives<'a, M: Scatter> { + Sphere(Sphere<'a, M>), + Triangle(Triangle<'a, M>), + MeshTriangle(MeshTriangle<'a, M>), } #[derive(Clone, Debug)] diff --git a/implementations/src/primitives/sphere.rs b/implementations/src/primitives/sphere.rs index c6c71fd..7086455 100644 --- a/implementations/src/primitives/sphere.rs +++ b/implementations/src/primitives/sphere.rs @@ -3,30 +3,29 @@ use crate::{ rt_core::*, utility::{coord::Coordinate, random_float}, }; -use std::sync::Arc; #[derive(Debug)] -pub struct Sphere { +pub struct Sphere<'a, M: Scatter> { pub center: Vec3, pub radius: Float, - pub material: Arc, + pub material: &'a M, } -impl Sphere +impl<'a, M> Sphere<'a, M> where M: Scatter, { - pub fn new(center: Vec3, radius: Float, material: &Arc) -> Self { + pub fn new(center: Vec3, radius: Float, material: &'a M) -> Self { Sphere { center, radius, - material: material.clone(), + material, } } } #[allow(clippy::suspicious_operation_groupings)] -impl Primitive for Sphere +impl<'a, M> Primitive for Sphere<'a, M> where M: Scatter, { @@ -173,7 +172,7 @@ where } } -impl AABound for Sphere { +impl<'a, M: Scatter> AABound for Sphere<'a, M> { fn get_aabb(&self) -> AABB { AABB::new( self.center - self.radius * Vec3::one(), diff --git a/implementations/src/primitives/triangle.rs b/implementations/src/primitives/triangle.rs index 3083559..8c408f9 100644 --- a/implementations/src/primitives/triangle.rs +++ b/implementations/src/primitives/triangle.rs @@ -8,47 +8,47 @@ use rand::{thread_rng, Rng}; use std::sync::Arc; #[derive(Clone, Debug)] -pub struct Triangle { +pub struct Triangle<'a, M: Scatter> { pub points: [Vec3; 3], pub normals: [Vec3; 3], - pub material: Arc, + pub material: &'a M, } -impl Triangle +impl<'a, M> Triangle<'a, M> where M: Scatter, { - pub fn new(points: [Vec3; 3], normals: [Vec3; 3], material: &Arc) -> Self { + pub fn new(points: [Vec3; 3], normals: [Vec3; 3], material: &'a M) -> Self { Triangle { points, normals, - material: material.clone(), + material, } } } #[derive(Debug)] -pub struct MeshTriangle { +pub struct MeshTriangle<'a, M: Scatter> { pub point_indices: [usize; 3], pub normal_indices: [usize; 3], - pub material: Arc, + pub material: &'a M, pub mesh: Arc, } -impl MeshTriangle +impl<'a, M> MeshTriangle<'a, M> where M: Scatter, { pub fn new( point_indices: [usize; 3], normal_indices: [usize; 3], - material: &Arc, + material: &'a M, mesh: &Arc, ) -> Self { MeshTriangle { point_indices, normal_indices, - material: material.clone(), + material, mesh: mesh.clone(), } } @@ -66,13 +66,13 @@ impl MeshData { } } -pub trait TriangleTrait { +pub trait TriangleTrait<'a, M: Scatter> { fn get_point(&self, index: usize) -> Vec3; fn get_normal(&self, index: usize) -> Vec3; - fn get_material(&self) -> &Arc; + fn get_material(&self) -> &'a M; } -impl TriangleTrait for Triangle +impl<'a, M> TriangleTrait<'a, M> for Triangle<'a, M> where M: Scatter, { @@ -82,12 +82,12 @@ where fn get_normal(&self, index: usize) -> Vec3 { self.normals[index] } - fn get_material(&self) -> &Arc { + fn get_material(&self) -> &'a M { &self.material } } -impl TriangleTrait for MeshTriangle +impl<'a, M> TriangleTrait<'a, M> for MeshTriangle<'a, M> where M: Scatter, { @@ -97,15 +97,15 @@ where fn get_normal(&self, index: usize) -> Vec3 { self.mesh.normals[self.normal_indices[index]] } - fn get_material(&self) -> &Arc { + fn get_material(&self) -> &'a M { &self.material } } -pub fn triangle_intersection, M: Scatter>( - triangle: &T, +pub fn triangle_intersection<'a, T: TriangleTrait<'a, M>, M: Scatter>( + triangle: &'a T, ray: &Ray, -) -> Option> { +) -> Option> { let mut p0t = triangle.get_point(0) - ray.origin; let mut p1t = triangle.get_point(1) - ray.origin; let mut p2t = triangle.get_point(2) - ray.origin; @@ -215,7 +215,7 @@ pub fn triangle_intersection, M: Scatter>( )) } -impl Primitive for Triangle +impl<'a, M> Primitive for Triangle<'a, M> where M: Scatter, { @@ -247,7 +247,7 @@ where } } -impl Primitive for MeshTriangle +impl<'a, M> Primitive for MeshTriangle<'a, M> where M: Scatter, { @@ -282,7 +282,7 @@ where self.material.is_light() } } -impl AABound for Triangle { +impl<'a, M: Scatter> AABound for Triangle<'a, M> { fn get_aabb(&self) -> AABB { AABB::new( self.points[0].min_by_component(self.points[1].min_by_component(self.points[2])), @@ -291,7 +291,7 @@ impl AABound for Triangle { } } -impl AABound for MeshTriangle { +impl<'a, M: Scatter> AABound for MeshTriangle<'a, M> { fn get_aabb(&self) -> AABB { let points = [ self.mesh.vertices[self.point_indices[0]], diff --git a/implementations/src/samplers/mod.rs b/implementations/src/samplers/mod.rs index 6e69200..617c684 100644 --- a/implementations/src/samplers/mod.rs +++ b/implementations/src/samplers/mod.rs @@ -1,22 +1,19 @@ use crate::{generate_values, next_float, random_float, rt_core::*, textures::Texture}; use rand::{rngs::SmallRng, thread_rng, SeedableRng}; use statistics::distributions::*; -use std::sync::Arc; pub mod random_sampler; #[derive(Debug)] -pub struct Sky { - texture: Arc, +pub struct Sky<'a, T: Texture> { + texture: &'a T, pub distribution: Option, sampler_res: (usize, usize), } -impl Sky { - pub fn new(texture: &Arc, sampler_res: (usize, usize)) -> Self { - let texture = texture.clone(); - - let values = generate_values(&*texture, sampler_res); +impl<'a, T: Texture> Sky<'a, T> { + pub fn new(texture: &'a T, sampler_res: (usize, usize)) -> Self { + let values = generate_values(texture, sampler_res); let distribution = if sampler_res.0 | sampler_res.1 != 0 { Some(Distribution2D::new(&values, sampler_res.0)) @@ -32,7 +29,7 @@ impl Sky { } } -impl NoHit for Sky { +impl<'a, T: Texture> NoHit for Sky<'a, T> { fn get_colour(&self, ray: &Ray) -> Vec3 { self.texture.colour_value(ray.direction, ray.origin) } @@ -83,7 +80,7 @@ mod tests { #[test] fn sky_sampling() { - let tex = std::sync::Arc::new(AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one()))); + let tex = AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one())); let sky = Sky::new(&tex, (60, 30)); diff --git a/rt_core/src/primitive.rs b/rt_core/src/primitive.rs index b59471b..22db639 100644 --- a/rt_core/src/primitive.rs +++ b/rt_core/src/primitive.rs @@ -1,5 +1,4 @@ use crate::{Float, Ray, Scatter, Vec2, Vec3}; -use std::sync::Arc; pub struct Hit { pub t: Float, @@ -10,12 +9,12 @@ pub struct Hit { pub out: bool, } -pub struct SurfaceIntersection { +pub struct SurfaceIntersection<'a, M: Scatter> { pub hit: Hit, - pub material: Arc, + pub material: &'a M, } -impl SurfaceIntersection +impl<'a, M> SurfaceIntersection<'a, M> where M: Scatter, { @@ -26,7 +25,7 @@ where normal: Vec3, uv: Option, out: bool, - material: &Arc, + material: &'a M, ) -> Self { SurfaceIntersection { hit: Hit { @@ -37,7 +36,7 @@ where uv, out, }, - material: material.clone(), + material, } } } diff --git a/rt_core/src/ray.rs b/rt_core/src/ray.rs index 2b1a12b..759e601 100644 --- a/rt_core/src/ray.rs +++ b/rt_core/src/ray.rs @@ -162,8 +162,7 @@ impl Ray { while depth < MAX_DEPTH { if !mat.is_delta() { // light sampling - if let Some((l_pos, le, l_pdf)) = Ray::sample_lights_test(bvh, &hit, sky, &mat, wo) - { + if let Some((l_pos, le, l_pdf)) = Ray::sample_lights_test(bvh, &hit, sky, mat, wo) { let l_wi = (l_pos - hit.point).normalised(); let m_pdf = mat.scattering_pdf(&hit, wo, l_wi); let mis_weight = power_heuristic(l_pdf, m_pdf); @@ -174,7 +173,7 @@ impl Ray { } // material sample and bounce - let (m_wi, m_pdf) = match Ray::sample_material_test(bvh, &hit, sky, &mat, ray) { + let (m_wi, m_pdf) = match Ray::sample_material_test(bvh, &hit, sky, mat, ray) { Some((m_wi, m_pdf)) => (m_wi, m_pdf), None => break, }; From eff8bea3574f84428975f4e7645954b0852e3f04 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Mon, 30 Jan 2023 21:06:04 +1100 Subject: [PATCH 15/39] Suppressed errors for frontend rework --- frontend/src/main.rs | 15 +++++++-------- frontend/src/utility.rs | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 4e216bb..e1d40ae 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,6 +1,5 @@ -use crate::{ - parameters::line_break, - utility::{get_progress_output, print_final_statistics, print_render_start, save_u8_to_image}, +use crate::utility::{ + get_progress_output, print_final_statistics, print_render_start, save_u8_to_image, }; #[cfg(feature = "gui")] use { @@ -29,10 +28,10 @@ mod gui; #[cfg(feature = "gui")] mod rendering; -mod generate; -mod load_model; +//mod generate; +//mod load_model; mod macros; -mod parameters; +//mod parameters; mod utility; #[cfg(feature = "gui")] @@ -84,7 +83,7 @@ impl Data { fn main() { let args: Vec = env::args().collect(); - if let Some((scene, parameters)) = parameters::process_args(args) { + /*if let Some((scene, parameters)) = parameters::process_args(args) { let (render_options, filename) = (parameters.render_options, parameters.filename.clone()); if !parameters.gui { let start = print_render_start( @@ -244,7 +243,7 @@ fn main() { #[cfg(not(feature = "gui"))] println!("feature: gui not enabled"); } - } + }*/ } #[cfg(feature = "gui")] diff --git a/frontend/src/utility.rs b/frontend/src/utility.rs index 51a7960..5cc3b61 100644 --- a/frontend/src/utility.rs +++ b/frontend/src/utility.rs @@ -61,7 +61,7 @@ pub fn get_readable_duration(duration: Duration) -> String { days_string + &hours_string + &minutes_string + &seconds_string } -pub fn create_bvh_with_info( +/*pub fn create_bvh_with_info( primitives: Vec

, bvh_type: SplitType, ) -> Arc> { @@ -78,7 +78,7 @@ pub fn create_bvh_with_info( println!("\tNumber of BVH nodes: {}\n", bvh.number_nodes()); bvh -} +}*/ pub fn get_progress_output(samples_completed: u64, total_samples: u64) { progress_bar(samples_completed as f64 / total_samples as f64); From cc8e39b63c333eb9542eb3d84655289ca30fd279 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Tue, 31 Jan 2023 10:15:41 +1100 Subject: [PATCH 16/39] Removed parsing code & changed to using albedo in lambertian --- frontend/Cargo.toml | 7 +- frontend/scene/Cargo.toml | 20 -- .../src/parse/acceleration_structures.rs | 126 ---------- frontend/scene/src/parse/camera.rs | 101 -------- frontend/scene/src/parse/materials.rs | 119 --------- frontend/scene/src/parse/mod.rs | 233 ------------------ frontend/scene/src/parse/primitives.rs | 131 ---------- frontend/scene/src/parse/scenes.rs | 164 ------------ frontend/scene/src/parse/sky.rs | 99 -------- frontend/scene/src/parse/textures.rs | 126 ---------- frontend/src/main.rs | 8 +- frontend/{scene/src/lib.rs => src/scene.rs} | 2 +- frontend/src/utility.rs | 9 +- implementations/src/materials/lambertian.rs | 17 +- 14 files changed, 19 insertions(+), 1143 deletions(-) delete mode 100644 frontend/scene/Cargo.toml delete mode 100644 frontend/scene/src/parse/acceleration_structures.rs delete mode 100644 frontend/scene/src/parse/camera.rs delete mode 100644 frontend/scene/src/parse/materials.rs delete mode 100644 frontend/scene/src/parse/mod.rs delete mode 100644 frontend/scene/src/parse/primitives.rs delete mode 100644 frontend/scene/src/parse/scenes.rs delete mode 100644 frontend/scene/src/parse/sky.rs delete mode 100644 frontend/scene/src/parse/textures.rs rename frontend/{scene/src/lib.rs => src/scene.rs} (99%) diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 6f83b69..c234b57 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -6,11 +6,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bumpalo = {version="3.12.0", features=["collections"]} chrono = "0.4.19" image = "0.23.14" +rayon = "1.5.1" rand = { version = "0.8.3", features = [ "small_rng" ] } rand_seeder = "0.2.2" -scene = { path = "./scene" } +implementations = { path = "../implementations" } +ouroboros = "0.15.5" vulkano = { version = "0.28.0", optional = true } vulkano-shaders = { version = "0.28.0", optional = true } vulkano-win = { version = "0.28.0", optional = true } @@ -18,5 +21,5 @@ wavefront_obj = "10.0.0" winit = { version = "0.26.1", optional = true } [features] -f64 = ["scene/f64"] +f64 = ["implementations/f64"] gui = ["dep:vulkano", "dep:vulkano-win", "dep:vulkano-shaders", "dep:winit"] \ No newline at end of file diff --git a/frontend/scene/Cargo.toml b/frontend/scene/Cargo.toml deleted file mode 100644 index e866ead..0000000 --- a/frontend/scene/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "scene" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -image = "0.23.14" -serde = { version = "1.0", features = [ "derive" ] } -implementations = { path = "../../implementations" } -toml = "0.5.10" -rand = "0.8.5" -rayon = "1.5.1" -wavefront_obj = "10.0.0" -ouroboros = "0.15.5" -bumpalo = {version="3.12.0", features=["collections"]} - -[features] -f64 = ["implementations/f64"] \ No newline at end of file diff --git a/frontend/scene/src/parse/acceleration_structures.rs b/frontend/scene/src/parse/acceleration_structures.rs deleted file mode 100644 index 0f6337e..0000000 --- a/frontend/scene/src/parse/acceleration_structures.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::parse::parse_item; -use crate::parse::Error; -use crate::parse::Load; -use implementations::AllMaterials; -use implementations::AllPrimitives; -use implementations::Bvh; -use toml::Value; - -use implementations::split::SplitType; -use implementations::AllTextures; - -use crate::*; -use serde::Deserialize; - -type PrimitiveInstance = AllPrimitives>; -type BvhInstance = Bvh>; - -pub fn parse_accleration_structure( - data: Value, - name: String, - primitives_map: &mut Vec, -) -> Result { - let data = match data.get(name.clone()) { - Some(value) => value, - None => return Err(Error::NoValue(name)), - }; - Ok( - parse_item::, AccelerationStructureLoad>( - data.clone(), - primitives_map, - )? - .try_into()?, - ) -} - -#[derive(Deserialize, Debug)] -pub struct AccelerationStructureLoad { - definition: Option, - #[serde(rename = "type")] - split_type: Option, - #[serde(skip)] - primitive_instances: Vec, -} - -#[derive(Debug)] -pub enum AccelerationStructureLoadError { - MissingField, - InvalidType, - PrimitiveNotFound, -} - -impl Load for AccelerationStructureLoad { - type LoadType = Vec; - fn load(&mut self, primitives_vec: &mut Self::LoadType) -> Result<(), Error> { - self.primitive_instances = primitives_vec.drain(..).collect(); - - Ok(()) - } - fn derive_from(&mut self, other: Self) { - if self.split_type.is_none() { - self.split_type = other.split_type; - } - } - fn get_definition(&self) -> Option { - self.definition.clone() - } - fn get_plural<'a>() -> &'a str { - unreachable!() - } -} - -impl TryInto for AccelerationStructureLoad { - type Error = AccelerationStructureLoadError; - - fn try_into(self) -> Result { - let split_type = match self.split_type { - Some(v) => v, - None => return Err(AccelerationStructureLoadError::MissingField), - }; - - Ok(Bvh::new(self.primitive_instances, { - match &split_type[..] { - "sah" => SplitType::Sah, - "middle" => SplitType::Middle, - "equal_counts" => SplitType::EqualCounts, - _ => return Err(AccelerationStructureLoadError::InvalidType), - } - })) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::parse::materials::parse_materials; - use crate::parse::primitives::parse_primitives; - use crate::parse::tests::EXAMPLE_TOML; - use crate::parse::textures::parse_textures; - use std::collections::HashMap; - - #[test] - pub fn parse_bvh() { - let value = EXAMPLE_TOML.parse::().unwrap(); - let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; - let textures = parse_textures(value.clone(), &texture_names).unwrap(); - - let mut textures: HashMap> = HashMap::from_iter( - texture_names - .into_iter() - .zip(textures.into_iter().map(Arc::new)), - ); - - let material_names = vec!["material_one".to_string()]; - let materials = parse_materials(value.clone(), &material_names, &mut textures).unwrap(); - let mut materials: HashMap>> = HashMap::from_iter( - material_names - .into_iter() - .zip(materials.into_iter().map(Arc::new)), - ); - - let mut primitives = - parse_primitives(value.clone(), &["sphere_one".to_string()], &mut materials).unwrap(); - - let _ = parse_accleration_structure(value, "bvh_one".to_string(), &mut primitives).unwrap(); - } -} diff --git a/frontend/scene/src/parse/camera.rs b/frontend/scene/src/parse/camera.rs deleted file mode 100644 index 9f13dab..0000000 --- a/frontend/scene/src/parse/camera.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::parse::*; - -use serde::Deserialize; - -use toml::Value; - -use crate::*; - -pub fn parse_cameras(data: Value, camera_names: &[String]) -> Result, Error> { - parse_items::<(), CameraLoad, SimpleCamera>(data, camera_names, &mut ()) -} - -#[derive(Debug)] -pub enum CameraLoadError { - MissingField, - InvalidType, -} - -#[derive(Deserialize, Debug)] -pub struct CameraLoad { - definition: Option, - #[serde(rename = "type")] - camera_type: Option, - origin: Option<[Float; 3]>, - lookat: Option<[Float; 3]>, - vup: Option<[Float; 3]>, - fov: Option, - aspect_ratio: Option, - aperture: Option, - focus_dist: Option, -} - -impl Load for CameraLoad { - type LoadType = (); - fn derive_from(&mut self, other: Self) { - if self.camera_type.is_none() { - self.camera_type = other.camera_type; - } - if self.origin.is_none() { - self.origin = other.origin; - } - if self.lookat.is_none() { - self.lookat = other.lookat; - } - if self.vup.is_none() { - self.vup = other.vup; - } - if self.fov.is_none() { - self.fov = other.fov; - } - if self.aspect_ratio.is_none() { - self.aspect_ratio = other.aspect_ratio; - } - if self.aperture.is_none() { - self.aperture = other.aperture; - } - if self.focus_dist.is_none() { - self.focus_dist = other.focus_dist; - } - } - fn get_definition(&self) -> Option { - self.definition.clone() - } - fn get_plural<'a>() -> &'a str { - "cameras" - } -} - -try_into!( - CameraLoad, - SimpleCamera, - CameraLoadError, - SimpleCamera::new( - origin.into(), - lookat.into(), - vup.into(), - fov, - aspect_ratio, - aperture, - focus_dist, - ), - origin, - lookat, - vup, - fov, - aspect_ratio, - aperture, - focus_dist -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::parse::tests::EXAMPLE_TOML; - - #[test] - pub fn parse_texture() { - let value = EXAMPLE_TOML.parse::().unwrap(); - let _ = parse_cameras(value, &["camera_one".to_string()]).unwrap(); - } -} diff --git a/frontend/scene/src/parse/materials.rs b/frontend/scene/src/parse/materials.rs deleted file mode 100644 index 015e4c3..0000000 --- a/frontend/scene/src/parse/materials.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::{parse::*, *}; -use implementations::{AllMaterials, AllTextures, Lambertian}; -use serde::Deserialize; -use std::{collections::HashMap, sync::Arc}; - -use toml::Value; - -use super::parse_items; - -pub fn parse_materials( - data: Value, - material_names: &[String], - textures_map: &mut HashMap>, -) -> Result>, Error> { - parse_items::>, MaterialLoad, AllMaterials>( - data, - material_names, - textures_map, - ) -} - -#[derive(Deserialize, Debug)] -pub struct MaterialLoad { - definition: Option, - #[serde(rename = "type")] - mat_type: Option, - texture: Option, - #[serde(skip)] - texture_instance: Option>, - absorbtion: Option, -} - -impl Load for MaterialLoad { - type LoadType = HashMap>; - fn derive_from(&mut self, other: Self) { - if self.mat_type.is_none() { - self.mat_type = other.mat_type; - } - if self.texture.is_none() { - self.texture = other.texture; - } - if self.absorbtion.is_none() { - self.absorbtion = other.absorbtion; - } - } - fn load(&mut self, textures_map: &mut Self::LoadType) -> Result<(), Error> { - let tex_name = match &self.texture { - Some(tex_name) => tex_name, - None => return Err(MaterialLoadError::MissingField.into()), - }; - - match textures_map.get(tex_name) { - Some(tex) => self.texture_instance = Some(tex.clone()), - None => return Err(MaterialLoadError::TextureNotFound.into()), - }; - - Ok(()) - } - fn get_definition(&self) -> Option { - self.definition.clone() - } - fn get_plural<'a>() -> &'a str { - "materials" - } -} - -#[derive(Debug)] -pub enum MaterialLoadError { - MissingField, - InvalidType, - TextureNotFound, -} - -type Lambert = Lambertian; - -try_into!( - MaterialLoad, - Lambert, - MaterialLoadError, - Lambertian::new(&texture_instance, absorbtion), - texture_instance, - absorbtion -); - -impl TryInto> for MaterialLoad { - type Error = MaterialLoadError; - - fn try_into(self) -> Result, Self::Error> { - match self.mat_type.clone() { - Some(m_type) => match &m_type[..] { - "lambertian" => Ok(AllMaterials::Lambertian(self.try_into()?)), - _ => Err(MaterialLoadError::InvalidType), - }, - _ => Err(MaterialLoadError::MissingField), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::parse::tests::EXAMPLE_TOML; - use crate::parse::textures::parse_textures; - - #[test] - pub fn parse_material() { - let value = EXAMPLE_TOML.parse::().unwrap(); - let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; - let textures = parse_textures(value.clone(), &texture_names).unwrap(); - - let mut textures: HashMap> = HashMap::from_iter( - texture_names - .into_iter() - .zip(textures.into_iter().map(Arc::new)), - ); - - let _ = parse_materials(value, &["material_one".to_string()], &mut textures).unwrap(); - } -} diff --git a/frontend/scene/src/parse/mod.rs b/frontend/scene/src/parse/mod.rs deleted file mode 100644 index d6c7739..0000000 --- a/frontend/scene/src/parse/mod.rs +++ /dev/null @@ -1,233 +0,0 @@ -use crate::parse::acceleration_structures::AccelerationStructureLoadError; -use crate::parse::scenes::SceneLoadError; -use crate::parse::{ - camera::CameraLoadError, materials::MaterialLoadError, primitives::PrimitiveLoadError, - sky::SkyLoadError, textures::TextureLoadError, -}; -use std::io; -use std::path::Path; -use toml::Value; - -pub mod acceleration_structures; -pub mod camera; -pub mod materials; -pub mod primitives; -pub mod scenes; -pub mod sky; -pub mod textures; - -#[macro_export] -macro_rules! try_into { - ($type:ident, $into_type:ident, $error:ident, $constructor:expr, $( $x:ident ), *) => { - impl TryInto<$into_type> for $type { - type Error = $error; - - fn try_into(self) -> Result<$into_type, Self::Error> { - $( - let $x = match self.$x { - Some(v) => v, - None => return Err($error::MissingField), - }; - )* - - Ok($constructor) - } - } - }; -} - -pub trait Load { - type LoadType; - fn derive_from(&mut self, other: Self); - fn load(&mut self, _: &mut Self::LoadType) -> Result<(), Error> { - Ok(()) - } - fn get_definition(&self) -> Option; - fn get_plural<'a>() -> &'a str; -} - -#[derive(Debug)] -pub enum Error { - IO(io::Error), - Parse(toml::de::Error), - TextureLoad(TextureLoadError), - MaterialLoad(MaterialLoadError), - PrimitiveLoad(PrimitiveLoadError), - SkyLoad(SkyLoadError), - CameraLoad(CameraLoadError), - AccelerationStructureLoad(AccelerationStructureLoadError), - SceneLoad(SceneLoadError), - NoValue(String), - WrongType(String), -} - -macro_rules! from { - ($error:path, $variant:ident) => { - impl From<$error> for Error { - fn from(error: $error) -> Self { - Error::$variant(error) - } - } - }; -} - -from!(io::Error, IO); -from!(toml::de::Error, Parse); -from!(TextureLoadError, TextureLoad); -from!(MaterialLoadError, MaterialLoad); -from!(PrimitiveLoadError, PrimitiveLoad); -from!(SkyLoadError, SkyLoad); -from!(CameraLoadError, CameraLoad); -from!(AccelerationStructureLoadError, AccelerationStructureLoad); -from!(SceneLoadError, SceneLoad); - -pub fn parse_value_from_file(filepath: &Path) -> Result { - let data = std::fs::read_to_string(filepath)?; - - Ok(data.parse::()?) -} - -pub fn parse_items<'de, O, L, T>( - data: Value, - names: &[String], - other: &mut O, -) -> Result, Error> -where - L: Load + serde::Deserialize<'de> + Load + TryInto, - Error: From<>::Error>, -{ - let mut loaded_names: Vec = Vec::new(); - let value = match data.get(L::get_plural()) { - Some(value) => value, - None => return Err(Error::NoValue(L::get_plural().to_string())), - }; - - match &value { - Value::Array(vec_values) => { - for value in vec_values { - let name = value.clone().try_into()?; - loaded_names.push(name); - } - } - _ => return Err(Error::WrongType(L::get_plural().to_string())), - }; - - for name in names.iter() { - if !loaded_names.contains(name) { - return Err(Error::NoValue(name.to_owned())); - } - } - - let mut loads = Vec::new(); - for string_name in names.iter() { - let value = match data.get(string_name) { - Some(value) => value, - None => return Err(Error::NoValue(string_name.clone())), - }; - match value { - val @ Value::Table(_) => { - loads.push(parse_item::(val.clone(), other)?); - } - _ => return Err(Error::WrongType(string_name.clone())), - } - } - - let items = loads - .into_iter() - .map(|l| l.try_into()) - .collect::, _>>()?; - - Ok(items) -} - -pub fn parse_item<'de, O, L>(data: Value, other: &mut O) -> Result -where - L: Load + serde::Deserialize<'de> + Load, -{ - let mut load: L = data.try_into()?; - let definition_data: Option = match load.get_definition() { - Some(ref def) => { - let data = std::fs::read_to_string(def)?.parse::()?; - Some(parse_item(data, other)?) - } - _ => None, - }; - - if let Some(data) = definition_data { - load.derive_from(data) - }; - - load.load(other)?; - - Ok(load) -} - -#[cfg(test)] -mod tests { - - pub const EXAMPLE_TOML: &str = r#" -# alternative format (must be up top or they will be a subtable) -texture_two = { type = "image", path = "./textures/rock.png" } -texture_four = { type = "solid_colour", colour = [0.5, 0.5, 0.5] } -texture_three = { definition = "./other.toml" } # can define stuff externally -material_two = { definition = "./other.toml" } - -# these are pub -scenes = ["scene_one"] -materials = ["material_one"] -primitives = ["sphere_one"] -textures = ["texture_four", "texture_one"] -skies = ["sky_one"] -cameras = ["camera_one"] - -# An example scene -[scene_one] -primitives = ["sphere_one", "a_triangle"] -bvh = "bvh_one" -camera = "camera_one" -sky = "sky_one" - -[bvh_one] -type = "sah" - -[camera_one] -type = "simple" -origin = [0.0, -1.0, 1.0] -lookat = [0.0, 0.0, 0.0] -vup = [0.0, 0.0, 1.0] -fov = 36.0 -focus_dist = 10.0 -aperture = 2.0 -aspect_ratio = 1.777777777 - -[material_one] -type = "lambertian" -absorbtion = 0.5 -texture = "texture_one" - -[sky_one] -texture = "texture_one" -sample_res = [ 20, 40 ] - -[texture_one] -type = "lerp" -colour_one = [ 0.0, 0.0, 0.0 ] -colour_two = [ 1.0, 1.0, 1.0 ] - -[sphere_one] -type = "sphere" -radius = 0.5 -position = [0.0, 0.0, 0.5] -material = "material_one" - -[a_triangle] -type = "triangle" -points = [ [0.0, 0.0, 0.0], [0.0, 0.5, 0.0], [0.5, 0.5, 0.0] ] -normals = [ [0.0, 0.0, 1.0] ] -mat = "material_two" - -[some_mesh] -type = "triangle_mesh" -data = "./models/dragon" -mat = "material_two""#; -} diff --git a/frontend/scene/src/parse/primitives.rs b/frontend/scene/src/parse/primitives.rs deleted file mode 100644 index 31ffe0a..0000000 --- a/frontend/scene/src/parse/primitives.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::implementations::{sphere::Sphere, AllMaterials, AllPrimitives, AllTextures}; -use crate::parse::*; -use crate::*; -use serde::Deserialize; -use std::collections::HashMap; -use std::sync::Arc; -use toml::Value; - -type Material = AllMaterials; - -pub fn parse_primitives( - data: Value, - primitive_names: &[String], - materials_map: &mut HashMap>, -) -> Result>, Error> { - parse_items::< - HashMap>>, - PrimitiveLoad, - AllPrimitives>, - >(data, primitive_names, materials_map) -} - -#[derive(Deserialize, Debug)] -pub struct PrimitiveLoad { - definition: Option, - #[serde(rename = "type")] - primitive_type: Option, - material: Option, - #[serde(skip)] - material_instance: Option>>, - position: Option<[Float; 3]>, - radius: Option, -} - -impl Load for PrimitiveLoad { - type LoadType = HashMap>>; - fn load(&mut self, materials_map: &mut Self::LoadType) -> Result<(), Error> { - let mat_name = match &self.material { - Some(mat_name) => mat_name, - None => return Err(PrimitiveLoadError::MissingField.into()), - }; - - match materials_map.get(mat_name) { - Some(mat) => self.material_instance = Some(mat.clone()), - None => return Err(PrimitiveLoadError::MaterialNotFound.into()), - } - - Ok(()) - } - fn derive_from(&mut self, other: Self) { - if self.primitive_type.is_none() { - self.primitive_type = other.primitive_type; - } - if self.material.is_none() { - self.material = other.material; - } - if self.radius.is_none() { - self.radius = other.radius; - } - } - fn get_definition(&self) -> Option { - self.definition.clone() - } - fn get_plural<'a>() -> &'a str { - "primitives" - } -} - -#[derive(Debug)] -pub enum PrimitiveLoadError { - MissingField, - InvalidType, - MaterialNotFound, -} - -type SphereInstance = Sphere; - -try_into!( - PrimitiveLoad, - SphereInstance, - PrimitiveLoadError, - Sphere::new(position.into(), radius, &material_instance), - position, - radius, - material_instance -); - -impl TryInto> for PrimitiveLoad { - type Error = PrimitiveLoadError; - - fn try_into(self) -> Result, Self::Error> { - match self.primitive_type.clone() { - Some(m_type) => match &m_type[..] { - "sphere" => Ok(AllPrimitives::Sphere(self.try_into()?)), - _ => Err(PrimitiveLoadError::InvalidType), - }, - _ => Err(PrimitiveLoadError::MissingField), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::parse::materials::parse_materials; - use crate::parse::tests::EXAMPLE_TOML; - use crate::parse::textures::parse_textures; - - #[test] - pub fn parse_primitive() { - let value = EXAMPLE_TOML.parse::().unwrap(); - let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; - let textures = parse_textures(value.clone(), &texture_names).unwrap(); - - let mut textures: HashMap> = HashMap::from_iter( - texture_names - .into_iter() - .zip(textures.into_iter().map(Arc::new)), - ); - - let material_names = vec!["material_one".to_string()]; - let materials = parse_materials(value.clone(), &material_names, &mut textures).unwrap(); - let mut materials: HashMap> = HashMap::from_iter( - material_names - .into_iter() - .zip(materials.into_iter().map(Arc::new)), - ); - - let _ = parse_primitives(value, &["sphere_one".to_string()], &mut materials).unwrap(); - } -} diff --git a/frontend/scene/src/parse/scenes.rs b/frontend/scene/src/parse/scenes.rs deleted file mode 100644 index 6425c70..0000000 --- a/frontend/scene/src/parse/scenes.rs +++ /dev/null @@ -1,164 +0,0 @@ -use crate::parse::parse_item; -use crate::parse::Error; -use crate::parse::Load; -use crate::Scene; -use implementations::AllPrimitives; -use implementations::Bvh; -use implementations::SimpleCamera; -use implementations::Sky; -use toml::Value; - -use implementations::AllMaterials; - -use implementations::AllTextures; - -use crate::*; -use implementations::random_sampler::RandomSampler; -use serde::Deserialize; - -type Material = AllMaterials; -type Primitive = AllPrimitives; -type BvhInstance = Bvh>; -type SceneInstance = Scene; -type Passthrough = ( - Option, - Option, - Option>, -); - -pub fn parse_scene( - data: Value, - name: String, - passthrough_data: &mut Passthrough, -) -> Result { - let data = match data.get(name.clone()) { - Some(value) => value, - None => return Err(Error::NoValue(name)), - }; - let load: SceneLoad = parse_item::(data.clone(), passthrough_data)?; - Ok(load.try_into()?) -} - -#[derive(Deserialize)] -pub struct SceneLoad { - definition: Option, - camera: Option, - sky: Option, - bvh: Option, - #[serde(skip)] - bvh_instance: Option>, - #[serde(skip)] - camera_instance: Option, - #[serde(skip)] - sky_instance: Option>, -} - -#[derive(Debug)] -pub enum SceneLoadError { - MissingField, - InvalidType, - BvhNotFound, - CameraNotFound, - SkyNotFound, - PrimitiveNotFound, -} - -impl Load for SceneLoad { - type LoadType = ( - Option, - Option, - Option>, - ); - fn load(&mut self, data: &mut Self::LoadType) -> Result<(), Error> { - self.bvh_instance = data.0.take(); - self.camera_instance = data.1.take(); - self.sky_instance = data.2.take(); - - Ok(()) - } - fn derive_from(&mut self, other: Self) { - if self.camera.is_none() { - self.camera = other.camera; - } - if self.sky.is_none() { - self.sky = other.sky; - } - if self.bvh.is_none() { - self.bvh = other.bvh; - } - } - fn get_definition(&self) -> Option { - self.definition.clone() - } - fn get_plural<'a>() -> &'a str { - "scenes" - } -} - -try_into!( - SceneLoad, - SceneInstance, - SceneLoadError, - Scene::new( - Arc::new(camera_instance), - Arc::new(sky_instance), - Arc::new(RandomSampler {}), - Arc::new(bvh_instance), - ), - bvh_instance, - camera_instance, - sky_instance -); - -#[cfg(test)] -mod tests { - use super::*; - use crate::parse::acceleration_structures::parse_accleration_structure; - use crate::parse::camera::parse_cameras; - use crate::parse::materials::parse_materials; - use crate::parse::primitives::parse_primitives; - use crate::parse::sky::parse_skies; - use crate::parse::tests::EXAMPLE_TOML; - use crate::parse::textures::parse_textures; - use std::collections::HashMap; - - #[test] - pub fn scene() { - let value = EXAMPLE_TOML.parse::().unwrap(); - let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; - let textures = parse_textures(value.clone(), &texture_names).unwrap(); - - let mut textures: HashMap> = HashMap::from_iter( - texture_names - .into_iter() - .zip(textures.into_iter().map(Arc::new)), - ); - - let material_names = vec!["material_one".to_string()]; - let materials = parse_materials(value.clone(), &material_names, &mut textures).unwrap(); - let mut materials: HashMap>> = HashMap::from_iter( - material_names - .into_iter() - .zip(materials.into_iter().map(Arc::new)), - ); - - let mut primitives = - parse_primitives(value.clone(), &["sphere_one".to_string()], &mut materials).unwrap(); - - let bvh = - parse_accleration_structure(value.clone(), "bvh_one".to_string(), &mut primitives) - .unwrap(); - let camera = parse_cameras(value.clone(), &["camera_one".to_string()]) - .unwrap() - .remove(0); - let sky = parse_skies(value.clone(), &["sky_one".to_string()], &mut textures) - .unwrap() - .remove(0); - let _ = parse_scene( - value, - "scene_one".to_string(), - &mut (Some(bvh), Some(camera), Some(sky)), - ) - .unwrap(); - } -} diff --git a/frontend/scene/src/parse/sky.rs b/frontend/scene/src/parse/sky.rs deleted file mode 100644 index 86c7153..0000000 --- a/frontend/scene/src/parse/sky.rs +++ /dev/null @@ -1,99 +0,0 @@ -use crate::parse::*; -use implementations::AllTextures; -use serde::Deserialize; -use std::collections::HashMap; - -use toml::Value; - -use crate::*; - -pub fn parse_skies( - data: Value, - sky_names: &[String], - textures_map: &mut HashMap>, -) -> Result>, Error> { - parse_items::>, SkyLoad, Sky>( - data, - sky_names, - textures_map, - ) -} - -#[derive(Deserialize, Debug)] -pub struct SkyLoad { - definition: Option, - texture: Option, - #[serde(skip)] - texture_instance: Option>, - sample_res: Option<[usize; 2]>, -} - -impl Load for SkyLoad { - type LoadType = HashMap>; - fn derive_from(&mut self, other: Self) { - if self.texture.is_none() { - self.texture = other.texture; - } - if self.sample_res.is_none() { - self.sample_res = other.sample_res; - } - } - fn load(&mut self, textures_map: &mut Self::LoadType) -> Result<(), Error> { - let tex_name = match &self.texture { - Some(tex_name) => tex_name, - None => return Err(MaterialLoadError::MissingField.into()), - }; - - match textures_map.get(tex_name) { - Some(tex) => self.texture_instance = Some(tex.clone()), - None => return Err(MaterialLoadError::TextureNotFound.into()), - } - - Ok(()) - } - fn get_definition(&self) -> Option { - self.definition.clone() - } - fn get_plural<'a>() -> &'a str { - "skies" - } -} - -type SkyInstance = Sky; - -try_into!( - SkyLoad, - SkyInstance, - SkyLoadError, - Sky::new(&texture_instance, (sample_res[0], sample_res[1])), - texture_instance, - sample_res -); - -#[derive(Debug)] -pub enum SkyLoadError { - MissingField, - InvalidType, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::parse::tests::EXAMPLE_TOML; - use crate::parse::textures::parse_textures; - - #[test] - pub fn parse_sky() { - let value = EXAMPLE_TOML.parse::().unwrap(); - let texture_names = vec!["texture_four".to_string(), "texture_one".to_string()]; - let textures = parse_textures(value.clone(), &texture_names).unwrap(); - - let mut textures: HashMap> = HashMap::from_iter( - texture_names - .into_iter() - .zip(textures.into_iter().map(Arc::new)), - ); - - let _ = parse_skies(value, &["sky_one".to_string()], &mut textures).unwrap(); - } -} diff --git a/frontend/scene/src/parse/textures.rs b/frontend/scene/src/parse/textures.rs deleted file mode 100644 index 430c781..0000000 --- a/frontend/scene/src/parse/textures.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::parse::*; -use implementations::{AllTextures, CheckeredTexture, ImageTexture, Lerp, Perlin, SolidColour}; -use serde::Deserialize; -use std::path::PathBuf; - -use toml::Value; - -use crate::*; - -pub fn parse_textures(data: Value, texture_names: &[String]) -> Result, Error> { - parse_items::<(), TextureLoad, AllTextures>(data, texture_names, &mut ()) -} - -#[derive(Deserialize, Debug)] -pub struct TextureLoad { - definition: Option, - #[serde(rename = "type")] - tex_type: Option, - colour: Option<[Float; 3]>, - colour_one: Option<[Float; 3]>, - colour_two: Option<[Float; 3]>, - path: Option, -} - -#[derive(Debug)] -pub enum TextureLoadError { - MissingField, - InvalidType, -} - -impl Load for TextureLoad { - type LoadType = (); - fn derive_from(&mut self, other: Self) { - if self.tex_type.is_none() { - self.tex_type = other.tex_type; - } - if self.colour.is_none() { - self.colour = other.colour; - } - if self.colour_one.is_none() { - self.colour_one = other.colour_one; - } - if self.colour_two.is_none() { - self.colour_two = other.colour_two; - } - if self.path.is_none() { - self.path = other.path; - } - } - fn get_definition(&self) -> Option { - self.definition.clone() - } - fn get_plural<'a>() -> &'a str { - "textures" - } -} - -try_into!( - TextureLoad, - SolidColour, - TextureLoadError, - SolidColour::new(colour.into()), - colour -); - -try_into!( - TextureLoad, - ImageTexture, - TextureLoadError, - ImageTexture::new(&path), - path -); - -try_into!( - TextureLoad, - CheckeredTexture, - TextureLoadError, - CheckeredTexture::new(colour_one.into(), colour_two.into()), - colour_one, - colour_two -); - -try_into!( - TextureLoad, - Lerp, - TextureLoadError, - Lerp::new(colour_one.into(), colour_two.into()), - colour_one, - colour_two -); - -try_into!(TextureLoad, Perlin, TextureLoadError, Perlin::new(),); - -impl TryInto for TextureLoad { - type Error = TextureLoadError; - - fn try_into(self) -> Result { - match self.tex_type.clone() { - Some(t_type) => match &t_type[..] { - "solid_colour" => Ok(AllTextures::SolidColour(self.try_into()?)), - "lerp" => Ok(AllTextures::Lerp(self.try_into()?)), - "perlin" => Ok(AllTextures::Perlin(Box::new(self.try_into()?))), - "checkered" => Ok(AllTextures::CheckeredTexture(self.try_into()?)), - "image" => Ok(AllTextures::ImageTexture(self.try_into()?)), - _ => Err(TextureLoadError::InvalidType), - }, - _ => Err(TextureLoadError::MissingField), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::parse::tests::EXAMPLE_TOML; - - #[test] - pub fn parse_texture() { - let value = EXAMPLE_TOML.parse::().unwrap(); - let _ = parse_textures( - value, - &["texture_four".to_string(), "texture_one".to_string()], - ) - .unwrap(); - } -} diff --git a/frontend/src/main.rs b/frontend/src/main.rs index e1d40ae..c34a0e5 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,6 +1,3 @@ -use crate::utility::{ - get_progress_output, print_final_statistics, print_render_start, save_u8_to_image, -}; #[cfg(feature = "gui")] use { gui::{Gui, RenderEvent}, @@ -20,7 +17,7 @@ use { winit::event_loop::EventLoopProxy, }; -use scene::rt_core::{Float, SamplerProgress}; +//use scene::rt_core::{Float, SamplerProgress}; use std::env; #[cfg(feature = "gui")] @@ -32,6 +29,7 @@ mod rendering; //mod load_model; mod macros; //mod parameters; +mod scene; mod utility; #[cfg(feature = "gui")] @@ -81,7 +79,7 @@ impl Data { } fn main() { - let args: Vec = env::args().collect(); + let _args: Vec = env::args().collect(); /*if let Some((scene, parameters)) = parameters::process_args(args) { let (render_options, filename) = (parameters.render_options, parameters.filename.clone()); diff --git a/frontend/scene/src/lib.rs b/frontend/src/scene.rs similarity index 99% rename from frontend/scene/src/lib.rs rename to frontend/src/scene.rs index a17311b..a2a7fbc 100644 --- a/frontend/scene/src/lib.rs +++ b/frontend/src/scene.rs @@ -1,4 +1,4 @@ -pub use implementations::{self, rt_core, SimpleCamera}; +use implementations::{self, rt_core, SimpleCamera}; use rand::Rng; use rayon::prelude::*; use rt_core::*; diff --git a/frontend/src/utility.rs b/frontend/src/utility.rs index 5cc3b61..49d428f 100644 --- a/frontend/src/utility.rs +++ b/frontend/src/utility.rs @@ -1,12 +1,10 @@ use chrono::Local; -use scene::{ - implementations::{aabb::AABound, split::SplitType, Axis, Bvh}, - rt_core::*, -}; +use implementations::rt_core::*; +use implementations::*; + use std::{ io::{stdout, Write}, process, - sync::Arc, time::{Duration, Instant}, }; @@ -206,7 +204,6 @@ pub fn rotate_around_axis(point: &mut Vec3, axis: Axis, sin: Float, cos: Float) mod tests { use super::*; - use scene::implementations::rt_core::PI; #[test] fn rotation() { diff --git a/implementations/src/materials/lambertian.rs b/implementations/src/materials/lambertian.rs index 734949b..fb9aeed 100644 --- a/implementations/src/materials/lambertian.rs +++ b/implementations/src/materials/lambertian.rs @@ -7,7 +7,7 @@ use crate::{ #[derive(Debug, Clone)] pub struct Lambertian<'a, T: Texture> { pub texture: &'a T, - pub absorption: Float, + pub albedo: Float, } #[cfg(all(feature = "f64"))] @@ -20,11 +20,8 @@ impl<'a, T> Lambertian<'a, T> where T: Texture, { - pub fn new(texture: &'a T, absorption: Float) -> Self { - Lambertian { - texture, - absorption, - } + pub fn new(texture: &'a T, albedo: Float) -> Self { + Lambertian { texture, albedo } } } @@ -50,9 +47,9 @@ where hit.normal.dot(wi).max(0.0) / PI } fn eval(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Vec3 { - self.texture.colour_value(wo, hit.point) - * (1.0 - self.absorption) - * hit.normal.dot(wi).max(0.0) - / PI + self.texture.colour_value(wo, hit.point) * self.albedo * hit.normal.dot(wi).max(0.0) / PI + } + fn eval_over_scattering_pdf(&self, hit: &Hit, wo: Vec3, _: Vec3) -> Vec3 { + self.texture.colour_value(wo, hit.point) * self.albedo } } From 43fcf32d4a8b8538b019f502a115b85a1e781dc6 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Tue, 31 Jan 2023 17:36:12 +1100 Subject: [PATCH 17/39] Moved statistics into implementations --- frontend/src/scene.rs | 5 ++-- implementations/Cargo.toml | 6 +++-- implementations/src/acceleration/aabb.rs | 3 ++- implementations/src/acceleration/mod.rs | 2 +- implementations/src/acceleration/split.rs | 3 ++- implementations/src/cameras/mod.rs | 3 ++- implementations/src/lib.rs | 5 ++-- implementations/src/materials/emissive.rs | 3 ++- implementations/src/materials/lambertian.rs | 2 +- implementations/src/materials/mod.rs | 2 +- implementations/src/materials/reflect.rs | 2 +- implementations/src/materials/refract.rs | 2 +- .../src/materials/trowbridge_reitz.rs | 3 ++- implementations/src/primitives/mod.rs | 2 +- implementations/src/primitives/sphere.rs | 3 ++- implementations/src/primitives/triangle.rs | 2 +- implementations/src/samplers/mod.rs | 7 +++--- .../src/samplers/random_sampler.rs | 5 +--- .../src => src/statistics}/bxdfs.rs | 6 ++--- .../src => src/statistics}/chi_squared.rs | 2 +- .../src => src/statistics}/distributions.rs | 5 ++-- .../src => src/statistics}/integrators.rs | 2 +- .../src/lib.rs => src/statistics/mod.rs} | 2 +- .../statistics}/spherical_sampling.rs | 13 ++++------- implementations/src/textures/mod.rs | 2 +- implementations/src/utility/coord.rs | 2 +- implementations/src/utility/mod.rs | 2 +- implementations/statistics/Cargo.toml | 23 ------------------- 28 files changed, 50 insertions(+), 69 deletions(-) rename implementations/{statistics/src => src/statistics}/bxdfs.rs (98%) rename implementations/{statistics/src => src/statistics}/chi_squared.rs (98%) rename implementations/{statistics/src => src/statistics}/distributions.rs (98%) rename implementations/{statistics/src => src/statistics}/integrators.rs (98%) rename implementations/{statistics/src/lib.rs => src/statistics/mod.rs} (98%) rename implementations/{statistics/src => src/statistics}/spherical_sampling.rs (94%) delete mode 100644 implementations/statistics/Cargo.toml diff --git a/frontend/src/scene.rs b/frontend/src/scene.rs index a2a7fbc..4132da0 100644 --- a/frontend/src/scene.rs +++ b/frontend/src/scene.rs @@ -1,7 +1,7 @@ -use implementations::{self, rt_core, SimpleCamera}; +use implementations::rt_core::*; +use implementations::*; use rand::Rng; use rayon::prelude::*; -use rt_core::*; use bumpalo::Bump; use ouroboros::self_referencing; @@ -125,7 +125,6 @@ mod tests { use super::*; use implementations::random_sampler::RandomSampler; use implementations::sphere::Sphere; - use implementations::*; use std::collections::HashMap; #[test] diff --git a/implementations/Cargo.toml b/implementations/Cargo.toml index e91baf8..d2273f8 100644 --- a/implementations/Cargo.toml +++ b/implementations/Cargo.toml @@ -10,8 +10,10 @@ image = "0.24.3" proc = { path = "./proc" } rand = { version = "0.8.3", features = [ "small_rng" ] } rayon = "1.5.1" -statistics = { path = "./statistics" } +rt_core = { path = "../rt_core" } bumpalo = {version="3.12.0", features=["collections"]} +num_cpus = "1.15" +statrs = "0.16.0" [dev-dependencies] @@ -19,4 +21,4 @@ chrono = "0.4.19" statrs = "0.16.0" [features] -f64 = ["statistics/f64"] \ No newline at end of file +f64 = ["rt_core/f64"] \ No newline at end of file diff --git a/implementations/src/acceleration/aabb.rs b/implementations/src/acceleration/aabb.rs index 366bf3a..073e43c 100644 --- a/implementations/src/acceleration/aabb.rs +++ b/implementations/src/acceleration/aabb.rs @@ -1,4 +1,5 @@ -use crate::{rt_core::*, utility::gamma}; +use crate::utility::gamma; +use rt_core::*; pub trait AABound { fn get_aabb(&self) -> AABB; diff --git a/implementations/src/acceleration/mod.rs b/implementations/src/acceleration/mod.rs index 9d98238..e6cd78e 100644 --- a/implementations/src/acceleration/mod.rs +++ b/implementations/src/acceleration/mod.rs @@ -1,10 +1,10 @@ use crate::{ aabb::{AABound, AABB}, acceleration::split::{Split, SplitType}, - rt_core::*, utility::sort_by_indices, Axis, }; +use rt_core::*; use std::{collections::VecDeque, marker::PhantomData}; #[cfg(all(feature = "f64"))] diff --git a/implementations/src/acceleration/split.rs b/implementations/src/acceleration/split.rs index 371c124..2f67391 100644 --- a/implementations/src/acceleration/split.rs +++ b/implementations/src/acceleration/split.rs @@ -1,4 +1,5 @@ -use crate::{aabb::AABB, acceleration::PrimitiveInfo, rt_core::*, Axis}; +use crate::{aabb::AABB, acceleration::PrimitiveInfo, Axis}; +use rt_core::*; const NUM_BUCKETS: usize = 12; const MAX_IN_NODE: usize = 255; diff --git a/implementations/src/cameras/mod.rs b/implementations/src/cameras/mod.rs index 8ee85bd..2dcf074 100644 --- a/implementations/src/cameras/mod.rs +++ b/implementations/src/cameras/mod.rs @@ -1,4 +1,5 @@ -use crate::{rt_core::*, utility::random_float}; +use crate::utility::random_float; +use rt_core::*; #[derive(Debug)] pub struct SimpleCamera { diff --git a/implementations/src/lib.rs b/implementations/src/lib.rs index 7fa0ed7..5e83978 100644 --- a/implementations/src/lib.rs +++ b/implementations/src/lib.rs @@ -3,18 +3,19 @@ mod cameras; mod materials; mod primitives; mod samplers; +mod statistics; mod textures; mod utility; -pub use statistics::rt_core; - pub use acceleration::*; pub use cameras::*; pub use materials::*; pub use primitives::*; pub use proc::*; pub use samplers::*; +pub use statistics::*; pub use textures::*; pub use utility::*; pub use primitives::triangle::Triangle; +pub use rt_core; diff --git a/implementations/src/materials/emissive.rs b/implementations/src/materials/emissive.rs index 61cbf85..e19d471 100644 --- a/implementations/src/materials/emissive.rs +++ b/implementations/src/materials/emissive.rs @@ -1,4 +1,5 @@ -use crate::{rt_core::*, textures::Texture, utility::offset_ray}; +use crate::{textures::Texture, utility::offset_ray}; +use rt_core::*; #[derive(Debug, Clone)] pub struct Emit<'a, T: Texture> { diff --git a/implementations/src/materials/lambertian.rs b/implementations/src/materials/lambertian.rs index fb9aeed..93a8d19 100644 --- a/implementations/src/materials/lambertian.rs +++ b/implementations/src/materials/lambertian.rs @@ -1,8 +1,8 @@ use crate::{ - rt_core::*, textures::Texture, utility::{coord::Coordinate, cosine_hemisphere_sampling, near_zero, offset_ray}, }; +use rt_core::*; #[derive(Debug, Clone)] pub struct Lambertian<'a, T: Texture> { diff --git a/implementations/src/materials/mod.rs b/implementations/src/materials/mod.rs index 18b2831..00d3ee9 100644 --- a/implementations/src/materials/mod.rs +++ b/implementations/src/materials/mod.rs @@ -1,5 +1,5 @@ -use crate::rt_core::{Float, Hit, Ray, Scatter, Vec3}; use proc::Scatter; +use rt_core::{Float, Hit, Ray, Scatter, Vec3}; pub mod emissive; pub mod lambertian; diff --git a/implementations/src/materials/reflect.rs b/implementations/src/materials/reflect.rs index b9bc3d1..880b3dc 100644 --- a/implementations/src/materials/reflect.rs +++ b/implementations/src/materials/reflect.rs @@ -1,8 +1,8 @@ use crate::{ - rt_core::*, textures::Texture, utility::{offset_ray, random_unit_vector}, }; +use rt_core::*; #[derive(Debug, Clone)] pub struct Reflect<'a, T: Texture> { diff --git a/implementations/src/materials/refract.rs b/implementations/src/materials/refract.rs index 8056e5c..9a1650f 100644 --- a/implementations/src/materials/refract.rs +++ b/implementations/src/materials/refract.rs @@ -1,9 +1,9 @@ use crate::{ materials::reflect::Reflect, - rt_core::*, textures::Texture, utility::{offset_ray, random_float}, }; +use rt_core::*; #[derive(Debug, Clone)] pub struct Refract<'a, T: Texture> { diff --git a/implementations/src/materials/trowbridge_reitz.rs b/implementations/src/materials/trowbridge_reitz.rs index 98bda8d..2535a4e 100644 --- a/implementations/src/materials/trowbridge_reitz.rs +++ b/implementations/src/materials/trowbridge_reitz.rs @@ -1,10 +1,11 @@ use crate::{ materials::refract, - rt_core::*, + statistics, textures::Texture, utility::{coord::Coordinate, offset_ray}, }; use rand::{rngs::SmallRng, thread_rng, SeedableRng}; +use rt_core::*; #[derive(Debug, Clone)] pub struct TrowbridgeReitz<'a, T: Texture> { diff --git a/implementations/src/primitives/mod.rs b/implementations/src/primitives/mod.rs index d62ea03..5665549 100644 --- a/implementations/src/primitives/mod.rs +++ b/implementations/src/primitives/mod.rs @@ -4,9 +4,9 @@ use crate::{ sphere::Sphere, triangle::{MeshTriangle, Triangle}, }, - rt_core::*, }; use proc::Primitive; +use rt_core::*; pub mod sphere; pub mod triangle; diff --git a/implementations/src/primitives/sphere.rs b/implementations/src/primitives/sphere.rs index 7086455..21a2757 100644 --- a/implementations/src/primitives/sphere.rs +++ b/implementations/src/primitives/sphere.rs @@ -1,9 +1,10 @@ use crate::{ aabb::{AABound, AABB}, - rt_core::*, utility::{coord::Coordinate, random_float}, }; +use rt_core::*; + #[derive(Debug)] pub struct Sphere<'a, M: Scatter> { pub center: Vec3, diff --git a/implementations/src/primitives/triangle.rs b/implementations/src/primitives/triangle.rs index 8c408f9..1861ced 100644 --- a/implementations/src/primitives/triangle.rs +++ b/implementations/src/primitives/triangle.rs @@ -1,10 +1,10 @@ use crate::{ aabb::{AABound, AABB}, primitives::Axis, - rt_core::*, utility::{check_side, gamma}, }; use rand::{thread_rng, Rng}; +use rt_core::*; use std::sync::Arc; #[derive(Clone, Debug)] diff --git a/implementations/src/samplers/mod.rs b/implementations/src/samplers/mod.rs index 617c684..e717aed 100644 --- a/implementations/src/samplers/mod.rs +++ b/implementations/src/samplers/mod.rs @@ -1,6 +1,7 @@ -use crate::{generate_values, next_float, random_float, rt_core::*, textures::Texture}; +use crate::statistics::distributions::*; +use crate::{generate_values, next_float, random_float, textures::Texture}; use rand::{rngs::SmallRng, thread_rng, SeedableRng}; -use statistics::distributions::*; +use rt_core::*; pub mod random_sampler; @@ -73,10 +74,10 @@ impl<'a, T: Texture> NoHit for Sky<'a, T> { #[cfg(test)] mod tests { + use crate::statistics::spherical_sampling::test_spherical_pdf; use crate::*; use rand::rngs::ThreadRng; use rt_core::*; - use statistics::spherical_sampling::test_spherical_pdf; #[test] fn sky_sampling() { diff --git a/implementations/src/samplers/random_sampler.rs b/implementations/src/samplers/random_sampler.rs index 8fba416..cc536c2 100644 --- a/implementations/src/samplers/random_sampler.rs +++ b/implementations/src/samplers/random_sampler.rs @@ -1,9 +1,6 @@ -use crate::rt_core::{ - AccelerationStructure, Camera, Float, NoHit, Primitive, Ray, RenderMethod, RenderOptions, - Sampler, SamplerProgress, Scatter, -}; use rand::Rng; use rayon::prelude::*; +use rt_core::*; pub struct RandomSampler; diff --git a/implementations/statistics/src/bxdfs.rs b/implementations/src/statistics/bxdfs.rs similarity index 98% rename from implementations/statistics/src/bxdfs.rs rename to implementations/src/statistics/bxdfs.rs index dcdd0ec..1d1327b 100644 --- a/implementations/statistics/src/bxdfs.rs +++ b/implementations/src/statistics/bxdfs.rs @@ -1,5 +1,5 @@ pub mod trowbridge_reitz { - use crate::*; + use crate::statistics::*; use rand::Rng; pub fn sample_h(alpha: Float, rng: &mut R) -> Vec3 { let r1: Float = rng.gen(); @@ -65,8 +65,8 @@ pub mod trowbridge_reitz { } pub mod trowbridge_reitz_vndf { - use crate::*; use rand::Rng; + use rt_core::*; // All in local frame pub fn d_ansiotropic(a_x: Float, a_y: Float, h: Vec3) -> Float { @@ -177,7 +177,7 @@ pub mod trowbridge_reitz_vndf { #[cfg(test)] pub mod test { use super::*; - use crate::{spherical_sampling::*, *}; + use crate::statistics::{spherical_sampling::*, *}; use rand::{rngs::ThreadRng, thread_rng, Rng}; #[test] diff --git a/implementations/statistics/src/chi_squared.rs b/implementations/src/statistics/chi_squared.rs similarity index 98% rename from implementations/statistics/src/chi_squared.rs rename to implementations/src/statistics/chi_squared.rs index 41e3473..d83d7ab 100644 --- a/implementations/statistics/src/chi_squared.rs +++ b/implementations/src/statistics/chi_squared.rs @@ -1,4 +1,4 @@ -use crate::*; +use crate::statistics::*; use rayon::prelude::*; use statrs::function::gamma::gamma_ur; diff --git a/implementations/statistics/src/distributions.rs b/implementations/src/statistics/distributions.rs similarity index 98% rename from implementations/statistics/src/distributions.rs rename to implementations/src/statistics/distributions.rs index 5d22998..58f0bfc 100644 --- a/implementations/statistics/src/distributions.rs +++ b/implementations/src/statistics/distributions.rs @@ -1,4 +1,5 @@ -use crate::Float; +use rt_core::Float; + use rand::Rng; #[derive(Debug, Clone, PartialEq)] @@ -114,7 +115,7 @@ impl Distribution2D { #[cfg(test)] mod tests { - use crate::{chi_squared::*, distributions::*, utility::*}; + use crate::statistics::{chi_squared::*, distributions::*, utility::*}; use rand::thread_rng; use rayon::prelude::*; diff --git a/implementations/statistics/src/integrators.rs b/implementations/src/statistics/integrators.rs similarity index 98% rename from implementations/statistics/src/integrators.rs rename to implementations/src/statistics/integrators.rs index d1dc9f4..90bf5c1 100644 --- a/implementations/statistics/src/integrators.rs +++ b/implementations/src/statistics/integrators.rs @@ -1,4 +1,4 @@ -use crate::{Float, Vec3}; +use rt_core::{Float, Vec3}; const MAX_DEPTH: usize = 6; const EPSILON: Float = 0.000001; diff --git a/implementations/statistics/src/lib.rs b/implementations/src/statistics/mod.rs similarity index 98% rename from implementations/statistics/src/lib.rs rename to implementations/src/statistics/mod.rs index 66d3cf4..ef4a189 100644 --- a/implementations/statistics/src/lib.rs +++ b/implementations/src/statistics/mod.rs @@ -1,4 +1,4 @@ -pub use rt_core::{self, *}; +use rt_core::{self, *}; pub mod bxdfs; pub mod chi_squared; diff --git a/implementations/statistics/src/spherical_sampling.rs b/implementations/src/statistics/spherical_sampling.rs similarity index 94% rename from implementations/statistics/src/spherical_sampling.rs rename to implementations/src/statistics/spherical_sampling.rs index 0e4d358..eb163c6 100644 --- a/implementations/statistics/src/spherical_sampling.rs +++ b/implementations/src/statistics/spherical_sampling.rs @@ -1,10 +1,7 @@ -use crate::chi_squared::chi2_probability; -use crate::chi_squared::chi_squared; -use crate::integrators::integrate_solid_angle; -use crate::utility::distribute_samples_over_threads; -use crate::utility::recursively_binary_average; -use crate::*; -use crate::{Float, Vec3, PI}; +use crate::statistics::chi_squared::*; +use crate::statistics::integrators::integrate_solid_angle; +use crate::statistics::utility::*; +use crate::statistics::*; use rand::rngs::ThreadRng; use rand::thread_rng; use rand::Rng; @@ -205,7 +202,7 @@ where #[cfg(test)] pub mod test { - use crate::{spherical_sampling::*, Float, Vec3, TAU}; + use super::*; use rand::Rng; pub fn to_vec(sin_theta: Float, cos_theta: Float, phi: Float) -> Vec3 { diff --git a/implementations/src/textures/mod.rs b/implementations/src/textures/mod.rs index a80ce85..e297854 100644 --- a/implementations/src/textures/mod.rs +++ b/implementations/src/textures/mod.rs @@ -1,7 +1,7 @@ -use crate::rt_core::*; use image::{io::Reader, GenericImageView}; use proc::Texture; use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; +use rt_core::*; use std::path::Path; const PERLIN_RVECS: usize = 256; diff --git a/implementations/src/utility/coord.rs b/implementations/src/utility/coord.rs index 740786f..6663540 100644 --- a/implementations/src/utility/coord.rs +++ b/implementations/src/utility/coord.rs @@ -1,4 +1,4 @@ -use crate::rt_core::Vec3; +use rt_core::Vec3; pub struct Coordinate { pub x: Vec3, diff --git a/implementations/src/utility/mod.rs b/implementations/src/utility/mod.rs index 12842f1..021d476 100644 --- a/implementations/src/utility/mod.rs +++ b/implementations/src/utility/mod.rs @@ -1,5 +1,5 @@ -use crate::rt_core::{Float, Vec3, PI}; use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; +use rt_core::{Float, Vec3, PI}; pub mod coord; diff --git a/implementations/statistics/Cargo.toml b/implementations/statistics/Cargo.toml deleted file mode 100644 index 5467767..0000000 --- a/implementations/statistics/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "statistics" -version = "0.1.0" -edition = "2021" - -[dependencies] -rand = "0.8.3" -num_cpus = "1.15" -statrs = "0.16.0" -rayon = "1.5.1" -rt_core = { path = "../../rt_core" } -image = "0.24.5" - -[dev-dependencies] -image = "0.24.5" -num_cpus = "1.15" -statrs = "0.16.0" -rand_core = "0.6.0" -rayon = "1.5.1" -rand = { version = "0.8.3", features = [ "small_rng" ] } - -[features] -f64 = ["rt_core/f64"] \ No newline at end of file From 61e0aade15261f8f0ce4225b66ef052aaa19912d Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 1 Feb 2023 15:49:52 +1100 Subject: [PATCH 18/39] Cleaned up TR/GGX code a bit --- implementations/src/materials/lambertian.rs | 4 +- .../src/materials/trowbridge_reitz.rs | 4 +- implementations/src/primitives/sphere.rs | 4 +- implementations/src/statistics/bxdfs.rs | 227 ------------------ implementations/src/statistics/bxdfs/mod.rs | 29 +++ .../src/statistics/bxdfs/trowbridge_reitz.rs | 63 +++++ .../statistics/bxdfs/trowbridge_reitz_vndf.rs | 190 +++++++++++++++ implementations/src/utility/coord.rs | 41 +++- implementations/tests/sampling.rs | 4 +- 9 files changed, 329 insertions(+), 237 deletions(-) delete mode 100644 implementations/src/statistics/bxdfs.rs create mode 100644 implementations/src/statistics/bxdfs/mod.rs create mode 100644 implementations/src/statistics/bxdfs/trowbridge_reitz.rs create mode 100644 implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs diff --git a/implementations/src/materials/lambertian.rs b/implementations/src/materials/lambertian.rs index 93a8d19..8176db2 100644 --- a/implementations/src/materials/lambertian.rs +++ b/implementations/src/materials/lambertian.rs @@ -31,8 +31,8 @@ where { fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { let coordinate_system = Coordinate::new_from_z(hit.normal); - let mut direction = cosine_hemisphere_sampling(); - coordinate_system.vec_to_coordinate(&mut direction); + let direction = cosine_hemisphere_sampling(); + let mut direction = coordinate_system.vec_to_coordinate(direction); if near_zero(direction) { direction = hit.normal; diff --git a/implementations/src/materials/trowbridge_reitz.rs b/implementations/src/materials/trowbridge_reitz.rs index 2535a4e..457b2b9 100644 --- a/implementations/src/materials/trowbridge_reitz.rs +++ b/implementations/src/materials/trowbridge_reitz.rs @@ -67,12 +67,12 @@ where fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { let coord = Coordinate::new_from_z(hit.normal); - let mut h = statistics::bxdfs::trowbridge_reitz::sample_h( + let h = statistics::bxdfs::trowbridge_reitz::sample_h( self.alpha, &mut SmallRng::from_rng(thread_rng()).unwrap(), ); - coord.vec_to_coordinate(&mut h); + let h = coord.vec_to_coordinate(h); let direction = ray.direction.reflected(h); diff --git a/implementations/src/primitives/sphere.rs b/implementations/src/primitives/sphere.rs index 21a2757..24ddc14 100644 --- a/implementations/src/primitives/sphere.rs +++ b/implementations/src/primitives/sphere.rs @@ -145,8 +145,8 @@ where // get sphere point let coord_system = Coordinate::new_from_z((in_point - self.center).normalised()); - let mut vec = Vec3::new(sin_alpha * phi.cos(), sin_alpha * phi.sin(), cos_alpha); - coord_system.vec_to_coordinate(&mut vec); + let vec = Vec3::new(sin_alpha * phi.cos(), sin_alpha * phi.sin(), cos_alpha); + let vec = coord_system.vec_to_coordinate(vec); self.center + self.radius * vec }; diff --git a/implementations/src/statistics/bxdfs.rs b/implementations/src/statistics/bxdfs.rs deleted file mode 100644 index 1d1327b..0000000 --- a/implementations/src/statistics/bxdfs.rs +++ /dev/null @@ -1,227 +0,0 @@ -pub mod trowbridge_reitz { - use crate::statistics::*; - use rand::Rng; - pub fn sample_h(alpha: Float, rng: &mut R) -> Vec3 { - let r1: Float = rng.gen(); - let r2: Float = rng.gen(); - let cos_theta = ((1.0 - r1) / (r1 * (alpha * alpha - 1.0) + 1.0)).sqrt(); - let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); - let phi_s = (TAU * r2).max(0.0).min(TAU); - Vec3::new(phi_s.cos() * sin_theta, phi_s.sin() * sin_theta, cos_theta).normalised() - } - - pub fn reference_d(h: Vec3, alpha: Float) -> Float { - if h.z <= 0.0 { - return 0.0; - } - - let a_sq = alpha * alpha; - let cos_theta = h.z; - let cos_theta_sq = cos_theta * cos_theta; - let sin_theta = (1.0 - cos_theta_sq).sqrt(); - let tan_theta = sin_theta / cos_theta; - let tmp = a_sq + tan_theta * tan_theta; - - a_sq / (PI * cos_theta_sq * cos_theta_sq * tmp * tmp) - } - - pub fn d(alpha: Float, cos_theta: Float) -> Float { - if cos_theta <= 0.0 { - return 0.0; - } - let a_sq = alpha * alpha; - let tmp = cos_theta * cos_theta * (a_sq - 1.0) + 1.0; - a_sq / (PI * tmp * tmp) - } - - pub fn alternative_d(alpha: Float, h: Vec3, cos_theta: Float) -> Float { - if cos_theta < 0.0 { - return 0.0; - } - let a_sq = alpha * alpha; - let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; - 1.0 / (PI * a_sq * tmp * tmp) - } - - pub fn pdf_h(h: Vec3, alpha: Float) -> Float { - // technically the paper has an .abs() but it isn't needed since h.z < 0 => D(m) = 0 - d(alpha, h.z) * h.z - } - - pub fn sample(alpha: Float, incoming: Vec3, rng: &mut R) -> Vec3 { - let h = sample_h(alpha, rng); - 2.0 * incoming.dot(h) * h - incoming - } - - pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { - let mut h = (incoming + outgoing).normalised(); - if h.dot(normal) < 0.0 { - h = -(incoming + outgoing).normalised(); - } - let cos_theta = normal.dot(h); - let d = d(alpha, cos_theta); - d * h.dot(normal).abs() / (4.0 * outgoing.dot(h).abs()) - } -} - -pub mod trowbridge_reitz_vndf { - use rand::Rng; - use rt_core::*; - - // All in local frame - pub fn d_ansiotropic(a_x: Float, a_y: Float, h: Vec3) -> Float { - let tmp = h.x * h.x / (a_x * a_x) + h.y * h.y / (a_y * a_y) + h.z * h.z; - 1.0 / (PI * a_x * a_y * tmp * tmp) - } - - pub fn d_isotropic(a: Float, h: Vec3) -> Float { - let a_sq = a * a; - - let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; - if (1.0 / (PI * a_sq * tmp * tmp)).is_nan() { - println!("{tmp} = ({}^2 + {}^2) / {a_sq} + {}^2", h.x, h.y, h.z); - } - 1.0 / (PI * a_sq * tmp * tmp) - } - - pub fn lambda_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { - let tmp = 1.0 + (a_x * a_x * v.x * v.x + a_y * a_y * v.y * v.y) / (v.z * v.z); - 0.5 * (tmp.sqrt() - 1.0) - } - - pub fn lambda_isotropic(a: Float, v: Vec3) -> Float { - let tmp = 1.0 + (a * a * (v.x * v.x + v.y * v.y)) / (v.z * v.z); - 0.5 * (tmp.sqrt() - 1.0) - } - - pub fn g1_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { - 1.0 / (1.0 + lambda_ansiotropic(a_x, a_y, v)) - } - pub fn g1_isotropic(a: Float, v: Vec3) -> Float { - 1.0 / (1.0 + lambda_isotropic(a, v)) - } - - pub fn vndf_ansiotropic(a_x: Float, a_y: Float, h: Vec3, v: Vec3) -> Float { - if h.z < 0.0 { - return 0.0; - } - g1_ansiotropic(a_x, a_y, v) * v.dot(h).max(0.0) * d_ansiotropic(a_x, a_y, h) / v.z - } - - pub fn vndf_isotropic(a: Float, h: Vec3, v: Vec3) -> Float { - if h.z < 0.0 { - return 0.0; - } - g1_isotropic(a, v) * v.dot(h).max(0.0) * d_isotropic(a, h) / v.z - } - pub fn sample_vndf_ansiotropic(a_x: Float, a_y: Float, v: Vec3, rng: &mut R) -> Vec3 { - // chapter 1 act 1 - // transform from ellipsoid configuration to hemisphere by multipling by the scaling factors on the x and y axies (a_x, a_y) - let v_hemisphere = Vec3::new(a_x * v.x, a_y * v.y, v.z).normalised(); - - // chapter 2 act 1 - - // interlude - let len_sq = v_hemisphere.x * v_hemisphere.x + v_hemisphere.y * v_hemisphere.y; - - let basis_two = if len_sq > 0.0 { - Vec3::new(-v_hemisphere.y, v_hemisphere.x, 0.0) / len_sq.sqrt() - } else { - Vec3::new(1.0, 0.0, 0.0) // len_sq = 0 implies v_hemisphere = Z, so X is a valid orthonormal basis - }; - let basis_three = v_hemisphere.cross(basis_two); // v_hemisphere is first basis - - // chapter 3 act 1 - let r = rng.gen::().sqrt(); - let phi = TAU * rng.gen::(); - let mut t = r * Vec2::new(phi.cos(), phi.sin()); - let s = 0.5 * (1.0 + v_hemisphere.z); - t.y = (1.0 - s) * (1.0 - t.x * t.x).sqrt() + s * t.y; - - // chapter 4 act 1 - let h_hemisphere = t.x * basis_two - + t.y * basis_three - + (1.0 - t.x * t.x - t.y * t.y).max(0.0).sqrt() * v_hemisphere; - - // chapter 5 final act - - // not dividing since microfacet normal is a covector - Vec3::new( - a_x * h_hemisphere.x, - a_y * h_hemisphere.y, - h_hemisphere.z.max(0.0), // avoid numerical errors - ) - .normalised() - } - - pub fn sample_vndf_isotropic(a: Float, v: Vec3, rng: &mut R) -> Vec3 { - sample_vndf_ansiotropic(a, a, v, rng) // cause I'm lazy - } - - pub fn sample_outgoing_isotropic(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { - let h = sample_vndf_isotropic(a, incoming, rng); - 2.0 * incoming.dot(h) * h - incoming - } - - pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { - let mut h = (incoming + outgoing).normalised(); - if h.dot(normal) < 0.0 { - h = -(incoming + outgoing).normalised(); - } - let _cos_theta = normal.dot(h); - let vndf = vndf_ansiotropic(alpha, alpha, h, incoming); - vndf / (4.0 * incoming.dot(h)) - } -} - -#[cfg(test)] -pub mod test { - use super::*; - use crate::statistics::{spherical_sampling::*, *}; - use rand::{rngs::ThreadRng, thread_rng, Rng}; - - #[test] - fn trowbridge_reitz_h() { - let alpha: Float = thread_rng().gen(); - let pdf = |outgoing: Vec3| trowbridge_reitz::pdf_h(outgoing, alpha); - let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample_h(alpha, rng); - test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); - } - - #[test] - fn trowbridge_reitz() { - let mut rng = thread_rng(); - let incoming: Vec3 = generate_wi(&mut rng); - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| { - trowbridge_reitz::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) - }; - let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); - test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); - } - #[test] - fn trowbridge_reitz_vndf_h() { - let mut rng = thread_rng(); - let incoming: Vec3 = generate_wi(&mut rng); - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| trowbridge_reitz_vndf::vndf_isotropic(alpha, outgoing, incoming); - let sample = |rng: &mut ThreadRng| { - trowbridge_reitz_vndf::sample_vndf_isotropic(alpha, incoming, rng) - }; - test_spherical_pdf("trowbrige reitz vndf h", &pdf, &sample, false); - } - - #[test] - fn trowbridge_reitz_vndf() { - let mut rng = thread_rng(); - let incoming: Vec3 = generate_wi(&mut rng); - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| { - trowbridge_reitz_vndf::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) - }; - let sample = |rng: &mut ThreadRng| { - trowbridge_reitz_vndf::sample_outgoing_isotropic(alpha, incoming, rng) - }; - test_spherical_pdf("trowbrige reitz vndf", &pdf, &sample, false); - } -} diff --git a/implementations/src/statistics/bxdfs/mod.rs b/implementations/src/statistics/bxdfs/mod.rs new file mode 100644 index 0000000..7679e9e --- /dev/null +++ b/implementations/src/statistics/bxdfs/mod.rs @@ -0,0 +1,29 @@ +pub mod trowbridge_reitz; +pub mod trowbridge_reitz_vndf; + +#[cfg(test)] +pub mod test { + use super::*; + use crate::statistics::{spherical_sampling::*, *}; + use rand::{rngs::ThreadRng, thread_rng, Rng}; + + #[test] + fn trowbridge_reitz_h() { + let alpha: Float = thread_rng().gen(); + let pdf = |outgoing: Vec3| trowbridge_reitz::pdf_h(outgoing, alpha); + let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample_h(alpha, rng); + test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); + } + + #[test] + fn trowbridge_reitz() { + let mut rng = thread_rng(); + let incoming: Vec3 = generate_wi(&mut rng); + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| { + trowbridge_reitz::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) + }; + let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); + test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); + } +} diff --git a/implementations/src/statistics/bxdfs/trowbridge_reitz.rs b/implementations/src/statistics/bxdfs/trowbridge_reitz.rs new file mode 100644 index 0000000..521ac5b --- /dev/null +++ b/implementations/src/statistics/bxdfs/trowbridge_reitz.rs @@ -0,0 +1,63 @@ +use crate::statistics::*; +use rand::Rng; +pub fn sample_h(alpha: Float, rng: &mut R) -> Vec3 { + let r1: Float = rng.gen(); + let r2: Float = rng.gen(); + let cos_theta = ((1.0 - r1) / (r1 * (alpha * alpha - 1.0) + 1.0)).sqrt(); + let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); + let phi_s = (TAU * r2).max(0.0).min(TAU); + Vec3::new(phi_s.cos() * sin_theta, phi_s.sin() * sin_theta, cos_theta).normalised() +} + +pub fn reference_d(h: Vec3, alpha: Float) -> Float { + if h.z <= 0.0 { + return 0.0; + } + + let a_sq = alpha * alpha; + let cos_theta = h.z; + let cos_theta_sq = cos_theta * cos_theta; + let sin_theta = (1.0 - cos_theta_sq).sqrt(); + let tan_theta = sin_theta / cos_theta; + let tmp = a_sq + tan_theta * tan_theta; + + a_sq / (PI * cos_theta_sq * cos_theta_sq * tmp * tmp) +} + +pub fn d(alpha: Float, cos_theta: Float) -> Float { + if cos_theta <= 0.0 { + return 0.0; + } + let a_sq = alpha * alpha; + let tmp = cos_theta * cos_theta * (a_sq - 1.0) + 1.0; + a_sq / (PI * tmp * tmp) +} + +pub fn alternative_d(alpha: Float, h: Vec3, cos_theta: Float) -> Float { + if cos_theta < 0.0 { + return 0.0; + } + let a_sq = alpha * alpha; + let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; + 1.0 / (PI * a_sq * tmp * tmp) +} + +pub fn pdf_h(h: Vec3, alpha: Float) -> Float { + // technically the paper has an .abs() but it isn't needed since h.z < 0 => D(m) = 0 + d(alpha, h.z) * h.z +} + +pub fn sample(alpha: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + let h = sample_h(alpha, rng); + 2.0 * incoming.dot(h) * h - incoming +} + +pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { + let mut h = (incoming + outgoing).normalised(); + if h.dot(normal) < 0.0 { + h = -(incoming + outgoing).normalised(); + } + let cos_theta = normal.dot(h); + let d = d(alpha, cos_theta); + d * h.dot(normal).abs() / (4.0 * outgoing.dot(h).abs()) +} diff --git a/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs b/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs new file mode 100644 index 0000000..98b2039 --- /dev/null +++ b/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs @@ -0,0 +1,190 @@ +use rand::Rng; +use rt_core::*; + +// All in local frame +pub fn d_ansiotropic(a_x: Float, a_y: Float, h: Vec3) -> Float { + let tmp = h.x * h.x / (a_x * a_x) + h.y * h.y / (a_y * a_y) + h.z * h.z; + 1.0 / (PI * a_x * a_y * tmp * tmp) +} + +pub fn d_isotropic(a: Float, h: Vec3) -> Float { + let a_sq = a * a; + + let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; + + 1.0 / (PI * a_sq * tmp * tmp) +} + +pub fn lambda_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { + let tmp = 1.0 + (a_x * a_x * v.x * v.x + a_y * a_y * v.y * v.y) / (v.z * v.z); + 0.5 * (tmp.sqrt() - 1.0) +} + +pub fn lambda_isotropic(a: Float, v: Vec3) -> Float { + let tmp = 1.0 + (a * a * (v.x * v.x + v.y * v.y)) / (v.z * v.z); + 0.5 * (tmp.sqrt() - 1.0) +} + +pub fn g1_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { + 1.0 / (1.0 + lambda_ansiotropic(a_x, a_y, v)) +} +pub fn g1_isotropic(a: Float, v: Vec3) -> Float { + 1.0 / (1.0 + lambda_isotropic(a, v)) +} + +pub fn vndf_ansiotropic(a_x: Float, a_y: Float, h: Vec3, v: Vec3) -> Float { + if h.z < 0.0 { + return 0.0; + } + g1_ansiotropic(a_x, a_y, v) * v.dot(h).max(0.0) * d_ansiotropic(a_x, a_y, h) / v.z +} + +pub fn vndf_isotropic(a: Float, h: Vec3, v: Vec3) -> Float { + if h.z < 0.0 { + return 0.0; + } + g1_isotropic(a, v) * v.dot(h).max(0.0) * d_isotropic(a, h) / v.z +} +pub fn sample_vndf_ansiotropic(a_x: Float, a_y: Float, v: Vec3, rng: &mut R) -> Vec3 { + // chapter 1 act 1 + // transform from ellipsoid configuration to hemisphere by multipling by the scaling factors on the x and y axies (a_x, a_y) + let v_hemisphere = Vec3::new(a_x * v.x, a_y * v.y, v.z).normalised(); + + // chapter 2 act 1 + + // interlude + let len_sq = v_hemisphere.x * v_hemisphere.x + v_hemisphere.y * v_hemisphere.y; + + let basis_two = if len_sq > 0.0 { + Vec3::new(-v_hemisphere.y, v_hemisphere.x, 0.0) / len_sq.sqrt() + } else { + Vec3::new(1.0, 0.0, 0.0) // len_sq = 0 implies v_hemisphere = Z, so X is a valid orthonormal basis + }; + let basis_three = v_hemisphere.cross(basis_two); // v_hemisphere is first basis + + // chapter 3 act 1 + let r = rng.gen::().sqrt(); + let phi = TAU * rng.gen::(); + let mut t = r * Vec2::new(phi.cos(), phi.sin()); + let s = 0.5 * (1.0 + v_hemisphere.z); + t.y = (1.0 - s) * (1.0 - t.x * t.x).sqrt() + s * t.y; + + // chapter 4 act 1 + let h_hemisphere = t.x * basis_two + + t.y * basis_three + + (1.0 - t.x * t.x - t.y * t.y).max(0.0).sqrt() * v_hemisphere; + + // chapter 5 final act + + // not dividing since microfacet normal is a covector + Vec3::new( + a_x * h_hemisphere.x, + a_y * h_hemisphere.y, + h_hemisphere.z.max(0.0), // avoid numerical errors + ) + .normalised() +} + +pub fn sample_vndf_isotropic(a: Float, v: Vec3, rng: &mut R) -> Vec3 { + sample_vndf_ansiotropic(a, a, v, rng) // cause I'm lazy +} + +pub fn sample_outgoing_isotropic(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + let h = sample_vndf_isotropic(a, incoming, rng); + 2.0 * incoming.dot(h) * h - incoming +} + +pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3) -> Float { + let mut h = (incoming + outgoing).normalised(); + if h.z < 0.0 { + h = -(incoming + outgoing).normalised(); + } + let vndf = vndf_ansiotropic(alpha, alpha, h, incoming); + vndf / (4.0 * incoming.dot(h)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::coord::Coordinate; + use crate::statistics::spherical_sampling::*; + use rand::{rngs::ThreadRng, thread_rng, Rng}; + + #[test] + fn trowbridge_reitz_vndf_h() { + let mut rng = thread_rng(); + let incoming: Vec3 = generate_wi(&mut rng); + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| vndf_isotropic(alpha, outgoing, incoming); + let sample = |rng: &mut ThreadRng| sample_vndf_isotropic(alpha, incoming, rng); + test_spherical_pdf("trowbrige reitz vndf h", &pdf, &sample, false); + } + + #[test] + fn trowbridge_reitz_vndf_h_non_local() { + let mut rng = thread_rng(); + + let normal = random_unit_vector(&mut rng); + + let to_local = Coordinate::new_from_z(normal); + let from_local = Coordinate::create_inverse(&to_local); + + let incoming: Vec3 = to_local.vec_to_coordinate(generate_wi(&mut rng)); + + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| { + vndf_isotropic( + alpha, + from_local.vec_to_coordinate(outgoing), + from_local.vec_to_coordinate(incoming), + ) + }; + let sample = |rng: &mut ThreadRng| { + to_local.vec_to_coordinate(sample_vndf_isotropic( + alpha, + from_local.vec_to_coordinate(incoming), + rng, + )) + }; + test_spherical_pdf("trowbrige reitz vndf h non local", &pdf, &sample, false); + } + + #[test] + fn trowbridge_reitz_vndf() { + let mut rng = thread_rng(); + let incoming: Vec3 = generate_wi(&mut rng); + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| pdf_outgoing(alpha, incoming, outgoing); + let sample = |rng: &mut ThreadRng| sample_outgoing_isotropic(alpha, incoming, rng); + test_spherical_pdf("trowbrige reitz vndf", &pdf, &sample, false); + } + + #[test] + fn trowbridge_reitz_vndf_non_local() { + let mut rng = thread_rng(); + + let normal = random_unit_vector(&mut rng); + + let to_local = Coordinate::new_from_z(normal); + let from_local = Coordinate::create_inverse(&to_local); + + let incoming: Vec3 = to_local.vec_to_coordinate(generate_wi(&mut rng)); + + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| { + pdf_outgoing( + alpha, + from_local.vec_to_coordinate(incoming), + from_local.vec_to_coordinate(outgoing), + ) + }; + let sample = |rng: &mut ThreadRng| { + to_local.vec_to_coordinate(sample_outgoing_isotropic( + alpha, + from_local.vec_to_coordinate(incoming), + rng, + )) + }; + test_spherical_pdf("trowbrige reitz vndf non local", &pdf, &sample, false); + } +} diff --git a/implementations/src/utility/coord.rs b/implementations/src/utility/coord.rs index 6663540..8cac9cc 100644 --- a/implementations/src/utility/coord.rs +++ b/implementations/src/utility/coord.rs @@ -19,7 +19,44 @@ impl Coordinate { z, } } - pub fn vec_to_coordinate(&self, vec: &mut Vec3) { - *vec = vec.x * self.x + vec.y * self.y + vec.z * self.z; + pub fn create_inverse(&self) -> Self { + let a = self.x.x; + let b = self.y.x; + let c = self.z.x; + let d = self.x.y; + let e = self.y.y; + let f = self.z.y; + let g = self.x.z; + let h = self.y.z; + let i = self.z.z; + + let tmp = Vec3::new(e * i - f * h, f * g - d * i, d * h - e * g); + let one_over_det = 1.0 / (a * tmp.x + b * tmp.y + c * tmp.z); + let x = one_over_det * tmp; + let y = one_over_det * Vec3::new(c * h - b * i, a * i - c * g, b * g - a * h); + let z = one_over_det * Vec3::new(b * f - c * e, c * d - a * f, a * e - b * d); + Coordinate { x, y, z } + } + pub fn vec_to_coordinate(&self, vec: Vec3) -> Vec3 { + vec.x * self.x + vec.y * self.y + vec.z * self.z + } +} + +#[cfg(test)] +mod tests { + use crate::random_unit_vector; + + use super::*; + + #[test] + fn inverse() { + let z = random_unit_vector(); + let to = Coordinate::new_from_z(z); + let from = to.create_inverse(); + let v = random_unit_vector(); + assert!( + (v - from.vec_to_coordinate(to.vec_to_coordinate(v))).mag_sq() < 0.000001 + && (v - to.vec_to_coordinate(from.vec_to_coordinate(v))).mag_sq() < 0.000001 + ); } } diff --git a/implementations/tests/sampling.rs b/implementations/tests/sampling.rs index 22423f5..2b5e07a 100644 --- a/implementations/tests/sampling.rs +++ b/implementations/tests/sampling.rs @@ -1,4 +1,4 @@ -use rand::rngs::ThreadRng; +/*use rand::rngs::ThreadRng; use rt_core::*; use statistics::spherical_sampling::test_spherical_pdf; @@ -294,4 +294,4 @@ fn mis_sky_sampling_furnace_test() { / samples as Float; assert!((ref_val - val).mag() < 0.001) -} +}*/ From 8f944965b1e0a53de89e55204d9375e84cd2b491 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 1 Feb 2023 22:06:15 +1100 Subject: [PATCH 19/39] cleaned up statistics/bxdfs --- implementations/src/materials/lambertian.rs | 2 +- .../src/materials/trowbridge_reitz.rs | 20 +- implementations/src/primitives/sphere.rs | 2 +- implementations/src/statistics/Cargo.toml | 23 ++ implementations/src/statistics/bxdfs/mod.rs | 35 +- .../src/statistics/bxdfs/trowbridge_reitz.rs | 77 +++- .../statistics/bxdfs/trowbridge_reitz_vndf.rs | 334 ++++++++++-------- .../src/statistics/spherical_sampling.rs | 2 +- implementations/src/statistics/src/bxdfs.rs | 227 ++++++++++++ .../src/statistics/src/chi_squared.rs | 76 ++++ .../src/statistics/src/distributions.rs | 300 ++++++++++++++++ .../src/statistics/src/integrators.rs | 76 ++++ implementations/src/statistics/src/lib.rs | 72 ++++ .../src/statistics/src/spherical_sampling.rs | 234 ++++++++++++ implementations/src/utility/coord.rs | 6 +- rt_core/src/material.rs | 1 + rt_core/src/vec.rs | 1 + 17 files changed, 1289 insertions(+), 199 deletions(-) create mode 100644 implementations/src/statistics/Cargo.toml create mode 100644 implementations/src/statistics/src/bxdfs.rs create mode 100644 implementations/src/statistics/src/chi_squared.rs create mode 100644 implementations/src/statistics/src/distributions.rs create mode 100644 implementations/src/statistics/src/integrators.rs create mode 100644 implementations/src/statistics/src/lib.rs create mode 100644 implementations/src/statistics/src/spherical_sampling.rs diff --git a/implementations/src/materials/lambertian.rs b/implementations/src/materials/lambertian.rs index 8176db2..4d55667 100644 --- a/implementations/src/materials/lambertian.rs +++ b/implementations/src/materials/lambertian.rs @@ -32,7 +32,7 @@ where fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { let coordinate_system = Coordinate::new_from_z(hit.normal); let direction = cosine_hemisphere_sampling(); - let mut direction = coordinate_system.vec_to_coordinate(direction); + let mut direction = coordinate_system.to_coord(direction); if near_zero(direction) { direction = hit.normal; diff --git a/implementations/src/materials/trowbridge_reitz.rs b/implementations/src/materials/trowbridge_reitz.rs index 457b2b9..94851b1 100644 --- a/implementations/src/materials/trowbridge_reitz.rs +++ b/implementations/src/materials/trowbridge_reitz.rs @@ -1,9 +1,4 @@ -use crate::{ - materials::refract, - statistics, - textures::Texture, - utility::{coord::Coordinate, offset_ray}, -}; +use crate::{materials::refract, textures::Texture, utility::offset_ray}; use rand::{rngs::SmallRng, thread_rng, SeedableRng}; use rt_core::*; @@ -65,25 +60,20 @@ where T: Texture, { fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { - let coord = Coordinate::new_from_z(hit.normal); - - let h = statistics::bxdfs::trowbridge_reitz::sample_h( + let direction = crate::statistics::bxdfs::trowbridge_reitz::sample( self.alpha, + ray.direction, + hit.normal, &mut SmallRng::from_rng(thread_rng()).unwrap(), ); - let h = coord.vec_to_coordinate(h); - - let direction = ray.direction.reflected(h); - let point = offset_ray(hit.point, hit.normal, hit.error, true); *ray = Ray::new(point, direction, ray.time); false } fn scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Float { - let wo = -wo; - let a = statistics::bxdfs::trowbridge_reitz::pdf_outgoing(self.alpha, wo, wi, hit.normal); + let a = crate::statistics::bxdfs::trowbridge_reitz::pdf(self.alpha, wo, wi, hit.normal); if a == 0.0 { INFINITY } else { diff --git a/implementations/src/primitives/sphere.rs b/implementations/src/primitives/sphere.rs index 24ddc14..c78ff53 100644 --- a/implementations/src/primitives/sphere.rs +++ b/implementations/src/primitives/sphere.rs @@ -146,7 +146,7 @@ where // get sphere point let coord_system = Coordinate::new_from_z((in_point - self.center).normalised()); let vec = Vec3::new(sin_alpha * phi.cos(), sin_alpha * phi.sin(), cos_alpha); - let vec = coord_system.vec_to_coordinate(vec); + let vec = coord_system.to_coord(vec); self.center + self.radius * vec }; diff --git a/implementations/src/statistics/Cargo.toml b/implementations/src/statistics/Cargo.toml new file mode 100644 index 0000000..5467767 --- /dev/null +++ b/implementations/src/statistics/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "statistics" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = "0.8.3" +num_cpus = "1.15" +statrs = "0.16.0" +rayon = "1.5.1" +rt_core = { path = "../../rt_core" } +image = "0.24.5" + +[dev-dependencies] +image = "0.24.5" +num_cpus = "1.15" +statrs = "0.16.0" +rand_core = "0.6.0" +rayon = "1.5.1" +rand = { version = "0.8.3", features = [ "small_rng" ] } + +[features] +f64 = ["rt_core/f64"] \ No newline at end of file diff --git a/implementations/src/statistics/bxdfs/mod.rs b/implementations/src/statistics/bxdfs/mod.rs index 7679e9e..88fc883 100644 --- a/implementations/src/statistics/bxdfs/mod.rs +++ b/implementations/src/statistics/bxdfs/mod.rs @@ -1,29 +1,12 @@ pub mod trowbridge_reitz; pub mod trowbridge_reitz_vndf; -#[cfg(test)] -pub mod test { - use super::*; - use crate::statistics::{spherical_sampling::*, *}; - use rand::{rngs::ThreadRng, thread_rng, Rng}; - - #[test] - fn trowbridge_reitz_h() { - let alpha: Float = thread_rng().gen(); - let pdf = |outgoing: Vec3| trowbridge_reitz::pdf_h(outgoing, alpha); - let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample_h(alpha, rng); - test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); - } - - #[test] - fn trowbridge_reitz() { - let mut rng = thread_rng(); - let incoming: Vec3 = generate_wi(&mut rng); - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| { - trowbridge_reitz::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) - }; - let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); - test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); - } -} +// All modules should adhere to the following: +// There should atleast provide the following functions +// sample(incoming, normal, ...) +// pdf(incoming, outgoing, normal, ...) +// If implementations of the above are provided in local space as well +// they must adhere to the same naming expect with _local and no normal parameter +// For the forementioned functions incoming will be pointing towards the surface +// outgoing will be pointing away from the surface and is the sampled direction +// Note that auxillary function do not have to adhere to the above diff --git a/implementations/src/statistics/bxdfs/trowbridge_reitz.rs b/implementations/src/statistics/bxdfs/trowbridge_reitz.rs index 521ac5b..28b3807 100644 --- a/implementations/src/statistics/bxdfs/trowbridge_reitz.rs +++ b/implementations/src/statistics/bxdfs/trowbridge_reitz.rs @@ -1,5 +1,7 @@ +use crate::coord::Coordinate; use crate::statistics::*; use rand::Rng; + pub fn sample_h(alpha: Float, rng: &mut R) -> Vec3 { let r1: Float = rng.gen(); let r2: Float = rng.gen(); @@ -42,22 +44,77 @@ pub fn alternative_d(alpha: Float, h: Vec3, cos_theta: Float) -> Float { 1.0 / (PI * a_sq * tmp * tmp) } -pub fn pdf_h(h: Vec3, alpha: Float) -> Float { +pub fn pdf_h(alpha: Float, h: Vec3) -> Float { // technically the paper has an .abs() but it isn't needed since h.z < 0 => D(m) = 0 d(alpha, h.z) * h.z } -pub fn sample(alpha: Float, incoming: Vec3, rng: &mut R) -> Vec3 { +pub fn sample_local(alpha: Float, incoming: Vec3, rng: &mut R) -> Vec3 { let h = sample_h(alpha, rng); - 2.0 * incoming.dot(h) * h - incoming + incoming.reflected(h) +} + +pub fn pdf_local(alpha: Float, incoming: Vec3, outgoing: Vec3) -> Float { + let mut h = (outgoing - incoming).normalised(); + if h.z < 0.0 { + h = (incoming - outgoing).normalised(); + } + let d = d(alpha, h.z); + d * h.z.abs() / (4.0 * outgoing.dot(h).abs()) +} + +pub fn sample(alpha: Float, incoming: Vec3, normal: Vec3, rng: &mut R) -> Vec3 { + let coord = Coordinate::new_from_z(normal); + let h = coord.to_coord(sample_h(alpha, rng)); + incoming.reflected(h) +} + +pub fn pdf(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { + let inverse = Coordinate::new_from_z(normal).create_inverse(); + let incoming = inverse.to_coord(incoming); + let outgoing = inverse.to_coord(outgoing); + let mut h = (outgoing - incoming).normalised(); + if h.z < 0.0 { + h = (incoming - outgoing).normalised(); + } + let d = d(alpha, h.z); + d * h.z.abs() / (4.0 * outgoing.dot(h).abs()) } -pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { - let mut h = (incoming + outgoing).normalised(); - if h.dot(normal) < 0.0 { - h = -(incoming + outgoing).normalised(); +#[cfg(test)] +mod tests { + use super::*; + use crate::statistics::spherical_sampling::*; + use rand::{rngs::ThreadRng, thread_rng, Rng}; + + #[test] + fn h() { + let mut rng = thread_rng(); + let alpha = rng.gen(); + let pdf = |outgoing: Vec3| pdf_h(alpha, outgoing); + let sample = |rng: &mut ThreadRng| sample_h(alpha, rng); + test_spherical_pdf("tr_h", &pdf, &sample, false); + } + + #[test] + fn tr() { + let mut rng = thread_rng(); + let incoming = generate_wi(&mut rng); + let alpha = rng.gen(); + let pdf = |outgoing: Vec3| pdf_local(alpha, incoming, outgoing); + let sample = |rng: &mut ThreadRng| sample_local(alpha, incoming, rng); + test_spherical_pdf("tr", &pdf, &sample, false); + } + + #[test] + fn non_local() { + let mut rng = thread_rng(); + let normal = random_unit_vector(&mut rng); + let to_local = Coordinate::new_from_z(normal); + let incoming = to_local.to_coord(generate_wi(&mut rng)); + let alpha = rng.gen(); + let pdf = |outgoing: Vec3| pdf(alpha, incoming, outgoing, normal); + let sample = |rng: &mut ThreadRng| sample(alpha, incoming, normal, rng); + test_spherical_pdf("tr_nl", &pdf, &sample, false); } - let cos_theta = normal.dot(h); - let d = d(alpha, cos_theta); - d * h.dot(normal).abs() / (4.0 * outgoing.dot(h).abs()) } diff --git a/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs b/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs index 98b2039..f497ff7 100644 --- a/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs +++ b/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs @@ -1,190 +1,240 @@ +use crate::coord::Coordinate; use rand::Rng; use rt_core::*; -// All in local frame -pub fn d_ansiotropic(a_x: Float, a_y: Float, h: Vec3) -> Float { - let tmp = h.x * h.x / (a_x * a_x) + h.y * h.y / (a_y * a_y) + h.z * h.z; - 1.0 / (PI * a_x * a_y * tmp * tmp) -} +pub mod isotropic { + use super::*; -pub fn d_isotropic(a: Float, h: Vec3) -> Float { - let a_sq = a * a; + pub fn d(a: Float, h: Vec3) -> Float { + let a_sq = a * a; - let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; + let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; - 1.0 / (PI * a_sq * tmp * tmp) -} + 1.0 / (PI * a_sq * tmp * tmp) + } -pub fn lambda_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { - let tmp = 1.0 + (a_x * a_x * v.x * v.x + a_y * a_y * v.y * v.y) / (v.z * v.z); - 0.5 * (tmp.sqrt() - 1.0) -} + pub fn lambda(a: Float, incoming: Vec3) -> Float { + let tmp = 1.0 + + (a * a * (incoming.x * incoming.x + incoming.y * incoming.y)) + / (incoming.z * incoming.z); + 0.5 * (tmp.sqrt() - 1.0) + } -pub fn lambda_isotropic(a: Float, v: Vec3) -> Float { - let tmp = 1.0 + (a * a * (v.x * v.x + v.y * v.y)) / (v.z * v.z); - 0.5 * (tmp.sqrt() - 1.0) -} + pub fn g1(a: Float, incoming: Vec3) -> Float { + 1.0 / (1.0 + lambda(a, incoming)) + } -pub fn g1_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { - 1.0 / (1.0 + lambda_ansiotropic(a_x, a_y, v)) -} -pub fn g1_isotropic(a: Float, v: Vec3) -> Float { - 1.0 / (1.0 + lambda_isotropic(a, v)) -} + pub fn vndf(a: Float, h: Vec3, incoming: Vec3) -> Float { + if h.z < 0.0 { + return 0.0; + } + g1(a, incoming) * incoming.dot(h).max(0.0) * d(a, h) / incoming.z + } -pub fn vndf_ansiotropic(a_x: Float, a_y: Float, h: Vec3, v: Vec3) -> Float { - if h.z < 0.0 { - return 0.0; + pub fn sample_vndf(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + ansiotropic::sample_vndf(a, a, incoming, rng) } - g1_ansiotropic(a_x, a_y, v) * v.dot(h).max(0.0) * d_ansiotropic(a_x, a_y, h) / v.z -} -pub fn vndf_isotropic(a: Float, h: Vec3, v: Vec3) -> Float { - if h.z < 0.0 { - return 0.0; + pub fn sample_local(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + let h = sample_vndf(a, -incoming, rng); + incoming.reflected(h) } - g1_isotropic(a, v) * v.dot(h).max(0.0) * d_isotropic(a, h) / v.z -} -pub fn sample_vndf_ansiotropic(a_x: Float, a_y: Float, v: Vec3, rng: &mut R) -> Vec3 { - // chapter 1 act 1 - // transform from ellipsoid configuration to hemisphere by multipling by the scaling factors on the x and y axies (a_x, a_y) - let v_hemisphere = Vec3::new(a_x * v.x, a_y * v.y, v.z).normalised(); - - // chapter 2 act 1 - - // interlude - let len_sq = v_hemisphere.x * v_hemisphere.x + v_hemisphere.y * v_hemisphere.y; - - let basis_two = if len_sq > 0.0 { - Vec3::new(-v_hemisphere.y, v_hemisphere.x, 0.0) / len_sq.sqrt() - } else { - Vec3::new(1.0, 0.0, 0.0) // len_sq = 0 implies v_hemisphere = Z, so X is a valid orthonormal basis - }; - let basis_three = v_hemisphere.cross(basis_two); // v_hemisphere is first basis - - // chapter 3 act 1 - let r = rng.gen::().sqrt(); - let phi = TAU * rng.gen::(); - let mut t = r * Vec2::new(phi.cos(), phi.sin()); - let s = 0.5 * (1.0 + v_hemisphere.z); - t.y = (1.0 - s) * (1.0 - t.x * t.x).sqrt() + s * t.y; - - // chapter 4 act 1 - let h_hemisphere = t.x * basis_two - + t.y * basis_three - + (1.0 - t.x * t.x - t.y * t.y).max(0.0).sqrt() * v_hemisphere; - - // chapter 5 final act - - // not dividing since microfacet normal is a covector - Vec3::new( - a_x * h_hemisphere.x, - a_y * h_hemisphere.y, - h_hemisphere.z.max(0.0), // avoid numerical errors - ) - .normalised() -} -pub fn sample_vndf_isotropic(a: Float, v: Vec3, rng: &mut R) -> Vec3 { - sample_vndf_ansiotropic(a, a, v, rng) // cause I'm lazy -} + pub fn pdf_local(alpha: Float, incoming: Vec3, outgoing: Vec3) -> Float { + let mut h = (outgoing - incoming).normalised(); + if h.z < 0.0 { + h = (incoming - outgoing).normalised(); + } + let incoming = -incoming; + let vndf = vndf(alpha, h, incoming); + vndf / (4.0 * incoming.dot(h)) + } + + pub fn sample(a: Float, incoming: Vec3, normal: Vec3, rng: &mut R) -> Vec3 { + let coord = Coordinate::new_from_z(normal); + let inverse = coord.create_inverse(); + let h = coord.to_coord(sample_vndf(a, -inverse.to_coord(incoming), rng)); + incoming.reflected(h) + } -pub fn sample_outgoing_isotropic(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { - let h = sample_vndf_isotropic(a, incoming, rng); - 2.0 * incoming.dot(h) * h - incoming + pub fn pdf(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { + let inverse = Coordinate::new_from_z(normal).create_inverse(); + let incoming = inverse.to_coord(incoming); + let outgoing = inverse.to_coord(outgoing); + let mut h = (outgoing - incoming).normalised(); + if h.z < 0.0 { + h = (incoming - outgoing).normalised(); + } + let incoming = -incoming; + let vndf = vndf(alpha, h, incoming); + vndf / (4.0 * incoming.dot(h)) + } } +pub mod ansiotropic { + use super::*; + + pub fn d(a_x: Float, a_y: Float, h: Vec3) -> Float { + let tmp = h.x * h.x / (a_x * a_x) + h.y * h.y / (a_y * a_y) + h.z * h.z; + 1.0 / (PI * a_x * a_y * tmp * tmp) + } + + pub fn lambda(a_x: Float, a_y: Float, incoming: Vec3) -> Float { + let tmp = 1.0 + + (a_x * a_x * incoming.x * incoming.x + a_y * a_y * incoming.y * incoming.y) + / (incoming.z * incoming.z); + 0.5 * (tmp.sqrt() - 1.0) + } -pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3) -> Float { - let mut h = (incoming + outgoing).normalised(); - if h.z < 0.0 { - h = -(incoming + outgoing).normalised(); + pub fn g1(a_x: Float, a_y: Float, incoming: Vec3) -> Float { + 1.0 / (1.0 + lambda(a_x, a_y, incoming)) + } + + pub fn vndf(a_x: Float, a_y: Float, h: Vec3, incoming: Vec3) -> Float { + if h.z < 0.0 { + return 0.0; + } + g1(a_x, a_y, incoming) * incoming.dot(h).max(0.0) * d(a_x, a_y, h) / incoming.z + } + + pub fn sample_vndf(a_x: Float, a_y: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + let v_hemisphere = Vec3::new(a_x * incoming.x, a_y * incoming.y, incoming.z).normalised(); + + let len_sq = v_hemisphere.x * v_hemisphere.x + v_hemisphere.y * v_hemisphere.y; + + let basis_two = if len_sq > 0.0 { + Vec3::new(-v_hemisphere.y, v_hemisphere.x, 0.0) / len_sq.sqrt() + } else { + Vec3::new(1.0, 0.0, 0.0) + }; + let basis_three = v_hemisphere.cross(basis_two); + + let r = rng.gen::().sqrt(); + let phi = TAU * rng.gen::(); + let mut t = r * Vec2::new(phi.cos(), phi.sin()); + let s = 0.5 * (1.0 + v_hemisphere.z); + t.y = (1.0 - s) * (1.0 - t.x * t.x).sqrt() + s * t.y; + + let h_hemisphere = t.x * basis_two + + t.y * basis_three + + (1.0 - t.x * t.x - t.y * t.y).max(0.0).sqrt() * v_hemisphere; + + Vec3::new( + a_x * h_hemisphere.x, + a_y * h_hemisphere.y, + h_hemisphere.z.max(0.0), + ) + .normalised() + } + + pub fn sample_local(a_x: Float, a_y: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + let h = sample_vndf(a_x, a_y, -incoming, rng); + incoming.reflected(h) + } + + pub fn pdf_local(a_x: Float, a_y: Float, incoming: Vec3, outgoing: Vec3) -> Float { + let mut h = (outgoing - incoming).normalised(); + if h.z < 0.0 { + h = (incoming - outgoing).normalised(); + } + let incoming = -incoming; + let vndf = vndf(a_x, a_y, h, incoming); + vndf / (4.0 * incoming.dot(h)) + } + + pub fn sample( + a_x: Float, + a_y: Float, + incoming: Vec3, + normal: Vec3, + rng: &mut R, + ) -> Vec3 { + let coord = Coordinate::new_from_z(normal); + let inverse = coord.create_inverse(); + let h = coord.to_coord(sample_vndf(a_x, a_y, -inverse.to_coord(incoming), rng)); + incoming.reflected(h) + } + + pub fn pdf(a_x: Float, a_y: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { + let inverse = Coordinate::new_from_z(normal).create_inverse(); + let incoming = inverse.to_coord(incoming); + let outgoing = inverse.to_coord(outgoing); + let mut h = (outgoing - incoming).normalised(); + if h.z < 0.0 { + h = (incoming - outgoing).normalised(); + } + let incoming = -incoming; + let vndf = vndf(a_x, a_y, h, incoming); + vndf / (4.0 * incoming.dot(h)) } - let vndf = vndf_ansiotropic(alpha, alpha, h, incoming); - vndf / (4.0 * incoming.dot(h)) } #[cfg(test)] mod tests { use super::*; - use crate::coord::Coordinate; use crate::statistics::spherical_sampling::*; use rand::{rngs::ThreadRng, thread_rng, Rng}; #[test] - fn trowbridge_reitz_vndf_h() { + fn isotropic_h() { let mut rng = thread_rng(); - let incoming: Vec3 = generate_wi(&mut rng); - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| vndf_isotropic(alpha, outgoing, incoming); - let sample = |rng: &mut ThreadRng| sample_vndf_isotropic(alpha, incoming, rng); - test_spherical_pdf("trowbrige reitz vndf h", &pdf, &sample, false); + let incoming = -generate_wi(&mut rng); + let alpha = rng.gen(); + let pdf = |outgoing: Vec3| isotropic::vndf(alpha, outgoing, incoming); + let sample = |rng: &mut ThreadRng| isotropic::sample_vndf(alpha, incoming, rng); + test_spherical_pdf("iso_tr_vndf_h", &pdf, &sample, false); } #[test] - fn trowbridge_reitz_vndf_h_non_local() { + fn isotropic() { let mut rng = thread_rng(); + let incoming = generate_wi(&mut rng); + let alpha = rng.gen(); + let pdf = |outgoing: Vec3| isotropic::pdf_local(alpha, incoming, outgoing); + let sample = |rng: &mut ThreadRng| isotropic::sample_local(alpha, incoming, rng); + test_spherical_pdf("iso_tr_vndf", &pdf, &sample, false); + } + #[test] + fn isotropic_non_local() { + let mut rng = thread_rng(); let normal = random_unit_vector(&mut rng); - let to_local = Coordinate::new_from_z(normal); - let from_local = Coordinate::create_inverse(&to_local); - - let incoming: Vec3 = to_local.vec_to_coordinate(generate_wi(&mut rng)); - - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| { - vndf_isotropic( - alpha, - from_local.vec_to_coordinate(outgoing), - from_local.vec_to_coordinate(incoming), - ) - }; - let sample = |rng: &mut ThreadRng| { - to_local.vec_to_coordinate(sample_vndf_isotropic( - alpha, - from_local.vec_to_coordinate(incoming), - rng, - )) - }; - test_spherical_pdf("trowbrige reitz vndf h non local", &pdf, &sample, false); + let incoming = to_local.to_coord(generate_wi(&mut rng)); + let alpha = rng.gen(); + let pdf = |outgoing: Vec3| isotropic::pdf(alpha, incoming, outgoing, normal); + let sample = |rng: &mut ThreadRng| isotropic::sample(alpha, incoming, normal, rng); + test_spherical_pdf("iso_tr_vndf_nl", &pdf, &sample, false); } #[test] - fn trowbridge_reitz_vndf() { + fn ansiotropic_h() { let mut rng = thread_rng(); - let incoming: Vec3 = generate_wi(&mut rng); - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| pdf_outgoing(alpha, incoming, outgoing); - let sample = |rng: &mut ThreadRng| sample_outgoing_isotropic(alpha, incoming, rng); - test_spherical_pdf("trowbrige reitz vndf", &pdf, &sample, false); + let incoming = -generate_wi(&mut rng); + let (a_x, a_y) = (rng.gen(), rng.gen()); + let pdf = |outgoing: Vec3| ansiotropic::vndf(a_x, a_y, outgoing, incoming); + let sample = |rng: &mut ThreadRng| ansiotropic::sample_vndf(a_x, a_y, incoming, rng); + test_spherical_pdf("ansio_tr_vndf_h", &pdf, &sample, false); } #[test] - fn trowbridge_reitz_vndf_non_local() { + fn ansiotropic() { let mut rng = thread_rng(); + let incoming = generate_wi(&mut rng); + let (a_x, a_y) = (rng.gen(), rng.gen()); + let pdf = |outgoing: Vec3| ansiotropic::pdf_local(a_x, a_y, incoming, outgoing); + let sample = |rng: &mut ThreadRng| ansiotropic::sample_local(a_x, a_y, incoming, rng); + test_spherical_pdf("ansio_tr_vndf", &pdf, &sample, false); + } + #[test] + fn ansiotropic_non_local() { + let mut rng = thread_rng(); let normal = random_unit_vector(&mut rng); - let to_local = Coordinate::new_from_z(normal); - let from_local = Coordinate::create_inverse(&to_local); - - let incoming: Vec3 = to_local.vec_to_coordinate(generate_wi(&mut rng)); - - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| { - pdf_outgoing( - alpha, - from_local.vec_to_coordinate(incoming), - from_local.vec_to_coordinate(outgoing), - ) - }; - let sample = |rng: &mut ThreadRng| { - to_local.vec_to_coordinate(sample_outgoing_isotropic( - alpha, - from_local.vec_to_coordinate(incoming), - rng, - )) - }; - test_spherical_pdf("trowbrige reitz vndf non local", &pdf, &sample, false); + let incoming = to_local.to_coord(generate_wi(&mut rng)); + let (a_x, a_y) = (rng.gen(), rng.gen()); + let pdf = |outgoing: Vec3| ansiotropic::pdf(a_x, a_y, incoming, outgoing, normal); + let sample = |rng: &mut ThreadRng| ansiotropic::sample(a_x, a_y, incoming, normal, rng); + test_spherical_pdf("ansio_tr_vndf_nl", &pdf, &sample, false); } } diff --git a/implementations/src/statistics/spherical_sampling.rs b/implementations/src/statistics/spherical_sampling.rs index eb163c6..219955b 100644 --- a/implementations/src/statistics/spherical_sampling.rs +++ b/implementations/src/statistics/spherical_sampling.rs @@ -213,7 +213,7 @@ pub mod test { let cos_theta: Float = rng.gen(); let phi = TAU as Float * rng.gen::(); - to_vec((1.0 - cos_theta * cos_theta).sqrt(), cos_theta, phi) + -to_vec((1.0 - cos_theta * cos_theta).sqrt(), cos_theta, phi) } #[test] diff --git a/implementations/src/statistics/src/bxdfs.rs b/implementations/src/statistics/src/bxdfs.rs new file mode 100644 index 0000000..dcdd0ec --- /dev/null +++ b/implementations/src/statistics/src/bxdfs.rs @@ -0,0 +1,227 @@ +pub mod trowbridge_reitz { + use crate::*; + use rand::Rng; + pub fn sample_h(alpha: Float, rng: &mut R) -> Vec3 { + let r1: Float = rng.gen(); + let r2: Float = rng.gen(); + let cos_theta = ((1.0 - r1) / (r1 * (alpha * alpha - 1.0) + 1.0)).sqrt(); + let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); + let phi_s = (TAU * r2).max(0.0).min(TAU); + Vec3::new(phi_s.cos() * sin_theta, phi_s.sin() * sin_theta, cos_theta).normalised() + } + + pub fn reference_d(h: Vec3, alpha: Float) -> Float { + if h.z <= 0.0 { + return 0.0; + } + + let a_sq = alpha * alpha; + let cos_theta = h.z; + let cos_theta_sq = cos_theta * cos_theta; + let sin_theta = (1.0 - cos_theta_sq).sqrt(); + let tan_theta = sin_theta / cos_theta; + let tmp = a_sq + tan_theta * tan_theta; + + a_sq / (PI * cos_theta_sq * cos_theta_sq * tmp * tmp) + } + + pub fn d(alpha: Float, cos_theta: Float) -> Float { + if cos_theta <= 0.0 { + return 0.0; + } + let a_sq = alpha * alpha; + let tmp = cos_theta * cos_theta * (a_sq - 1.0) + 1.0; + a_sq / (PI * tmp * tmp) + } + + pub fn alternative_d(alpha: Float, h: Vec3, cos_theta: Float) -> Float { + if cos_theta < 0.0 { + return 0.0; + } + let a_sq = alpha * alpha; + let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; + 1.0 / (PI * a_sq * tmp * tmp) + } + + pub fn pdf_h(h: Vec3, alpha: Float) -> Float { + // technically the paper has an .abs() but it isn't needed since h.z < 0 => D(m) = 0 + d(alpha, h.z) * h.z + } + + pub fn sample(alpha: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + let h = sample_h(alpha, rng); + 2.0 * incoming.dot(h) * h - incoming + } + + pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { + let mut h = (incoming + outgoing).normalised(); + if h.dot(normal) < 0.0 { + h = -(incoming + outgoing).normalised(); + } + let cos_theta = normal.dot(h); + let d = d(alpha, cos_theta); + d * h.dot(normal).abs() / (4.0 * outgoing.dot(h).abs()) + } +} + +pub mod trowbridge_reitz_vndf { + use crate::*; + use rand::Rng; + + // All in local frame + pub fn d_ansiotropic(a_x: Float, a_y: Float, h: Vec3) -> Float { + let tmp = h.x * h.x / (a_x * a_x) + h.y * h.y / (a_y * a_y) + h.z * h.z; + 1.0 / (PI * a_x * a_y * tmp * tmp) + } + + pub fn d_isotropic(a: Float, h: Vec3) -> Float { + let a_sq = a * a; + + let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; + if (1.0 / (PI * a_sq * tmp * tmp)).is_nan() { + println!("{tmp} = ({}^2 + {}^2) / {a_sq} + {}^2", h.x, h.y, h.z); + } + 1.0 / (PI * a_sq * tmp * tmp) + } + + pub fn lambda_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { + let tmp = 1.0 + (a_x * a_x * v.x * v.x + a_y * a_y * v.y * v.y) / (v.z * v.z); + 0.5 * (tmp.sqrt() - 1.0) + } + + pub fn lambda_isotropic(a: Float, v: Vec3) -> Float { + let tmp = 1.0 + (a * a * (v.x * v.x + v.y * v.y)) / (v.z * v.z); + 0.5 * (tmp.sqrt() - 1.0) + } + + pub fn g1_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { + 1.0 / (1.0 + lambda_ansiotropic(a_x, a_y, v)) + } + pub fn g1_isotropic(a: Float, v: Vec3) -> Float { + 1.0 / (1.0 + lambda_isotropic(a, v)) + } + + pub fn vndf_ansiotropic(a_x: Float, a_y: Float, h: Vec3, v: Vec3) -> Float { + if h.z < 0.0 { + return 0.0; + } + g1_ansiotropic(a_x, a_y, v) * v.dot(h).max(0.0) * d_ansiotropic(a_x, a_y, h) / v.z + } + + pub fn vndf_isotropic(a: Float, h: Vec3, v: Vec3) -> Float { + if h.z < 0.0 { + return 0.0; + } + g1_isotropic(a, v) * v.dot(h).max(0.0) * d_isotropic(a, h) / v.z + } + pub fn sample_vndf_ansiotropic(a_x: Float, a_y: Float, v: Vec3, rng: &mut R) -> Vec3 { + // chapter 1 act 1 + // transform from ellipsoid configuration to hemisphere by multipling by the scaling factors on the x and y axies (a_x, a_y) + let v_hemisphere = Vec3::new(a_x * v.x, a_y * v.y, v.z).normalised(); + + // chapter 2 act 1 + + // interlude + let len_sq = v_hemisphere.x * v_hemisphere.x + v_hemisphere.y * v_hemisphere.y; + + let basis_two = if len_sq > 0.0 { + Vec3::new(-v_hemisphere.y, v_hemisphere.x, 0.0) / len_sq.sqrt() + } else { + Vec3::new(1.0, 0.0, 0.0) // len_sq = 0 implies v_hemisphere = Z, so X is a valid orthonormal basis + }; + let basis_three = v_hemisphere.cross(basis_two); // v_hemisphere is first basis + + // chapter 3 act 1 + let r = rng.gen::().sqrt(); + let phi = TAU * rng.gen::(); + let mut t = r * Vec2::new(phi.cos(), phi.sin()); + let s = 0.5 * (1.0 + v_hemisphere.z); + t.y = (1.0 - s) * (1.0 - t.x * t.x).sqrt() + s * t.y; + + // chapter 4 act 1 + let h_hemisphere = t.x * basis_two + + t.y * basis_three + + (1.0 - t.x * t.x - t.y * t.y).max(0.0).sqrt() * v_hemisphere; + + // chapter 5 final act + + // not dividing since microfacet normal is a covector + Vec3::new( + a_x * h_hemisphere.x, + a_y * h_hemisphere.y, + h_hemisphere.z.max(0.0), // avoid numerical errors + ) + .normalised() + } + + pub fn sample_vndf_isotropic(a: Float, v: Vec3, rng: &mut R) -> Vec3 { + sample_vndf_ansiotropic(a, a, v, rng) // cause I'm lazy + } + + pub fn sample_outgoing_isotropic(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { + let h = sample_vndf_isotropic(a, incoming, rng); + 2.0 * incoming.dot(h) * h - incoming + } + + pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { + let mut h = (incoming + outgoing).normalised(); + if h.dot(normal) < 0.0 { + h = -(incoming + outgoing).normalised(); + } + let _cos_theta = normal.dot(h); + let vndf = vndf_ansiotropic(alpha, alpha, h, incoming); + vndf / (4.0 * incoming.dot(h)) + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::{spherical_sampling::*, *}; + use rand::{rngs::ThreadRng, thread_rng, Rng}; + + #[test] + fn trowbridge_reitz_h() { + let alpha: Float = thread_rng().gen(); + let pdf = |outgoing: Vec3| trowbridge_reitz::pdf_h(outgoing, alpha); + let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample_h(alpha, rng); + test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); + } + + #[test] + fn trowbridge_reitz() { + let mut rng = thread_rng(); + let incoming: Vec3 = generate_wi(&mut rng); + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| { + trowbridge_reitz::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) + }; + let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); + test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); + } + #[test] + fn trowbridge_reitz_vndf_h() { + let mut rng = thread_rng(); + let incoming: Vec3 = generate_wi(&mut rng); + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| trowbridge_reitz_vndf::vndf_isotropic(alpha, outgoing, incoming); + let sample = |rng: &mut ThreadRng| { + trowbridge_reitz_vndf::sample_vndf_isotropic(alpha, incoming, rng) + }; + test_spherical_pdf("trowbrige reitz vndf h", &pdf, &sample, false); + } + + #[test] + fn trowbridge_reitz_vndf() { + let mut rng = thread_rng(); + let incoming: Vec3 = generate_wi(&mut rng); + let alpha: Float = rng.gen(); + let pdf = |outgoing: Vec3| { + trowbridge_reitz_vndf::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) + }; + let sample = |rng: &mut ThreadRng| { + trowbridge_reitz_vndf::sample_outgoing_isotropic(alpha, incoming, rng) + }; + test_spherical_pdf("trowbrige reitz vndf", &pdf, &sample, false); + } +} diff --git a/implementations/src/statistics/src/chi_squared.rs b/implementations/src/statistics/src/chi_squared.rs new file mode 100644 index 0000000..41e3473 --- /dev/null +++ b/implementations/src/statistics/src/chi_squared.rs @@ -0,0 +1,76 @@ +use crate::*; +use rayon::prelude::*; +use statrs::function::gamma::gamma_ur; + +use std::cmp::Ordering::*; +pub fn chi2_probability(dof: f64, distance: f64) -> f64 { + match distance.partial_cmp(&0.0).unwrap() { + Less => panic!("distance < 0.0"), + Equal => 1.0, + Greater => { + if distance.is_infinite() { + 0.0 + } else { + gamma_ur(dof * 0.5, distance * 0.5) + } + } + } +} + +pub fn chi_squared( + freq_table: &[Float], + expected_freq_table: &[Float], + samples: usize, +) -> (usize, Float) { + assert_eq!(freq_table.len(), expected_freq_table.len()); + + let mut values = expected_freq_table + .into_par_iter() + .zip(freq_table.into_par_iter()) + .collect::>(); + + values.sort_by(|a, b| a.0.partial_cmp(b.0).unwrap()); + + let mut df = 0; + + let mut expected_pooled = 0.0; + let mut actual_pooled = 0.0; + + let mut chi_squared = 0.0; + + for (expected, actual) in values { + if *expected == 0.0 { + if *actual > (samples / 100_000) as Float { + chi_squared += INFINITY as Float; + } + } else if expected_pooled > 5.0 { + // prevent df = 0 when all values are less than 5 + let diff = actual_pooled - expected_pooled; + chi_squared += diff * diff / expected_pooled; + df += 1; + } else if *expected < 5.0 || (expected_pooled > 0.0 && expected_pooled < 5.0) { + expected_pooled += expected; + actual_pooled += actual; + } else { + let diff = actual - expected; + chi_squared += diff * diff / expected; + df += 1; + } + } + + if actual_pooled > 0.0 || expected_pooled > 0.0 { + let diff = actual_pooled - expected_pooled; + chi_squared += diff * diff / expected_pooled; + df += 1; + } + + df -= 1; + + (df, chi_squared) +} + +#[cfg(test)] +pub mod test {} + +#[cfg(test)] +pub use test::*; diff --git a/implementations/src/statistics/src/distributions.rs b/implementations/src/statistics/src/distributions.rs new file mode 100644 index 0000000..5d22998 --- /dev/null +++ b/implementations/src/statistics/src/distributions.rs @@ -0,0 +1,300 @@ +use crate::Float; +use rand::Rng; + +#[derive(Debug, Clone, PartialEq)] +pub struct Distribution1D { + pub pdf: Vec, + pub cdf: Vec, +} + +impl Distribution1D { + pub fn new(values: &[Float]) -> Self { + if values.is_empty() { + panic!("Empty pdf passed to Distribution1D::from_pdf!"); + } + + let n = values.len(); + + let mut intervals = vec![0.0]; + + for i in 1..=n { + let last = intervals[i - 1]; + intervals.push(last + values[i - 1] as Float); + } + + let c = intervals[n]; + for (_, value) in intervals.iter_mut().enumerate() { + if c != 0.0 { + *value /= c as Float; + } + } + + let mut pdf = Vec::new(); + let mut last = 0.0; + for value in &intervals[1..] { + pdf.push(value - last); + last = *value; + } + + Self { + pdf, + cdf: intervals, + } + } + + pub fn sample_naive(&self, rng: &mut R) -> usize { + let threshold = rng.gen(); + + self.cdf.iter().position(|v| v >= &threshold).unwrap() - 1 + } + pub fn sample(&self, rng: &mut R) -> usize { + let num = rng.gen(); + + let pred = |i| self.cdf[i] <= num; + + { + let mut first = 0; + let mut len = self.cdf.len(); + while len > 0 { + let half = len >> 1; + let middle = first + half; + + if pred(middle) { + first = middle + 1; + len -= half + 1; + } else { + len = half; + } + } + (first - 1).clamp(0, self.cdf.len() - 2) + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Distribution2D { + pub x_distributions: Vec, + pub y_distribution: Distribution1D, + pub dim: (usize, usize), +} + +impl Distribution2D { + pub fn new(values: &[Float], width: usize) -> Self { + assert!(values.len() % width == 0 && !values.is_empty()); + let mut y_values = Vec::new(); + let mut x_distributions = Vec::new(); + for vec_x in values.chunks_exact(width) { + x_distributions.push(Distribution1D::new(vec_x)); + let row_sum: Float = vec_x.iter().sum(); + y_values.push(row_sum); + } + let y_distribution = Distribution1D::new(&y_values); + + Self { + x_distributions, + y_distribution, + dim: (width, values.len() / width), + } + } + pub fn sample(&self, rng: &mut R) -> (usize, usize) { + let v = self.y_distribution.sample(rng); + let u = self.x_distributions[v].sample(rng); + (u, v) + } + pub fn pdf(&self, u: Float, v: Float) -> Float { + let u = ((self.dim.0 as Float * u) as usize).clamp(0, self.dim.0 - 1); + let v = ((self.dim.1 as Float * v) as usize).clamp(0, self.dim.1 - 1); + + self.y_distribution.pdf[v] * self.x_distributions[v].pdf[u] + } + pub fn dim(&self) -> (usize, usize) { + self.dim + } +} + +#[cfg(test)] +mod tests { + use crate::{chi_squared::*, distributions::*, utility::*}; + use rand::thread_rng; + use rayon::prelude::*; + + macro_rules! random_1d { + ($len:expr) => {{ + const SAMPLES: usize = 100_000; + const SAMPLE_LEN: usize = 100; + const NUMBER_TESTS: usize = 10; + const CHI_SQUARED_THRESHOLD: Float = 0.01; + const BATCH_EXPONENT: usize = 6; + const BATCHES: usize = 2usize.pow(BATCH_EXPONENT as u32); + + let mut rng = thread_rng(); + + let values: Vec = (0..SAMPLE_LEN) + .into_iter() + .map(|_| rng.gen_range(0.0..100.0)) + .collect(); + + let cdf = Distribution1D::new(&values); + + let expected_values: Vec = cdf.pdf.iter().map(|v| v * SAMPLES as Float).collect(); + + let func = |samples| { + const SAMPLE_LEN_MINUS_ONE: usize = SAMPLE_LEN - 1; + let mut rng = thread_rng(); + let mut sampled_values = vec![0u64; SAMPLE_LEN]; + for _ in 0..samples { + match cdf.sample(&mut rng) { + index @ 0..=SAMPLE_LEN_MINUS_ONE => { + sampled_values[index] += 1; + } + _ => unreachable!(), + } + } + sampled_values + }; + + // Šidák correction + let threshold = 1.0 - (1.0 - CHI_SQUARED_THRESHOLD).powf(1.0 / NUMBER_TESTS as Float); + + for i in 0..NUMBER_TESTS { + let sampled_vecs: Vec> = (0..BATCHES) + .map(|_| { + distribute_samples_over_threads(SAMPLES as u64, &func) + .into_iter() + .map(|v| v as Float) + .collect() + }) + .collect(); + let sampled_values: Vec = (0..SAMPLE_LEN) + .into_par_iter() + .map(|i| { + recursively_binary_average((0..BATCHES).map(|j| sampled_vecs[j][i]).collect()) + }) + .collect(); + + let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); + + let p_value = chi2_probability(df as f64, chi_squared as f64); + if p_value < threshold as f64 { + panic!("LERP: recieved p value of {p_value} with {SAMPLES} samples on test {i}/{NUMBER_TESTS}") + } + }} + }; + } + + #[test] + fn random_1d_small() { + random_1d!(50) + } + + #[test] + fn random_1d_medium() { + random_1d!(500) + } + #[test] + fn random_1d_large() { + random_1d!(5000) + } + + macro_rules! random_2d { + ($x:expr, $y:expr) => { { + const SAMPLES: usize = 100_000; + const X_RES_MINUS_ONE: usize = $x - 1; + const Y_RES_MINUS_ONE: usize = $y - 1; + const SAMPLES_RES: (usize, usize) = (X_RES_MINUS_ONE + 1, Y_RES_MINUS_ONE + 1); + const SAMPLE_LEN: usize = SAMPLES_RES.0 * SAMPLES_RES.1; + const NUMBER_TESTS: usize = 10; + const CHI_SQUARED_THRESHOLD: Float = 0.01; + const BATCH_EXPONENT: usize = 6; + const BATCHES: usize = 2usize.pow(BATCH_EXPONENT as u32); + + //use rand_core::SeedableRng; + + //let mut rng = SmallRng::seed_from_u64(321321); + let mut rng = thread_rng(); + + let values: Vec = (0..SAMPLE_LEN) + .into_iter() + .map(|_| rng.gen_range(0.0..100.0)) + .collect(); + + let dist = Distribution2D::new(&values, SAMPLES_RES.0); + + let mut expected_values: Vec = Vec::new(); + for y in 0..SAMPLES_RES.1 { + for x in 0..SAMPLES_RES.0 { + expected_values.push( + dist.y_distribution.pdf[y] * dist.x_distributions[y].pdf[x] * SAMPLES as Float, + ); + } + } + + // dist.pdf.iter().map(|v| v * SAMPLES as Float).collect(); + + let func = |samples| { + const SAMPLE_LEN_MINUS_ONE: usize = SAMPLE_LEN - 1; + let mut rng = thread_rng(); + let mut sampled_values = vec![0u64; SAMPLE_LEN]; + for _ in 0..samples { + match dist.sample(&mut rng) { + indices @ (0..=X_RES_MINUS_ONE, 0..=Y_RES_MINUS_ONE) + if indices.0 + (X_RES_MINUS_ONE + 1) * indices.1 < SAMPLE_LEN => + { + sampled_values[indices.0 + indices.1 * (X_RES_MINUS_ONE + 1)] += 1; + } + _ => unreachable!(), + } + } + sampled_values + }; + + // Šidák correction + let threshold = 1.0 - (1.0 - CHI_SQUARED_THRESHOLD).powf(1.0 / NUMBER_TESTS as Float); + + for i in 0..NUMBER_TESTS { + let sampled_vecs: Vec> = (0..BATCHES) + .into_iter() + .map(|_| { + distribute_samples_over_threads(SAMPLES as u64, &func) + .into_iter() + .map(|v| v as Float) + .collect() + }) + .collect(); + let sampled_values: Vec = (0..SAMPLE_LEN) + .into_par_iter() + .map(|i| { + recursively_binary_average( + (0..BATCHES) + .into_iter() + .map(|j| sampled_vecs[j][i]) + .collect(), + ) + }) + .collect(); + + let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); + + let p_value = chi2_probability(df as f64, chi_squared as f64); + if p_value < threshold as f64 { + panic!("LERP: recieved p value of {p_value} with {SAMPLES} samples on test {i}/{NUMBER_TESTS}") + } + } + }}; + } + + #[test] + fn random_2d_small() { + random_2d!(3, 3) + } + + #[test] + fn random_2d_medium() { + random_2d!(30, 60) + } + + #[test] + fn random_2d_large() { + random_2d!(800, 1200) + } +} diff --git a/implementations/src/statistics/src/integrators.rs b/implementations/src/statistics/src/integrators.rs new file mode 100644 index 0000000..d1dc9f4 --- /dev/null +++ b/implementations/src/statistics/src/integrators.rs @@ -0,0 +1,76 @@ +use crate::{Float, Vec3}; +const MAX_DEPTH: usize = 6; +const EPSILON: Float = 0.000001; + +pub fn integrate_solid_angle( + pdf: &F, + theta_start: Float, + theta_end: Float, + phi_start: Float, + phi_end: Float, +) -> Float +where + F: Fn(Vec3) -> Float, +{ + let pdf = |phi: Float, a, b| { + adaptive_simpsons( + |theta| { + pdf(Vec3::new( + phi.cos() * theta.sin(), + phi.sin() * theta.sin(), + theta.cos(), + )) * theta.sin().abs() + }, + a, + b, + ) + }; + + adaptive_simpsons(|phi| pdf(phi, theta_start, theta_end), phi_start, phi_end) +} + +pub fn adaptive_simpsons(function: F, a: Float, b: Float) -> Float +where + F: Fn(Float) -> Float, +{ + fn aux( + function: &F, + a: Float, + b: Float, + c: Float, + fa: Float, + fb: Float, + fc: Float, + i: Float, + epsilon: Float, + depth: usize, + ) -> Float + where + F: Fn(Float) -> Float, + { + let d = 0.5 * (a + b); + let e = 0.5 * (b + c); + let fd = function(d); + let fe = function(e); + + let h = c - a; + let i0 = (1.0 / 12.0) * h * (fa + 4.0 * fd + fb); + let i1 = (1.0 / 12.0) * h * (fb + 4.0 * fe + fc); + let ip = i0 + i1; + + if depth >= MAX_DEPTH || (ip - i).abs() < 15.0 * epsilon { + return ip + (1.0 / 15.0) * (ip - i); + } + + aux(function, a, d, b, fa, fd, fb, i0, 0.5 * epsilon, depth + 1) + + aux(function, b, e, c, fb, fe, fc, i1, 0.5 * epsilon, depth + 1) + } + let c = b; + let b = 0.5 * (a + b); + + let fa = function(a); + let fb = function(b); + let fc = function(c); + let i = (c - a) * (1.0 / 6.0) * (fa + 4.0 * fb + fc); + aux(&function, a, b, c, fa, fb, fc, i, EPSILON, 0) +} diff --git a/implementations/src/statistics/src/lib.rs b/implementations/src/statistics/src/lib.rs new file mode 100644 index 0000000..66d3cf4 --- /dev/null +++ b/implementations/src/statistics/src/lib.rs @@ -0,0 +1,72 @@ +pub use rt_core::{self, *}; + +pub mod bxdfs; +pub mod chi_squared; +pub mod distributions; +pub mod integrators; +pub mod spherical_sampling; + +pub mod utility { + + use rayon::prelude::*; + use std::ops::*; + + pub fn distribute_samples_over_threads(samples: u64, f: &F) -> Vec + where + T: Add + Send, + F: Fn(u64) -> Vec + Sync, + Vec: FromIterator<::Output>, + { + let thread_count = num_cpus::get(); + let mut samples_per_thread = vec![samples / thread_count as u64; thread_count]; + let diff = ((samples / thread_count as u64) * thread_count as u64) - samples; + let last = samples_per_thread.len() - 1; + samples_per_thread[last] += diff; + + samples_per_thread + .into_par_iter() + .map(f) + .reduce_with(|a, b| { + a.into_iter() + .zip(b.into_iter()) + .map(|(a, b)| a + b) + .collect() + }) + .unwrap() + } + + use rt_core::Float; + + pub fn recursively_binary_average(mut values: Vec) -> T + where + ::Output: Mul, + T: Copy, + Vec: FromIterator<<::Output as Mul>::Output>, + { + let mut len = values.len(); + if len & (len - 1) != 0 && len != 0 { + panic!("values.len() is not a power of 2"); + } + while len != 1 { + len /= 2; + + let (a, b) = values.split_at(len); + + values = a + .iter() + .zip(b.iter()) + .map(|(&a, &b)| (a + b) * 0.5) + .collect(); + } + + values[0] + } + + #[cfg(test)] + #[test] + fn binary_average() { + assert!( + (recursively_binary_average::(vec![1.0, 3.0, 7.0, 1.0]) - 3.0).abs() < 0.0000001 + ); + } +} diff --git a/implementations/src/statistics/src/spherical_sampling.rs b/implementations/src/statistics/src/spherical_sampling.rs new file mode 100644 index 0000000..0e4d358 --- /dev/null +++ b/implementations/src/statistics/src/spherical_sampling.rs @@ -0,0 +1,234 @@ +use crate::chi_squared::chi2_probability; +use crate::chi_squared::chi_squared; +use crate::integrators::integrate_solid_angle; +use crate::utility::distribute_samples_over_threads; +use crate::utility::recursively_binary_average; +use crate::*; +use crate::{Float, Vec3, PI}; +use rand::rngs::ThreadRng; +use rand::thread_rng; +use rand::Rng; +use rayon::prelude::*; + +pub fn cosine_hemisphere_sampling(rng: &mut R) -> Vec3 { + let cos_theta = (1.0 - rng.gen::()).sqrt(); + let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); + let phi = 2.0 * PI * rng.gen::(); + Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) +} + +pub fn cosine_hemisphere_pdf(wo: Vec3) -> Float { + wo.z.max(0.0) / PI +} + +pub fn specular_sampling(n: Float, rng: &mut R) -> Vec3 { + let a = rng.gen::().powf(1.0 / (n + 1.0)); + let term = (1.0 - a * a).sqrt(); + let phi = 2.0 * PI * rng.gen::(); + Vec3::new(term * phi.cos(), term * phi.sin(), a) +} + +pub fn random_unit_vector(rng: &mut R) -> Vec3 { + let (mut x, mut y, mut z) = (1.0, 1.0, 1.0); + while x * x + y * y + z * z > 1.0 { + x = rng.gen_range(-1.0..1.0); + y = rng.gen_range(-1.0..1.0); + z = rng.gen_range(-1.0..1.0); + } + + Vec3::new(x, y, z).normalised() +} + +pub fn test_spherical_pdf(name: &str, pdf: &P, sample: &S, hemisphere: bool) +where + P: Fn(Vec3) -> Float, + S: Fn(&mut ThreadRng) -> Vec3 + Send + Sync, +{ + const THETA_RES: usize = 80; + const PHI_RES: usize = 160; + const SAMPLES: usize = 100_000; + const SAMPLE_LEN: usize = THETA_RES * PHI_RES; + const NUMBER_TESTS: usize = 10; + const CHI_SQUARED_THRESHOLD: Float = 0.01; + const BATCH_EXPONENT: usize = 6; + const BATCHES: usize = 2usize.pow(BATCH_EXPONENT as u32); + + let mut expected_values = Vec::new(); + + let theta_step = if hemisphere { FRAC_PI_2 } else { PI } / THETA_RES as Float; + let phi_step = TAU / PHI_RES as Float; + for phi_i in 0..PHI_RES { + for theta_i in 0..THETA_RES { + let theta_start = theta_i as Float * theta_step; + let phi_start = phi_i as Float * phi_step; + expected_values.push(integrate_solid_angle( + pdf, + theta_start, + theta_start + theta_step, + phi_start, + phi_start + phi_step, + )); + } + } + + let pdf_sum = expected_values.iter().sum::(); + if (pdf_sum - 1.0).abs() > 0.001 { + panic!("reference pdf doesn't integrate to 1: {pdf_sum}"); + } + + let mut expected_values: Vec = expected_values + .into_iter() + .map(|v| v * SAMPLES as Float) + .collect(); + + let func = |samples| { + const SAMPLE_LEN_MINUS_ONE: usize = SAMPLE_LEN - 1; + let mut rng = thread_rng(); + let mut sampled_values = vec![0u64; SAMPLE_LEN]; + for _ in 0..samples { + let wo = sample(&mut rng); + + let sin_theta = (1.0 - wo.z * wo.z).sqrt(); + if sin_theta < 0.0 { + panic!("sin_theta ({sin_theta}) < 0.0"); + } + let theta = wo.z.acos(); + let mut phi = (wo.y).atan2(wo.x); + if phi < 0.0 { + phi += 2.0 * PI; + } + let theta_i = theta / theta_step; + let phi_i = phi / phi_step; + let index = (phi_i as usize * THETA_RES + theta_i as usize).min(SAMPLE_LEN_MINUS_ONE); + + sampled_values[index] += 1; + } + sampled_values + }; + + let threshold = 1.0 - (1.0 - CHI_SQUARED_THRESHOLD).powf(1.0 / NUMBER_TESTS as Float); + + for i in 0..NUMBER_TESTS { + let sampled_vecs: Vec> = (0..BATCHES) + .map(|_| { + distribute_samples_over_threads(SAMPLES as u64, &func) + .into_iter() + .map(|v| v as Float) + .collect() + }) + .collect(); + let mut sampled_values: Vec = (0..SAMPLE_LEN) + .into_par_iter() + .map(|i| { + recursively_binary_average::( + (0..BATCHES).map(|j| sampled_vecs[j][i]).collect(), + ) + }) + .collect(); + + let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); + + let p_value = chi2_probability(df as f64, chi_squared as f64); + if p_value < threshold as f64 { + let expected_abs_max = expected_values + .iter() + .max_by(|x, y| x.abs().partial_cmp(&y.abs()).unwrap()) + .unwrap() + .abs(); + + let sampled_abs_max = sampled_values + .iter() + .max_by(|x, y| x.abs().partial_cmp(&y.abs()).unwrap()) + .unwrap() + .abs(); + + let get_colour = |value: Float, max_abs_value: Float| -> Vec3 { + if value > 0.0 { + let t = value / max_abs_value; + + t * Vec3::new(1.0, 0.0, 0.0) + } else if value == 0.0 { + Vec3::new(0.0, 1.0, 0.0) + } else { + Vec3::new(0.0, 0.0, 1.0) + } + }; + + let transpose = |vec: &mut Vec| { + *vec = (0..(PHI_RES * THETA_RES)) + .map(|i| { + let y = i % PHI_RES; + let x = i / PHI_RES; + + vec[y * THETA_RES + x] + }) + .collect::>(); + }; + + transpose(&mut expected_values); + transpose(&mut sampled_values); + + let mut image = expected_values + .into_iter() + .map(|v| get_colour(v, expected_abs_max)) + .collect::>(); + image.extend( + (0..PHI_RES) + .map(|_| Vec3::new(0.12, 0.95, 0.95)) + .collect::>(), + ); + image.extend( + sampled_values + .into_iter() + .map(|v| get_colour(v, sampled_abs_max)) + .collect::>(), + ); + let image = image + .into_iter() + .flat_map(|v| [v.x, v.y, v.z]) + .map(|v| (v * 256.0).clamp(0.0, 255.0) as u8) + .collect::>(); + + image::save_buffer( + format!("{name}_failed_output_test_{i}.png"), + &image, + PHI_RES.try_into().unwrap(), + (THETA_RES * 2 + 1).try_into().unwrap(), + image::ColorType::Rgb8, + ) + .unwrap(); + + panic!("{name}: recieved p value of {p_value} with {SAMPLES} samples averaged over {BATCHES} batches on test {i}/{NUMBER_TESTS}") + } + } +} + +#[cfg(test)] +pub mod test { + use crate::{spherical_sampling::*, Float, Vec3, TAU}; + use rand::Rng; + + pub fn to_vec(sin_theta: Float, cos_theta: Float, phi: Float) -> Vec3 { + Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) + } + + pub fn generate_wi(rng: &mut R) -> Vec3 { + let cos_theta: Float = rng.gen(); + let phi = TAU as Float * rng.gen::(); + + to_vec((1.0 - cos_theta * cos_theta).sqrt(), cos_theta, phi) + } + + #[test] + fn cosine_hemisphere() { + test_spherical_pdf( + "cosine hemisphere sampling", + &cosine_hemisphere_pdf, + &cosine_hemisphere_sampling, + true, + ); + } +} + +#[cfg(test)] +pub use test::*; diff --git a/implementations/src/utility/coord.rs b/implementations/src/utility/coord.rs index 8cac9cc..7d742f8 100644 --- a/implementations/src/utility/coord.rs +++ b/implementations/src/utility/coord.rs @@ -37,7 +37,7 @@ impl Coordinate { let z = one_over_det * Vec3::new(b * f - c * e, c * d - a * f, a * e - b * d); Coordinate { x, y, z } } - pub fn vec_to_coordinate(&self, vec: Vec3) -> Vec3 { + pub fn to_coord(&self, vec: Vec3) -> Vec3 { vec.x * self.x + vec.y * self.y + vec.z * self.z } } @@ -55,8 +55,8 @@ mod tests { let from = to.create_inverse(); let v = random_unit_vector(); assert!( - (v - from.vec_to_coordinate(to.vec_to_coordinate(v))).mag_sq() < 0.000001 - && (v - to.vec_to_coordinate(from.vec_to_coordinate(v))).mag_sq() < 0.000001 + (v - from.to_coord(to.to_coord(v))).mag_sq() < 0.000001 + && (v - to.to_coord(from.to_coord(v))).mag_sq() < 0.000001 ); } } diff --git a/rt_core/src/material.rs b/rt_core/src/material.rs index bcfe3d5..c3819bf 100644 --- a/rt_core/src/material.rs +++ b/rt_core/src/material.rs @@ -1,5 +1,6 @@ use crate::{Float, Hit, Ray, Vec3}; +// wo (and ray.direction in scatter_ray) points towards the surface and wi away by convention pub trait Scatter { fn scatter_ray(&self, _ray: &mut Ray, _hit: &Hit) -> bool { true diff --git a/rt_core/src/vec.rs b/rt_core/src/vec.rs index d987196..bd7d6ab 100644 --- a/rt_core/src/vec.rs +++ b/rt_core/src/vec.rs @@ -183,6 +183,7 @@ impl Vec3 { pub fn abs(self) -> Self { Vec3::new(self.x.abs(), self.y.abs(), self.z.abs()) } + // note: self is pointing towards surface and normal away #[inline] pub fn reflect(&mut self, normal: Self) { *self -= 2.0 * self.dot(normal) * normal From 68c2394cca8195e2edea290fbb75af270c08eda7 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 1 Feb 2023 22:12:25 +1100 Subject: [PATCH 20/39] removed accidental additions --- implementations/src/statistics/Cargo.toml | 23 -- implementations/src/statistics/src/bxdfs.rs | 227 ------------- .../src/statistics/src/chi_squared.rs | 76 ----- .../src/statistics/src/distributions.rs | 300 ------------------ .../src/statistics/src/integrators.rs | 76 ----- implementations/src/statistics/src/lib.rs | 72 ----- .../src/statistics/src/spherical_sampling.rs | 234 -------------- 7 files changed, 1008 deletions(-) delete mode 100644 implementations/src/statistics/Cargo.toml delete mode 100644 implementations/src/statistics/src/bxdfs.rs delete mode 100644 implementations/src/statistics/src/chi_squared.rs delete mode 100644 implementations/src/statistics/src/distributions.rs delete mode 100644 implementations/src/statistics/src/integrators.rs delete mode 100644 implementations/src/statistics/src/lib.rs delete mode 100644 implementations/src/statistics/src/spherical_sampling.rs diff --git a/implementations/src/statistics/Cargo.toml b/implementations/src/statistics/Cargo.toml deleted file mode 100644 index 5467767..0000000 --- a/implementations/src/statistics/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "statistics" -version = "0.1.0" -edition = "2021" - -[dependencies] -rand = "0.8.3" -num_cpus = "1.15" -statrs = "0.16.0" -rayon = "1.5.1" -rt_core = { path = "../../rt_core" } -image = "0.24.5" - -[dev-dependencies] -image = "0.24.5" -num_cpus = "1.15" -statrs = "0.16.0" -rand_core = "0.6.0" -rayon = "1.5.1" -rand = { version = "0.8.3", features = [ "small_rng" ] } - -[features] -f64 = ["rt_core/f64"] \ No newline at end of file diff --git a/implementations/src/statistics/src/bxdfs.rs b/implementations/src/statistics/src/bxdfs.rs deleted file mode 100644 index dcdd0ec..0000000 --- a/implementations/src/statistics/src/bxdfs.rs +++ /dev/null @@ -1,227 +0,0 @@ -pub mod trowbridge_reitz { - use crate::*; - use rand::Rng; - pub fn sample_h(alpha: Float, rng: &mut R) -> Vec3 { - let r1: Float = rng.gen(); - let r2: Float = rng.gen(); - let cos_theta = ((1.0 - r1) / (r1 * (alpha * alpha - 1.0) + 1.0)).sqrt(); - let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); - let phi_s = (TAU * r2).max(0.0).min(TAU); - Vec3::new(phi_s.cos() * sin_theta, phi_s.sin() * sin_theta, cos_theta).normalised() - } - - pub fn reference_d(h: Vec3, alpha: Float) -> Float { - if h.z <= 0.0 { - return 0.0; - } - - let a_sq = alpha * alpha; - let cos_theta = h.z; - let cos_theta_sq = cos_theta * cos_theta; - let sin_theta = (1.0 - cos_theta_sq).sqrt(); - let tan_theta = sin_theta / cos_theta; - let tmp = a_sq + tan_theta * tan_theta; - - a_sq / (PI * cos_theta_sq * cos_theta_sq * tmp * tmp) - } - - pub fn d(alpha: Float, cos_theta: Float) -> Float { - if cos_theta <= 0.0 { - return 0.0; - } - let a_sq = alpha * alpha; - let tmp = cos_theta * cos_theta * (a_sq - 1.0) + 1.0; - a_sq / (PI * tmp * tmp) - } - - pub fn alternative_d(alpha: Float, h: Vec3, cos_theta: Float) -> Float { - if cos_theta < 0.0 { - return 0.0; - } - let a_sq = alpha * alpha; - let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; - 1.0 / (PI * a_sq * tmp * tmp) - } - - pub fn pdf_h(h: Vec3, alpha: Float) -> Float { - // technically the paper has an .abs() but it isn't needed since h.z < 0 => D(m) = 0 - d(alpha, h.z) * h.z - } - - pub fn sample(alpha: Float, incoming: Vec3, rng: &mut R) -> Vec3 { - let h = sample_h(alpha, rng); - 2.0 * incoming.dot(h) * h - incoming - } - - pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { - let mut h = (incoming + outgoing).normalised(); - if h.dot(normal) < 0.0 { - h = -(incoming + outgoing).normalised(); - } - let cos_theta = normal.dot(h); - let d = d(alpha, cos_theta); - d * h.dot(normal).abs() / (4.0 * outgoing.dot(h).abs()) - } -} - -pub mod trowbridge_reitz_vndf { - use crate::*; - use rand::Rng; - - // All in local frame - pub fn d_ansiotropic(a_x: Float, a_y: Float, h: Vec3) -> Float { - let tmp = h.x * h.x / (a_x * a_x) + h.y * h.y / (a_y * a_y) + h.z * h.z; - 1.0 / (PI * a_x * a_y * tmp * tmp) - } - - pub fn d_isotropic(a: Float, h: Vec3) -> Float { - let a_sq = a * a; - - let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; - if (1.0 / (PI * a_sq * tmp * tmp)).is_nan() { - println!("{tmp} = ({}^2 + {}^2) / {a_sq} + {}^2", h.x, h.y, h.z); - } - 1.0 / (PI * a_sq * tmp * tmp) - } - - pub fn lambda_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { - let tmp = 1.0 + (a_x * a_x * v.x * v.x + a_y * a_y * v.y * v.y) / (v.z * v.z); - 0.5 * (tmp.sqrt() - 1.0) - } - - pub fn lambda_isotropic(a: Float, v: Vec3) -> Float { - let tmp = 1.0 + (a * a * (v.x * v.x + v.y * v.y)) / (v.z * v.z); - 0.5 * (tmp.sqrt() - 1.0) - } - - pub fn g1_ansiotropic(a_x: Float, a_y: Float, v: Vec3) -> Float { - 1.0 / (1.0 + lambda_ansiotropic(a_x, a_y, v)) - } - pub fn g1_isotropic(a: Float, v: Vec3) -> Float { - 1.0 / (1.0 + lambda_isotropic(a, v)) - } - - pub fn vndf_ansiotropic(a_x: Float, a_y: Float, h: Vec3, v: Vec3) -> Float { - if h.z < 0.0 { - return 0.0; - } - g1_ansiotropic(a_x, a_y, v) * v.dot(h).max(0.0) * d_ansiotropic(a_x, a_y, h) / v.z - } - - pub fn vndf_isotropic(a: Float, h: Vec3, v: Vec3) -> Float { - if h.z < 0.0 { - return 0.0; - } - g1_isotropic(a, v) * v.dot(h).max(0.0) * d_isotropic(a, h) / v.z - } - pub fn sample_vndf_ansiotropic(a_x: Float, a_y: Float, v: Vec3, rng: &mut R) -> Vec3 { - // chapter 1 act 1 - // transform from ellipsoid configuration to hemisphere by multipling by the scaling factors on the x and y axies (a_x, a_y) - let v_hemisphere = Vec3::new(a_x * v.x, a_y * v.y, v.z).normalised(); - - // chapter 2 act 1 - - // interlude - let len_sq = v_hemisphere.x * v_hemisphere.x + v_hemisphere.y * v_hemisphere.y; - - let basis_two = if len_sq > 0.0 { - Vec3::new(-v_hemisphere.y, v_hemisphere.x, 0.0) / len_sq.sqrt() - } else { - Vec3::new(1.0, 0.0, 0.0) // len_sq = 0 implies v_hemisphere = Z, so X is a valid orthonormal basis - }; - let basis_three = v_hemisphere.cross(basis_two); // v_hemisphere is first basis - - // chapter 3 act 1 - let r = rng.gen::().sqrt(); - let phi = TAU * rng.gen::(); - let mut t = r * Vec2::new(phi.cos(), phi.sin()); - let s = 0.5 * (1.0 + v_hemisphere.z); - t.y = (1.0 - s) * (1.0 - t.x * t.x).sqrt() + s * t.y; - - // chapter 4 act 1 - let h_hemisphere = t.x * basis_two - + t.y * basis_three - + (1.0 - t.x * t.x - t.y * t.y).max(0.0).sqrt() * v_hemisphere; - - // chapter 5 final act - - // not dividing since microfacet normal is a covector - Vec3::new( - a_x * h_hemisphere.x, - a_y * h_hemisphere.y, - h_hemisphere.z.max(0.0), // avoid numerical errors - ) - .normalised() - } - - pub fn sample_vndf_isotropic(a: Float, v: Vec3, rng: &mut R) -> Vec3 { - sample_vndf_ansiotropic(a, a, v, rng) // cause I'm lazy - } - - pub fn sample_outgoing_isotropic(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { - let h = sample_vndf_isotropic(a, incoming, rng); - 2.0 * incoming.dot(h) * h - incoming - } - - pub fn pdf_outgoing(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float { - let mut h = (incoming + outgoing).normalised(); - if h.dot(normal) < 0.0 { - h = -(incoming + outgoing).normalised(); - } - let _cos_theta = normal.dot(h); - let vndf = vndf_ansiotropic(alpha, alpha, h, incoming); - vndf / (4.0 * incoming.dot(h)) - } -} - -#[cfg(test)] -pub mod test { - use super::*; - use crate::{spherical_sampling::*, *}; - use rand::{rngs::ThreadRng, thread_rng, Rng}; - - #[test] - fn trowbridge_reitz_h() { - let alpha: Float = thread_rng().gen(); - let pdf = |outgoing: Vec3| trowbridge_reitz::pdf_h(outgoing, alpha); - let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample_h(alpha, rng); - test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); - } - - #[test] - fn trowbridge_reitz() { - let mut rng = thread_rng(); - let incoming: Vec3 = generate_wi(&mut rng); - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| { - trowbridge_reitz::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) - }; - let sample = |rng: &mut ThreadRng| trowbridge_reitz::sample(alpha, incoming, rng); - test_spherical_pdf("trowbrige reitz h", &pdf, &sample, false); - } - #[test] - fn trowbridge_reitz_vndf_h() { - let mut rng = thread_rng(); - let incoming: Vec3 = generate_wi(&mut rng); - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| trowbridge_reitz_vndf::vndf_isotropic(alpha, outgoing, incoming); - let sample = |rng: &mut ThreadRng| { - trowbridge_reitz_vndf::sample_vndf_isotropic(alpha, incoming, rng) - }; - test_spherical_pdf("trowbrige reitz vndf h", &pdf, &sample, false); - } - - #[test] - fn trowbridge_reitz_vndf() { - let mut rng = thread_rng(); - let incoming: Vec3 = generate_wi(&mut rng); - let alpha: Float = rng.gen(); - let pdf = |outgoing: Vec3| { - trowbridge_reitz_vndf::pdf_outgoing(alpha, incoming, outgoing, Vec3::new(0.0, 0.0, 1.0)) - }; - let sample = |rng: &mut ThreadRng| { - trowbridge_reitz_vndf::sample_outgoing_isotropic(alpha, incoming, rng) - }; - test_spherical_pdf("trowbrige reitz vndf", &pdf, &sample, false); - } -} diff --git a/implementations/src/statistics/src/chi_squared.rs b/implementations/src/statistics/src/chi_squared.rs deleted file mode 100644 index 41e3473..0000000 --- a/implementations/src/statistics/src/chi_squared.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::*; -use rayon::prelude::*; -use statrs::function::gamma::gamma_ur; - -use std::cmp::Ordering::*; -pub fn chi2_probability(dof: f64, distance: f64) -> f64 { - match distance.partial_cmp(&0.0).unwrap() { - Less => panic!("distance < 0.0"), - Equal => 1.0, - Greater => { - if distance.is_infinite() { - 0.0 - } else { - gamma_ur(dof * 0.5, distance * 0.5) - } - } - } -} - -pub fn chi_squared( - freq_table: &[Float], - expected_freq_table: &[Float], - samples: usize, -) -> (usize, Float) { - assert_eq!(freq_table.len(), expected_freq_table.len()); - - let mut values = expected_freq_table - .into_par_iter() - .zip(freq_table.into_par_iter()) - .collect::>(); - - values.sort_by(|a, b| a.0.partial_cmp(b.0).unwrap()); - - let mut df = 0; - - let mut expected_pooled = 0.0; - let mut actual_pooled = 0.0; - - let mut chi_squared = 0.0; - - for (expected, actual) in values { - if *expected == 0.0 { - if *actual > (samples / 100_000) as Float { - chi_squared += INFINITY as Float; - } - } else if expected_pooled > 5.0 { - // prevent df = 0 when all values are less than 5 - let diff = actual_pooled - expected_pooled; - chi_squared += diff * diff / expected_pooled; - df += 1; - } else if *expected < 5.0 || (expected_pooled > 0.0 && expected_pooled < 5.0) { - expected_pooled += expected; - actual_pooled += actual; - } else { - let diff = actual - expected; - chi_squared += diff * diff / expected; - df += 1; - } - } - - if actual_pooled > 0.0 || expected_pooled > 0.0 { - let diff = actual_pooled - expected_pooled; - chi_squared += diff * diff / expected_pooled; - df += 1; - } - - df -= 1; - - (df, chi_squared) -} - -#[cfg(test)] -pub mod test {} - -#[cfg(test)] -pub use test::*; diff --git a/implementations/src/statistics/src/distributions.rs b/implementations/src/statistics/src/distributions.rs deleted file mode 100644 index 5d22998..0000000 --- a/implementations/src/statistics/src/distributions.rs +++ /dev/null @@ -1,300 +0,0 @@ -use crate::Float; -use rand::Rng; - -#[derive(Debug, Clone, PartialEq)] -pub struct Distribution1D { - pub pdf: Vec, - pub cdf: Vec, -} - -impl Distribution1D { - pub fn new(values: &[Float]) -> Self { - if values.is_empty() { - panic!("Empty pdf passed to Distribution1D::from_pdf!"); - } - - let n = values.len(); - - let mut intervals = vec![0.0]; - - for i in 1..=n { - let last = intervals[i - 1]; - intervals.push(last + values[i - 1] as Float); - } - - let c = intervals[n]; - for (_, value) in intervals.iter_mut().enumerate() { - if c != 0.0 { - *value /= c as Float; - } - } - - let mut pdf = Vec::new(); - let mut last = 0.0; - for value in &intervals[1..] { - pdf.push(value - last); - last = *value; - } - - Self { - pdf, - cdf: intervals, - } - } - - pub fn sample_naive(&self, rng: &mut R) -> usize { - let threshold = rng.gen(); - - self.cdf.iter().position(|v| v >= &threshold).unwrap() - 1 - } - pub fn sample(&self, rng: &mut R) -> usize { - let num = rng.gen(); - - let pred = |i| self.cdf[i] <= num; - - { - let mut first = 0; - let mut len = self.cdf.len(); - while len > 0 { - let half = len >> 1; - let middle = first + half; - - if pred(middle) { - first = middle + 1; - len -= half + 1; - } else { - len = half; - } - } - (first - 1).clamp(0, self.cdf.len() - 2) - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Distribution2D { - pub x_distributions: Vec, - pub y_distribution: Distribution1D, - pub dim: (usize, usize), -} - -impl Distribution2D { - pub fn new(values: &[Float], width: usize) -> Self { - assert!(values.len() % width == 0 && !values.is_empty()); - let mut y_values = Vec::new(); - let mut x_distributions = Vec::new(); - for vec_x in values.chunks_exact(width) { - x_distributions.push(Distribution1D::new(vec_x)); - let row_sum: Float = vec_x.iter().sum(); - y_values.push(row_sum); - } - let y_distribution = Distribution1D::new(&y_values); - - Self { - x_distributions, - y_distribution, - dim: (width, values.len() / width), - } - } - pub fn sample(&self, rng: &mut R) -> (usize, usize) { - let v = self.y_distribution.sample(rng); - let u = self.x_distributions[v].sample(rng); - (u, v) - } - pub fn pdf(&self, u: Float, v: Float) -> Float { - let u = ((self.dim.0 as Float * u) as usize).clamp(0, self.dim.0 - 1); - let v = ((self.dim.1 as Float * v) as usize).clamp(0, self.dim.1 - 1); - - self.y_distribution.pdf[v] * self.x_distributions[v].pdf[u] - } - pub fn dim(&self) -> (usize, usize) { - self.dim - } -} - -#[cfg(test)] -mod tests { - use crate::{chi_squared::*, distributions::*, utility::*}; - use rand::thread_rng; - use rayon::prelude::*; - - macro_rules! random_1d { - ($len:expr) => {{ - const SAMPLES: usize = 100_000; - const SAMPLE_LEN: usize = 100; - const NUMBER_TESTS: usize = 10; - const CHI_SQUARED_THRESHOLD: Float = 0.01; - const BATCH_EXPONENT: usize = 6; - const BATCHES: usize = 2usize.pow(BATCH_EXPONENT as u32); - - let mut rng = thread_rng(); - - let values: Vec = (0..SAMPLE_LEN) - .into_iter() - .map(|_| rng.gen_range(0.0..100.0)) - .collect(); - - let cdf = Distribution1D::new(&values); - - let expected_values: Vec = cdf.pdf.iter().map(|v| v * SAMPLES as Float).collect(); - - let func = |samples| { - const SAMPLE_LEN_MINUS_ONE: usize = SAMPLE_LEN - 1; - let mut rng = thread_rng(); - let mut sampled_values = vec![0u64; SAMPLE_LEN]; - for _ in 0..samples { - match cdf.sample(&mut rng) { - index @ 0..=SAMPLE_LEN_MINUS_ONE => { - sampled_values[index] += 1; - } - _ => unreachable!(), - } - } - sampled_values - }; - - // Šidák correction - let threshold = 1.0 - (1.0 - CHI_SQUARED_THRESHOLD).powf(1.0 / NUMBER_TESTS as Float); - - for i in 0..NUMBER_TESTS { - let sampled_vecs: Vec> = (0..BATCHES) - .map(|_| { - distribute_samples_over_threads(SAMPLES as u64, &func) - .into_iter() - .map(|v| v as Float) - .collect() - }) - .collect(); - let sampled_values: Vec = (0..SAMPLE_LEN) - .into_par_iter() - .map(|i| { - recursively_binary_average((0..BATCHES).map(|j| sampled_vecs[j][i]).collect()) - }) - .collect(); - - let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); - - let p_value = chi2_probability(df as f64, chi_squared as f64); - if p_value < threshold as f64 { - panic!("LERP: recieved p value of {p_value} with {SAMPLES} samples on test {i}/{NUMBER_TESTS}") - } - }} - }; - } - - #[test] - fn random_1d_small() { - random_1d!(50) - } - - #[test] - fn random_1d_medium() { - random_1d!(500) - } - #[test] - fn random_1d_large() { - random_1d!(5000) - } - - macro_rules! random_2d { - ($x:expr, $y:expr) => { { - const SAMPLES: usize = 100_000; - const X_RES_MINUS_ONE: usize = $x - 1; - const Y_RES_MINUS_ONE: usize = $y - 1; - const SAMPLES_RES: (usize, usize) = (X_RES_MINUS_ONE + 1, Y_RES_MINUS_ONE + 1); - const SAMPLE_LEN: usize = SAMPLES_RES.0 * SAMPLES_RES.1; - const NUMBER_TESTS: usize = 10; - const CHI_SQUARED_THRESHOLD: Float = 0.01; - const BATCH_EXPONENT: usize = 6; - const BATCHES: usize = 2usize.pow(BATCH_EXPONENT as u32); - - //use rand_core::SeedableRng; - - //let mut rng = SmallRng::seed_from_u64(321321); - let mut rng = thread_rng(); - - let values: Vec = (0..SAMPLE_LEN) - .into_iter() - .map(|_| rng.gen_range(0.0..100.0)) - .collect(); - - let dist = Distribution2D::new(&values, SAMPLES_RES.0); - - let mut expected_values: Vec = Vec::new(); - for y in 0..SAMPLES_RES.1 { - for x in 0..SAMPLES_RES.0 { - expected_values.push( - dist.y_distribution.pdf[y] * dist.x_distributions[y].pdf[x] * SAMPLES as Float, - ); - } - } - - // dist.pdf.iter().map(|v| v * SAMPLES as Float).collect(); - - let func = |samples| { - const SAMPLE_LEN_MINUS_ONE: usize = SAMPLE_LEN - 1; - let mut rng = thread_rng(); - let mut sampled_values = vec![0u64; SAMPLE_LEN]; - for _ in 0..samples { - match dist.sample(&mut rng) { - indices @ (0..=X_RES_MINUS_ONE, 0..=Y_RES_MINUS_ONE) - if indices.0 + (X_RES_MINUS_ONE + 1) * indices.1 < SAMPLE_LEN => - { - sampled_values[indices.0 + indices.1 * (X_RES_MINUS_ONE + 1)] += 1; - } - _ => unreachable!(), - } - } - sampled_values - }; - - // Šidák correction - let threshold = 1.0 - (1.0 - CHI_SQUARED_THRESHOLD).powf(1.0 / NUMBER_TESTS as Float); - - for i in 0..NUMBER_TESTS { - let sampled_vecs: Vec> = (0..BATCHES) - .into_iter() - .map(|_| { - distribute_samples_over_threads(SAMPLES as u64, &func) - .into_iter() - .map(|v| v as Float) - .collect() - }) - .collect(); - let sampled_values: Vec = (0..SAMPLE_LEN) - .into_par_iter() - .map(|i| { - recursively_binary_average( - (0..BATCHES) - .into_iter() - .map(|j| sampled_vecs[j][i]) - .collect(), - ) - }) - .collect(); - - let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); - - let p_value = chi2_probability(df as f64, chi_squared as f64); - if p_value < threshold as f64 { - panic!("LERP: recieved p value of {p_value} with {SAMPLES} samples on test {i}/{NUMBER_TESTS}") - } - } - }}; - } - - #[test] - fn random_2d_small() { - random_2d!(3, 3) - } - - #[test] - fn random_2d_medium() { - random_2d!(30, 60) - } - - #[test] - fn random_2d_large() { - random_2d!(800, 1200) - } -} diff --git a/implementations/src/statistics/src/integrators.rs b/implementations/src/statistics/src/integrators.rs deleted file mode 100644 index d1dc9f4..0000000 --- a/implementations/src/statistics/src/integrators.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::{Float, Vec3}; -const MAX_DEPTH: usize = 6; -const EPSILON: Float = 0.000001; - -pub fn integrate_solid_angle( - pdf: &F, - theta_start: Float, - theta_end: Float, - phi_start: Float, - phi_end: Float, -) -> Float -where - F: Fn(Vec3) -> Float, -{ - let pdf = |phi: Float, a, b| { - adaptive_simpsons( - |theta| { - pdf(Vec3::new( - phi.cos() * theta.sin(), - phi.sin() * theta.sin(), - theta.cos(), - )) * theta.sin().abs() - }, - a, - b, - ) - }; - - adaptive_simpsons(|phi| pdf(phi, theta_start, theta_end), phi_start, phi_end) -} - -pub fn adaptive_simpsons(function: F, a: Float, b: Float) -> Float -where - F: Fn(Float) -> Float, -{ - fn aux( - function: &F, - a: Float, - b: Float, - c: Float, - fa: Float, - fb: Float, - fc: Float, - i: Float, - epsilon: Float, - depth: usize, - ) -> Float - where - F: Fn(Float) -> Float, - { - let d = 0.5 * (a + b); - let e = 0.5 * (b + c); - let fd = function(d); - let fe = function(e); - - let h = c - a; - let i0 = (1.0 / 12.0) * h * (fa + 4.0 * fd + fb); - let i1 = (1.0 / 12.0) * h * (fb + 4.0 * fe + fc); - let ip = i0 + i1; - - if depth >= MAX_DEPTH || (ip - i).abs() < 15.0 * epsilon { - return ip + (1.0 / 15.0) * (ip - i); - } - - aux(function, a, d, b, fa, fd, fb, i0, 0.5 * epsilon, depth + 1) - + aux(function, b, e, c, fb, fe, fc, i1, 0.5 * epsilon, depth + 1) - } - let c = b; - let b = 0.5 * (a + b); - - let fa = function(a); - let fb = function(b); - let fc = function(c); - let i = (c - a) * (1.0 / 6.0) * (fa + 4.0 * fb + fc); - aux(&function, a, b, c, fa, fb, fc, i, EPSILON, 0) -} diff --git a/implementations/src/statistics/src/lib.rs b/implementations/src/statistics/src/lib.rs deleted file mode 100644 index 66d3cf4..0000000 --- a/implementations/src/statistics/src/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -pub use rt_core::{self, *}; - -pub mod bxdfs; -pub mod chi_squared; -pub mod distributions; -pub mod integrators; -pub mod spherical_sampling; - -pub mod utility { - - use rayon::prelude::*; - use std::ops::*; - - pub fn distribute_samples_over_threads(samples: u64, f: &F) -> Vec - where - T: Add + Send, - F: Fn(u64) -> Vec + Sync, - Vec: FromIterator<::Output>, - { - let thread_count = num_cpus::get(); - let mut samples_per_thread = vec![samples / thread_count as u64; thread_count]; - let diff = ((samples / thread_count as u64) * thread_count as u64) - samples; - let last = samples_per_thread.len() - 1; - samples_per_thread[last] += diff; - - samples_per_thread - .into_par_iter() - .map(f) - .reduce_with(|a, b| { - a.into_iter() - .zip(b.into_iter()) - .map(|(a, b)| a + b) - .collect() - }) - .unwrap() - } - - use rt_core::Float; - - pub fn recursively_binary_average(mut values: Vec) -> T - where - ::Output: Mul, - T: Copy, - Vec: FromIterator<<::Output as Mul>::Output>, - { - let mut len = values.len(); - if len & (len - 1) != 0 && len != 0 { - panic!("values.len() is not a power of 2"); - } - while len != 1 { - len /= 2; - - let (a, b) = values.split_at(len); - - values = a - .iter() - .zip(b.iter()) - .map(|(&a, &b)| (a + b) * 0.5) - .collect(); - } - - values[0] - } - - #[cfg(test)] - #[test] - fn binary_average() { - assert!( - (recursively_binary_average::(vec![1.0, 3.0, 7.0, 1.0]) - 3.0).abs() < 0.0000001 - ); - } -} diff --git a/implementations/src/statistics/src/spherical_sampling.rs b/implementations/src/statistics/src/spherical_sampling.rs deleted file mode 100644 index 0e4d358..0000000 --- a/implementations/src/statistics/src/spherical_sampling.rs +++ /dev/null @@ -1,234 +0,0 @@ -use crate::chi_squared::chi2_probability; -use crate::chi_squared::chi_squared; -use crate::integrators::integrate_solid_angle; -use crate::utility::distribute_samples_over_threads; -use crate::utility::recursively_binary_average; -use crate::*; -use crate::{Float, Vec3, PI}; -use rand::rngs::ThreadRng; -use rand::thread_rng; -use rand::Rng; -use rayon::prelude::*; - -pub fn cosine_hemisphere_sampling(rng: &mut R) -> Vec3 { - let cos_theta = (1.0 - rng.gen::()).sqrt(); - let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); - let phi = 2.0 * PI * rng.gen::(); - Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) -} - -pub fn cosine_hemisphere_pdf(wo: Vec3) -> Float { - wo.z.max(0.0) / PI -} - -pub fn specular_sampling(n: Float, rng: &mut R) -> Vec3 { - let a = rng.gen::().powf(1.0 / (n + 1.0)); - let term = (1.0 - a * a).sqrt(); - let phi = 2.0 * PI * rng.gen::(); - Vec3::new(term * phi.cos(), term * phi.sin(), a) -} - -pub fn random_unit_vector(rng: &mut R) -> Vec3 { - let (mut x, mut y, mut z) = (1.0, 1.0, 1.0); - while x * x + y * y + z * z > 1.0 { - x = rng.gen_range(-1.0..1.0); - y = rng.gen_range(-1.0..1.0); - z = rng.gen_range(-1.0..1.0); - } - - Vec3::new(x, y, z).normalised() -} - -pub fn test_spherical_pdf(name: &str, pdf: &P, sample: &S, hemisphere: bool) -where - P: Fn(Vec3) -> Float, - S: Fn(&mut ThreadRng) -> Vec3 + Send + Sync, -{ - const THETA_RES: usize = 80; - const PHI_RES: usize = 160; - const SAMPLES: usize = 100_000; - const SAMPLE_LEN: usize = THETA_RES * PHI_RES; - const NUMBER_TESTS: usize = 10; - const CHI_SQUARED_THRESHOLD: Float = 0.01; - const BATCH_EXPONENT: usize = 6; - const BATCHES: usize = 2usize.pow(BATCH_EXPONENT as u32); - - let mut expected_values = Vec::new(); - - let theta_step = if hemisphere { FRAC_PI_2 } else { PI } / THETA_RES as Float; - let phi_step = TAU / PHI_RES as Float; - for phi_i in 0..PHI_RES { - for theta_i in 0..THETA_RES { - let theta_start = theta_i as Float * theta_step; - let phi_start = phi_i as Float * phi_step; - expected_values.push(integrate_solid_angle( - pdf, - theta_start, - theta_start + theta_step, - phi_start, - phi_start + phi_step, - )); - } - } - - let pdf_sum = expected_values.iter().sum::(); - if (pdf_sum - 1.0).abs() > 0.001 { - panic!("reference pdf doesn't integrate to 1: {pdf_sum}"); - } - - let mut expected_values: Vec = expected_values - .into_iter() - .map(|v| v * SAMPLES as Float) - .collect(); - - let func = |samples| { - const SAMPLE_LEN_MINUS_ONE: usize = SAMPLE_LEN - 1; - let mut rng = thread_rng(); - let mut sampled_values = vec![0u64; SAMPLE_LEN]; - for _ in 0..samples { - let wo = sample(&mut rng); - - let sin_theta = (1.0 - wo.z * wo.z).sqrt(); - if sin_theta < 0.0 { - panic!("sin_theta ({sin_theta}) < 0.0"); - } - let theta = wo.z.acos(); - let mut phi = (wo.y).atan2(wo.x); - if phi < 0.0 { - phi += 2.0 * PI; - } - let theta_i = theta / theta_step; - let phi_i = phi / phi_step; - let index = (phi_i as usize * THETA_RES + theta_i as usize).min(SAMPLE_LEN_MINUS_ONE); - - sampled_values[index] += 1; - } - sampled_values - }; - - let threshold = 1.0 - (1.0 - CHI_SQUARED_THRESHOLD).powf(1.0 / NUMBER_TESTS as Float); - - for i in 0..NUMBER_TESTS { - let sampled_vecs: Vec> = (0..BATCHES) - .map(|_| { - distribute_samples_over_threads(SAMPLES as u64, &func) - .into_iter() - .map(|v| v as Float) - .collect() - }) - .collect(); - let mut sampled_values: Vec = (0..SAMPLE_LEN) - .into_par_iter() - .map(|i| { - recursively_binary_average::( - (0..BATCHES).map(|j| sampled_vecs[j][i]).collect(), - ) - }) - .collect(); - - let (df, chi_squared) = chi_squared(&sampled_values, &expected_values, SAMPLES); - - let p_value = chi2_probability(df as f64, chi_squared as f64); - if p_value < threshold as f64 { - let expected_abs_max = expected_values - .iter() - .max_by(|x, y| x.abs().partial_cmp(&y.abs()).unwrap()) - .unwrap() - .abs(); - - let sampled_abs_max = sampled_values - .iter() - .max_by(|x, y| x.abs().partial_cmp(&y.abs()).unwrap()) - .unwrap() - .abs(); - - let get_colour = |value: Float, max_abs_value: Float| -> Vec3 { - if value > 0.0 { - let t = value / max_abs_value; - - t * Vec3::new(1.0, 0.0, 0.0) - } else if value == 0.0 { - Vec3::new(0.0, 1.0, 0.0) - } else { - Vec3::new(0.0, 0.0, 1.0) - } - }; - - let transpose = |vec: &mut Vec| { - *vec = (0..(PHI_RES * THETA_RES)) - .map(|i| { - let y = i % PHI_RES; - let x = i / PHI_RES; - - vec[y * THETA_RES + x] - }) - .collect::>(); - }; - - transpose(&mut expected_values); - transpose(&mut sampled_values); - - let mut image = expected_values - .into_iter() - .map(|v| get_colour(v, expected_abs_max)) - .collect::>(); - image.extend( - (0..PHI_RES) - .map(|_| Vec3::new(0.12, 0.95, 0.95)) - .collect::>(), - ); - image.extend( - sampled_values - .into_iter() - .map(|v| get_colour(v, sampled_abs_max)) - .collect::>(), - ); - let image = image - .into_iter() - .flat_map(|v| [v.x, v.y, v.z]) - .map(|v| (v * 256.0).clamp(0.0, 255.0) as u8) - .collect::>(); - - image::save_buffer( - format!("{name}_failed_output_test_{i}.png"), - &image, - PHI_RES.try_into().unwrap(), - (THETA_RES * 2 + 1).try_into().unwrap(), - image::ColorType::Rgb8, - ) - .unwrap(); - - panic!("{name}: recieved p value of {p_value} with {SAMPLES} samples averaged over {BATCHES} batches on test {i}/{NUMBER_TESTS}") - } - } -} - -#[cfg(test)] -pub mod test { - use crate::{spherical_sampling::*, Float, Vec3, TAU}; - use rand::Rng; - - pub fn to_vec(sin_theta: Float, cos_theta: Float, phi: Float) -> Vec3 { - Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) - } - - pub fn generate_wi(rng: &mut R) -> Vec3 { - let cos_theta: Float = rng.gen(); - let phi = TAU as Float * rng.gen::(); - - to_vec((1.0 - cos_theta * cos_theta).sqrt(), cos_theta, phi) - } - - #[test] - fn cosine_hemisphere() { - test_spherical_pdf( - "cosine hemisphere sampling", - &cosine_hemisphere_pdf, - &cosine_hemisphere_sampling, - true, - ); - } -} - -#[cfg(test)] -pub use test::*; From dd6bb50cd095afe7d0bcf4ff9b5745be4525c5db Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 1 Feb 2023 22:23:52 +1100 Subject: [PATCH 21/39] Added lambertian to statistics/bxdfs --- implementations/src/materials/lambertian.rs | 22 ++++----- .../src/statistics/bxdfs/lambertian.rs | 49 +++++++++++++++++++ implementations/src/statistics/bxdfs/mod.rs | 1 + 3 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 implementations/src/statistics/bxdfs/lambertian.rs diff --git a/implementations/src/materials/lambertian.rs b/implementations/src/materials/lambertian.rs index 4d55667..0521f96 100644 --- a/implementations/src/materials/lambertian.rs +++ b/implementations/src/materials/lambertian.rs @@ -1,7 +1,5 @@ -use crate::{ - textures::Texture, - utility::{coord::Coordinate, cosine_hemisphere_sampling, near_zero, offset_ray}, -}; +use crate::{textures::Texture, utility::offset_ray}; +use rand::{rngs::SmallRng, thread_rng, SeedableRng}; use rt_core::*; #[derive(Debug, Clone)] @@ -30,21 +28,19 @@ where T: Texture, { fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { - let coordinate_system = Coordinate::new_from_z(hit.normal); - let direction = cosine_hemisphere_sampling(); - let mut direction = coordinate_system.to_coord(direction); - - if near_zero(direction) { - direction = hit.normal; - } + let direction = crate::statistics::bxdfs::lambertian::sample( + ray.direction, + hit.normal, + &mut SmallRng::from_rng(thread_rng()).unwrap(), + ); let point = offset_ray(hit.point, hit.normal, hit.error, true); *ray = Ray::new(point, direction, ray.time); false } - fn scattering_pdf(&self, hit: &Hit, _: Vec3, wi: Vec3) -> Float { - hit.normal.dot(wi).max(0.0) / PI + fn scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Float { + crate::statistics::bxdfs::lambertian::pdf(wo, wi, hit.normal) } fn eval(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Vec3 { self.texture.colour_value(wo, hit.point) * self.albedo * hit.normal.dot(wi).max(0.0) / PI diff --git a/implementations/src/statistics/bxdfs/lambertian.rs b/implementations/src/statistics/bxdfs/lambertian.rs new file mode 100644 index 0000000..0c82026 --- /dev/null +++ b/implementations/src/statistics/bxdfs/lambertian.rs @@ -0,0 +1,49 @@ +use crate::coord::Coordinate; +use crate::statistics::*; +use rand::Rng; + +pub fn sample_local(_: Vec3, rng: &mut R) -> Vec3 { + let cos_theta = (1.0 - rng.gen::()).sqrt(); + let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); + let phi = 2.0 * PI * rng.gen::(); + Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta) +} + +pub fn pdf_local(_: Vec3, outgoing: Vec3) -> Float { + outgoing.z.max(0.0) / PI +} + +pub fn sample(incoming: Vec3, normal: Vec3, rng: &mut R) -> Vec3 { + Coordinate::new_from_z(normal).to_coord(sample_local(incoming, rng)) +} + +pub fn pdf(_: Vec3, outgoing: Vec3, normal: Vec3) -> Float { + outgoing.dot(normal).max(0.0) / PI +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::statistics::spherical_sampling::*; + use rand::{rngs::ThreadRng, thread_rng}; + + #[test] + fn lambertian() { + let mut rng = thread_rng(); + let incoming = generate_wi(&mut rng); + let pdf = |outgoing: Vec3| pdf_local(incoming, outgoing); + let sample = |rng: &mut ThreadRng| sample_local(incoming, rng); + test_spherical_pdf("lambertian", &pdf, &sample, false); + } + + #[test] + fn non_local() { + let mut rng = thread_rng(); + let normal = random_unit_vector(&mut rng); + let to_local = Coordinate::new_from_z(normal); + let incoming = to_local.to_coord(generate_wi(&mut rng)); + let pdf = |outgoing: Vec3| pdf(incoming, outgoing, normal); + let sample = |rng: &mut ThreadRng| sample(incoming, normal, rng); + test_spherical_pdf("lambertian_nl", &pdf, &sample, false); + } +} diff --git a/implementations/src/statistics/bxdfs/mod.rs b/implementations/src/statistics/bxdfs/mod.rs index 88fc883..8281b26 100644 --- a/implementations/src/statistics/bxdfs/mod.rs +++ b/implementations/src/statistics/bxdfs/mod.rs @@ -1,3 +1,4 @@ +pub mod lambertian; pub mod trowbridge_reitz; pub mod trowbridge_reitz_vndf; From d70bdf0a8bdf16df4ae1f24a7a243526f70d0d5e Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Mon, 6 Feb 2023 17:11:28 +1100 Subject: [PATCH 22/39] More TR/GGX cleanup --- .../src/materials/trowbridge_reitz.rs | 49 ++----- .../src/statistics/bxdfs/trowbridge_reitz.rs | 134 ++++++++++++++---- 2 files changed, 122 insertions(+), 61 deletions(-) diff --git a/implementations/src/materials/trowbridge_reitz.rs b/implementations/src/materials/trowbridge_reitz.rs index 94851b1..990e75c 100644 --- a/implementations/src/materials/trowbridge_reitz.rs +++ b/implementations/src/materials/trowbridge_reitz.rs @@ -1,4 +1,6 @@ -use crate::{materials::refract, textures::Texture, utility::offset_ray}; +use crate::{ + materials::refract, statistics::bxdfs::trowbridge_reitz, textures::Texture, utility::offset_ray, +}; use rand::{rngs::SmallRng, thread_rng, SeedableRng}; use rt_core::*; @@ -29,30 +31,6 @@ where let f0 = lerp(f0, self.texture.colour_value(wi, hit.point), self.metallic); refract::fresnel(wo.dot(h), f0) } - - fn geometry_partial_ggx(&self, h: Vec3, v: Vec3) -> Float { - 1.0 / (1.0 + self.lambda_ggx(h, v)) - } - - fn geometry_ggx(&self, h: Vec3, wo: Vec3, wi: Vec3) -> Float { - 1.0 / (1.0 + self.lambda_ggx(h, wo) + self.lambda_ggx(h, wi)) - } - - fn lambda_ggx(&self, h: Vec3, v: Vec3) -> Float { - let voh = v.dot(h); - let voh_sq = voh * voh; - let tan_sq = (1.0 - voh_sq) / voh_sq; - - ((1.0 + self.alpha * self.alpha * tan_sq).sqrt() - 1.0) * 0.5 - } - - fn microfacet_ndf_ggx(&self, hit: &Hit, h: Vec3) -> Float { - let noh = hit.normal.dot(h); - let alpha_sq = self.alpha * self.alpha; - let noh_sq = noh * noh; - let den = noh_sq * (alpha_sq - 1.0) + 1.0; - alpha_sq / (PI * den * den) - } } impl<'a, T> Scatter for TrowbridgeReitz<'a, T> @@ -60,7 +38,7 @@ where T: Texture, { fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { - let direction = crate::statistics::bxdfs::trowbridge_reitz::sample( + let direction = trowbridge_reitz::sample( self.alpha, ray.direction, hit.normal, @@ -73,7 +51,7 @@ where false } fn scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Float { - let a = crate::statistics::bxdfs::trowbridge_reitz::pdf(self.alpha, wo, wi, hit.normal); + let a = trowbridge_reitz::pdf(self.alpha, wo, wi, hit.normal); if a == 0.0 { INFINITY } else { @@ -88,12 +66,10 @@ where return Vec3::zero(); } - let spec_component = self.fresnel(hit, wo, wi, h) - * self.geometry_ggx(h, wo, wi) - * self.microfacet_ndf_ggx(hit, h) - / (4.0 * wo.dot(hit.normal) * wi.dot(hit.normal)); - - spec_component * hit.normal.dot(wi).abs() + self.fresnel(hit, wo, wi, h) + * trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi) + * trowbridge_reitz::d(self.alpha, hit.normal.dot(h)) + / (4.0 * wo.dot(hit.normal).abs()) } fn eval_over_scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Vec3 { let wo = -wo; @@ -103,10 +79,9 @@ where return Vec3::zero(); } - self.microfacet_ndf_ggx(hit, h); - - self.fresnel(hit, wo, wi, h) * self.geometry_ggx(h, wo, wi) - / self.geometry_partial_ggx(h, wo) + self.fresnel(hit, wo, wi, h) + * trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi) + * wo.dot(h) / (wo.dot(hit.normal) * h.dot(hit.normal)).abs() } } diff --git a/implementations/src/statistics/bxdfs/trowbridge_reitz.rs b/implementations/src/statistics/bxdfs/trowbridge_reitz.rs index 28b3807..e3d9860 100644 --- a/implementations/src/statistics/bxdfs/trowbridge_reitz.rs +++ b/implementations/src/statistics/bxdfs/trowbridge_reitz.rs @@ -11,21 +11,6 @@ pub fn sample_h(alpha: Float, rng: &mut R) -> Vec3 { Vec3::new(phi_s.cos() * sin_theta, phi_s.sin() * sin_theta, cos_theta).normalised() } -pub fn reference_d(h: Vec3, alpha: Float) -> Float { - if h.z <= 0.0 { - return 0.0; - } - - let a_sq = alpha * alpha; - let cos_theta = h.z; - let cos_theta_sq = cos_theta * cos_theta; - let sin_theta = (1.0 - cos_theta_sq).sqrt(); - let tan_theta = sin_theta / cos_theta; - let tmp = a_sq + tan_theta * tan_theta; - - a_sq / (PI * cos_theta_sq * cos_theta_sq * tmp * tmp) -} - pub fn d(alpha: Float, cos_theta: Float) -> Float { if cos_theta <= 0.0 { return 0.0; @@ -35,15 +20,6 @@ pub fn d(alpha: Float, cos_theta: Float) -> Float { a_sq / (PI * tmp * tmp) } -pub fn alternative_d(alpha: Float, h: Vec3, cos_theta: Float) -> Float { - if cos_theta < 0.0 { - return 0.0; - } - let a_sq = alpha * alpha; - let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; - 1.0 / (PI * a_sq * tmp * tmp) -} - pub fn pdf_h(alpha: Float, h: Vec3) -> Float { // technically the paper has an .abs() but it isn't needed since h.z < 0 => D(m) = 0 d(alpha, h.z) * h.z @@ -81,9 +57,36 @@ pub fn pdf(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float d * h.z.abs() / (4.0 * outgoing.dot(h).abs()) } +pub fn g2(alpha: Float, normal: Vec3, h: Vec3, incoming: Vec3, outgoing: Vec3) -> Float { + if incoming.dot(h) / incoming.z <= 0.0 || outgoing.dot(h) / outgoing.z <= 0.0 { + return 0.0; + } + let alpha_sq = alpha * alpha; + let one_minus_alpha_sq = 1.0 - alpha_sq; + let cos_i = normal.dot(incoming); + let cos_i_sq = cos_i * cos_i; + let tmp_a = alpha_sq + one_minus_alpha_sq * cos_i_sq; + let cos_o = normal.dot(outgoing); + let cos_o_sq = cos_o * cos_o; + let tmp_b = alpha_sq + one_minus_alpha_sq * cos_o_sq; + 2.0 * cos_i * cos_o / (cos_o * tmp_a.sqrt() + cos_i * tmp_b.sqrt()) +} + +pub fn g1(alpha: Float, normal: Vec3, h: Vec3, v: Vec3) -> Float { + if v.dot(h) / v.z <= 0.0 { + return 0.0; + } + let cos = normal.dot(v); + let cos_sq = cos * cos; + let alpha_sq = alpha * alpha; + let tmp = alpha_sq + (1.0 - alpha_sq) * cos_sq; + 2.0 * cos / (tmp.sqrt() + cos) +} + #[cfg(test)] mod tests { use super::*; + use crate::integrators::integrate_solid_angle; use crate::statistics::spherical_sampling::*; use rand::{rngs::ThreadRng, thread_rng, Rng}; @@ -117,4 +120,87 @@ mod tests { let sample = |rng: &mut ThreadRng| sample(alpha, incoming, normal, rng); test_spherical_pdf("tr_nl", &pdf, &sample, false); } + + #[test] + fn g1_cos_test() { + let mut rng = thread_rng(); + let incoming = -generate_wi(&mut rng); + let cos_theta = incoming.z; + let alpha = rng.gen(); + let test = |h: Vec3| { + g1(alpha, Vec3::new(0.0, 0.0, 1.0), h, incoming) + * incoming.dot(h).max(0.0) + * d(alpha, h.z) + }; + + const THETA_RES: usize = 80; + const PHI_RES: usize = 160; + + let mut expected_values = Vec::new(); + + let theta_step = PI / THETA_RES as Float; + let phi_step = TAU / PHI_RES as Float; + for phi_i in 0..PHI_RES { + for theta_i in 0..THETA_RES { + let theta_start = theta_i as Float * theta_step; + let phi_start = phi_i as Float * phi_step; + expected_values.push(integrate_solid_angle( + &test, + theta_start, + theta_start + theta_step, + phi_start, + phi_start + phi_step, + )); + } + } + + let pdf_sum = expected_values.iter().sum::(); + + assert!((pdf_sum - cos_theta).abs() < 0.0001); + } + + #[test] + fn weak_furnace_test() { + let mut rng = thread_rng(); + let wo = -generate_wi(&mut rng); + + let alpha: Float = rng.gen(); + + let test = |wi: Vec3| { + let mut h = (wi + wo).normalised(); + if h.z < 0.0 { + h = -h; + } + let denom = 4.0 * wo.z.abs(); + if denom < 0.000000001 { + 0.0 + } else { + g1(alpha, Vec3::new(0.0, 0.0, 1.0), h, wo) * d(alpha, h.z) / denom + } + }; + + const THETA_RES: usize = 80; + const PHI_RES: usize = 160; + + let mut expected_values = Vec::new(); + + let theta_step = PI / THETA_RES as Float; + let phi_step = TAU / PHI_RES as Float; + for phi_i in 0..PHI_RES { + for theta_i in 0..THETA_RES { + let theta_start = theta_i as Float * theta_step; + let phi_start = phi_i as Float * phi_step; + expected_values.push(integrate_solid_angle( + &test, + theta_start, + theta_start + theta_step, + phi_start, + phi_start + phi_step, + )); + } + } + + let pdf_sum = expected_values.iter().sum::(); + assert!((pdf_sum - 1.0).abs() < 0.0001); + } } From ad5adc1ac426a3c8a46b2b940c9840d17a653a19 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Mon, 6 Feb 2023 20:46:27 +1100 Subject: [PATCH 23/39] Added g2 test --- .../src/statistics/bxdfs/trowbridge_reitz.rs | 70 +++++++------------ .../src/statistics/spherical_sampling.rs | 25 +++++++ 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/implementations/src/statistics/bxdfs/trowbridge_reitz.rs b/implementations/src/statistics/bxdfs/trowbridge_reitz.rs index e3d9860..25e1702 100644 --- a/implementations/src/statistics/bxdfs/trowbridge_reitz.rs +++ b/implementations/src/statistics/bxdfs/trowbridge_reitz.rs @@ -86,7 +86,6 @@ pub fn g1(alpha: Float, normal: Vec3, h: Vec3, v: Vec3) -> Float { #[cfg(test)] mod tests { use super::*; - use crate::integrators::integrate_solid_angle; use crate::statistics::spherical_sampling::*; use rand::{rngs::ThreadRng, thread_rng, Rng}; @@ -133,30 +132,8 @@ mod tests { * d(alpha, h.z) }; - const THETA_RES: usize = 80; - const PHI_RES: usize = 160; - - let mut expected_values = Vec::new(); - - let theta_step = PI / THETA_RES as Float; - let phi_step = TAU / PHI_RES as Float; - for phi_i in 0..PHI_RES { - for theta_i in 0..THETA_RES { - let theta_start = theta_i as Float * theta_step; - let phi_start = phi_i as Float * phi_step; - expected_values.push(integrate_solid_angle( - &test, - theta_start, - theta_start + theta_step, - phi_start, - phi_start + phi_step, - )); - } - } - - let pdf_sum = expected_values.iter().sum::(); - - assert!((pdf_sum - cos_theta).abs() < 0.0001); + let integral = integrate_over_sphere(&test); + assert!((integral - cos_theta).abs() < 0.0001); } #[test] @@ -179,28 +156,29 @@ mod tests { } }; - const THETA_RES: usize = 80; - const PHI_RES: usize = 160; - - let mut expected_values = Vec::new(); - - let theta_step = PI / THETA_RES as Float; - let phi_step = TAU / PHI_RES as Float; - for phi_i in 0..PHI_RES { - for theta_i in 0..THETA_RES { - let theta_start = theta_i as Float * theta_step; - let phi_start = phi_i as Float * phi_step; - expected_values.push(integrate_solid_angle( - &test, - theta_start, - theta_start + theta_step, - phi_start, - phi_start + phi_step, - )); + let integral = integrate_over_sphere(&test); + assert!((integral - 1.0).abs() < 0.0001); + } + + #[test] + fn g2_test() { + let mut rng = thread_rng(); + let a = -generate_wi(&mut rng); + let alpha = rng.gen(); + let test = |b: Vec3| { + let mut h = (a + b).normalised(); + if h.z < 0.0 { + h = -h; } - } + let denom = 4.0 * a.z.abs(); + if denom < 0.000000001 { + 0.0 + } else { + g2(alpha, Vec3::new(0.0, 0.0, 1.0), h, a, b) * d(alpha, h.z) / denom + } + }; - let pdf_sum = expected_values.iter().sum::(); - assert!((pdf_sum - 1.0).abs() < 0.0001); + let integral = integrate_over_sphere(&test); + assert!(integral <= 1.0); } } diff --git a/implementations/src/statistics/spherical_sampling.rs b/implementations/src/statistics/spherical_sampling.rs index 219955b..df8ba45 100644 --- a/implementations/src/statistics/spherical_sampling.rs +++ b/implementations/src/statistics/spherical_sampling.rs @@ -36,6 +36,31 @@ pub fn random_unit_vector(rng: &mut R) -> Vec3 { Vec3::new(x, y, z).normalised() } +pub fn integrate_over_sphere Float>(function: &F) -> Float { + let mut values = Vec::new(); + + const THETA_RES: usize = 80; + const PHI_RES: usize = 160; + + let theta_step = PI / THETA_RES as Float; + let phi_step = TAU / PHI_RES as Float; + for phi_i in 0..PHI_RES { + for theta_i in 0..THETA_RES { + let theta_start = theta_i as Float * theta_step; + let phi_start = phi_i as Float * phi_step; + values.push(integrate_solid_angle( + function, + theta_start, + theta_start + theta_step, + phi_start, + phi_start + phi_step, + )); + } + } + + values.iter().sum::() +} + pub fn test_spherical_pdf(name: &str, pdf: &P, sample: &S, hemisphere: bool) where P: Fn(Vec3) -> Float, From c76603ce34b6b5b96ed3f83a30bde5ba824a1551 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Mon, 27 Feb 2023 16:25:10 +1100 Subject: [PATCH 24/39] reused TR utilities --- .../statistics/bxdfs/trowbridge_reitz_vndf.rs | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs b/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs index f497ff7..b1dd45a 100644 --- a/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs +++ b/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs @@ -4,31 +4,15 @@ use rt_core::*; pub mod isotropic { use super::*; - - pub fn d(a: Float, h: Vec3) -> Float { - let a_sq = a * a; - - let tmp = (h.x * h.x + h.y * h.y) / a_sq + h.z * h.z; - - 1.0 / (PI * a_sq * tmp * tmp) - } - - pub fn lambda(a: Float, incoming: Vec3) -> Float { - let tmp = 1.0 - + (a * a * (incoming.x * incoming.x + incoming.y * incoming.y)) - / (incoming.z * incoming.z); - 0.5 * (tmp.sqrt() - 1.0) - } - - pub fn g1(a: Float, incoming: Vec3) -> Float { - 1.0 / (1.0 + lambda(a, incoming)) - } + use crate::bxdfs::trowbridge_reitz::d; + use crate::bxdfs::trowbridge_reitz::g1; pub fn vndf(a: Float, h: Vec3, incoming: Vec3) -> Float { if h.z < 0.0 { return 0.0; } - g1(a, incoming) * incoming.dot(h).max(0.0) * d(a, h) / incoming.z + g1(a, Vec3::new(0.0, 0.0, 1.0), h, incoming) * incoming.dot(h).max(0.0) * d(a, h.z) + / incoming.z } pub fn sample_vndf(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { From e5f4875176777f00d2d8476a0d46e8db0bdbacdb Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Mon, 13 Mar 2023 21:09:44 +1100 Subject: [PATCH 25/39] frontend rework pt 1 - Scene uses custom region allocator - clap is now used for argument handling - custom scene format --- frontend/Cargo.toml | 6 +- frontend/loader/Cargo.toml | 15 + frontend/loader/src/lib.rs | 471 ++++++++++++++++++ frontend/loader/src/materials.rs | 117 +++++ frontend/loader/src/misc.rs | 30 ++ frontend/loader/src/parser.rs | 191 +++++++ frontend/loader/src/primitives.rs | 89 ++++ frontend/loader/src/textures.rs | 116 +++++ frontend/region/Cargo.toml | 13 + frontend/region/src/lib.rs | 341 +++++++++++++ frontend/region/src/wrappers.rs | 93 ++++ frontend/src/generate.rs | 301 ----------- frontend/src/main.rs | 26 +- frontend/src/parameters.rs | 471 +++--------------- frontend/src/scene.rs | 353 +++++-------- implementations/Cargo.toml | 3 + implementations/src/acceleration/mod.rs | 19 +- implementations/src/acceleration/split.rs | 2 + implementations/src/primitives/mod.rs | 2 +- implementations/src/primitives/sphere.rs | 2 +- implementations/src/primitives/triangle.rs | 2 +- .../src/samplers/random_sampler.rs | 10 +- implementations/src/textures/mod.rs | 2 +- rt_core/Cargo.toml | 2 + rt_core/src/acceleration.rs | 2 +- rt_core/src/material.rs | 2 +- rt_core/src/primitive.rs | 2 +- rt_core/src/sampler.rs | 19 +- scenes/scene.ssml | 60 +++ 29 files changed, 1792 insertions(+), 970 deletions(-) create mode 100644 frontend/loader/Cargo.toml create mode 100644 frontend/loader/src/lib.rs create mode 100644 frontend/loader/src/materials.rs create mode 100644 frontend/loader/src/misc.rs create mode 100644 frontend/loader/src/parser.rs create mode 100644 frontend/loader/src/primitives.rs create mode 100644 frontend/loader/src/textures.rs create mode 100644 frontend/region/Cargo.toml create mode 100644 frontend/region/src/lib.rs create mode 100644 frontend/region/src/wrappers.rs delete mode 100644 frontend/src/generate.rs create mode 100644 scenes/scene.ssml diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index c234b57..f40750f 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -19,7 +19,11 @@ vulkano-shaders = { version = "0.28.0", optional = true } vulkano-win = { version = "0.28.0", optional = true } wavefront_obj = "10.0.0" winit = { version = "0.26.1", optional = true } +region = { path = "./region" } +loader = { path = "./loader" } +simple_logger = "4.0" +clap = { version = "4.1.8", features = ["derive", "wrap_help"] } [features] f64 = ["implementations/f64"] -gui = ["dep:vulkano", "dep:vulkano-win", "dep:vulkano-shaders", "dep:winit"] \ No newline at end of file +gui = ["dep:vulkano", "dep:vulkano-win", "dep:vulkano-shaders", "dep:winit"] diff --git a/frontend/loader/Cargo.toml b/frontend/loader/Cargo.toml new file mode 100644 index 0000000..9df53e1 --- /dev/null +++ b/frontend/loader/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "loader" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4" +nom = "7" +region = { path = "../region" } +rt_core = { path = "../../rt_core" } +implementations = { path = "../../implementations" } +thiserror = "1.0" + +[features] +f64 = ["rt_core/f64", "implementations/f64"] diff --git a/frontend/loader/src/lib.rs b/frontend/loader/src/lib.rs new file mode 100644 index 0000000..7cff30f --- /dev/null +++ b/frontend/loader/src/lib.rs @@ -0,0 +1,471 @@ +pub mod materials; +pub mod misc; +pub mod parser; +pub mod primitives; +pub mod textures; + +use implementations::Texture; +use region::{Region, RegionRes, RegionUniqSlice}; +use rt_core::{Camera, Float, NoHit, Primitive, Scatter, Vec2, Vec3}; +use std::{collections::HashMap, fmt}; +use thiserror::Error; + +pub trait Load: Sized { + /// Take a set of properties and load an object from, optionally also + /// provide a resource name for this object. Such as a texture name + /// or material ID. + fn load(props: Properties) -> Result<(Option, Self), LoadErr>; +} + +#[derive(Default)] +pub struct Lookup { + texture: HashMap>, + scatter: HashMap>, +} + +impl fmt::Debug for Lookup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("Lookup") + .field("texture", &format_args!("{:?}", self.texture.keys())) + .field("scatter", &format_args!("{:?}", self.scatter.keys())) + .finish() + } +} + +impl Lookup { + pub fn new() -> Self { + Default::default() + } + + pub fn texture_insert( + &mut self, + name: &str, + res: RegionRes, + ) -> Option> { + let key = name.into(); + let res = unsafe { std::mem::transmute(res) }; + self.texture + .insert(key, res) + .map(|o| unsafe { std::mem::transmute(o) }) + } + pub fn scatter_insert( + &mut self, + name: &str, + res: RegionRes, + ) -> Option> { + let key = name.into(); + let res = unsafe { std::mem::transmute(res) }; + self.scatter + .insert(key, res) + .map(|o| unsafe { std::mem::transmute(o) }) + } + + pub fn texture_lookup(&self, name: &str) -> Option> { + self.texture + .get(name) + .map(|o| unsafe { std::mem::transmute(o.clone()) }) + } + pub fn scatter_lookup(&self, name: &str) -> Option> { + self.scatter + .get(name) + .map(|o| unsafe { std::mem::transmute(o.clone()) }) + } +} + +#[derive(Debug)] +pub struct Properties<'a> { + lookup: &'a Lookup, + name: Option, + props: HashMap, + autocast: bool, +} + +#[derive(Debug)] +enum PropertiesValue { + Vec3(Vec3), + Vec2(Vec2), + Float(Float), + Text(String), +} + +impl<'a> From> for PropertiesValue { + fn from(u: parser::ObjectValue<'a>) -> Self { + use parser::ObjectValue::*; + match u { + Num1(a) => Self::Float(a), + Num2(a, b) => Self::Vec2(Vec2::new(a, b)), + Num3(a, b, c) => Self::Vec3(Vec3::new(a, b, c)), + Text(t) => Self::Text(t.into()), + } + } +} + +impl<'a> Properties<'a> { + pub fn new(lookup: &'a Lookup, object: &parser::Object) -> Self { + Self { + lookup, + name: object.name.map(Into::into), + props: object + .values + .iter() + .map(|(&k, &v)| (k.into(), v.into())) + .collect(), + autocast: true, + } + } + + pub fn auto_cast(&mut self, doit: bool) { + self.autocast = doit; + } + + pub fn texture(&self, name: &str) -> Option> { + self.lookup.texture_lookup(self.text(name)?) + } + pub fn scatter(&self, name: &str) -> Option> { + self.lookup.scatter_lookup(self.text(name)?) + } + pub fn vec3(&self, name: &str) -> Option { + match self.props.get(name) { + Some(PropertiesValue::Vec3(x)) => Some(*x), + Some(PropertiesValue::Float(x)) if self.autocast => Some(*x * Vec3::one()), + _ => None, + } + } + pub fn vec2(&self, name: &str) -> Option { + match self.props.get(name) { + Some(PropertiesValue::Vec2(x)) => Some(*x), + Some(PropertiesValue::Float(x)) if self.autocast => Some(*x * Vec2::one()), + _ => None, + } + } + pub fn float(&self, name: &str) -> Option { + match self.props.get(name) { + Some(PropertiesValue::Float(x)) => Some(*x), + _ => None, + } + } + pub fn text(&self, name: &str) -> Option<&str> { + match self.props.get(name) { + Some(PropertiesValue::Text(x)) => Some(x), + _ => None, + } + } + pub fn name(&mut self) -> Option { + self.name.take() + } + + pub fn default_texture(&self) -> RegionRes { + self.lookup + .texture_lookup("__DEFAULT_TEX") + .expect("default texture not loaded") + } + pub fn default_scatter(&self) -> RegionRes { + self.lookup + .scatter_lookup("__DEFAULT_MAT") + .expect("default material not loaded") + } +} + +#[derive(Error, Debug)] +pub enum LoadErr { + #[error("failed to load a file for the given reason")] + FileNotRead(std::path::PathBuf, std::io::Error), + #[error("failed to parse the scene config for the given reason")] + ParseError(parser::ParseError), + #[error("missing required type for object")] + MissingRequiredVariantType, + #[error("missing required value for object")] + MissingRequired(String), + #[error("missing required camera object")] + MissingCamera, + #[error("unknown error")] + Any(Box), +} + +pub fn load_file_full<'a, T, M, P, C, S>( + region: &'a mut Region, + file: &str, +) -> Result<(RegionUniqSlice<'a, P>, C, S), LoadErr> +where + T: Texture + Load, + M: Scatter + Load, + P: Primitive + Load + Clone, + C: Camera + Load, + S: NoHit + Load, +{ + let scene_file = match std::fs::read_to_string(file) { + Ok(s) => s, + Err(e) => return Err(LoadErr::FileNotRead(file.into(), e)), + }; + log::debug!("Parsing scene file {}", file); + let scene_conf = match parser::from_str(&scene_file) { + Ok(c) => c, + Err(e) => return Err(LoadErr::ParseError(e)), + }; + + let mut lookup = Lookup::new(); + + log::info!("Loading textures..."); + let textures = load_textures::(&scene_conf, &lookup)?; + + region_insert_with_lookup(region, textures, |n, t| lookup.texture_insert(n, t)); + + log::info!("Loading materials..."); + let materials = load_materials::(&scene_conf, &lookup)?; + + region_insert_with_lookup(region, materials, |n, s| lookup.scatter_insert(n, s)); + + log::info!("Loading primitives..."); + let primitives = { + let primitives = load_primitives::

(&scene_conf, &lookup)?; + //let block_size = std::mem::size_of::

() * primitives.len(); + //let block = region.allocate_block(block_size); + region.alloc_slice(&primitives) + }; + + log::info!("Loading other objects..."); + let camera = load_scene_camera(&scene_conf, &lookup)?; + let sky = load_scene_sky(&scene_conf, &lookup)?; + + Ok((primitives, camera, sky)) +} + +pub fn load_str_full<'a, T, M, P, C, S>( + region: &'a mut Region, + data: &str, +) -> Result<(RegionUniqSlice<'a, P>, C, S), LoadErr> +where + T: Texture + Load, + M: Scatter + Load, + P: Primitive + Load + Clone, + C: Camera + Load, + S: NoHit + Load, +{ + let scene_conf = match parser::from_str(data) { + Ok(c) => c, + Err(e) => return Err(LoadErr::ParseError(e)), + }; + + let mut lookup = Lookup::new(); + + log::info!("Loading textures..."); + let textures = load_textures::(&scene_conf, &lookup)?; + + region_insert_with_lookup(region, textures, |n, t| lookup.texture_insert(n, t)); + + log::info!("Loading materials..."); + let materials = load_materials::(&scene_conf, &lookup)?; + + region_insert_with_lookup(region, materials, |n, s| lookup.scatter_insert(n, s)); + + log::info!("Loading primitives..."); + let primitives = { + let primitives = load_primitives::

(&scene_conf, &lookup)?; + //let block_size = std::mem::size_of::

() * primitives.len(); + //let block = region.allocate_block(block_size); + region.alloc_slice(&primitives) + }; + + log::info!("Loading other objects..."); + let camera = load_scene_camera(&scene_conf, &lookup)?; + let sky = load_scene_sky(&scene_conf, &lookup)?; + + Ok((primitives, camera, sky)) +} + +pub fn load_scene_camera(objects: &[parser::Object], lookup: &Lookup) -> Result +where + C: Camera + Load, +{ + // Find a camera object + let props = Properties::new( + lookup, + objects + .iter() + .find(|o| o.kind.is_camera()) + .ok_or(LoadErr::MissingCamera)?, + ); + Ok(C::load(props)?.1) +} + +pub fn load_scene_sky(objects: &[parser::Object], lookup: &Lookup) -> Result +where + S: NoHit + Load, +{ + // Find a sky object, if none warn and use a default + let obj = objects.iter().find(|o| o.kind.is_sky()); + let props = match obj { + Some(o) => Properties::new(lookup, o), + None => { + log::warn!("no sky object was provided in scene file, using default"); + Properties::new(lookup, &Default::default()) + } + }; + Ok(S::load(props)?.1) +} + +fn region_insert_with_lookup( + region: &mut Region, + items: Vec<(Option, T)>, + mut insert_fn: impl FnMut(&str, RegionRes) -> Option>, +) { + //let block_size = std::alloc::Layout::array::(items.len()).unwrap().size(); + //let block = region.allocate_block(block_size); + for (name, item) in items.into_iter() { + let uniq = region.alloc(item); + if let Some(name) = name { + if insert_fn(&name, uniq.shared()).is_some() { + log::warn!("Overwrote previous object of name: '{name}'"); + } + } + } +} + +fn load_textures( + objects: &[parser::Object], + lookup: &Lookup, +) -> Result, T)>, LoadErr> { + let mut textures = Vec::new(); + for obj in objects.iter().filter(|o| o.kind.is_texture()) { + let props = Properties::new(lookup, obj); + textures.push(::load(props)?); + } + // Load default texture, assumes that T contains SolidColor + { + use parser::{Object, ObjectKind, ObjectValue}; + let def_obj = Object { + kind: ObjectKind::Texture, + name: Some("__DEFAULT_TEX"), + values: [ + ("type", ObjectValue::Text("solid")), + ("colour", ObjectValue::Num1(1.0)), + ] + .into(), + }; + let props = Properties::new(lookup, &def_obj); + textures.push(::load(props)?); + } + Ok(textures) +} + +fn load_materials( + objects: &[parser::Object], + lookup: &Lookup, +) -> Result, S)>, LoadErr> { + let mut materials = Vec::new(); + for obj in objects.iter().filter(|o| o.kind.is_material()) { + let props = Properties::new(lookup, obj); + materials.push(::load(props)?); + } + // Load default material, assumes that S contains Lambertian + { + use parser::{Object, ObjectKind, ObjectValue}; + let def_obj = Object { + kind: ObjectKind::Material, + name: Some("__DEFAULT_MAT"), + values: [ + ("type", ObjectValue::Text("lambertian")), + ("texture", ObjectValue::Text("__DEFAULT_TEX")), + ("albedo", ObjectValue::Num1(1.0)), + ] + .into(), + }; + let props = Properties::new(lookup, &def_obj); + materials.push(::load(props)?); + } + Ok(materials) +} + +fn load_primitives( + objects: &[parser::Object], + lookup: &Lookup, +) -> Result, LoadErr> { + let mut primitives = Vec::new(); + for obj in objects.iter().filter(|o| o.kind.is_primitive()) { + let props = Properties::new(lookup, obj); + primitives.push(

::load(props)?.1); + } + Ok(primitives) +} + +#[cfg(test)] +mod tests { + use super::*; + + use implementations::*; + + const DATA: &str = "camera ( + origin -5 3 -3 + lookat 0 0.5 0 + vup 0 1 0 + fov 34.0 + aperture 0.0 + focus_dis 10.0 +) + +texture sky ( + type solid + colour 0.0 +) + +sky ( + texture sky +) + +texture grey ( + type solid + colour 0.5 +) + +texture white ( + type solid + colour 1.0 +) + +material ground ( + type lambertian + texture grey + albedo 0.5 +) + +material light ( + type emissive + texture white + strength 1.5 +) + +primitive ( + type sphere + material ground + centre 0 -1000 0 + radius 1000 +) + +primitive ( + type sphere + material light + centre 0 0.5 0 + radius 0.5 +) + +primitive ( + type sphere + material ground + centre -0.45 0.15 -0.45 + radius 0.05 +)"; + + #[test] + fn scene() { + let mut region = Region::new().unwrap(); + type Tex = AllTextures; + type Mat<'a> = AllMaterials<'a, Tex>; + type Prim<'a> = AllPrimitives<'a, Mat<'a>>; + type SkyType<'a> = Sky<'a, Tex>; + let stuff = + load_str_full::(&mut region, DATA).unwrap(); + + let (p, _, _) = stuff; + let _: Bvh = Bvh::new(p, split::SplitType::Sah); + } +} diff --git a/frontend/loader/src/materials.rs b/frontend/loader/src/materials.rs new file mode 100644 index 0000000..7154bd8 --- /dev/null +++ b/frontend/loader/src/materials.rs @@ -0,0 +1,117 @@ +use crate::Properties; +use crate::*; +use implementations::emissive::Emit; +use implementations::*; + +impl Load for AllMaterials<'_, T> { + fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + let kind = match props.text("type") { + Some(k) => k, + None => return Err(LoadErr::MissingRequiredVariantType), + }; + + Ok(match kind { + "emissive" => { + let x = Emit::load(props)?; + (x.0, Self::Emit(x.1)) + } + "lambertian" => { + let x = Lambertian::load(props)?; + (x.0, Self::Lambertian(x.1)) + } + "reflect" => { + let x = Reflect::load(props)?; + (x.0, Self::Reflect(x.1)) + } + "refract" => { + let x = Refract::load(props)?; + (x.0, Self::Refract(x.1)) + } + o => { + return Err(LoadErr::MissingRequired(format!( + "required a known value for material type, found '{o}'" + ))) + } + }) + } +} + +impl Load for Lambertian<'_, T> { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let tex = props + .texture("texture") + .unwrap_or_else(|| props.default_texture()); + let albedo = props.float("albedo").unwrap_or(0.5); + + let name = props.name(); + + Ok((name, Self::new(unsafe { &*(&*tex as *const _) }, albedo))) + } +} + +impl Load for Emit<'_, T> { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let tex = props + .texture("texture") + .unwrap_or_else(|| props.default_texture()); + let strength = props.float("strength").unwrap_or(1.5); + + let name = props.name(); + + Ok((name, Self::new(unsafe { &*(&*tex as *const _) }, strength))) + } +} + +impl Load for Reflect<'_, T> { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let tex = props + .texture("texture") + .unwrap_or_else(|| props.default_texture()); + let fuzz = props.float("fuzz").unwrap_or(0.1); + + let name = props.name(); + + Ok((name, Self::new(unsafe { &*(&*tex as *const _) }, fuzz))) + } +} + +impl Load for Refract<'_, T> { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let tex = props + .texture("texture") + .unwrap_or_else(|| props.default_texture()); + let eta = props.float("eta").unwrap_or(1.5); + + let name = props.name(); + + Ok((name, Self::new(unsafe { &*(&*tex as *const _) }, eta))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn lambertian() { + let mut region = Region::new().unwrap(); + let mut lookup = Lookup::new(); + let file = " +texture grey ( + type solid + colour 0.5 +) +material ground ( + type lambertian + texture grey + albedo 0.5 +)"; + let data = parser::from_str(file).unwrap(); + let textures = load_textures::(&data, &lookup).unwrap(); + { + //let lookup = Arc::get_mut(&mut lookup).unwrap(); + region_insert_with_lookup(&mut region, textures, |n, t| lookup.texture_insert(n, t)); + } + let _ = load_materials::>(&data, &lookup).unwrap(); + } +} diff --git a/frontend/loader/src/misc.rs b/frontend/loader/src/misc.rs new file mode 100644 index 0000000..552ba26 --- /dev/null +++ b/frontend/loader/src/misc.rs @@ -0,0 +1,30 @@ +use crate::Properties; +use crate::*; + +use implementations::*; + +impl Load for SimpleCamera { + fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + let origin = props.vec3("origin").unwrap_or(Vec3::new(3., 0., 0.)); + let lookat = props.vec3("lookat").unwrap_or(Vec3::zero()); + let vup = props.vec3("vup").unwrap_or(Vec3::new(0., 1., 0.)); + let fov = props.float("fov").unwrap_or(40.0); + let aperture = props.float("aperture").unwrap_or(0.0); + let focus = props.float("focus_dis").unwrap_or(10.0); + + let cam = Self::new(origin, lookat, vup, fov, 16.0 / 9.0, aperture, focus); + Ok((None, cam)) + } +} + +impl Load for Sky<'_, T> { + fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + let tex = props + .texture("texture") + .unwrap_or_else(|| props.default_texture()); + let res = props.vec2("sampler_res").unwrap_or(Vec2::new(100., 100.)); + + let sky = Self::new(unsafe { &*(&*tex as *const _) }, (res.x as _, res.y as _)); + Ok((None, sky)) + } +} diff --git a/frontend/loader/src/parser.rs b/frontend/loader/src/parser.rs new file mode 100644 index 0000000..fbff497 --- /dev/null +++ b/frontend/loader/src/parser.rs @@ -0,0 +1,191 @@ +use nom::Finish; +use rt_core::Float; +use std::collections::HashMap; +use thiserror::Error; + +/// What kind was parsed from the scene file. Variants match the initial keyword +/// used before the name of the object. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ObjectKind { + Camera, + Material, + Primitive, + Sky, + Texture, + Other, +} + +/// An unowned value for a key in the scene. This could be a collection of three +/// floats or a string referring to a filename or such. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ObjectValue<'a> { + Num1(Float), + Num2(Float, Float), + Num3(Float, Float, Float), + Text(&'a str), +} + +/// An unowned object in the scene format. Contains the type of the object and +/// an optional name if provided. Each key-value pair is stored in a `HashMap`. +#[derive(Debug, Clone)] +pub struct Object<'a> { + pub kind: ObjectKind, + pub name: Option<&'a str>, + pub values: HashMap<&'a str, ObjectValue<'a>>, +} + +impl ObjectKind { + pub fn is_camera(&self) -> bool { + matches!(self, ObjectKind::Camera) + } + + pub fn is_material(&self) -> bool { + matches!(self, ObjectKind::Material) + } + + pub fn is_primitive(&self) -> bool { + matches!(self, ObjectKind::Primitive) + } + + pub fn is_sky(&self) -> bool { + matches!(self, ObjectKind::Sky) + } + + pub fn is_texture(&self) -> bool { + matches!(self, ObjectKind::Texture) + } +} + +impl<'a> Object<'a> { + pub fn lookup(&self, key: &str) -> Option> { + self.values.get(key).cloned() + } +} + +impl<'a> Default for Object<'a> { + fn default() -> Self { + Self { + kind: ObjectKind::Other, + name: Option::default(), + values: HashMap::default(), + } + } +} + +/// Despite being listed as version 1, there is still more to be added +/// (probably). For example comments, and probably addition value types. +mod ver1 { + use super::{Object, ObjectKind, ObjectValue}; + + use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{ + alpha1, alphanumeric1, line_ending, multispace0, not_line_ending, space0, space1, + }, + combinator::{map, opt, recognize}, + error::{ParseError, VerboseError}, + multi::{many0, many0_count}, + number::complete::double, + sequence::{delimited, pair, preceded, terminated, tuple}, + IResult, + }; + use rt_core::Float; + use std::collections::HashMap; + + pub type Res = IResult>; + + pub fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( + inner: F, + ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> + where + F: Fn(&'a str) -> IResult<&'a str, O, E>, + { + delimited(multispace0, inner, multispace0) + } + + pub fn identifier(i: &str) -> Res<&str, &str> { + recognize(pair( + alt((alpha1, tag("_"))), + many0_count(alt((alphanumeric1, tag("_")))), + ))(i) + } + + pub fn value(i: &str) -> Res<&str, ObjectValue> { + let f = |i| preceded(space0, double)(i); + alt(( + map(tuple((f, f, f)), |(a, b, c)| { + ObjectValue::Num3(a as Float, b as Float, c as Float) + }), + map(tuple((f, f)), |(a, b)| { + ObjectValue::Num2(a as Float, b as Float) + }), + map(f, |a| ObjectValue::Num1(a as Float)), + map(preceded(space0, not_line_ending), ObjectValue::Text), + ))(i) + } + + pub fn keyvalue(i: &str) -> Res<&str, (&str, ObjectValue)> { + tuple((delimited(space0, identifier, space1), value))(i) + } + + pub fn values(i: &str) -> Res<&str, HashMap<&str, ObjectValue>> { + delimited( + ws(tag("(")), + map(many0(terminated(keyvalue, line_ending)), |vec| { + vec.into_iter().collect() + }), + ws(tag(")")), + )(i) + } + + pub fn objectkind(i: &str) -> Res<&str, ObjectKind> { + alt(( + map(tag("camera"), |_| ObjectKind::Camera), + map(tag("material"), |_| ObjectKind::Material), + map(tag("primitive"), |_| ObjectKind::Primitive), + map(tag("sky"), |_| ObjectKind::Sky), + map(tag("texture"), |_| ObjectKind::Texture), + ))(i) + } + + pub fn object(i: &str) -> Res<&str, Object> { + map( + tuple((objectkind, opt(preceded(space1, identifier)), values)), + |(kind, name, values)| Object { kind, name, values }, + )(i) + } + + pub fn parse(i: &str) -> Res<&str, Vec> { + many0(ws(object))(i) + } +} + +fn by_version(i: &str) -> ver1::Res<&str, Vec> { + use nom::{bytes::complete::tag, combinator::opt}; + + match opt(ver1::ws(tag("#ver1")))(i) { + Ok((o, Some(_))) => ver1::parse(o), + Ok((o, _)) => ver1::parse(o), + Err(e) => Err(e), + } +} + +/// Possible errors that can occur when parsing the scene file. +#[derive(Error, Debug)] +pub enum ParseError { + /// No idea what went wrong, ask Milo probably + #[error("no idea what wrong, parsing fucked up")] + ParsingError, +} + +pub fn from_str(src: &str) -> Result, ParseError> { + match by_version(src).finish() { + Ok((o, _)) if !o.is_empty() => Err(ParseError::ParsingError), + Ok((_, o)) => Ok(o), + Err(e) => { + eprintln!("Error parsing scene file: {e:#?}"); + Err(ParseError::ParsingError) + } + } +} diff --git a/frontend/loader/src/primitives.rs b/frontend/loader/src/primitives.rs new file mode 100644 index 0000000..e1d75b2 --- /dev/null +++ b/frontend/loader/src/primitives.rs @@ -0,0 +1,89 @@ +use crate::Properties; +use crate::*; +use implementations::sphere::Sphere; +use implementations::*; + +use rt_core::Scatter; + +impl Load for Sphere<'_, M> { + fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + let mat: region::RegionRes = props + .scatter("material") + .unwrap_or_else(|| props.default_scatter()); + let radius = props.float("radius").unwrap_or(1.0); + let centre = match props.vec3("centre") { + Some(c) => c, + None => { + return Err(LoadErr::MissingRequired( + "expected centre on sphere, found nothing".to_string(), + )) + } + }; + + Ok(( + None, + Self::new(centre, radius, unsafe { &*(&*mat as *const _) }), + )) + } +} + +impl Load for AllPrimitives<'_, M> { + fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + let kind = match props.text("type") { + Some(k) => k, + None => return Err(LoadErr::MissingRequiredVariantType), + }; + + Ok(match kind { + "sphere" => { + let x = Sphere::load(props)?; + (x.0, Self::Sphere(x.1)) + } + _ => todo!(), + /*o => { + return Err(LoadErr::MissingRequired(format!( + "required a known value for material type, found '{o}'" + ))) + }*/ + }) + } +} + +// TODO LOAD FOR TRIANGLE & MESH + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sphere() { + let mut region = Region::new().unwrap(); + let mut lookup = Lookup::new(); + let file = " +texture grey ( + type solid + colour 0.5 +) +material ground ( + type lambertian + texture grey + albedo 0.5 +) +primitive ( + type sphere + material ground + centre 0 -1000 0 + radius 1000 +)"; + let data = parser::from_str(file).unwrap(); + let textures = load_textures::(&data, &lookup).unwrap(); + + region_insert_with_lookup(&mut region, textures, |n, t| lookup.texture_insert(n, t)); + + let materials = load_materials::>(&data, &lookup).unwrap(); + + region_insert_with_lookup(&mut region, materials, |n, t| lookup.scatter_insert(n, t)); + + load_primitives::>>(&data, &lookup).unwrap(); + } +} diff --git a/frontend/loader/src/textures.rs b/frontend/loader/src/textures.rs new file mode 100644 index 0000000..adc3ae9 --- /dev/null +++ b/frontend/loader/src/textures.rs @@ -0,0 +1,116 @@ +use crate::*; + +use implementations::*; + +impl Load for AllTextures { + fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + let kind = match props.text("type") { + Some(k) => k, + None => return Err(LoadErr::MissingRequiredVariantType), + }; + + Ok(match kind { + "checkered" => { + let x = CheckeredTexture::load(props)?; + (x.0, Self::CheckeredTexture(x.1)) + } + "solid" => { + let x = SolidColour::load(props)?; + (x.0, Self::SolidColour(x.1)) + } + "image" => { + let x = ImageTexture::load(props)?; + (x.0, Self::ImageTexture(x.1)) + } + "lerp" => { + let x = Lerp::load(props)?; + (x.0, Self::Lerp(x.1)) + } + "perlin" => { + let x = Perlin::load(props)?; + (x.0, Self::Perlin(Box::new(x.1))) + } + o => { + return Err(LoadErr::MissingRequired(format!( + "required a known value for texture type, found '{o}'" + ))) + } + }) + } +} + +impl Load for CheckeredTexture { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let primary = props.vec3("primary").unwrap_or(Vec3::one()); + let secondary = props.vec3("secondary").unwrap_or(Vec3::zero()); + let name = props.name(); + Ok((name, Self::new(primary, secondary))) + } +} + +impl Load for ImageTexture { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let name = props.name(); + let filename = match props.text("filename") { + Some(f) => f, + None => return Err(LoadErr::MissingRequired("filename".to_string())), + }; + Ok((name, Self::new(&filename))) + } +} + +impl Load for Perlin { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let name = props.name(); + Ok((name, Self::new())) + } +} + +impl Load for Lerp { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let primary = props.vec3("primary").unwrap_or(Vec3::one()); + let secondary = props.vec3("secondary").unwrap_or(Vec3::zero()); + let name = props.name(); + Ok((name, Self::new(primary, secondary))) + } +} + +impl Load for SolidColour { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let colour = props.vec3("colour").unwrap_or(0.5 * Vec3::one()); + let name = props.name(); + Ok((name, Self::new(colour))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn coloured_texture() { + let lookup = Lookup::new(); + let thing = "texture grey ( + type solid + colour 0.5 +)"; + let a = parser::from_str(thing).unwrap(); + let props = Properties::new(&lookup, &a[0]); + let b = ::load(props).unwrap(); + println!("{b:?}"); + } + + #[test] + fn checkered_texture() { + let lookup = Lookup::new(); + let thing = "texture checkered ( + type checkered + primary 0.5 0.5 0.0 + secondary 0.0 +)"; + let a = parser::from_str(thing).unwrap(); + let props = Properties::new(&lookup, &a[0]); + let b = ::load(props).unwrap(); + println!("{b:?}"); + } +} diff --git a/frontend/region/Cargo.toml b/frontend/region/Cargo.toml new file mode 100644 index 0000000..c7f524e --- /dev/null +++ b/frontend/region/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "region" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[profile.release-with-debug] +inherits = "release" +debug = true + diff --git a/frontend/region/src/lib.rs b/frontend/region/src/lib.rs new file mode 100644 index 0000000..b5323b4 --- /dev/null +++ b/frontend/region/src/lib.rs @@ -0,0 +1,341 @@ +#![cfg_attr(not(test), no_std)] +#![feature(strict_provenance)] +#![feature(vec_into_raw_parts)] + +extern crate alloc; + +use alloc::alloc::handle_alloc_error; +use alloc::boxed::Box; +use core::alloc::Layout; +use core::cell::Cell; +use core::mem::{size_of, ManuallyDrop}; +use core::ptr::NonNull; + +pub mod wrappers; +pub use wrappers::*; + +const ALIGNMENT: usize = 32; +const BLOCK_SIZE: usize = 512 * ALIGNMENT; + +const DEFAULT_BLOCK_LAYOUT: Layout = + unsafe { Layout::from_size_align_unchecked(BLOCK_SIZE, ALIGNMENT) }; + +#[repr(C)] +struct Block { + pub head: Cell>, + pub bytes_left: usize, + pub next: Cell>>, + pub size: usize, +} + +fn align_up(current_pos: usize, alignment: usize) -> usize { + let modu = current_pos % alignment; + if modu == 0 { + 0 + } else { + alignment - modu + } +} + +impl Block { + pub fn new() -> Self { + let alloc_ptr = unsafe { alloc::alloc::alloc(DEFAULT_BLOCK_LAYOUT) }; + if alloc_ptr.is_null() { + handle_alloc_error(DEFAULT_BLOCK_LAYOUT); + } + + // unwrap should never fail due to the above + let head = unsafe { Cell::new(NonNull::new(alloc_ptr).unwrap_unchecked()) }; + + Block { + head, + bytes_left: BLOCK_SIZE, + next: Cell::new(None), + size: BLOCK_SIZE, + } + } + pub fn new_with_size(size: usize) -> Self { + let layout = Layout::from_size_align(size, ALIGNMENT) + .expect("size rounded up to the nearest align overflows!"); + + let alloc_ptr = unsafe { alloc::alloc::alloc(layout) }; + if alloc_ptr.is_null() { + handle_alloc_error(layout); + } + + // unwrap can never fail due to the above + let head = unsafe { Cell::new(NonNull::new(alloc_ptr).unwrap_unchecked()) }; + + Block { + head, + bytes_left: size, + next: Cell::new(None), + size, + } + } + pub fn new_block(&mut self, size: Option) -> NonNull { + if self.next.get().is_some() { + unreachable!() + } + + let block = Box::new(if let Some(size) = size { + Block::new_with_size(size) + } else { + Block::new() + }); + + let block = alloc::boxed::Box::::into_raw(block); + + // into_raw cannot return nullptr + let ptr = unsafe { NonNull::new(block.cast()).unwrap_unchecked() }; + self.next = Cell::new(Some(ptr)); + ptr + } + pub fn write_head(&self) -> *mut u8 { + self.head.get().as_ptr() + } + pub fn bytes_left(&self) -> usize { + self.bytes_left + } + pub fn offset_write_head(&mut self, offset: usize) { + *self.head.get_mut() = NonNull::new(unsafe { self.write_head().add(offset) }).unwrap(); + self.bytes_left -= offset; + } +} + +impl Drop for Block { + fn drop(&mut self) { + match self.next.get_mut() { + Some(block) => unsafe { + let _ = Box::from_raw(block.as_ptr()); + }, + None => {} + } + + let ptr = unsafe { self.write_head().sub(self.size - self.bytes_left) }; + let layout = Layout::from_size_align(self.size, ALIGNMENT).unwrap(); + unsafe { + alloc::alloc::dealloc(ptr, layout); + } + } +} + +#[repr(C)] +pub struct Region { + first: NonNull, + current: Cell>, +} + +unsafe impl Send for Region {} +unsafe impl Sync for Region {} + +impl Region { + pub fn new() -> ManuallyDrop { + let block = Box::::into_raw(Box::new(Block::new())); + + // into_raw cannot return nullptr + let first = unsafe { NonNull::new(block.cast()).unwrap_unchecked() }; + let current = Cell::new(first); + + ManuallyDrop::new(Self { first, current }) + } + #[inline(always)] + fn ptr_alloc(&mut self, data: T) -> *mut T { + self.generic_alloc( + data, + |data_ptr, data| unsafe { core::ptr::write(data_ptr, data) }, + size_of::(), + |block, data| Self::ptr_alloc(block, data), + ) + } + #[inline(always)] + fn ptr_slice_alloc(&mut self, data: &[T]) -> *mut [T] { + let ptr = self + .generic_alloc( + data, + |data_ptr, data| unsafe { + core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr as *mut T, data.len()) + }, + size_of::() * data.len(), + |block, data| Self::ptr_slice_alloc(block, data).cast(), + ) + .cast(); + + unsafe { core::slice::from_raw_parts_mut(ptr, data.len()) } + } + #[inline(always)] + fn generic_alloc *mut T>( + &mut self, + data: T, + write: F, + size: usize, + alloc: A, + ) -> *mut T { + let align = core::mem::align_of::(); + + let block = unsafe { self.current.get_mut().as_mut() }; + + // write head offset taking into consideration padding before cause of alignment + let write_offset = align_up(block.write_head().addr(), align); + let total_size = size + write_offset; + + if total_size > block.bytes_left() { + // check it is possible to fit data in block to avoid infinite allocation + if size > BLOCK_SIZE { + let ptr = block.new_block(Some(size)); + self.current = Cell::new(ptr); + + return alloc(self, data); + } + + // allocate new block in region + let ptr = block.new_block(None); + self.current = Cell::new(ptr); + return alloc(self, data); + } + + // offset ptr head to take into consideration alignment of allocated type + block.offset_write_head(write_offset); + + // get data ptr + let data_ptr = block.write_head() as *mut T; + + // write data + write(data_ptr, data); + + // offset write head since we have moved data to block + block.offset_write_head(size); + + data_ptr + } + pub fn alloc(&mut self, data: T) -> RegionUniq { + let data_ptr = self.ptr_alloc(data); + unsafe { RegionUniq(&mut *data_ptr) } + } + pub fn alloc_slice(&mut self, data: &[T]) -> RegionUniqSlice { + let data_ptr = self.ptr_slice_alloc(data); + unsafe { RegionUniqSlice(&mut *data_ptr) } + } +} + +impl Drop for Region { + fn drop(&mut self) { + unsafe { + let _ = Box::from_raw(self.first.as_ptr()); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create_region() { + let mut region = Region::new(); + unsafe { core::mem::ManuallyDrop::<_>::drop(&mut region) } + } + + #[test] + fn single_alloc() { + let mut region = Region::new(); + + #[derive(Debug, PartialEq, Clone)] + enum TestEnum { + Val1(u32), + Val2, + Val3(f64), + } + #[derive(Debug, PartialEq, Clone)] + struct TestStruct { + a: f64, + b: [u32; 5], + c: Vec, + } + let vec = vec![TestEnum::Val1(37), TestEnum::Val2, TestEnum::Val3(-1.3)]; + let (vec_ptr, len, cap) = vec.into_raw_parts(); + + let data = TestStruct { + a: 2.713, + b: [3, 7, 123, 43124, 0], + c: unsafe { Vec::from_raw_parts(vec_ptr, len, cap) }, + }; + let data = region.alloc(data); + let tester = TestStruct { + a: 2.713, + b: [3, 7, 123, 43124, 0], + c: unsafe { Vec::from_raw_parts(vec_ptr, len, cap) }, + }; + + assert_eq!(tester.c.as_ptr(), data.c.as_ptr()); + assert_eq!(tester, *data); + + unsafe { core::mem::ManuallyDrop::<_>::drop(&mut region) } + } + + #[test] + fn multi_alloc() { + let mut region = Region::new(); + let mut data = Vec::new(); + + const NUM_ALLOC: usize = 4; + + for i in 0..NUM_ALLOC { + data.push(*region.alloc([i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7])); + } + for (i, element) in data.into_iter().enumerate() { + assert_eq!( + [i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7], + element + ) + } + unsafe { core::mem::ManuallyDrop::<_>::drop(&mut region) } + } + + #[test] + fn multiple_blocks() { + let mut region = Region::new(); + let mut data = Vec::new(); + + const NUM_ALLOC: usize = BLOCK_SIZE * 10; + + for i in 0..NUM_ALLOC { + data.push(*region.alloc([i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7])); + } + for (i, element) in data.into_iter().enumerate() { + assert_eq!( + [i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7], + element + ) + } + unsafe { core::mem::ManuallyDrop::<_>::drop(&mut region) } + } + + #[test] + fn slice_allocation() { + let mut region = Region::new(); + let data: Vec = vec![1, 2, 3, 4, 5]; + + let a = region.alloc_slice(&data); + assert_eq!([1, 2, 3, 4, 5], *a); + unsafe { core::mem::ManuallyDrop::<_>::drop(&mut region) } + } + + #[test] + fn large_allocation() { + let mut region = Region::new(); + let data = [4u8; 100_000]; // investige overflow with larger values + let a = region.alloc(data); + assert_eq!(data, *a); + unsafe { core::mem::ManuallyDrop::<_>::drop(&mut region) } + } + + #[test] + fn large_slice_allocation() { + let mut region = Region::new(); + let data = [4u8; 400_000]; + let a = region.alloc_slice(&data); + assert_eq!(data, *a); + unsafe { core::mem::ManuallyDrop::<_>::drop(&mut region) } + } +} diff --git a/frontend/region/src/wrappers.rs b/frontend/region/src/wrappers.rs new file mode 100644 index 0000000..d20d9d6 --- /dev/null +++ b/frontend/region/src/wrappers.rs @@ -0,0 +1,93 @@ +use core::ops::{Deref, DerefMut}; +use core::ptr::NonNull; +pub struct RegionUniq<'a, T>(pub(crate) &'a mut T); + +impl<'a, T: Sync> RegionUniq<'a, T> { + pub fn shared(self) -> RegionRes { + // .as_mut_ptr() not in stable + unsafe { RegionRes(NonNull::new_unchecked(self.0 as *mut T)) } + } +} + +impl<'a, T> Deref for RegionUniq<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a, T> DerefMut for RegionUniq<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0 + } +} + +pub struct RegionRes(pub(crate) NonNull); + +unsafe impl Sync for RegionRes {} + +impl Deref for RegionRes { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.0.as_ptr() } + } +} + +// investige how this compares to #[derive(Clone)] +impl core::clone::Clone for RegionRes { + fn clone(&self) -> Self { + Self(self.0) + } +} + +pub struct RegionUniqSlice<'a, T>(pub(crate) &'a mut [T]); + +impl<'a, T: Sync> RegionUniqSlice<'a, T> { + pub fn shared(self) -> RegionResSlice { + unsafe { RegionResSlice(NonNull::new_unchecked(self.0.as_mut_ptr()), self.0.len()) } + } + pub fn zero_slice(&self) -> RegionResSlice { + unsafe { RegionResSlice(NonNull::new_unchecked(self.0.as_ptr() as *mut _), 0) } + } +} + +impl<'a, T> Deref for RegionUniqSlice<'a, T> { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a, T> DerefMut for RegionUniqSlice<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0 + } +} + +pub struct RegionResSlice(pub(crate) NonNull, pub(crate) usize); + +unsafe impl Sync for RegionResSlice {} + +impl Deref for RegionResSlice { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + unsafe { core::slice::from_raw_parts(self.0.as_ptr(), self.1) } + } +} + +impl DerefMut for RegionResSlice { + fn deref_mut(&mut self) -> &mut [T] { + unsafe { core::slice::from_raw_parts_mut(self.0.as_mut(), self.1) } + } +} + +// again investigate #[derive(Clone)] +impl core::clone::Clone for RegionResSlice { + fn clone(&self) -> Self { + Self(self.0, self.1) + } +} diff --git a/frontend/src/generate.rs b/frontend/src/generate.rs deleted file mode 100644 index 53cc665..0000000 --- a/frontend/src/generate.rs +++ /dev/null @@ -1,301 +0,0 @@ -use crate::{utility::create_bvh_with_info, *}; -use rand::{distributions::Alphanumeric, rngs::SmallRng, thread_rng, Rng, SeedableRng}; -use rand_seeder::Seeder; -use scene::implementations::{ - random_sampler::RandomSampler, rt_core::Float, split::SplitType, AllMaterials, AllPrimitives, - AllTextures, Bvh, Lambertian, -}; -use scene::*; - -type MaterialType = AllMaterials; -type PrimitiveType = AllPrimitives; -type BvhType = Bvh; -pub type SceneType = Scene; - -pub fn get_seed(length: usize) -> String { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - std::iter::repeat(()) - .map(|()| rng.sample(Alphanumeric)) - .map(char::from) - .take(length) - .collect() -} - -pub fn classic(bvh_type: SplitType, aspect_ratio: Float, seed: Option) -> SceneType { - let mut primitives = Vec::new(); - - let ground = sphere!(0, 0, -1000, 1000, &diffuse!(0.5, 0.5, 0.5, 0.5)); - - let sphere_one = sphere!(0, 0, 1, 1, &refract!(&solid_colour!(colour!(1)), 1.5)); - - let sphere_two = sphere!(-4, 0, 1, 1, &diffuse!(0.4, 0.2, 0.1, 0.5)); - - let sphere_three = sphere!(4, 0, 1, 1, &reflect!(&solid_colour!(0.7, 0.6, 0.5), 0)); - - primitives.push(ground); - primitives.push(sphere_one); - primitives.push(sphere_two); - primitives.push(sphere_three); - let seed = match seed { - Some(seed) => seed, - None => get_seed(32), - }; - - println!("\tseed: {seed}"); - let mut rng: SmallRng = Seeder::from(seed).make_rng(); - - for a in -11..11 { - for b in -11..11 { - let center = position!( - a as Float + 0.9 * rng.gen::(), - b as Float + 0.9 * rng.gen::(), - 0.2 - ); - - if (center - position!(4.0, 0.0, 0.2)).mag() > 0.9 { - let choose_material: Float = rng.gen(); - let colour = colour!(rng.gen::(), rng.gen::(), rng.gen::()); - - let sphere; - - if choose_material < 0.8 { - sphere = sphere!(center, 0.2, &diffuse!(&solid_colour!(colour), 0.5)); - } else if choose_material < 0.95 { - sphere = sphere!( - center, - 0.2, - &reflect!(&solid_colour!(colour), rng.gen::() / 2.0) - ); - } else { - sphere = sphere!(center, 0.2, &refract!(&solid_colour!(colour!(1)), 1.5)); - } - primitives.push(sphere); - } - } - } - - let sky = sky!(&texture_lerp!(colour!(0.5, 0.7, 1), colour!(1))); - - let camera = camera!( - position!(13, -3, 2), - position!(0, 0, 0), - position!(0, 0, 1), - 29, - aspect_ratio, - 0.1, - 10 - ); - - let bvh = create_bvh_with_info(primitives, bvh_type); - - scene!(camera, sky, random_sampler!(), bvh) -} - -pub fn bxdf_testing(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { - let mut primitives = Vec::new(); - - let testing_mat = &std::sync::Arc::new(implementations::AllMaterials::Lambertian( - Lambertian::new(&solid_colour!(0.93, 0.62, 0.54), 0.5), - )); - - primitives.extend(aarect!( - -500.0, - -500.0, - 500.0, - 500.0, - -40.0, - &axis!(Z), - &diffuse!(0.5, 0.5, 0.5, 0.5) - )); - - let glowy = sphere!( - 0, - -100.5, - 0, - 50, - &emit!(&solid_colour!(colour!(0, 1, 0)), 5.5) - ); - - let glowy_two = sphere!( - 0, - 0.0, - 300, - 50, - &emit!(&solid_colour!(colour!(1, 1, 1)), 10.5) - ); - - let materials = vec![(testing_mat.clone(), "default")]; - - primitives.extend(crate::load_model::load_model_with_materials( - "../res/dragon.obj", - &materials, - )); - - primitives.push(glowy); - primitives.push(glowy_two); - - let camera = camera!( - position!(700, -700, 700), - position!(0, 0, 0), - position!(0, 0, 1), - 34, - aspect_ratio, - 0, - 10 - ); - - let bvh = create_bvh_with_info(primitives, bvh_type); - - scene!( - camera, - sky!(&image!(&"../res/skymaps/lilienstein.webp".to_owned())), - random_sampler!(), - bvh - ) -} - -pub fn furnace(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { - let mut primitives = Vec::new(); - - let inner = &diffuse!(1, 1, 1, 0.9); - let emit = &emit!(&solid_colour!(colour!(1)), 1); - - primitives.push(sphere!(0, 0, 0, 0.5, inner)); - primitives.push(sphere!(0, 0, 0, 10, emit)); - - let sky = sky!(); - - let camera = camera!( - position!(3, 0, 0), - position!(0, 0, 0), - position!(0, 1, 0), - 40, - aspect_ratio, - 0, - 10 - ); - - let bvh = create_bvh_with_info(primitives, bvh_type); - scene!(camera, sky, random_sampler!(), bvh) -} - -pub fn overshadowed(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { - let mut primitives = Vec::new(); - - let ground = sphere!(0, -1000, 0, 1000, &diffuse!(0.5, 0.5, 0.5, 0.5)); - - let glowy = sphere!(0, 0.5, 0, 0.5, &emit!(&solid_colour!(colour!(1)), 1.5)); - - let cube = aacuboid!( - -0.5, - 0.1, - -0.5, - -0.4, - 0.2, - -0.4, - &diffuse!(0.5, 0.5, 0.5, 0.5) - ); - - primitives.push(ground); - primitives.push(glowy); - primitives.extend(cube); - - let camera = camera!( - position!(-5, 3, -3), - position!(0, 0.5, 0), - position!(0, 1, 0), - 34, - aspect_ratio, - 0, - 10 - ); - - let bvh = create_bvh_with_info(primitives, bvh_type); - - scene!(camera, sky!(), random_sampler!(), bvh) -} - -/*pub fn scene_six(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { - let mut primitives = Vec::new(); - - let ground = sphere!(0, -1000, 0, 1000, &diffuse!(0.5, 0.5, 0.5, 0.5)); - - let glowy = sphere!(5, 3.5, 5, 1.5, &emit!(&solid_colour!(colour!(1)), 5)); - - primitives.push(ground); - primitives.push(glowy); - primitives.extend(model!( - "res/dragon.obj", - &refract!(&solid_colour!(1, 1, 1), 1.52) - )); - - let camera = camera!( - position!(-20, 20, -25), - position!(0, 3.5, 0), - position!(0, 1, 0), - 34, - aspect_ratio, - 0, - 10 - ); - - let bvh = create_bvh_with_info(primitives, bvh_type); - - scene!(camera, sky!(), random_sampler!(), bvh) -}*/ - -pub fn cornell(bvh_type: SplitType, aspect_ratio: Float) -> SceneType { - let mut primitives = Vec::new(); - - let red = &diffuse!(0.65, 0.05, 0.05, 0.0); - let white = &diffuse!(0.73, 0.73, 0.73, 0.0); - let green = &diffuse!(0.12, 0.45, 0.15, 0.0); - let light = &emit!(&solid_colour!(colour!(1)), 15); - - primitives.extend(aarect!(0.0, 0.0, 555.0, 555.0, 555.0, &axis!(X), green)); - primitives.extend(aarect!(0.0, 0.0, 555.0, 555.0, 0.0, &axis!(X), red)); - - primitives.extend(aarect!(0.0, 0.0, 555.0, 555.0, 555.0, &axis!(Y), white)); - primitives.extend(aarect!(0.0, 0.0, 555.0, 555.0, 0.0, &axis!(Y), white)); - - primitives.extend(aarect!(0.0, 0.0, 555.0, 555.0, 555.0, &axis!(Z), white)); - primitives.extend(aarect!(213.0, 227.0, 343.0, 332.0, 554.0, &axis!(Y), light)); - - primitives.extend(cuboid!( - 265.0, - 0.0, - 295.0, - 430.0, - 330.0, - 460.0, - rotation!(0, 15, 0, D), - white - )); - - primitives.extend(cuboid!( - 130.0, - 0.0, - 65.0, - 295.0, - 165.0, - 230.0, - rotation!(0, -18, 0, D), - white - )); - - let sky = sky!(); - - let camera = camera!( - position!(278, 278, -800), - position!(278, 278, 0), - position!(0, 1, 0), - 40, - aspect_ratio, - 0, - 10 - ); - - let bvh = create_bvh_with_info(primitives, bvh_type); - - scene!(camera, sky, random_sampler!(), bvh) -} diff --git a/frontend/src/main.rs b/frontend/src/main.rs index c34a0e5..a9045c5 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,3 +1,7 @@ +use implementations::rt_core::*; + +use crate::utility::*; + #[cfg(feature = "gui")] use { gui::{Gui, RenderEvent}, @@ -17,18 +21,14 @@ use { winit::event_loop::EventLoopProxy, }; -//use scene::rt_core::{Float, SamplerProgress}; -use std::env; - #[cfg(feature = "gui")] mod gui; #[cfg(feature = "gui")] mod rendering; -//mod generate; //mod load_model; mod macros; -//mod parameters; +mod parameters; mod scene; mod utility; @@ -79,9 +79,7 @@ impl Data { } fn main() { - let _args: Vec = env::args().collect(); - - /*if let Some((scene, parameters)) = parameters::process_args(args) { + if let Some((scene, parameters)) = parameters::process_args() { let (render_options, filename) = (parameters.render_options, parameters.filename.clone()); if !parameters.gui { let start = print_render_start( @@ -106,14 +104,14 @@ fn main() { get_progress_output(sp.samples_completed, render_options.samples_per_pixel); }; - scene.generate_image_threaded(render_options, Some((&mut image, progress_bar_output))); + scene.render(render_options, Some((&mut image, progress_bar_output))); let output = ℑ let ray_count = output.rays_shot; print_final_statistics(start, ray_count, None); - line_break(); + println!("--------------------------------"); let output: Vec = output .current_image @@ -194,7 +192,7 @@ fn main() { let buffer = data.buffer.clone(); let to_sc = data.to_sc.clone(); - scene.generate_image_threaded( + scene.render( render_options, Some(( &mut data, @@ -208,7 +206,7 @@ fn main() { let samples = samples.load(Ordering::Relaxed); print_final_statistics(start, ray_count, Some(samples)); - line_break(); + println!("--------------------------------"); moved_render_canceled.store(false, Ordering::Relaxed); @@ -227,7 +225,7 @@ fn main() { let samples = samples.load(Ordering::Relaxed); print_final_statistics(start, ray_count, Some(samples)); - line_break(); + println!("--------------------------------"); save_file( filename, @@ -241,7 +239,7 @@ fn main() { #[cfg(not(feature = "gui"))] println!("feature: gui not enabled"); } - }*/ + } } #[cfg(feature = "gui")] diff --git a/frontend/src/parameters.rs b/frontend/src/parameters.rs index 1ae6260..8f1a472 100644 --- a/frontend/src/parameters.rs +++ b/frontend/src/parameters.rs @@ -1,10 +1,14 @@ -use crate::generate::SceneType; -use chrono::Local; -use scene::{ - implementations::split::SplitType, - rt_core::{Float, RenderMethod, RenderOptions}, -}; -use std::process; +use crate::scene::Scene; +use clap::Parser; + +use implementations::{rt_core::*, split::SplitType, *}; +use region::Region; + +type MaterialType<'a> = AllMaterials<'a, AllTextures>; +type PrimitiveType<'a> = AllPrimitives<'a, MaterialType<'a>>; +type BvhType<'a> = Bvh, MaterialType<'a>>; +pub type SceneType<'a> = + Scene, PrimitiveType<'a>, SimpleCamera, Sky<'a, AllTextures>, BvhType<'a>>; pub struct Parameters { pub render_options: RenderOptions, @@ -12,408 +16,59 @@ pub struct Parameters { pub filename: Option, } -impl Parameters { - pub fn new(render_options: RenderOptions, gui: bool, filename: Option) -> Self { - Self { - render_options, - gui, - filename, - } - } -} - -pub fn line_break() { - println!("--------------------------------"); -} - -macro_rules! scene { - ($scene_name:ident, $bvh_type:expr, $aspect_ratio:expr) => {{ - line_break(); - let time = Local::now(); - println!("{} - Scene Generation started", time.format("%X")); - crate::generate::$scene_name($bvh_type, $aspect_ratio) - }}; - ($scene_name:ident, $bvh_type:expr, $aspect_ratio:expr, $seed:expr) => {{ - line_break(); - let time = Local::now(); - println!("{} - Scene Generation started", time.format("%X")); - crate::generate::$scene_name($bvh_type, $aspect_ratio, $seed) - }}; -} - -pub fn process_args(args: Vec) -> Option<(SceneType, Parameters)> { - let mut scene_index = None; - let mut samples = None; - let mut width = None; - let mut height = None; - let mut filename = None; - let mut bvh_type = None; - let mut seed = None; - let mut render_method = None; - let mut gui = true; - - if args.len() == 1 { - println!("No arguments specified defaulting to help."); - display_help(); - process::exit(0); - } - - for arg_i in (0..(args.len() / 2)).map(|i| i * 2 + 1) { - if let Some(arg) = args.get(arg_i) { - match &arg[..] { - "-H" => { - display_help(); - process::exit(0); - } - "--help" => { - display_help(); - process::exit(0); - } - "-G" | "--gui" => match &args[arg_i + 1].to_lowercase()[..] { - "false" => { - gui = false; - } - "true" => {} - _ => { - println!("Invalid option {} for --gui", args[arg_i + 1]); - } - }, - "-L" | "--list" => { - get_list(); - } - "-I" | "--info" => { - get_info(&args, arg_i + 1); - } - "-S" | "--scene" => { - scene_index = Some(arg_i + 1); - } - "-N" | "--samples" => { - samples = Some(get_samples(&args, arg_i + 1)); - } - "-X" | "--width" => { - width = Some(get_dimension(&args, arg_i + 1)); - } - "-Y" | "--height" => { - height = Some(get_dimension(&args, arg_i + 1)); - } - "-B" | "--bvh" => { - bvh_type = Some(get_bvh_type(&args, arg_i + 1)); - } - "-O" | "--output" => { - filename = get_filename(&args, arg_i + 1); - } - "-R" | "--render_type" => { - render_method = get_render_method(&args, arg_i + 1); - } - "-J" | "--seed" => { - seed = Some(get_seed(&args, arg_i + 1)); - } - _ => {} - } - } - } - match scene_index { - Some(scene_index) => { - let mut render_options: RenderOptions = Default::default(); - render_options.samples_per_pixel = samples.unwrap_or(render_options.samples_per_pixel); - render_options.width = width.unwrap_or(render_options.width); - render_options.height = height.unwrap_or(render_options.height); - render_options.render_method = render_method.unwrap_or(render_options.render_method); - - let aspect_ratio = render_options.width as Float / render_options.height as Float; - let bvh_type = bvh_type.unwrap_or_default(); - let scene = get_scene(&args, scene_index, bvh_type, aspect_ratio, seed); - - let parameters = Parameters::new(render_options, gui, filename); - Some((scene, parameters)) - } - None => None, - } -} - -fn display_help() { - println!("Usage: cpu_raytracer [OPTION...]"); - println!("A headless CPU raytracer!\n"); - println!("Arguments:"); - println!("-H, --help"); - println!("\t Displays help."); - println!("-L, --list"); - println!("\t Lists all valid scenes."); - println!("-I [index], --info [index]"); - println!("\t Prints info for scene"); - println!("-S [index], --scene [index]"); - println!("\t Renders scene"); - println!("-N [samples], --samples [samples]"); - println!("\t Set samples per pixel. (Note 0 -> u64::MAX)"); - println!("-X [pixels], --width [pixels]"); - println!("\t Sets width of image"); - println!("-Y [pixels], --height [pixels]"); - println!("\t Sets height of image"); - println!("-B [split_type], --bvh [split_type]"); - println!("\t Sets split type for BVH."); - println!("\t supported split types: \"equal\", \"middle\""); - println!("-O [filename], --output [filename]"); - println!("\t Filename of output with supported file extension. If not specified no image will be output."); - println!("\t supported file extensions: \"png\", \"jpeg\""); - println!("-J [seed], --seed [seed]"); - println!("-G [true/false] --gui [true/false]"); - println!("\t Show render preview while rendering"); - println!("-R [render_method], --render_method [render_method]"); - println!("\t Possible options: mis, naive"); - println!("Seed for scene generation (if supported).") -} - -fn get_list() { - println!("-------------------"); - println!("1: Marbles"); - println!("-------------------"); - println!("Objects: 4-125"); - println!("Sky: Yes"); - println!("Motion Blur: Yes"); - println!("-------------------"); - println!("-------------------"); - println!("4: Overshadowed"); - println!("-------------------"); - println!("Objects: 3"); - println!("Sky: No"); - println!("Motion Blur: No"); - println!("-------------------"); - println!("-------------------"); - println!("6: Glass Dragon"); - println!("-------------------"); - println!("Objects: 3"); - println!("Sky: No"); - println!("Motion Blur: No"); - println!("-------------------"); - println!("-------------------"); - println!("8: Cornell Box"); - println!("-------------------"); - println!("Objects: 6"); - println!("Sky: No"); - println!("Motion Blur: No"); - println!("-------------------"); -} - -fn get_info(args: &[String], index: usize) { - match args.get(index) { - None => { - println!("Please specify a value for scene!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - Some(string) => match &string.to_ascii_lowercase()[..] { - "marbles" => { - println!("Marbles"); - println!("Objects: 4-125"); - println!("Sky: Yes"); - println!("Motion Blur: Yes"); - } - "overshadowed" => { - println!("Overshadowed"); - println!("Objects: 3"); - println!("Sky: No"); - println!("Motion Blur: No"); - } - "dragon" => { - println!("Glass Dragon"); - println!("Objects: 3"); - println!("Sky: No"); - println!("Motion Blur: No"); - } - "cornell" => { - println!("Cornell Box"); - println!("Objects: 6"); - println!("Sky: No"); - println!("Motion Blur: No"); - } - "furnace" => { - println!("Furnace Test"); - println!("Objects: 6"); - println!("Sky: No"); - println!("Motion Blur: No"); - } - _ => { - println!("{string} is not a valid scene index!"); - println!("Please specify a valid for scene!"); - println!("Do -L or--list to view scenes or do -H or --help for more information."); - process::exit(0); - } - }, - } -} - -fn get_scene( - args: &[String], - index: usize, +#[derive(Parser, Debug)] +#[command(about, long_about=None)] +#[command(name = "Pathtracer")] +#[command(about = "An expiremental pathtracer written in Rust")] +struct Cli { + #[arg(short, long, default_value_t = false)] + gui: bool, + #[arg(short, long, default_value_t = 128)] + samples: u64, + #[arg(short, long, default_value_t = 1920)] + width: u64, + #[arg(short, long, default_value_t = 1080)] + height: u64, + #[arg(short, long)] + filepath: String, + #[arg(short, long,value_enum, default_value_t = SplitType::Sah)] bvh_type: SplitType, - aspect_ratio: Float, - seed: Option, -) -> SceneType { - match args.get(index) { - None => { - println!("Please specify a value for scene!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - - Some(string) => match &string[..] { - "classic" => { - scene!(classic, bvh_type, aspect_ratio, seed) - } - "overshadowed" => { - scene!(overshadowed, bvh_type, aspect_ratio) - } - "cornell" => { - scene!(cornell, bvh_type, aspect_ratio) - } - "furnace" => { - scene!(furnace, bvh_type, aspect_ratio) - } - "bxdf_testing" => { - scene!(bxdf_testing, bvh_type, aspect_ratio) - } - _ => { - println!("{string} is not a valid scene index!"); - println!("Please specify a valid for scene!"); - println!("Do -L or--list to view scenes or do -H or --help for more information."); - process::exit(0); - } - }, - } -} - -fn get_seed(args: &[String], index: usize) -> String { - match args.get(index) { - Some(string) => string.to_string(), - None => { - println!("Please specify a value for seed!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - } -} - -fn get_render_method(args: &[String], index: usize) -> Option { - Some(match args.get(index) { - Some(string) => match &string.to_ascii_lowercase()[..] { - "naive" => RenderMethod::Naive, - "mis" => RenderMethod::MIS, - render_method => { - println!("Unsupported render method: {render_method}"); - println!("Do -H or --help for more information."); - process::exit(0); - } - }, - None => { - println!("Please specify a valid render method!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - }) -} - -fn get_filename(args: &[String], index: usize) -> Option { - Some(match args.get(index) { - Some(string) => { - let split_vec: Vec<&str> = string.split('.').collect(); - if split_vec.len() < 2 { - println!("Please specify a valid extension!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - - match split_vec[split_vec.len() - 1] { - "jpeg" => string.to_string(), - "png" => string.to_string(), - _ => { - println!( - "Unsupported file extension: {}", - split_vec[split_vec.len() - 1] - ); - println!("Do -H or --help for more information."); - process::exit(0); - } - } - } - None => { - println!("Please specify a valid filename!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - }) -} - -fn get_bvh_type(args: &[String], index: usize) -> SplitType { - match args.get(index) { - Some(string) => match &string.to_lowercase()[..] { - "equal" => SplitType::EqualCounts, - "middle" => SplitType::Middle, - "sah" => SplitType::Sah, - bvh_type => { - println!("{bvh_type} is not a valid value for BVH type!"); - println!("Please specify a valid value for BVH type!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - }, - None => { - println!("Please specify a value for BVH type!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - } -} - -fn get_samples(args: &[String], index: usize) -> u64 { - match args.get(index) { - Some(string) => match string.parse::() { - Ok(parsed) => match parsed { - 0 => { - println!("Samples must be non zero positive integer."); - println!("Please specify a valid value for height!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - _ => parsed, - }, - Err(_) => { - println!("{string} is not a valid value for samples!"); - println!("Please specify a valid value for height!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - }, - None => { - println!("Please specify a valid value for samples!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - } + #[arg(short, long,value_enum, default_value_t = RenderMethod::MIS)] + render_method: RenderMethod, + #[arg(short, long)] + output: Option, } -fn get_dimension(args: &[String], index: usize) -> u64 { - match args.get(index) { - Some(string) => match string.parse::() { - Ok(parsed) => match parsed { - 0 => { - println!("Height must be non zero positive integer."); - println!("Please specify a valid value for height!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - _ => parsed, - }, - Err(_) => { - println!("{string} is not a valid value for height!"); - println!("Please specify a valid value for height!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - }, - None => { - println!("Please specify a valid value for height!"); - println!("Do -H or --help for more information."); - process::exit(0); - } - } +pub fn process_args() -> Option<(SceneType<'static>, Parameters)> { + let cli = Cli::parse(); + + let mut region = Region::new(); + let (primitives, camera, sky) = match loader::load_file_full::< + AllTextures, + MaterialType, + PrimitiveType, + SimpleCamera, + Sky<'_, AllTextures>, + >(&mut region, &cli.filepath) + { + Ok(a) => a, + Err(e) => panic!("{e:?}"), + }; + + let bvh = Bvh::new(primitives, cli.bvh_type); + + let scene = Scene::new(bvh, camera, sky, region); + + let render_ops = RenderOptions { + width: cli.width, + height: cli.height, + samples_per_pixel: cli.samples, + render_method: cli.render_method, + }; + let params = Parameters { + render_options: render_ops, + gui: cli.gui, + filename: cli.output, + }; + Some((scene, params)) } diff --git a/frontend/src/scene.rs b/frontend/src/scene.rs index 4132da0..8d0bc40 100644 --- a/frontend/src/scene.rs +++ b/frontend/src/scene.rs @@ -1,232 +1,153 @@ +use implementations::random_sampler::RandomSampler; use implementations::rt_core::*; -use implementations::*; -use rand::Rng; -use rayon::prelude::*; - -use bumpalo::Bump; -use ouroboros::self_referencing; - -#[self_referencing] -pub struct SceneHolder { - arena: Bump, - #[borrows(arena)] - #[covariant] - scene: Scene<'this, S, A, N>, -} - -pub struct Scene<'a, S: Sampler, A: AccelerationStructure + Send + Sync, N: NoHit + Send + Sync> { - arena: &'a Bump, - camera: SimpleCamera, - _sampler: S, - core_scene: Option>, -} - -pub struct SceneData { - acceleration_structure: A, - sky: N, +use region::Region; +use std::mem::ManuallyDrop; + +pub struct Scene +where + M: Scatter, + P: Primitive, + C: Camera, + S: NoHit, + A: AccelerationStructure, +{ + acceleration: A, + camera: C, + sky: S, + region: ManuallyDrop, } -impl< - 'a, - S: Sampler + Send + Sync, - A: AccelerationStructure + Send + Sync, - N: NoHit + Send + Sync, - > Scene<'a, S, A, N> +impl Scene +where + M: Scatter, + P: Primitive, + C: Camera, + S: NoHit, + A: AccelerationStructure, { - pub fn render( - &self, - render_options: RenderOptions, - mut presentation_update: Option<(&mut T, F)>, - ) where - F: Fn(&mut T, &SamplerProgress, u64), - { - let channels = 3; - let pixel_num = render_options.width * render_options.height; - - let mut accumulator_buffers = ( - SamplerProgress::new(pixel_num, channels), - SamplerProgress::new(pixel_num, channels), - ); - - let pixel_chunk_size = 10000; - let chunk_size = pixel_chunk_size * channels; - - let camera = &self.camera; - let core_scene = self.core_scene.as_ref().unwrap(); - let (sky, acceleration_structure) = (&core_scene.sky, &core_scene.acceleration_structure); - - for i in 0..render_options.samples_per_pixel { - let (previous, current) = if i % 2 == 0 { - (&accumulator_buffers.0, &mut accumulator_buffers.1) - } else { - (&accumulator_buffers.1, &mut accumulator_buffers.0) - }; - - rayon::scope(|s| { - s.spawn(|_| { - current.rays_shot = current - .current_image - .par_chunks_mut(chunk_size as usize) - .enumerate() - .map(|(chunk_i, chunk)| { - let mut rng = rand::thread_rng(); - let mut rays_shot = 0; - for chunk_pixel_i in 0..(chunk.len() / 3) { - let pixel_i = - chunk_pixel_i as u64 + pixel_chunk_size * chunk_i as u64; - let x = pixel_i % render_options.width; - let y = (pixel_i - x) / render_options.width; - let u = (rng.gen_range(0.0..1.0) + x as Float) - / render_options.width as Float; - let v = 1.0 - - (rng.gen_range(0.0..1.0) + y as Float) - / render_options.height as Float; - - let mut ray = camera.get_ray(u, v); // remember to add le DOF - let result = match render_options.render_method { - RenderMethod::Naive => { - Ray::get_colour_naive(&mut ray, sky, acceleration_structure) - } - RenderMethod::MIS => { - Ray::get_colour(&mut ray, sky, acceleration_structure) - } - }; - - chunk[chunk_pixel_i * channels as usize] = result.0.x; - chunk[chunk_pixel_i * channels as usize + 1] = result.0.y; - chunk[chunk_pixel_i * channels as usize + 2] = result.0.z; - rays_shot += result.1; - } - rays_shot - }) - .sum(); - }); - }); - if i != 0 { - if let Some((ref mut data, f)) = presentation_update.as_mut() { - f(data, previous, i) - }; - } - } - - let (previous, _) = if render_options.samples_per_pixel % 2 == 0 { - (&accumulator_buffers.0, &mut accumulator_buffers.1) - } else { - (&accumulator_buffers.1, &mut accumulator_buffers.0) - }; - if let Some((ref mut data, f)) = presentation_update.as_mut() { - f(data, previous, render_options.samples_per_pixel) + pub fn new(acceleration: A, camera: C, sky: S, region: ManuallyDrop) -> Self { + Self { + acceleration, + camera, + sky, + region, } } + pub fn render( + &self, + opts: RenderOptions, + update: Option<(&mut T, impl Fn(&mut T, &SamplerProgress, u64))>, + ) { + let sampler = RandomSampler {}; + sampler.sample_image(opts, &self.camera, &self.sky, &self.acceleration, update); + } +} + +unsafe impl Send for Scene +where + M: Scatter, + P: Primitive, + C: Camera, + S: NoHit, + A: AccelerationStructure, +{ } #[cfg(test)] mod tests { use super::*; - use implementations::random_sampler::RandomSampler; - use implementations::sphere::Sphere; - use std::collections::HashMap; + use implementations::*; + use loader::load_str_full; + + const DATA: &str = "camera ( + origin -5 3 -3 + lookat 0 0.5 0 + vup 0 1 0 + fov 34.0 + aperture 0.0 + focus_dis 10.0 +) + +texture sky ( + type solid + colour 0.0 +) + +sky ( + texture sky +) + +texture grey ( + type solid + colour 0.5 +) + +texture white ( + type solid + colour 1.0 +) + +material ground ( + type lambertian + texture grey + albedo 0.5 +) + +material light ( + type emissive + texture white + strength 1.5 +) + +primitive ( + type sphere + material ground + centre 0 -1000 0 + radius 1000 +) + +primitive ( + type sphere + material light + centre 0 0.5 0 + radius 0.5 +) + +primitive ( + type sphere + material ground + centre -0.45 0.15 -0.45 + radius 0.05 +)"; #[test] - pub fn create_scene() { - type TextureType = AllTextures; - type MaterialType<'a> = AllMaterials<'a, TextureType>; - - let mut texture_search: HashMap<&str, &TextureType> = HashMap::new(); - let mut material_search: HashMap<&str, &MaterialType> = HashMap::new(); - - let mut scene = SceneHolderBuilder { - arena: Bump::new(), - scene_builder: |bump| Scene { - arena: bump, - core_scene: None, - camera: SimpleCamera::new( - Vec3::new(-5.0, -3.0, 3.0), - Vec3::new(0.0, 0.0, 0.5), - Vec3::new(0.0, 0.0, 1.0), - 34.0, - 16.0 / 9.0, - 0.0, - 10.0, - ), - _sampler: RandomSampler {}, + fn scene() { + let mut region = Region::new(); + type Tex = AllTextures; + type Mat<'a> = AllMaterials<'a, Tex>; + type Prim<'a> = AllPrimitives<'a, Mat<'a>>; + type SkyType<'a> = Sky<'a, Tex>; + let stuff = + load_str_full::(&mut region, DATA).unwrap(); + + let (p, camera, sky) = stuff; + let bvh: Bvh = Bvh::new(p, split::SplitType::Sah); + + let scene = Scene { + acceleration: bvh, + camera, + sky, + region, + }; + + scene.render::<()>( + RenderOptions { + samples_per_pixel: 1, + render_method: RenderMethod::MIS, + width: 1920, + height: 1080, }, - } - .build(); - - scene.with_mut(|scene| { - // add textures - texture_search.insert( - "Grey", - scene - .arena - .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::new( - 0.5, 0.5, 0.5, - )))), - ); - - texture_search.insert( - "White", - scene - .arena - .alloc(AllTextures::SolidColour(SolidColour::new(Vec3::one()))), - ); - - texture_search.insert( - "black white lerp", - scene - .arena - .alloc(AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one()))), - ); - - // add materials - material_search.insert( - "Light", - scene - .arena - .alloc(AllMaterials::Emit(Emit::new(texture_search["White"], 1.5))), - ); - - material_search.insert( - "Diffuse", - scene.arena.alloc(AllMaterials::Lambertian(Lambertian::new( - texture_search["Grey"], - 0.5, - ))), - ); - let mut primitives = bumpalo::collections::Vec::new_in(scene.arena); - - // add primitives - primitives.extend([ - AllPrimitives::Sphere(Sphere::new( - Vec3::new(0.0, 0.0, -1000.0), - 1000.0, - material_search["Diffuse"], - )), - AllPrimitives::Sphere(Sphere::new( - Vec3::new(0.0, 0.0, 0.5), - 0.5, - material_search["Light"], - )), - ]); - - // construct bvh - let bvh = Bvh::new(primitives, split::SplitType::Sah); - - scene.scene.core_scene = Some(SceneData { - acceleration_structure: bvh, - sky: Sky::new(texture_search["black white lerp"], (0, 0)), - }); - - scene.scene.render::<(), fn(&mut _, &_, _)>( - RenderOptions { - samples_per_pixel: 1, - render_method: RenderMethod::MIS, - width: 100, - height: 50, - }, - None, - ); - }); + None as Option<(&mut (), fn(&mut (), &SamplerProgress, u64))>, + ); } } diff --git a/implementations/Cargo.toml b/implementations/Cargo.toml index d2273f8..abd4fcd 100644 --- a/implementations/Cargo.toml +++ b/implementations/Cargo.toml @@ -13,7 +13,10 @@ rayon = "1.5.1" rt_core = { path = "../rt_core" } bumpalo = {version="3.12.0", features=["collections"]} num_cpus = "1.15" +region = { path = "../frontend/region"} statrs = "0.16.0" +clap = { version = "4.1.8", features = [ "derive" ] } + [dev-dependencies] diff --git a/implementations/src/acceleration/mod.rs b/implementations/src/acceleration/mod.rs index e6cd78e..3f4a549 100644 --- a/implementations/src/acceleration/mod.rs +++ b/implementations/src/acceleration/mod.rs @@ -4,6 +4,8 @@ use crate::{ utility::sort_by_indices, Axis, }; +use region::RegionResSlice; + use rt_core::*; use std::{collections::VecDeque, marker::PhantomData}; @@ -38,30 +40,29 @@ impl PrimitiveInfo { } } -pub struct Bvh<'a, P: Primitive, M: Scatter> { +pub struct Bvh { split_type: SplitType, nodes: Vec, - pub primitives: &'a [P], + pub primitives: RegionResSlice

, pub lights: Vec, phantom: PhantomData, } -unsafe impl<'a, P: Primitive + AABound + Send, M: Scatter + Send> Sync for Bvh<'a, P, M> {} +//unsafe impl<'a, P: Primitive + AABound + Send, M: Scatter + Send> Sync for Bvh<'a, P, M> {} -impl<'a, P, M> Bvh<'a, P, M> +impl Bvh where P: Primitive + AABound, M: Scatter, { - pub fn new(mut primitives: bumpalo::collections::Vec<'a, P>, split_type: SplitType) -> Self { + pub fn new(mut primitives: region::RegionUniqSlice<'_, P>, split_type: SplitType) -> Self { let mut bvh = Self { split_type, nodes: Vec::new(), - primitives: &[], + primitives: primitives.zero_slice(), lights: Vec::new(), phantom: PhantomData, }; - //let prims = let mut primitives_info: Vec = primitives .iter() .enumerate() @@ -81,7 +82,7 @@ where } } - bvh.primitives = primitives.into_bump_slice(); + bvh.primitives = primitives.shared(); //primitives.into_bump_slice(); bvh } @@ -181,7 +182,7 @@ where } } -impl<'a, P, M> AccelerationStructure for Bvh<'a, P, M> +impl AccelerationStructure for Bvh where P: Primitive, M: Scatter, diff --git a/implementations/src/acceleration/split.rs b/implementations/src/acceleration/split.rs index 2f67391..bde3802 100644 --- a/implementations/src/acceleration/split.rs +++ b/implementations/src/acceleration/split.rs @@ -1,4 +1,5 @@ use crate::{aabb::AABB, acceleration::PrimitiveInfo, Axis}; +use clap::ValueEnum; use rt_core::*; const NUM_BUCKETS: usize = 12; @@ -30,6 +31,7 @@ macro_rules! partition { }}; } +#[derive(Debug, ValueEnum, Copy, Clone)] pub enum SplitType { Sah, Middle, diff --git a/implementations/src/primitives/mod.rs b/implementations/src/primitives/mod.rs index 5665549..f9d7894 100644 --- a/implementations/src/primitives/mod.rs +++ b/implementations/src/primitives/mod.rs @@ -11,7 +11,7 @@ use rt_core::*; pub mod sphere; pub mod triangle; -#[derive(Primitive, Debug)] +#[derive(Primitive, Debug, Clone)] pub enum AllPrimitives<'a, M: Scatter> { Sphere(Sphere<'a, M>), Triangle(Triangle<'a, M>), diff --git a/implementations/src/primitives/sphere.rs b/implementations/src/primitives/sphere.rs index c78ff53..fb2008d 100644 --- a/implementations/src/primitives/sphere.rs +++ b/implementations/src/primitives/sphere.rs @@ -5,7 +5,7 @@ use crate::{ use rt_core::*; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Sphere<'a, M: Scatter> { pub center: Vec3, pub radius: Float, diff --git a/implementations/src/primitives/triangle.rs b/implementations/src/primitives/triangle.rs index 1861ced..58a14a2 100644 --- a/implementations/src/primitives/triangle.rs +++ b/implementations/src/primitives/triangle.rs @@ -27,7 +27,7 @@ where } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MeshTriangle<'a, M: Scatter> { pub point_indices: [usize; 3], pub normal_indices: [usize; 3], diff --git a/implementations/src/samplers/random_sampler.rs b/implementations/src/samplers/random_sampler.rs index cc536c2..1dfacdd 100644 --- a/implementations/src/samplers/random_sampler.rs +++ b/implementations/src/samplers/random_sampler.rs @@ -13,12 +13,12 @@ impl Sampler for RandomSampler { acceleration_structure: &A, mut presentation_update: Option<(&mut T, F)>, ) where - C: Camera + Send + Sync, - P: Primitive + Sync + Send + 'static, - M: Scatter + Send + Sync + 'static, + C: Camera, + P: Primitive, + M: Scatter, F: Fn(&mut T, &SamplerProgress, u64), - A: AccelerationStructure + Send + Sync, - S: NoHit + Send + Sync, + A: AccelerationStructure, + S: NoHit, { let channels = 3; let pixel_num = render_options.width * render_options.height; diff --git a/implementations/src/textures/mod.rs b/implementations/src/textures/mod.rs index e297854..3333599 100644 --- a/implementations/src/textures/mod.rs +++ b/implementations/src/textures/mod.rs @@ -6,7 +6,7 @@ use std::path::Path; const PERLIN_RVECS: usize = 256; -pub trait Texture { +pub trait Texture: Sync { fn colour_value(&self, _: Vec3, _: Vec3) -> Vec3 { Vec3::new(1.0, 1.0, 1.0) } diff --git a/rt_core/Cargo.toml b/rt_core/Cargo.toml index c523f84..67ddec5 100644 --- a/rt_core/Cargo.toml +++ b/rt_core/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" [dependencies] rand = { version = "0.8.3", features = [ "small_rng" ] } +clap = { version = "4.1.8", features = [ "derive" ] } + [features] f64 = [] \ No newline at end of file diff --git a/rt_core/src/acceleration.rs b/rt_core/src/acceleration.rs index f494b51..0da56b4 100644 --- a/rt_core/src/acceleration.rs +++ b/rt_core/src/acceleration.rs @@ -1,6 +1,6 @@ use crate::{Primitive, Ray, Scatter, SurfaceIntersection}; -pub trait AccelerationStructure { +pub trait AccelerationStructure: Sync { type Object: Primitive; type Material: Scatter; fn get_intersection_candidates(&self, ray: &Ray) -> Vec<(usize, usize)>; diff --git a/rt_core/src/material.rs b/rt_core/src/material.rs index c3819bf..4e2f68b 100644 --- a/rt_core/src/material.rs +++ b/rt_core/src/material.rs @@ -1,7 +1,7 @@ use crate::{Float, Hit, Ray, Vec3}; // wo (and ray.direction in scatter_ray) points towards the surface and wi away by convention -pub trait Scatter { +pub trait Scatter: Sync { fn scatter_ray(&self, _ray: &mut Ray, _hit: &Hit) -> bool { true } diff --git a/rt_core/src/primitive.rs b/rt_core/src/primitive.rs index 22db639..14a9414 100644 --- a/rt_core/src/primitive.rs +++ b/rt_core/src/primitive.rs @@ -41,7 +41,7 @@ where } } -pub trait Primitive { +pub trait Primitive: Sync { type Material: Scatter; fn get_int(&self, _: &Ray) -> Option>; diff --git a/rt_core/src/sampler.rs b/rt_core/src/sampler.rs index 84bbec4..2d18370 100644 --- a/rt_core/src/sampler.rs +++ b/rt_core/src/sampler.rs @@ -1,6 +1,7 @@ use crate::{AccelerationStructure, Float, Primitive, Ray, Scatter, Vec3}; +use clap::ValueEnum; -pub trait Sampler { +pub trait Sampler: Sync { fn sample_image( &self, _render_options: RenderOptions, @@ -9,12 +10,12 @@ pub trait Sampler { _acceleration_structure: &A, _update_function: Option<(&mut T, F)>, ) where - C: Camera + Send + Sync, - P: Primitive + Sync + Send + 'static, - M: Scatter + Send + Sync + 'static, + C: Camera, + P: Primitive, + M: Scatter, F: Fn(&mut T, &SamplerProgress, u64), - A: AccelerationStructure + Send + Sync, - S: NoHit + Send + Sync; + A: AccelerationStructure, + S: NoHit; } #[derive(Copy, Clone, Debug)] @@ -36,7 +37,7 @@ impl Default for RenderOptions { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, ValueEnum)] pub enum RenderMethod { Naive, MIS, @@ -58,11 +59,11 @@ impl SamplerProgress { } } -pub trait Camera { +pub trait Camera: Sync { fn get_ray(&self, u: Float, v: Float) -> Ray; } -pub trait NoHit { +pub trait NoHit: Sync { fn get_colour(&self, ray: &Ray) -> Vec3; fn pdf(&self, _: Vec3) -> Float { unimplemented!() diff --git a/scenes/scene.ssml b/scenes/scene.ssml new file mode 100644 index 0000000..e26c3d2 --- /dev/null +++ b/scenes/scene.ssml @@ -0,0 +1,60 @@ +camera ( + origin -5 3 -3 + lookat 0 0.5 0 + vup 0 1 0 + fov 34.0 + aperture 0.0 + focus_dis 10.0 +) + +texture sky ( + type solid + colour 0.0 +) + +sky ( + texture sky +) + +texture grey ( + type solid + colour 0.5 +) + +texture white ( + type solid + colour 1.0 +) + +material ground ( + type lambertian + texture grey + albedo 0.5 +) + +material light ( + type emissive + texture white + strength 1.5 +) + +primitive ( + type sphere + material ground + centre 0 -1000 0 + radius 1000 +) + +primitive ( + type sphere + material light + centre 0 0.5 0 + radius 0.5 +) + +primitive ( + type sphere + material ground + centre -0.45 0.15 -0.45 + radius 0.05 +) From 3eb6393471451ee8363c4e6db23b210ab833c1b6 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Tue, 14 Mar 2023 12:39:59 +1100 Subject: [PATCH 26/39] rearranged project structure --- frontend/Cargo.toml => Cargo.toml | 10 +- LICENSE | 674 ------------------ .../implementations}/Cargo.toml | 4 +- .../implementations}/proc/Cargo.toml | 0 .../implementations}/proc/src/lib.rs | 0 .../implementations}/src/acceleration/aabb.rs | 0 .../implementations}/src/acceleration/mod.rs | 0 .../src/acceleration/split.rs | 0 .../implementations/src/camera.rs | 1 + .../implementations}/src/lib.rs | 6 +- .../src/materials/emissive.rs | 0 .../src/materials/lambertian.rs | 0 .../implementations}/src/materials/mod.rs | 0 .../implementations}/src/materials/reflect.rs | 0 .../implementations}/src/materials/refract.rs | 0 .../src/materials/trowbridge_reitz.rs | 0 .../implementations}/src/primitives/mod.rs | 0 .../implementations}/src/primitives/sphere.rs | 0 .../src/primitives/triangle.rs | 0 .../implementations/src/samplers/mod.rs | 18 +- .../src/samplers/random_sampler.rs | 1 + .../implementations/src/sky.rs | 19 +- .../src/statistics/bxdfs/lambertian.rs | 0 .../src/statistics/bxdfs/mod.rs | 0 .../src/statistics/bxdfs/trowbridge_reitz.rs | 0 .../statistics/bxdfs/trowbridge_reitz_vndf.rs | 0 .../src/statistics/chi_squared.rs | 0 .../src/statistics/distributions.rs | 0 .../src/statistics/integrators.rs | 0 .../implementations}/src/statistics/mod.rs | 0 .../src/statistics/spherical_sampling.rs | 0 .../implementations}/src/textures/mod.rs | 0 .../implementations}/src/utility/coord.rs | 0 .../implementations}/src/utility/mod.rs | 0 .../implementations}/tests/sampling.rs | 0 {frontend => crates}/loader/Cargo.toml | 5 +- {frontend => crates}/loader/src/lib.rs | 6 +- {frontend => crates}/loader/src/materials.rs | 7 +- {frontend => crates}/loader/src/misc.rs | 0 {frontend => crates}/loader/src/parser.rs | 4 +- {frontend => crates}/loader/src/primitives.rs | 2 +- {frontend => crates}/loader/src/textures.rs | 0 crates/region/Cargo.toml | 6 + {frontend => crates}/region/src/lib.rs | 0 {frontend => crates}/region/src/wrappers.rs | 0 {rt_core => crates/rt_core}/Cargo.toml | 4 - .../rt_core}/src/acceleration.rs | 0 {rt_core => crates/rt_core}/src/lib.rs | 0 {rt_core => crates/rt_core}/src/material.rs | 0 {rt_core => crates/rt_core}/src/primitive.rs | 0 {rt_core => crates/rt_core}/src/ray.rs | 0 crates/rt_core/src/sampler.rs | 14 + {rt_core => crates/rt_core}/src/vec.rs | 0 download_res.sh | 2 - frontend/region/Cargo.toml | 13 - {frontend/src => src}/gui.rs | 0 {frontend/src => src}/load_model.rs | 0 {frontend/src => src}/macros.rs | 0 {frontend/src => src}/main.rs | 1 + {frontend/src => src}/parameters.rs | 2 +- {frontend/src => src}/rendering.rs | 0 {frontend/src => src}/scene.rs | 13 +- {frontend/src => src}/utility.rs | 0 63 files changed, 65 insertions(+), 747 deletions(-) rename frontend/Cargo.toml => Cargo.toml (79%) delete mode 100644 LICENSE rename {implementations => crates/implementations}/Cargo.toml (90%) rename {implementations => crates/implementations}/proc/Cargo.toml (100%) rename {implementations => crates/implementations}/proc/src/lib.rs (100%) rename {implementations => crates/implementations}/src/acceleration/aabb.rs (100%) rename {implementations => crates/implementations}/src/acceleration/mod.rs (100%) rename {implementations => crates/implementations}/src/acceleration/split.rs (100%) rename implementations/src/cameras/mod.rs => crates/implementations/src/camera.rs (98%) rename {implementations => crates/implementations}/src/lib.rs (85%) rename {implementations => crates/implementations}/src/materials/emissive.rs (100%) rename {implementations => crates/implementations}/src/materials/lambertian.rs (100%) rename {implementations => crates/implementations}/src/materials/mod.rs (100%) rename {implementations => crates/implementations}/src/materials/reflect.rs (100%) rename {implementations => crates/implementations}/src/materials/refract.rs (100%) rename {implementations => crates/implementations}/src/materials/trowbridge_reitz.rs (100%) rename {implementations => crates/implementations}/src/primitives/mod.rs (100%) rename {implementations => crates/implementations}/src/primitives/sphere.rs (100%) rename {implementations => crates/implementations}/src/primitives/triangle.rs (100%) rename rt_core/src/sampler.rs => crates/implementations/src/samplers/mod.rs (80%) rename {implementations => crates/implementations}/src/samplers/random_sampler.rs (99%) rename implementations/src/samplers/mod.rs => crates/implementations/src/sky.rs (86%) rename {implementations => crates/implementations}/src/statistics/bxdfs/lambertian.rs (100%) rename {implementations => crates/implementations}/src/statistics/bxdfs/mod.rs (100%) rename {implementations => crates/implementations}/src/statistics/bxdfs/trowbridge_reitz.rs (100%) rename {implementations => crates/implementations}/src/statistics/bxdfs/trowbridge_reitz_vndf.rs (100%) rename {implementations => crates/implementations}/src/statistics/chi_squared.rs (100%) rename {implementations => crates/implementations}/src/statistics/distributions.rs (100%) rename {implementations => crates/implementations}/src/statistics/integrators.rs (100%) rename {implementations => crates/implementations}/src/statistics/mod.rs (100%) rename {implementations => crates/implementations}/src/statistics/spherical_sampling.rs (100%) rename {implementations => crates/implementations}/src/textures/mod.rs (100%) rename {implementations => crates/implementations}/src/utility/coord.rs (100%) rename {implementations => crates/implementations}/src/utility/mod.rs (100%) rename {implementations => crates/implementations}/tests/sampling.rs (100%) rename {frontend => crates}/loader/Cargo.toml (54%) rename {frontend => crates}/loader/src/lib.rs (98%) rename {frontend => crates}/loader/src/materials.rs (93%) rename {frontend => crates}/loader/src/misc.rs (100%) rename {frontend => crates}/loader/src/parser.rs (98%) rename {frontend => crates}/loader/src/primitives.rs (97%) rename {frontend => crates}/loader/src/textures.rs (100%) create mode 100644 crates/region/Cargo.toml rename {frontend => crates}/region/src/lib.rs (100%) rename {frontend => crates}/region/src/wrappers.rs (100%) rename {rt_core => crates/rt_core}/Cargo.toml (50%) rename {rt_core => crates/rt_core}/src/acceleration.rs (100%) rename {rt_core => crates/rt_core}/src/lib.rs (100%) rename {rt_core => crates/rt_core}/src/material.rs (100%) rename {rt_core => crates/rt_core}/src/primitive.rs (100%) rename {rt_core => crates/rt_core}/src/ray.rs (100%) create mode 100644 crates/rt_core/src/sampler.rs rename {rt_core => crates/rt_core}/src/vec.rs (100%) delete mode 100755 download_res.sh delete mode 100644 frontend/region/Cargo.toml rename {frontend/src => src}/gui.rs (100%) rename {frontend/src => src}/load_model.rs (100%) rename {frontend/src => src}/macros.rs (100%) rename {frontend/src => src}/main.rs (99%) rename {frontend/src => src}/parameters.rs (97%) rename {frontend/src => src}/rendering.rs (100%) rename {frontend/src => src}/scene.rs (94%) rename {frontend/src => src}/utility.rs (100%) diff --git a/frontend/Cargo.toml b/Cargo.toml similarity index 79% rename from frontend/Cargo.toml rename to Cargo.toml index f40750f..2b8bae9 100644 --- a/frontend/Cargo.toml +++ b/Cargo.toml @@ -4,23 +4,23 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = ["crates/*"] [dependencies] -bumpalo = {version="3.12.0", features=["collections"]} chrono = "0.4.19" image = "0.23.14" rayon = "1.5.1" rand = { version = "0.8.3", features = [ "small_rng" ] } rand_seeder = "0.2.2" -implementations = { path = "../implementations" } -ouroboros = "0.15.5" +implementations = { path = "./crates/implementations" } vulkano = { version = "0.28.0", optional = true } vulkano-shaders = { version = "0.28.0", optional = true } vulkano-win = { version = "0.28.0", optional = true } wavefront_obj = "10.0.0" winit = { version = "0.26.1", optional = true } -region = { path = "./region" } -loader = { path = "./loader" } +region = { path = "./crates/region" } +loader = { path = "./crates/loader" } simple_logger = "4.0" clap = { version = "4.1.8", features = ["derive", "wrap_help"] } diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/implementations/Cargo.toml b/crates/implementations/Cargo.toml similarity index 90% rename from implementations/Cargo.toml rename to crates/implementations/Cargo.toml index abd4fcd..d36d01a 100644 --- a/implementations/Cargo.toml +++ b/crates/implementations/Cargo.toml @@ -13,7 +13,7 @@ rayon = "1.5.1" rt_core = { path = "../rt_core" } bumpalo = {version="3.12.0", features=["collections"]} num_cpus = "1.15" -region = { path = "../frontend/region"} +region = { path = "../region"} statrs = "0.16.0" clap = { version = "4.1.8", features = [ "derive" ] } @@ -24,4 +24,4 @@ chrono = "0.4.19" statrs = "0.16.0" [features] -f64 = ["rt_core/f64"] \ No newline at end of file +f64 = ["rt_core/f64"] diff --git a/implementations/proc/Cargo.toml b/crates/implementations/proc/Cargo.toml similarity index 100% rename from implementations/proc/Cargo.toml rename to crates/implementations/proc/Cargo.toml diff --git a/implementations/proc/src/lib.rs b/crates/implementations/proc/src/lib.rs similarity index 100% rename from implementations/proc/src/lib.rs rename to crates/implementations/proc/src/lib.rs diff --git a/implementations/src/acceleration/aabb.rs b/crates/implementations/src/acceleration/aabb.rs similarity index 100% rename from implementations/src/acceleration/aabb.rs rename to crates/implementations/src/acceleration/aabb.rs diff --git a/implementations/src/acceleration/mod.rs b/crates/implementations/src/acceleration/mod.rs similarity index 100% rename from implementations/src/acceleration/mod.rs rename to crates/implementations/src/acceleration/mod.rs diff --git a/implementations/src/acceleration/split.rs b/crates/implementations/src/acceleration/split.rs similarity index 100% rename from implementations/src/acceleration/split.rs rename to crates/implementations/src/acceleration/split.rs diff --git a/implementations/src/cameras/mod.rs b/crates/implementations/src/camera.rs similarity index 98% rename from implementations/src/cameras/mod.rs rename to crates/implementations/src/camera.rs index 2dcf074..b9f31e7 100644 --- a/implementations/src/cameras/mod.rs +++ b/crates/implementations/src/camera.rs @@ -1,4 +1,5 @@ use crate::utility::random_float; +use crate::Camera; use rt_core::*; #[derive(Debug)] diff --git a/implementations/src/lib.rs b/crates/implementations/src/lib.rs similarity index 85% rename from implementations/src/lib.rs rename to crates/implementations/src/lib.rs index 5e83978..02202d8 100644 --- a/implementations/src/lib.rs +++ b/crates/implementations/src/lib.rs @@ -1,18 +1,20 @@ mod acceleration; -mod cameras; +mod camera; mod materials; mod primitives; mod samplers; +mod sky; mod statistics; mod textures; mod utility; pub use acceleration::*; -pub use cameras::*; +pub use camera::*; pub use materials::*; pub use primitives::*; pub use proc::*; pub use samplers::*; +pub use sky::*; pub use statistics::*; pub use textures::*; pub use utility::*; diff --git a/implementations/src/materials/emissive.rs b/crates/implementations/src/materials/emissive.rs similarity index 100% rename from implementations/src/materials/emissive.rs rename to crates/implementations/src/materials/emissive.rs diff --git a/implementations/src/materials/lambertian.rs b/crates/implementations/src/materials/lambertian.rs similarity index 100% rename from implementations/src/materials/lambertian.rs rename to crates/implementations/src/materials/lambertian.rs diff --git a/implementations/src/materials/mod.rs b/crates/implementations/src/materials/mod.rs similarity index 100% rename from implementations/src/materials/mod.rs rename to crates/implementations/src/materials/mod.rs diff --git a/implementations/src/materials/reflect.rs b/crates/implementations/src/materials/reflect.rs similarity index 100% rename from implementations/src/materials/reflect.rs rename to crates/implementations/src/materials/reflect.rs diff --git a/implementations/src/materials/refract.rs b/crates/implementations/src/materials/refract.rs similarity index 100% rename from implementations/src/materials/refract.rs rename to crates/implementations/src/materials/refract.rs diff --git a/implementations/src/materials/trowbridge_reitz.rs b/crates/implementations/src/materials/trowbridge_reitz.rs similarity index 100% rename from implementations/src/materials/trowbridge_reitz.rs rename to crates/implementations/src/materials/trowbridge_reitz.rs diff --git a/implementations/src/primitives/mod.rs b/crates/implementations/src/primitives/mod.rs similarity index 100% rename from implementations/src/primitives/mod.rs rename to crates/implementations/src/primitives/mod.rs diff --git a/implementations/src/primitives/sphere.rs b/crates/implementations/src/primitives/sphere.rs similarity index 100% rename from implementations/src/primitives/sphere.rs rename to crates/implementations/src/primitives/sphere.rs diff --git a/implementations/src/primitives/triangle.rs b/crates/implementations/src/primitives/triangle.rs similarity index 100% rename from implementations/src/primitives/triangle.rs rename to crates/implementations/src/primitives/triangle.rs diff --git a/rt_core/src/sampler.rs b/crates/implementations/src/samplers/mod.rs similarity index 80% rename from rt_core/src/sampler.rs rename to crates/implementations/src/samplers/mod.rs index 2d18370..ebe93ee 100644 --- a/rt_core/src/sampler.rs +++ b/crates/implementations/src/samplers/mod.rs @@ -1,4 +1,7 @@ -use crate::{AccelerationStructure, Float, Primitive, Ray, Scatter, Vec3}; +use rt_core::*; + +pub mod random_sampler; + use clap::ValueEnum; pub trait Sampler: Sync { @@ -62,16 +65,3 @@ impl SamplerProgress { pub trait Camera: Sync { fn get_ray(&self, u: Float, v: Float) -> Ray; } - -pub trait NoHit: Sync { - fn get_colour(&self, ray: &Ray) -> Vec3; - fn pdf(&self, _: Vec3) -> Float { - unimplemented!() - } - fn can_sample(&self) -> bool { - false - } - fn sample(&self) -> Vec3 { - unimplemented!() - } -} diff --git a/implementations/src/samplers/random_sampler.rs b/crates/implementations/src/samplers/random_sampler.rs similarity index 99% rename from implementations/src/samplers/random_sampler.rs rename to crates/implementations/src/samplers/random_sampler.rs index 1dfacdd..ffcbf9d 100644 --- a/implementations/src/samplers/random_sampler.rs +++ b/crates/implementations/src/samplers/random_sampler.rs @@ -1,3 +1,4 @@ +use crate::*; use rand::Rng; use rayon::prelude::*; use rt_core::*; diff --git a/implementations/src/samplers/mod.rs b/crates/implementations/src/sky.rs similarity index 86% rename from implementations/src/samplers/mod.rs rename to crates/implementations/src/sky.rs index e717aed..c3b2225 100644 --- a/implementations/src/samplers/mod.rs +++ b/crates/implementations/src/sky.rs @@ -1,9 +1,13 @@ -use crate::statistics::distributions::*; -use crate::{generate_values, next_float, random_float, textures::Texture}; -use rand::{rngs::SmallRng, thread_rng, SeedableRng}; +use crate::distributions::Distribution2D; +use crate::generate_values; +use crate::next_float; +use crate::random_float; +use rand::rngs::SmallRng; +use rand::thread_rng; +use rand::SeedableRng; use rt_core::*; -pub mod random_sampler; +use crate::Texture; #[derive(Debug)] pub struct Sky<'a, T: Texture> { @@ -74,10 +78,11 @@ impl<'a, T: Texture> NoHit for Sky<'a, T> { #[cfg(test)] mod tests { - use crate::statistics::spherical_sampling::test_spherical_pdf; - use crate::*; + use super::*; + use crate::spherical_sampling::test_spherical_pdf; + use crate::AllTextures; + use crate::Lerp; use rand::rngs::ThreadRng; - use rt_core::*; #[test] fn sky_sampling() { diff --git a/implementations/src/statistics/bxdfs/lambertian.rs b/crates/implementations/src/statistics/bxdfs/lambertian.rs similarity index 100% rename from implementations/src/statistics/bxdfs/lambertian.rs rename to crates/implementations/src/statistics/bxdfs/lambertian.rs diff --git a/implementations/src/statistics/bxdfs/mod.rs b/crates/implementations/src/statistics/bxdfs/mod.rs similarity index 100% rename from implementations/src/statistics/bxdfs/mod.rs rename to crates/implementations/src/statistics/bxdfs/mod.rs diff --git a/implementations/src/statistics/bxdfs/trowbridge_reitz.rs b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs similarity index 100% rename from implementations/src/statistics/bxdfs/trowbridge_reitz.rs rename to crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs diff --git a/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs similarity index 100% rename from implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs rename to crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs diff --git a/implementations/src/statistics/chi_squared.rs b/crates/implementations/src/statistics/chi_squared.rs similarity index 100% rename from implementations/src/statistics/chi_squared.rs rename to crates/implementations/src/statistics/chi_squared.rs diff --git a/implementations/src/statistics/distributions.rs b/crates/implementations/src/statistics/distributions.rs similarity index 100% rename from implementations/src/statistics/distributions.rs rename to crates/implementations/src/statistics/distributions.rs diff --git a/implementations/src/statistics/integrators.rs b/crates/implementations/src/statistics/integrators.rs similarity index 100% rename from implementations/src/statistics/integrators.rs rename to crates/implementations/src/statistics/integrators.rs diff --git a/implementations/src/statistics/mod.rs b/crates/implementations/src/statistics/mod.rs similarity index 100% rename from implementations/src/statistics/mod.rs rename to crates/implementations/src/statistics/mod.rs diff --git a/implementations/src/statistics/spherical_sampling.rs b/crates/implementations/src/statistics/spherical_sampling.rs similarity index 100% rename from implementations/src/statistics/spherical_sampling.rs rename to crates/implementations/src/statistics/spherical_sampling.rs diff --git a/implementations/src/textures/mod.rs b/crates/implementations/src/textures/mod.rs similarity index 100% rename from implementations/src/textures/mod.rs rename to crates/implementations/src/textures/mod.rs diff --git a/implementations/src/utility/coord.rs b/crates/implementations/src/utility/coord.rs similarity index 100% rename from implementations/src/utility/coord.rs rename to crates/implementations/src/utility/coord.rs diff --git a/implementations/src/utility/mod.rs b/crates/implementations/src/utility/mod.rs similarity index 100% rename from implementations/src/utility/mod.rs rename to crates/implementations/src/utility/mod.rs diff --git a/implementations/tests/sampling.rs b/crates/implementations/tests/sampling.rs similarity index 100% rename from implementations/tests/sampling.rs rename to crates/implementations/tests/sampling.rs diff --git a/frontend/loader/Cargo.toml b/crates/loader/Cargo.toml similarity index 54% rename from frontend/loader/Cargo.toml rename to crates/loader/Cargo.toml index 9df53e1..c22b6f0 100644 --- a/frontend/loader/Cargo.toml +++ b/crates/loader/Cargo.toml @@ -7,9 +7,8 @@ edition = "2021" log = "0.4" nom = "7" region = { path = "../region" } -rt_core = { path = "../../rt_core" } -implementations = { path = "../../implementations" } +implementations = { path = "../implementations" } thiserror = "1.0" [features] -f64 = ["rt_core/f64", "implementations/f64"] +f64 = ["implementations/f64"] diff --git a/frontend/loader/src/lib.rs b/crates/loader/src/lib.rs similarity index 98% rename from frontend/loader/src/lib.rs rename to crates/loader/src/lib.rs index 7cff30f..e51d077 100644 --- a/frontend/loader/src/lib.rs +++ b/crates/loader/src/lib.rs @@ -4,9 +4,9 @@ pub mod parser; pub mod primitives; pub mod textures; -use implementations::Texture; +use implementations::rt_core::{Float, NoHit, Primitive, Scatter, Vec2, Vec3}; +use implementations::{Camera, Texture}; use region::{Region, RegionRes, RegionUniqSlice}; -use rt_core::{Camera, Float, NoHit, Primitive, Scatter, Vec2, Vec3}; use std::{collections::HashMap, fmt}; use thiserror::Error; @@ -457,7 +457,7 @@ primitive ( #[test] fn scene() { - let mut region = Region::new().unwrap(); + let mut region = Region::new(); type Tex = AllTextures; type Mat<'a> = AllMaterials<'a, Tex>; type Prim<'a> = AllPrimitives<'a, Mat<'a>>; diff --git a/frontend/loader/src/materials.rs b/crates/loader/src/materials.rs similarity index 93% rename from frontend/loader/src/materials.rs rename to crates/loader/src/materials.rs index 7154bd8..88626b8 100644 --- a/frontend/loader/src/materials.rs +++ b/crates/loader/src/materials.rs @@ -94,7 +94,7 @@ mod tests { #[test] fn lambertian() { - let mut region = Region::new().unwrap(); + let mut region = Region::new(); let mut lookup = Lookup::new(); let file = " texture grey ( @@ -108,10 +108,7 @@ material ground ( )"; let data = parser::from_str(file).unwrap(); let textures = load_textures::(&data, &lookup).unwrap(); - { - //let lookup = Arc::get_mut(&mut lookup).unwrap(); - region_insert_with_lookup(&mut region, textures, |n, t| lookup.texture_insert(n, t)); - } + region_insert_with_lookup(&mut region, textures, |n, t| lookup.texture_insert(n, t)); let _ = load_materials::>(&data, &lookup).unwrap(); } } diff --git a/frontend/loader/src/misc.rs b/crates/loader/src/misc.rs similarity index 100% rename from frontend/loader/src/misc.rs rename to crates/loader/src/misc.rs diff --git a/frontend/loader/src/parser.rs b/crates/loader/src/parser.rs similarity index 98% rename from frontend/loader/src/parser.rs rename to crates/loader/src/parser.rs index fbff497..f11d496 100644 --- a/frontend/loader/src/parser.rs +++ b/crates/loader/src/parser.rs @@ -1,5 +1,5 @@ +use implementations::rt_core::Float; use nom::Finish; -use rt_core::Float; use std::collections::HashMap; use thiserror::Error; @@ -77,6 +77,7 @@ impl<'a> Default for Object<'a> { mod ver1 { use super::{Object, ObjectKind, ObjectValue}; + use implementations::rt_core::Float; use nom::{ branch::alt, bytes::complete::tag, @@ -90,7 +91,6 @@ mod ver1 { sequence::{delimited, pair, preceded, terminated, tuple}, IResult, }; - use rt_core::Float; use std::collections::HashMap; pub type Res = IResult>; diff --git a/frontend/loader/src/primitives.rs b/crates/loader/src/primitives.rs similarity index 97% rename from frontend/loader/src/primitives.rs rename to crates/loader/src/primitives.rs index e1d75b2..d131baa 100644 --- a/frontend/loader/src/primitives.rs +++ b/crates/loader/src/primitives.rs @@ -57,7 +57,7 @@ mod tests { #[test] fn sphere() { - let mut region = Region::new().unwrap(); + let mut region = Region::new(); let mut lookup = Lookup::new(); let file = " texture grey ( diff --git a/frontend/loader/src/textures.rs b/crates/loader/src/textures.rs similarity index 100% rename from frontend/loader/src/textures.rs rename to crates/loader/src/textures.rs diff --git a/crates/region/Cargo.toml b/crates/region/Cargo.toml new file mode 100644 index 0000000..9de23a8 --- /dev/null +++ b/crates/region/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "region" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/frontend/region/src/lib.rs b/crates/region/src/lib.rs similarity index 100% rename from frontend/region/src/lib.rs rename to crates/region/src/lib.rs diff --git a/frontend/region/src/wrappers.rs b/crates/region/src/wrappers.rs similarity index 100% rename from frontend/region/src/wrappers.rs rename to crates/region/src/wrappers.rs diff --git a/rt_core/Cargo.toml b/crates/rt_core/Cargo.toml similarity index 50% rename from rt_core/Cargo.toml rename to crates/rt_core/Cargo.toml index 67ddec5..8224612 100644 --- a/rt_core/Cargo.toml +++ b/crates/rt_core/Cargo.toml @@ -3,12 +3,8 @@ name = "rt_core" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] rand = { version = "0.8.3", features = [ "small_rng" ] } -clap = { version = "4.1.8", features = [ "derive" ] } - [features] f64 = [] \ No newline at end of file diff --git a/rt_core/src/acceleration.rs b/crates/rt_core/src/acceleration.rs similarity index 100% rename from rt_core/src/acceleration.rs rename to crates/rt_core/src/acceleration.rs diff --git a/rt_core/src/lib.rs b/crates/rt_core/src/lib.rs similarity index 100% rename from rt_core/src/lib.rs rename to crates/rt_core/src/lib.rs diff --git a/rt_core/src/material.rs b/crates/rt_core/src/material.rs similarity index 100% rename from rt_core/src/material.rs rename to crates/rt_core/src/material.rs diff --git a/rt_core/src/primitive.rs b/crates/rt_core/src/primitive.rs similarity index 100% rename from rt_core/src/primitive.rs rename to crates/rt_core/src/primitive.rs diff --git a/rt_core/src/ray.rs b/crates/rt_core/src/ray.rs similarity index 100% rename from rt_core/src/ray.rs rename to crates/rt_core/src/ray.rs diff --git a/crates/rt_core/src/sampler.rs b/crates/rt_core/src/sampler.rs new file mode 100644 index 0000000..c0699d8 --- /dev/null +++ b/crates/rt_core/src/sampler.rs @@ -0,0 +1,14 @@ +use crate::{Float, Ray, Vec3}; + +pub trait NoHit: Sync { + fn get_colour(&self, ray: &Ray) -> Vec3; + fn pdf(&self, _: Vec3) -> Float { + unimplemented!() + } + fn can_sample(&self) -> bool { + false + } + fn sample(&self) -> Vec3 { + unimplemented!() + } +} diff --git a/rt_core/src/vec.rs b/crates/rt_core/src/vec.rs similarity index 100% rename from rt_core/src/vec.rs rename to crates/rt_core/src/vec.rs diff --git a/download_res.sh b/download_res.sh deleted file mode 100755 index c7ca526..0000000 --- a/download_res.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -mkdir frontend/res \ No newline at end of file diff --git a/frontend/region/Cargo.toml b/frontend/region/Cargo.toml deleted file mode 100644 index c7f524e..0000000 --- a/frontend/region/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "region" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -[profile.release-with-debug] -inherits = "release" -debug = true - diff --git a/frontend/src/gui.rs b/src/gui.rs similarity index 100% rename from frontend/src/gui.rs rename to src/gui.rs diff --git a/frontend/src/load_model.rs b/src/load_model.rs similarity index 100% rename from frontend/src/load_model.rs rename to src/load_model.rs diff --git a/frontend/src/macros.rs b/src/macros.rs similarity index 100% rename from frontend/src/macros.rs rename to src/macros.rs diff --git a/frontend/src/main.rs b/src/main.rs similarity index 99% rename from frontend/src/main.rs rename to src/main.rs index a9045c5..461a93e 100644 --- a/frontend/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use implementations::rt_core::*; +use implementations::SamplerProgress; use crate::utility::*; diff --git a/frontend/src/parameters.rs b/src/parameters.rs similarity index 97% rename from frontend/src/parameters.rs rename to src/parameters.rs index 8f1a472..4b9cc4f 100644 --- a/frontend/src/parameters.rs +++ b/src/parameters.rs @@ -1,7 +1,7 @@ use crate::scene::Scene; use clap::Parser; -use implementations::{rt_core::*, split::SplitType, *}; +use implementations::{split::SplitType, *}; use region::Region; type MaterialType<'a> = AllMaterials<'a, AllTextures>; diff --git a/frontend/src/rendering.rs b/src/rendering.rs similarity index 100% rename from frontend/src/rendering.rs rename to src/rendering.rs diff --git a/frontend/src/scene.rs b/src/scene.rs similarity index 94% rename from frontend/src/scene.rs rename to src/scene.rs index 8d0bc40..5cf30af 100644 --- a/frontend/src/scene.rs +++ b/src/scene.rs @@ -1,5 +1,6 @@ use implementations::random_sampler::RandomSampler; use implementations::rt_core::*; +use implementations::*; use region::Region; use std::mem::ManuallyDrop; @@ -14,7 +15,7 @@ where acceleration: A, camera: C, sky: S, - region: ManuallyDrop, + _region: ManuallyDrop, } impl Scene @@ -30,7 +31,7 @@ where acceleration, camera, sky, - region, + _region: region, } } pub fn render( @@ -56,7 +57,6 @@ where #[cfg(test)] mod tests { use super::*; - use implementations::*; use loader::load_str_full; const DATA: &str = "camera ( @@ -133,12 +133,7 @@ primitive ( let (p, camera, sky) = stuff; let bvh: Bvh = Bvh::new(p, split::SplitType::Sah); - let scene = Scene { - acceleration: bvh, - camera, - sky, - region, - }; + let scene = Scene::new(bvh, camera, sky, region); scene.render::<()>( RenderOptions { diff --git a/frontend/src/utility.rs b/src/utility.rs similarity index 100% rename from frontend/src/utility.rs rename to src/utility.rs From 65e85de3765169e85d23a3fb1382bd0f29909cdd Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Tue, 14 Mar 2023 16:34:20 +1100 Subject: [PATCH 27/39] Moved most of the gui code to a workspace --- Cargo.toml | 1 + crates/gui/Cargo.toml | 13 + {src => crates/gui/src}/gui.rs | 3 +- crates/gui/src/lib.rs | 167 +++++++++ {src => crates/gui/src}/rendering.rs | 0 src/main.rs | 515 ++++++++++----------------- src/utility.rs | 19 - 7 files changed, 378 insertions(+), 340 deletions(-) create mode 100644 crates/gui/Cargo.toml rename {src => crates/gui/src}/gui.rs (99%) create mode 100644 crates/gui/src/lib.rs rename {src => crates/gui/src}/rendering.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 2b8bae9..5ba4010 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ region = { path = "./crates/region" } loader = { path = "./crates/loader" } simple_logger = "4.0" clap = { version = "4.1.8", features = ["derive", "wrap_help"] } +gui = { path = "./crates/gui" } [features] f64 = ["implementations/f64"] diff --git a/crates/gui/Cargo.toml b/crates/gui/Cargo.toml new file mode 100644 index 0000000..f65950e --- /dev/null +++ b/crates/gui/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "gui" +version = "0.1.0" +edition = "2021" + +[dependencies] +vulkano = { version = "0.28.0" } +vulkano-shaders = { version = "0.28.0" } +vulkano-win = { version = "0.28.0" } +winit = { version = "0.26.1" } +implementations = { path = "../implementations" } +chrono = "0.4.19" +image = "0.23.14" diff --git a/src/gui.rs b/crates/gui/src/gui.rs similarity index 99% rename from src/gui.rs rename to crates/gui/src/gui.rs index 20871ac..e79ff9e 100644 --- a/src/gui.rs +++ b/crates/gui/src/gui.rs @@ -1,4 +1,5 @@ -use crate::rendering::*; +use crate::rendering::CpuRendering; +use crate::rendering::RenderInfo; use std::sync::Arc; use vulkano::{ command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs new file mode 100644 index 0000000..fd68fbd --- /dev/null +++ b/crates/gui/src/lib.rs @@ -0,0 +1,167 @@ +mod gui; +mod rendering; + +use implementations::SamplerProgress; + +use { + std::sync::{ + atomic::{AtomicBool, AtomicU64, Ordering}, + Arc, + }, + vulkano::{ + buffer::CpuAccessibleBuffer, + command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, + device::{Device, Queue}, + image::StorageImage, + sync::{self, GpuFuture}, + }, + winit::event_loop::EventLoopProxy, +}; + +pub use crate::gui::{Gui, RenderEvent}; +pub use crate::rendering::Future; + +pub struct Data { + pub queue: Arc, + pub device: Arc, + pub to_sc: rendering::Future, + pub from_sc: rendering::Future, + pub command_buffers: [Arc; 2], + pub buffer: Arc>, + pub sc_index: Arc, + pub samples: Arc, + pub total_samples: u64, + pub rays_shot: Arc, + pub event_proxy: EventLoopProxy, +} + +impl Data { + pub fn new( + queue: Arc, + device: Arc, + to_sc: rendering::Future, + from_sc: rendering::Future, + command_buffers: [Arc; 2], + buffer: Arc>, + sc_index: Arc, + samples: Arc, + total_samples: u64, + rays_shot: Arc, + event_proxy: EventLoopProxy, + ) -> Self { + Data { + queue, + device, + to_sc, + from_sc, + command_buffers, + buffer, + sc_index, + samples, + total_samples, + rays_shot, + event_proxy, + } + } +} + +pub fn create_command_buffers( + device: Arc, + queue: Arc, + buffer: Arc>, + sc: [Arc; 2], +) -> [Arc; 2] { + let mut command_buffer_0 = None; + let mut command_buffer_1 = None; + for (i, sc_image) in sc.iter().enumerate() { + let mut builder = AutoCommandBufferBuilder::primary( + device.clone(), + queue.family(), + CommandBufferUsage::MultipleSubmit, + ) + .unwrap(); + + builder + .copy_buffer_to_image(buffer.clone(), sc_image.clone()) + .unwrap(); + if i == 0 { + command_buffer_0 = Some(builder.build().unwrap()); + } else { + command_buffer_1 = Some(builder.build().unwrap()); + } + } + + [ + Arc::new(command_buffer_0.unwrap()), + Arc::new(command_buffer_1.unwrap()), + ] +} + +pub fn sample_update(data: &mut Data, previous: &SamplerProgress, i: u64) { + // update infomation about the rays shot and samples completed in the current render + data.samples.fetch_add(1, Ordering::Relaxed); + data.rays_shot + .fetch_add(previous.rays_shot, Ordering::Relaxed); + + // wait on from_sc future if is_some() + match &*data.from_sc.lock().unwrap() { + Some(future) => { + future.wait(None).unwrap(); + } + None => {} + } + match &*data.to_sc.lock().unwrap() { + Some(future) => { + future.wait(None).unwrap(); + } + None => {} + } + + { + // get access to CpuAccessibleBuffer + let mut buf = data.buffer.write().unwrap(); + buf.chunks_mut(4) + .zip(previous.current_image.chunks(3)) + .for_each(|(pres, acc)| { + pres[0] += (acc[0] as f32 - pres[0]) / i as f32; + pres[1] += (acc[1] as f32 - pres[1]) / i as f32; + pres[2] += (acc[2] as f32 - pres[2]) / i as f32; + pres[3] = 1.0; + }); + } + + // copy to cpu swapchain + let command_buffer = + data.command_buffers[data.sc_index.load(Ordering::Relaxed) as usize].clone(); + + // copy to swapchain and store op in to_sc future + { + let to_sc = &mut *data.to_sc.lock().unwrap(); + *to_sc = Some( + match to_sc.take() { + Some(future) => future + .then_execute(data.queue.clone(), command_buffer) + .unwrap() + .boxed_send_sync(), + None => sync::now(data.device.clone()) + .then_execute(data.queue.clone(), command_buffer) + .unwrap() + .boxed_send_sync(), + } + .then_signal_fence_and_flush() + .unwrap(), // change to match + ); + } + + // modify sc_index to !sc_index + data.sc_index + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |x| Some(!x)) + .unwrap(); + + //get_progress_output(i, data.total_samples); + + // signal sample is ready to be presented + data.event_proxy + .send_event(RenderEvent::SampleCompleted) + .unwrap(); +} diff --git a/src/rendering.rs b/crates/gui/src/rendering.rs similarity index 100% rename from src/rendering.rs rename to crates/gui/src/rendering.rs diff --git a/src/main.rs b/src/main.rs index 461a93e..a400014 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,349 +1,224 @@ +use crate::parameters::Parameters; +use crate::scene::Scene; use implementations::rt_core::*; -use implementations::SamplerProgress; - -use crate::utility::*; +use implementations::*; #[cfg(feature = "gui")] use { - gui::{Gui, RenderEvent}, - std::sync::{ - atomic::{AtomicBool, AtomicU64, Ordering}, - Arc, - }, - vulkano::{ - buffer::CpuAccessibleBuffer, - command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, - device::{Device, Queue}, - image::StorageImage, - instance::Instance, - sync::{self, GpuFuture}, - Version, - }, + gui::*, + std::sync::{atomic::*, Arc}, + vulkano::{buffer::CpuAccessibleBuffer, instance::Instance}, winit::event_loop::EventLoopProxy, }; -#[cfg(feature = "gui")] -mod gui; -#[cfg(feature = "gui")] -mod rendering; +use crate::utility::*; -//mod load_model; mod macros; mod parameters; mod scene; mod utility; #[cfg(feature = "gui")] -struct Data { - queue: Arc, - device: Arc, - to_sc: rendering::Future, - from_sc: rendering::Future, - command_buffers: [Arc; 2], - buffer: Arc>, - sc_index: Arc, - samples: Arc, - total_samples: u64, - rays_shot: Arc, - event_proxy: EventLoopProxy, -} +fn render_gui( + render_options: RenderOptions, + filename: Option, + scene: Scene, +) where + M: Scatter + 'static, + P: Primitive + 'static, + C: Camera + 'static, + S: NoHit + 'static, + A: AccelerationStructure + 'static, +{ + let required_extensions = vulkano_win::required_extensions(); + let instance = Instance::new( + None, + vulkano::instance::Version::V1_5, + &required_extensions, + None, + ) + .unwrap(); + let gui = Gui::new( + &instance, + render_options.width as u32, + render_options.height as u32, + ); + + let event_loop_proxy: Option> = + gui.event_loop.as_ref().map(|el| el.create_proxy()); + let iter = [0.0f32, 0.0, 0.0, 0.0] + .repeat((render_options.width * render_options.height) as usize) + .into_iter(); + let buffer = CpuAccessibleBuffer::from_iter( + gui.device.clone(), + vulkano::buffer::BufferUsage::all(), + true, + iter, + ) + .unwrap(); + + let samples = Arc::new(AtomicU64::new(0)); + let ray_count = Arc::new(AtomicU64::new(0)); + + let command_buffers = create_command_buffers( + gui.device.clone(), + gui.queue.clone(), + buffer.clone(), + gui.cpu_rendering.cpu_swapchain.clone(), + ); + + let mut data = Data::new( + gui.queue.clone(), + gui.device.clone(), + gui.cpu_rendering.to_sc.clone(), + gui.cpu_rendering.from_sc.clone(), + command_buffers, + buffer.clone(), + gui.cpu_rendering.copy_to_first.clone(), + samples.clone(), + render_options.samples_per_pixel, + ray_count.clone(), + event_loop_proxy.unwrap(), + ); + + let image_copy_finished = data.to_sc.clone(); + + let start = print_render_start(render_options.width, render_options.height, None); + + let render_canceled = Arc::new(AtomicBool::new(true)); + + let moved_render_canceled = render_canceled.clone(); + let moved_filename = filename.clone(); + + std::thread::spawn(move || { + let ray_count = data.rays_shot.clone(); + let samples = data.samples.clone(); + let buffer = data.buffer.clone(); + let to_sc = data.to_sc.clone(); + + scene.render( + render_options, + Some(( + &mut data, + |data: &mut Data, previous: &SamplerProgress, i: u64| { + sample_update(data, previous, i); + }, + )), + ); -#[cfg(feature = "gui")] -impl Data { - pub fn new( - queue: Arc, - device: Arc, - to_sc: rendering::Future, - from_sc: rendering::Future, - command_buffers: [Arc; 2], - buffer: Arc>, - sc_index: Arc, - samples: Arc, - total_samples: u64, - rays_shot: Arc, - event_proxy: EventLoopProxy, - ) -> Self { - Data { - queue, - device, + let ray_count = ray_count.load(Ordering::Relaxed); + let samples = samples.load(Ordering::Relaxed); + + print_final_statistics(start, ray_count, Some(samples)); + println!("--------------------------------"); + + moved_render_canceled.store(false, Ordering::Relaxed); + + save_file( + moved_filename, + render_options.width, + render_options.height, + &*buffer.read().unwrap(), to_sc, - from_sc, - command_buffers, - buffer, - sc_index, - samples, - total_samples, - rays_shot, - event_proxy, - } + ); + }); + + gui.run(); + if render_canceled.load(Ordering::Relaxed) { + let ray_count = ray_count.load(Ordering::Relaxed); + let samples = samples.load(Ordering::Relaxed); + + print_final_statistics(start, ray_count, Some(samples)); + println!("--------------------------------"); + + save_file( + filename, + render_options.width, + render_options.height, + &*buffer.read().unwrap(), + image_copy_finished, + ); } } -fn main() { - if let Some((scene, parameters)) = parameters::process_args() { - let (render_options, filename) = (parameters.render_options, parameters.filename.clone()); - if !parameters.gui { - let start = print_render_start( - render_options.width, - render_options.height, - Some(render_options.samples_per_pixel), - ); - - let mut image = SamplerProgress::new(render_options.width * render_options.height, 3); - let progress_bar_output = - |sp: &mut SamplerProgress, previous: &SamplerProgress, i: u64| { - sp.samples_completed += 1; - sp.rays_shot += previous.rays_shot; - - sp.current_image - .iter_mut() - .zip(previous.current_image.iter()) - .for_each(|(pres, acc)| { - *pres += (acc - *pres) / i as Float; // since copies first buffer when i=1 - }); - - get_progress_output(sp.samples_completed, render_options.samples_per_pixel); - }; - - scene.render(render_options, Some((&mut image, progress_bar_output))); - - let output = ℑ - - let ray_count = output.rays_shot; - - print_final_statistics(start, ray_count, None); - println!("--------------------------------"); - - let output: Vec = output - .current_image - .iter() - .map(|val| (val.sqrt() * 255.999) as u8) - .collect(); - - if let Some(filename) = filename { - save_u8_to_image( - render_options.width, - render_options.height, - output, - filename, - false, - ); - } - } else { - #[cfg(feature = "gui")] - { - let required_extensions = vulkano_win::required_extensions(); - let instance = - Instance::new(None, Version::V1_5, &required_extensions, None).unwrap(); - let gui = Gui::new( - &instance, - render_options.width as u32, - render_options.height as u32, - ); - - let event_loop_proxy: Option> = - gui.event_loop.as_ref().map(|el| el.create_proxy()); - let iter = [0.0f32, 0.0, 0.0, 0.0] - .repeat((render_options.width * render_options.height) as usize) - .into_iter(); - let buffer = CpuAccessibleBuffer::from_iter( - gui.device.clone(), - vulkano::buffer::BufferUsage::all(), - true, - iter, - ) - .unwrap(); - - let samples = Arc::new(AtomicU64::new(0)); - let ray_count = Arc::new(AtomicU64::new(0)); - - let command_buffers = create_command_buffers( - gui.device.clone(), - gui.queue.clone(), - buffer.clone(), - gui.cpu_rendering.cpu_swapchain.clone(), - ); - - let mut data = Data::new( - gui.queue.clone(), - gui.device.clone(), - gui.cpu_rendering.to_sc.clone(), - gui.cpu_rendering.from_sc.clone(), - command_buffers, - buffer.clone(), - gui.cpu_rendering.copy_to_first.clone(), - samples.clone(), - render_options.samples_per_pixel, - ray_count.clone(), - event_loop_proxy.unwrap(), - ); - - let image_copy_finished = data.to_sc.clone(); - - let start = print_render_start(render_options.width, render_options.height, None); - - let render_canceled = Arc::new(AtomicBool::new(true)); - - let moved_render_canceled = render_canceled.clone(); - let moved_filename = filename.clone(); - - std::thread::spawn(move || { - let ray_count = data.rays_shot.clone(); - let samples = data.samples.clone(); - let buffer = data.buffer.clone(); - let to_sc = data.to_sc.clone(); - - scene.render( - render_options, - Some(( - &mut data, - |data: &mut Data, previous: &SamplerProgress, i: u64| { - sample_update(data, previous, i); - }, - )), - ); - - let ray_count = ray_count.load(Ordering::Relaxed); - let samples = samples.load(Ordering::Relaxed); - - print_final_statistics(start, ray_count, Some(samples)); - println!("--------------------------------"); - - moved_render_canceled.store(false, Ordering::Relaxed); - - save_file( - moved_filename, - render_options.width, - render_options.height, - &*buffer.read().unwrap(), - to_sc, - ); - }); - - gui.run(); - if render_canceled.load(Ordering::Relaxed) { - let ray_count = ray_count.load(Ordering::Relaxed); - let samples = samples.load(Ordering::Relaxed); - - print_final_statistics(start, ray_count, Some(samples)); - println!("--------------------------------"); - - save_file( - filename, - render_options.width, - render_options.height, - &*buffer.read().unwrap(), - image_copy_finished, - ); - } - } - #[cfg(not(feature = "gui"))] - println!("feature: gui not enabled"); - } - } -} +fn render_tui( + render_options: RenderOptions, + filename: Option, + scene: Scene, +) where + M: Scatter, + P: Primitive, + C: Camera, + S: NoHit, + A: AccelerationStructure, +{ + let start = print_render_start( + render_options.width, + render_options.height, + Some(render_options.samples_per_pixel), + ); + + let mut image = SamplerProgress::new(render_options.width * render_options.height, 3); + let progress_bar_output = |sp: &mut SamplerProgress, previous: &SamplerProgress, i: u64| { + sp.samples_completed += 1; + sp.rays_shot += previous.rays_shot; + + sp.current_image + .iter_mut() + .zip(previous.current_image.iter()) + .for_each(|(pres, acc)| { + *pres += (acc - *pres) / i as Float; // since copies first buffer when i=1 + }); -#[cfg(feature = "gui")] -fn create_command_buffers( - device: Arc, - queue: Arc, - buffer: Arc>, - sc: [Arc; 2], -) -> [Arc; 2] { - let mut command_buffer_0 = None; - let mut command_buffer_1 = None; - for (i, sc_image) in sc.iter().enumerate() { - let mut builder = AutoCommandBufferBuilder::primary( - device.clone(), - queue.family(), - CommandBufferUsage::MultipleSubmit, - ) - .unwrap(); - - builder - .copy_buffer_to_image(buffer.clone(), sc_image.clone()) - .unwrap(); - if i == 0 { - command_buffer_0 = Some(builder.build().unwrap()); - } else { - command_buffer_1 = Some(builder.build().unwrap()); - } - } + get_progress_output(sp.samples_completed, render_options.samples_per_pixel); + }; - [ - Arc::new(command_buffer_0.unwrap()), - Arc::new(command_buffer_1.unwrap()), - ] -} + scene.render(render_options, Some((&mut image, progress_bar_output))); -#[cfg(feature = "gui")] -fn sample_update(data: &mut Data, previous: &SamplerProgress, i: u64) { - // update infomation about the rays shot and samples completed in the current render - data.samples.fetch_add(1, Ordering::Relaxed); - data.rays_shot - .fetch_add(previous.rays_shot, Ordering::Relaxed); - - // wait on from_sc future if is_some() - match &*data.from_sc.lock().unwrap() { - Some(future) => { - future.wait(None).unwrap(); - } - None => {} - } - match &*data.to_sc.lock().unwrap() { - Some(future) => { - future.wait(None).unwrap(); - } - None => {} - } + let output = ℑ - { - // get access to CpuAccessibleBuffer - let mut buf = data.buffer.write().unwrap(); - buf.chunks_mut(4) - .zip(previous.current_image.chunks(3)) - .for_each(|(pres, acc)| { - pres[0] += (acc[0] as f32 - pres[0]) / i as f32; - pres[1] += (acc[1] as f32 - pres[1]) / i as f32; - pres[2] += (acc[2] as f32 - pres[2]) / i as f32; - pres[3] = 1.0; - }); - } + let ray_count = output.rays_shot; - // copy to cpu swapchain - let command_buffer = - data.command_buffers[data.sc_index.load(Ordering::Relaxed) as usize].clone(); - - // copy to swapchain and store op in to_sc future - { - let to_sc = &mut *data.to_sc.lock().unwrap(); - *to_sc = Some( - match to_sc.take() { - Some(future) => future - .then_execute(data.queue.clone(), command_buffer) - .unwrap() - .boxed_send_sync(), - None => sync::now(data.device.clone()) - .then_execute(data.queue.clone(), command_buffer) - .unwrap() - .boxed_send_sync(), - } - .then_signal_fence_and_flush() - .unwrap(), // change to match - ); - } + print_final_statistics(start, ray_count, None); + println!("--------------------------------"); - // modify sc_index to !sc_index - data.sc_index - .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |x| Some(!x)) - .unwrap(); + let output: Vec = output + .current_image + .iter() + .map(|val| (val.sqrt() * 255.999) as u8) + .collect(); - get_progress_output(i, data.total_samples); + if let Some(filename) = filename { + save_u8_to_image( + render_options.width, + render_options.height, + output, + filename, + false, + ); + } +} - // signal sample is ready to be presented - data.event_proxy - .send_event(RenderEvent::SampleCompleted) - .unwrap(); +fn main() { + let (scene, parameters) = match parameters::process_args() { + Some(data) => data, + None => return, + }; + + let Parameters { + render_options, + gui, + filename, + } = parameters; + + if !gui { + render_tui(render_options, filename, scene); + } else { + #[cfg(feature = "gui")] + render_gui(render_options, filename, scene); + #[cfg(not(feature = "gui"))] + println!("feature: gui not enabled"); + } } #[cfg(feature = "gui")] @@ -352,7 +227,7 @@ fn save_file( width: u64, height: u64, buffer: &[f32], - image_fence: rendering::Future, + image_fence: Future, ) { match filename { Some(filename) => { diff --git a/src/utility.rs b/src/utility.rs index 49d428f..d298f02 100644 --- a/src/utility.rs +++ b/src/utility.rs @@ -59,25 +59,6 @@ pub fn get_readable_duration(duration: Duration) -> String { days_string + &hours_string + &minutes_string + &seconds_string } -/*pub fn create_bvh_with_info( - primitives: Vec

, - bvh_type: SplitType, -) -> Arc> { - let time = Local::now(); - - println!("\n{} - Bvh construction started at", time.format("%X")); - - let start = Instant::now(); - let bvh = Arc::new(Bvh::new(primitives, bvh_type)); - let end = Instant::now(); - let duration = end.checked_duration_since(start).unwrap(); - - println!("\tBvh construction finished in: {}ms", duration.as_millis()); - println!("\tNumber of BVH nodes: {}\n", bvh.number_nodes()); - - bvh -}*/ - pub fn get_progress_output(samples_completed: u64, total_samples: u64) { progress_bar(samples_completed as f64 / total_samples as f64); From 39647f8eab6e3d10ac398ca2e46de51d198a8352 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Tue, 14 Mar 2023 21:35:11 +1100 Subject: [PATCH 28/39] Improved logging p1 --- Cargo.toml | 5 +- crates/gui/Cargo.toml | 3 +- crates/gui/src/gui.rs | 22 +++--- crates/gui/src/lib.rs | 13 +++- crates/output/Cargo.toml | 13 ++++ crates/output/src/lib.rs | 126 ++++++++++++++++++++++++++++++++++ src/main.rs | 47 +++++++++---- src/utility.rs | 145 --------------------------------------- 8 files changed, 197 insertions(+), 177 deletions(-) create mode 100644 crates/output/Cargo.toml create mode 100644 crates/output/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 5ba4010..d360e21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,6 @@ edition = "2021" members = ["crates/*"] [dependencies] -chrono = "0.4.19" -image = "0.23.14" rayon = "1.5.1" rand = { version = "0.8.3", features = [ "small_rng" ] } rand_seeder = "0.2.2" @@ -21,9 +19,10 @@ wavefront_obj = "10.0.0" winit = { version = "0.26.1", optional = true } region = { path = "./crates/region" } loader = { path = "./crates/loader" } -simple_logger = "4.0" +output = { path = "./crates/output" } clap = { version = "4.1.8", features = ["derive", "wrap_help"] } gui = { path = "./crates/gui" } +indicatif = "0.17.3" [features] f64 = ["implementations/f64"] diff --git a/crates/gui/Cargo.toml b/crates/gui/Cargo.toml index f65950e..4bab343 100644 --- a/crates/gui/Cargo.toml +++ b/crates/gui/Cargo.toml @@ -9,5 +9,4 @@ vulkano-shaders = { version = "0.28.0" } vulkano-win = { version = "0.28.0" } winit = { version = "0.26.1" } implementations = { path = "../implementations" } -chrono = "0.4.19" -image = "0.23.14" +indicatif = "0.17.3" diff --git a/crates/gui/src/gui.rs b/crates/gui/src/gui.rs index e79ff9e..460e51b 100644 --- a/crates/gui/src/gui.rs +++ b/crates/gui/src/gui.rs @@ -150,8 +150,8 @@ layout(set = 0, binding = 0, rgba32f) uniform readonly image2D cpu_input; layout(set = 0, binding = 1, rgba8) uniform writeonly image2D image_output; void main() { - vec4 data = sqrt(imageLoad(cpu_input, ivec2(gl_GlobalInvocationID.xy))); - imageStore(image_output, ivec2(gl_GlobalInvocationID.xy), data); + vec4 data = sqrt(imageLoad(cpu_input, ivec2(gl_GlobalInvocationID.xy))); + imageStore(image_output, ivec2(gl_GlobalInvocationID.xy), data); }"} } @@ -212,14 +212,13 @@ void main() { Event::DeviceEvent { event: winit::event::DeviceEvent::Key(key), .. - } => match key.virtual_keycode { - Some(code) => { + } => { + if let Some(code) = key.virtual_keycode { if code == winit::event::VirtualKeyCode::Escape { *control_flow = ControlFlow::Exit; } } - None => {} - }, + } Event::WindowEvent { event: WindowEvent::CloseRequested, .. @@ -245,9 +244,8 @@ void main() { } fn update(&mut self) { - match self.presentation_finished.as_mut() { - Some(future) => future.cleanup_finished(), - None => {} + if let Some(future) = self.presentation_finished.as_mut() { + future.cleanup_finished() } self.presentation_finished = Some(sync::now(self.device.clone()).boxed()); @@ -259,7 +257,7 @@ void main() { return; } Err(e) => { - panic!("Failed to acquire next image: {:?}", e) + panic!("Failed to acquire next image: {e:?}") } }; @@ -327,7 +325,7 @@ void main() { self.presentation_finished = Some(sync::now(self.device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {:?}", e); + println!("Failed to flush future: {e:?}"); self.presentation_finished = Some(sync::now(self.device.clone()).boxed()); } } @@ -338,7 +336,7 @@ void main() { match self.swapchain.recreate().dimensions(dimensions).build() { Ok(r) => r, Err(SwapchainCreationError::UnsupportedDimensions) => return, - Err(e) => panic!("Failed to recreate swapchain: {:?}", e), + Err(e) => panic!("Failed to recreate swapchain: {e:?}"), }; let extent: [u32; 2] = self.surface.window().inner_size().into(); diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs index fd68fbd..41123eb 100644 --- a/crates/gui/src/lib.rs +++ b/crates/gui/src/lib.rs @@ -2,6 +2,8 @@ mod gui; mod rendering; use implementations::SamplerProgress; +use indicatif::ProgressBar; +use indicatif::ProgressStyle; use { std::sync::{ @@ -33,6 +35,7 @@ pub struct Data { pub total_samples: u64, pub rays_shot: Arc, pub event_proxy: EventLoopProxy, + pub bar: ProgressBar, } impl Data { @@ -61,6 +64,11 @@ impl Data { total_samples, rays_shot, event_proxy, + bar: ProgressBar::new(total_samples).with_style( + ProgressStyle::default_bar() + .template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}") + .unwrap(), + ), } } } @@ -158,7 +166,10 @@ pub fn sample_update(data: &mut Data, previous: &SamplerProgress, i: u64) { .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |x| Some(!x)) .unwrap(); - //get_progress_output(i, data.total_samples); + data.bar.set_position(data.samples.load(Ordering::Relaxed)); + if data.samples.load(Ordering::Relaxed) == data.total_samples { + data.bar.abandon() + } // signal sample is ready to be presented data.event_proxy diff --git a/crates/output/Cargo.toml b/crates/output/Cargo.toml new file mode 100644 index 0000000..ae60e43 --- /dev/null +++ b/crates/output/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "output" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +indicatif = "0.17.3" +log = { version = "^0.4.14", features = ["std"] } +chrono = "0.4.19" +image = "0.23.14" +simple_logger = { version = "4.0", features = ["timestamps"] } \ No newline at end of file diff --git a/crates/output/src/lib.rs b/crates/output/src/lib.rs new file mode 100644 index 0000000..03146f5 --- /dev/null +++ b/crates/output/src/lib.rs @@ -0,0 +1,126 @@ +use chrono::Local; +use std::process; +use std::time::Instant; + +use std::io::Write; +use std::time::Duration; + +pub fn create_logger() { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Info) + .with_module_level("winit", log::LevelFilter::Warn) + .without_timestamps() + .env() + .init() + .unwrap(); +} + +pub fn get_readable_duration(duration: Duration) -> String { + let days = duration.as_secs() / 86400; + + let days_string = match days { + 0 => "".to_string(), + 1 => format!("{days} day, "), + _ => format!("{days} days, "), + }; + + let hours = (duration.as_secs() - days * 86400) / 3600; + let hours_string = match hours { + 0 => "".to_string(), + 1 => format!("{hours} hour, "), + _ => format!("{hours} hours, "), + }; + + let minutes = (duration.as_secs() - days * 86400 - hours * 3600) / 60; + let minutes_string = match minutes { + 0 => "".to_string(), + 1 => format!("{minutes} minute, "), + _ => format!("{minutes} minutes, "), + }; + + let seconds = duration.as_secs() % 60; + let seconds_string = match seconds { + 0 => "~0 seconds".to_string(), + 1 => format!("{seconds} second"), + _ => format!("{seconds} seconds"), + }; + days_string + &hours_string + &minutes_string + &seconds_string +} + +pub fn save_u8_to_image(width: u64, height: u64, image: Vec, filename: String, alpha: bool) { + let split = filename.split('.').collect::>(); + if split.len() != 2 { + println!("Invalid filename: {filename}"); + process::exit(0); + } + + let extension = split[1]; + + match extension { + "png" | "jpg" | "jpeg" | "exr" | "tiff" => { + image::save_buffer( + filename, + &image, + width.try_into().unwrap(), + height.try_into().unwrap(), + if alpha { + image::ColorType::Rgba8 + } else { + image::ColorType::Rgb8 + }, + ) + .unwrap(); + } + "ppm" => { + let mut data = format!("P3\n{width} {height}\n255\n").as_bytes().to_owned(); + + image.iter().enumerate().for_each(|(i, &v)| { + if i % 3 == 0 { + data.extend_from_slice(format!("{v}\n").as_bytes()) + } else { + data.extend_from_slice(format!("{v} ").as_bytes()) + } + }); + + let mut file = std::fs::File::create(filename).unwrap(); + file.write_all(&data).unwrap(); + } + _ => { + println!("Unknown filetype: .{extension}"); + } + } +} + +pub fn print_final_statistics(start: Instant, ray_count: u64, samples: Option) { + let end = Instant::now(); + let duration = end.checked_duration_since(start).unwrap(); + + match samples { + Some(samples) => log::info!( + "Finished rendering {samples} samples in {} and {ray_count} Rays at {:.2} Mray/s - {}", + get_readable_duration(duration), + (ray_count as f64 / duration.as_secs_f64()) / 1000000.0, + Local::now().format("%X") + ), + None => log::info!( + "Finished rendering in {} and {ray_count} Rays at {:.2} Mray/s - {}", + get_readable_duration(duration), + (ray_count as f64 / duration.as_secs_f64()) / 1000000.0, + Local::now().format("%X") + ), + } +} + +pub fn print_render_start(width: u64, height: u64, samples: Option) -> Instant { + match samples { + Some(samples) => log::info!( + "Render started ({width}x{height}x{samples}) - {}", + Local::now().format("%X") + ), + None => log::info!( + "Render started ({width}x{height}x∞) - {}", + Local::now().format("%X") + ), + } + Instant::now() +} diff --git a/src/main.rs b/src/main.rs index a400014..0178675 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,9 @@ use crate::parameters::Parameters; use crate::scene::Scene; use implementations::rt_core::*; use implementations::*; +use indicatif::ProgressBar; +use indicatif::ProgressStyle; +use output::*; #[cfg(feature = "gui")] use { @@ -11,8 +14,6 @@ use { winit::event_loop::EventLoopProxy, }; -use crate::utility::*; - mod macros; mod parameters; mod scene; @@ -83,7 +84,11 @@ fn render_gui( let image_copy_finished = data.to_sc.clone(); - let start = print_render_start(render_options.width, render_options.height, None); + let start = print_render_start( + render_options.width, + render_options.height, + Some(render_options.samples_per_pixel), + ); let render_canceled = Arc::new(AtomicBool::new(true)); @@ -110,7 +115,6 @@ fn render_gui( let samples = samples.load(Ordering::Relaxed); print_final_statistics(start, ray_count, Some(samples)); - println!("--------------------------------"); moved_render_canceled.store(false, Ordering::Relaxed); @@ -129,7 +133,6 @@ fn render_gui( let samples = samples.load(Ordering::Relaxed); print_final_statistics(start, ray_count, Some(samples)); - println!("--------------------------------"); save_file( filename, @@ -158,31 +161,46 @@ fn render_tui( Some(render_options.samples_per_pixel), ); - let mut image = SamplerProgress::new(render_options.width * render_options.height, 3); - let progress_bar_output = |sp: &mut SamplerProgress, previous: &SamplerProgress, i: u64| { - sp.samples_completed += 1; - sp.rays_shot += previous.rays_shot; + struct Progress { + pub sampler_progress: SamplerProgress, + pub bar: ProgressBar, + } + + let mut image = Progress { + sampler_progress: SamplerProgress::new(render_options.width * render_options.height, 3), + bar: ProgressBar::new(render_options.samples_per_pixel).with_style( + ProgressStyle::default_bar() + .template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}") + .unwrap(), + ), + }; + let progress_bar_output = |sp: &mut Progress, previous: &SamplerProgress, i: u64| { + sp.sampler_progress.samples_completed += 1; + sp.sampler_progress.rays_shot += previous.rays_shot; - sp.current_image + sp.sampler_progress + .current_image .iter_mut() .zip(previous.current_image.iter()) .for_each(|(pres, acc)| { *pres += (acc - *pres) / i as Float; // since copies first buffer when i=1 }); - - get_progress_output(sp.samples_completed, render_options.samples_per_pixel); + sp.bar.set_position(sp.sampler_progress.samples_completed); + if sp.sampler_progress.samples_completed == render_options.samples_per_pixel { + sp.bar.finish_and_clear() + } }; scene.render(render_options, Some((&mut image, progress_bar_output))); let output = ℑ - let ray_count = output.rays_shot; + let ray_count = output.sampler_progress.rays_shot; print_final_statistics(start, ray_count, None); - println!("--------------------------------"); let output: Vec = output + .sampler_progress .current_image .iter() .map(|val| (val.sqrt() * 255.999) as u8) @@ -200,6 +218,7 @@ fn render_tui( } fn main() { + create_logger(); let (scene, parameters) = match parameters::process_args() { Some(data) => data, None => return, diff --git a/src/utility.rs b/src/utility.rs index d298f02..6b3604d 100644 --- a/src/utility.rs +++ b/src/utility.rs @@ -1,151 +1,6 @@ -use chrono::Local; use implementations::rt_core::*; use implementations::*; -use std::{ - io::{stdout, Write}, - process, - time::{Duration, Instant}, -}; - -const BAR_LENGTH: u32 = 30; - -pub fn progress_bar(percentage: f64) { - print!("\r["); - for i in 1..=BAR_LENGTH { - if percentage >= i as f64 / BAR_LENGTH as f64 - && percentage < (i + 1) as f64 / BAR_LENGTH as f64 - && i != BAR_LENGTH - { - print!(">"); - } else if percentage >= i as f64 / BAR_LENGTH as f64 { - print!("="); - } else { - print!("-"); - } - } - print!("]"); -} - -pub fn get_readable_duration(duration: Duration) -> String { - let days = duration.as_secs() / 86400; - - let days_string = match days { - 0 => "".to_string(), - 1 => format!("{days} day, "), - _ => format!("{days} days, "), - }; - - let hours = (duration.as_secs() - days * 86400) / 3600; - let hours_string = match hours { - 0 => "".to_string(), - 1 => format!("{hours} hour, "), - _ => format!("{hours} hours, "), - }; - - let minutes = (duration.as_secs() - days * 86400 - hours * 3600) / 60; - let minutes_string = match minutes { - 0 => "".to_string(), - 1 => format!("{minutes} minute, "), - _ => format!("{minutes} minutes, "), - }; - - let seconds = duration.as_secs() % 60; - let seconds_string = match seconds { - 0 => "~0 seconds".to_string(), - 1 => format!("{seconds} second"), - _ => format!("{seconds} seconds"), - }; - days_string + &hours_string + &minutes_string + &seconds_string -} - -pub fn get_progress_output(samples_completed: u64, total_samples: u64) { - progress_bar(samples_completed as f64 / total_samples as f64); - - print!(" ({samples_completed}/{total_samples}) samples"); - - stdout().flush().unwrap(); -} - -pub fn save_u8_to_image(width: u64, height: u64, image: Vec, filename: String, alpha: bool) { - let split = filename.split('.').collect::>(); - if split.len() != 2 { - println!("Invalid filename: {filename}"); - process::exit(0); - } - - let extension = split[1]; - - match extension { - "png" | "jpg" | "jpeg" | "exr" | "tiff" => { - image::save_buffer( - filename, - &image, - width.try_into().unwrap(), - height.try_into().unwrap(), - if alpha { - image::ColorType::Rgba8 - } else { - image::ColorType::Rgb8 - }, - ) - .unwrap(); - } - "ppm" => { - let mut data = format!("P3\n{width} {height}\n255\n").as_bytes().to_owned(); - - image.iter().enumerate().for_each(|(i, &v)| { - if i % 3 == 0 { - data.extend_from_slice(format!("{v}\n").as_bytes()) - } else { - data.extend_from_slice(format!("{v} ").as_bytes()) - } - }); - - let mut file = std::fs::File::create(filename).unwrap(); - file.write_all(&data).unwrap(); - } - _ => { - println!("Unknown filetype: .{extension}"); - } - } -} - -pub fn print_final_statistics(start: Instant, ray_count: u64, samples: Option) { - let end = Instant::now(); - let duration = end.checked_duration_since(start).unwrap(); - let time = Local::now(); - println!( - "\u{001b}[2K\r{} - Finised rendering image", - time.format("%X") - ); - println!("\tRender Time: {}", get_readable_duration(duration)); - println!("\tRays: {ray_count}"); - match samples { - Some(samples) => println!("\tSamples: {samples}"), - None => { - println!() - } - } - - println!( - "\tMrays/s: {:.2}", - (ray_count as f64 / duration.as_secs_f64()) / 1000000.0 - ); -} - -pub fn print_render_start(width: u64, height: u64, samples: Option) -> Instant { - let time = Local::now(); - println!("{} - Render started", time.format("%X")); - println!("\tWidth: {width}"); - println!("\tHeight: {height}"); - match samples { - Some(samples) => println!("\tSamples per pixel: {samples}\n"), - None => println!(), - } - Instant::now() -} - pub fn rotate_around_point( point: &mut Vec3, center_point: Vec3, From 04451eeab1191b00dfed6933cfc65a89f5696d2a Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 15 Mar 2023 18:00:23 +1100 Subject: [PATCH 29/39] Improved terminal printing + fixed crash on exit --- Cargo.toml | 18 +++--- crates/gui/src/gui.rs | 6 +- crates/gui/src/lib.rs | 10 +++- crates/implementations/src/samplers/mod.rs | 2 +- .../src/samplers/random_sampler.rs | 8 ++- crates/output/Cargo.toml | 2 +- crates/output/src/lib.rs | 56 ++++++++++--------- scenes/rtweekend1.ssml | 43 ++++++++++++++ src/main.rs | 39 ++++++------- src/scene.rs | 4 +- 10 files changed, 120 insertions(+), 68 deletions(-) create mode 100644 scenes/rtweekend1.ssml diff --git a/Cargo.toml b/Cargo.toml index d360e21..8b9bdaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,22 +8,22 @@ edition = "2021" members = ["crates/*"] [dependencies] -rayon = "1.5.1" +clap = { version = "4.1.8", features = ["derive", "wrap_help"] } +gui = { path = "./crates/gui", optional = true } +implementations = { path = "./crates/implementations" } +indicatif = "0.17.3" +loader = { path = "./crates/loader" } +output = { path = "./crates/output" } rand = { version = "0.8.3", features = [ "small_rng" ] } rand_seeder = "0.2.2" -implementations = { path = "./crates/implementations" } +rayon = "1.5.1" +region = { path = "./crates/region" } vulkano = { version = "0.28.0", optional = true } vulkano-shaders = { version = "0.28.0", optional = true } vulkano-win = { version = "0.28.0", optional = true } wavefront_obj = "10.0.0" winit = { version = "0.26.1", optional = true } -region = { path = "./crates/region" } -loader = { path = "./crates/loader" } -output = { path = "./crates/output" } -clap = { version = "4.1.8", features = ["derive", "wrap_help"] } -gui = { path = "./crates/gui" } -indicatif = "0.17.3" [features] f64 = ["implementations/f64"] -gui = ["dep:vulkano", "dep:vulkano-win", "dep:vulkano-shaders", "dep:winit"] +gui = ["dep:vulkano", "dep:vulkano-win", "dep:vulkano-shaders", "dep:winit", "dep:gui"] diff --git a/crates/gui/src/gui.rs b/crates/gui/src/gui.rs index 460e51b..2a04a22 100644 --- a/crates/gui/src/gui.rs +++ b/crates/gui/src/gui.rs @@ -1,5 +1,6 @@ use crate::rendering::CpuRendering; use crate::rendering::RenderInfo; +use std::sync::atomic::AtomicBool; use std::sync::Arc; use vulkano::{ command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, @@ -41,10 +42,11 @@ pub struct Gui { render_info: RenderInfo, combined_buffer: Arc, presentation_finished: Option>, + exit: Arc, } impl Gui { - pub fn new(instance: &Arc, width: u32, height: u32) -> Self { + pub fn new(instance: &Arc, width: u32, height: u32, exit: Arc) -> Self { let event_loop: EventLoop = EventLoop::with_user_event(); let surface = WindowBuilder::new() .build_vk_surface(&event_loop, instance.clone()) @@ -200,6 +202,7 @@ void main() { presentation_command_buffers, combined_buffer, presentation_finished: None, + exit, } } @@ -215,6 +218,7 @@ void main() { } => { if let Some(code) = key.virtual_keycode { if code == winit::event::VirtualKeyCode::Escape { + self.exit.store(true, std::sync::atomic::Ordering::Relaxed); *control_flow = ControlFlow::Exit; } } diff --git a/crates/gui/src/lib.rs b/crates/gui/src/lib.rs index 41123eb..9abb406 100644 --- a/crates/gui/src/lib.rs +++ b/crates/gui/src/lib.rs @@ -35,6 +35,7 @@ pub struct Data { pub total_samples: u64, pub rays_shot: Arc, pub event_proxy: EventLoopProxy, + pub exit: Arc, pub bar: ProgressBar, } @@ -50,6 +51,7 @@ impl Data { samples: Arc, total_samples: u64, rays_shot: Arc, + exit: Arc, event_proxy: EventLoopProxy, ) -> Self { Data { @@ -64,6 +66,7 @@ impl Data { total_samples, rays_shot, event_proxy, + exit, bar: ProgressBar::new(total_samples).with_style( ProgressStyle::default_bar() .template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}") @@ -105,7 +108,10 @@ pub fn create_command_buffers( ] } -pub fn sample_update(data: &mut Data, previous: &SamplerProgress, i: u64) { +pub fn sample_update(data: &mut Data, previous: &SamplerProgress, i: u64) -> bool { + if data.exit.load(Ordering::Relaxed) { + return true; + } // update infomation about the rays shot and samples completed in the current render data.samples.fetch_add(1, Ordering::Relaxed); data.rays_shot @@ -174,5 +180,5 @@ pub fn sample_update(data: &mut Data, previous: &SamplerProgress, i: u64) { // signal sample is ready to be presented data.event_proxy .send_event(RenderEvent::SampleCompleted) - .unwrap(); + .is_err() } diff --git a/crates/implementations/src/samplers/mod.rs b/crates/implementations/src/samplers/mod.rs index ebe93ee..687082a 100644 --- a/crates/implementations/src/samplers/mod.rs +++ b/crates/implementations/src/samplers/mod.rs @@ -16,7 +16,7 @@ pub trait Sampler: Sync { C: Camera, P: Primitive, M: Scatter, - F: Fn(&mut T, &SamplerProgress, u64), + F: Fn(&mut T, &SamplerProgress, u64) -> bool, A: AccelerationStructure, S: NoHit; } diff --git a/crates/implementations/src/samplers/random_sampler.rs b/crates/implementations/src/samplers/random_sampler.rs index ffcbf9d..02e185d 100644 --- a/crates/implementations/src/samplers/random_sampler.rs +++ b/crates/implementations/src/samplers/random_sampler.rs @@ -17,7 +17,7 @@ impl Sampler for RandomSampler { C: Camera, P: Primitive, M: Scatter, - F: Fn(&mut T, &SamplerProgress, u64), + F: Fn(&mut T, &SamplerProgress, u64) -> bool, A: AccelerationStructure, S: NoHit, { @@ -81,7 +81,9 @@ impl Sampler for RandomSampler { }); if i != 0 { if let Some((ref mut data, f)) = presentation_update.as_mut() { - f(data, previous, i) + if f(data, previous, i) { + return; + } }; } } @@ -92,7 +94,7 @@ impl Sampler for RandomSampler { (&accumulator_buffers.1, &mut accumulator_buffers.0) }; if let Some((ref mut data, f)) = presentation_update.as_mut() { - f(data, previous, render_options.samples_per_pixel) + f(data, previous, render_options.samples_per_pixel); } } } diff --git a/crates/output/Cargo.toml b/crates/output/Cargo.toml index ae60e43..a4d8a45 100644 --- a/crates/output/Cargo.toml +++ b/crates/output/Cargo.toml @@ -10,4 +10,4 @@ indicatif = "0.17.3" log = { version = "^0.4.14", features = ["std"] } chrono = "0.4.19" image = "0.23.14" -simple_logger = { version = "4.0", features = ["timestamps"] } \ No newline at end of file +fern = { version = "0.6", features = ["colored"] } \ No newline at end of file diff --git a/crates/output/src/lib.rs b/crates/output/src/lib.rs index 03146f5..cbc447c 100644 --- a/crates/output/src/lib.rs +++ b/crates/output/src/lib.rs @@ -1,4 +1,5 @@ -use chrono::Local; +use fern::colors::{Color, ColoredLevelConfig}; + use std::process; use std::time::Instant; @@ -6,12 +7,26 @@ use std::io::Write; use std::time::Duration; pub fn create_logger() { - simple_logger::SimpleLogger::new() - .with_level(log::LevelFilter::Info) - .with_module_level("winit", log::LevelFilter::Warn) - .without_timestamps() - .env() - .init() + let colors = ColoredLevelConfig::new() + .error(Color::Red) + .warn(Color::Yellow) + .info(Color::Cyan) + .debug(Color::Magenta); + + fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "{} {} [{}] {}", + chrono::Local::now().format("%H:%M:%S"), + colors.color(record.level()), + record.target(), + message + )) + }) + .level(log::LevelFilter::Info) + .level_for("winit", log::LevelFilter::Warn) + .chain(std::io::stderr()) + .apply() .unwrap(); } @@ -91,36 +106,25 @@ pub fn save_u8_to_image(width: u64, height: u64, image: Vec, filename: Strin } } -pub fn print_final_statistics(start: Instant, ray_count: u64, samples: Option) { +pub fn print_final_statistics(start: Instant, ray_count: u64, samples: u64) { let end = Instant::now(); let duration = end.checked_duration_since(start).unwrap(); - match samples { - Some(samples) => log::info!( - "Finished rendering {samples} samples in {} and {ray_count} Rays at {:.2} Mray/s - {}", + log::info!( + "Finished rendering:\n\tSamples:\t{samples}\n\tTime taken:\t{}\n\tRays shot:\t{ray_count} @ {:.2} Mray/s", get_readable_duration(duration), (ray_count as f64 / duration.as_secs_f64()) / 1000000.0, - Local::now().format("%X") - ), - None => log::info!( - "Finished rendering in {} and {ray_count} Rays at {:.2} Mray/s - {}", - get_readable_duration(duration), - (ray_count as f64 / duration.as_secs_f64()) / 1000000.0, - Local::now().format("%X") - ), - } + ) } pub fn print_render_start(width: u64, height: u64, samples: Option) -> Instant { match samples { Some(samples) => log::info!( - "Render started ({width}x{height}x{samples}) - {}", - Local::now().format("%X") - ), - None => log::info!( - "Render started ({width}x{height}x∞) - {}", - Local::now().format("%X") + "Render started:\n\tWidth:\t\t{width}\n\tHeight:\t\t{height}\n\tSamples:\t{samples}" ), + None => { + log::info!("Render started:\n\tWidth:\t\t{width}\n\tHeight:\t\t{height}\n\tSamples:\t∞") + } } Instant::now() } diff --git a/scenes/rtweekend1.ssml b/scenes/rtweekend1.ssml new file mode 100644 index 0000000..ae69b28 --- /dev/null +++ b/scenes/rtweekend1.ssml @@ -0,0 +1,43 @@ +camera ( + origin 0 0 0 + lookat 0 1 0 + vup 0 0 1 + fov 121.28449291441745 + aperture 0.0 + focus_dis 1.0 +) + +texture sky ( + type lerp + primary 0.5 0.7 1.0 + secondary 1.0 +) + +sky ( + texture sky +) + +texture grey ( + type solid + colour 0.5 +) + +material ground ( + type lambertian + texture grey + albedo 1.0 +) + +primitive ( + type sphere + material ground + centre 0 1 -100.5 + radius 100 +) + +primitive ( + type sphere + material ground + centre 0 1 0 + radius 0.5 +) diff --git a/src/main.rs b/src/main.rs index 0178675..c34abbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,10 +39,13 @@ fn render_gui( None, ) .unwrap(); + let exit = Arc::new(AtomicBool::new(false)); + let gui = Gui::new( &instance, render_options.width as u32, render_options.height as u32, + exit.clone(), ); let event_loop_proxy: Option> = @@ -79,11 +82,10 @@ fn render_gui( samples.clone(), render_options.samples_per_pixel, ray_count.clone(), + exit, event_loop_proxy.unwrap(), ); - let image_copy_finished = data.to_sc.clone(); - let start = print_render_start( render_options.width, render_options.height, @@ -95,7 +97,7 @@ fn render_gui( let moved_render_canceled = render_canceled.clone(); let moved_filename = filename.clone(); - std::thread::spawn(move || { + let handle = std::thread::spawn(move || { let ray_count = data.rays_shot.clone(); let samples = data.samples.clone(); let buffer = data.buffer.clone(); @@ -105,8 +107,8 @@ fn render_gui( render_options, Some(( &mut data, - |data: &mut Data, previous: &SamplerProgress, i: u64| { - sample_update(data, previous, i); + |data: &mut Data, previous: &SamplerProgress, i: u64| -> bool { + sample_update(data, previous, i) }, )), ); @@ -114,7 +116,7 @@ fn render_gui( let ray_count = ray_count.load(Ordering::Relaxed); let samples = samples.load(Ordering::Relaxed); - print_final_statistics(start, ray_count, Some(samples)); + print_final_statistics(start, ray_count, samples); moved_render_canceled.store(false, Ordering::Relaxed); @@ -128,20 +130,7 @@ fn render_gui( }); gui.run(); - if render_canceled.load(Ordering::Relaxed) { - let ray_count = ray_count.load(Ordering::Relaxed); - let samples = samples.load(Ordering::Relaxed); - - print_final_statistics(start, ray_count, Some(samples)); - - save_file( - filename, - render_options.width, - render_options.height, - &*buffer.read().unwrap(), - image_copy_finished, - ); - } + handle.join().unwrap(); } fn render_tui( @@ -174,7 +163,7 @@ fn render_tui( .unwrap(), ), }; - let progress_bar_output = |sp: &mut Progress, previous: &SamplerProgress, i: u64| { + let progress_bar_output = |sp: &mut Progress, previous: &SamplerProgress, i: u64| -> bool { sp.sampler_progress.samples_completed += 1; sp.sampler_progress.rays_shot += previous.rays_shot; @@ -189,6 +178,7 @@ fn render_tui( if sp.sampler_progress.samples_completed == render_options.samples_per_pixel { sp.bar.finish_and_clear() } + false }; scene.render(render_options, Some((&mut image, progress_bar_output))); @@ -197,7 +187,7 @@ fn render_tui( let ray_count = output.sampler_progress.rays_shot; - print_final_statistics(start, ray_count, None); + print_final_statistics(start, ray_count, output.sampler_progress.samples_completed); let output: Vec = output .sampler_progress @@ -260,7 +250,10 @@ fn save_file( save_u8_to_image( width, height, - buffer.iter().map(|val| (val * 255.999) as u8).collect(), + buffer + .iter() + .map(|val| (val.sqrt() * 255.999) as u8) + .collect(), filename, true, ) diff --git a/src/scene.rs b/src/scene.rs index 5cf30af..ba60d3c 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -37,7 +37,7 @@ where pub fn render( &self, opts: RenderOptions, - update: Option<(&mut T, impl Fn(&mut T, &SamplerProgress, u64))>, + update: Option<(&mut T, impl Fn(&mut T, &SamplerProgress, u64) -> bool)>, ) { let sampler = RandomSampler {}; sampler.sample_image(opts, &self.camera, &self.sky, &self.acceleration, update); @@ -142,7 +142,7 @@ primitive ( width: 1920, height: 1080, }, - None as Option<(&mut (), fn(&mut (), &SamplerProgress, u64))>, + None as Option<(&mut (), fn(&mut (), &SamplerProgress, u64) -> bool)>, ); } } From f7245661036461151d621b4c2f6fbc72252ec2f1 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 15 Mar 2023 20:14:35 +1100 Subject: [PATCH 30/39] Added loading support for aacuboids --- .../implementations/src/primitives/sphere.rs | 2 +- .../src/primitives/triangle.rs | 8 +- crates/loader/src/lib.rs | 30 ++++- crates/loader/src/meshes.rs | 103 ++++++++++++++++++ crates/loader/src/parser.rs | 6 + crates/rt_core/src/vec.rs | 25 +++++ scenes/scene.ssml | 10 +- 7 files changed, 168 insertions(+), 16 deletions(-) create mode 100644 crates/loader/src/meshes.rs diff --git a/crates/implementations/src/primitives/sphere.rs b/crates/implementations/src/primitives/sphere.rs index fb2008d..eab75b7 100644 --- a/crates/implementations/src/primitives/sphere.rs +++ b/crates/implementations/src/primitives/sphere.rs @@ -97,7 +97,7 @@ where normal, self.get_uv(point), out, - &self.material, + self.material, )) } else { None diff --git a/crates/implementations/src/primitives/triangle.rs b/crates/implementations/src/primitives/triangle.rs index 58a14a2..f82573b 100644 --- a/crates/implementations/src/primitives/triangle.rs +++ b/crates/implementations/src/primitives/triangle.rs @@ -43,13 +43,13 @@ where point_indices: [usize; 3], normal_indices: [usize; 3], material: &'a M, - mesh: &Arc, + mesh: Arc, ) -> Self { MeshTriangle { point_indices, normal_indices, material, - mesh: mesh.clone(), + mesh, } } } @@ -83,7 +83,7 @@ where self.normals[index] } fn get_material(&self) -> &'a M { - &self.material + self.material } } @@ -98,7 +98,7 @@ where self.mesh.normals[self.normal_indices[index]] } fn get_material(&self) -> &'a M { - &self.material + self.material } } diff --git a/crates/loader/src/lib.rs b/crates/loader/src/lib.rs index e51d077..e254a89 100644 --- a/crates/loader/src/lib.rs +++ b/crates/loader/src/lib.rs @@ -1,4 +1,5 @@ pub mod materials; +pub mod meshes; pub mod misc; pub mod parser; pub mod primitives; @@ -192,6 +193,7 @@ where P: Primitive + Load + Clone, C: Camera + Load, S: NoHit + Load, + Vec

: Load, { let scene_file = match std::fs::read_to_string(file) { Ok(s) => s, @@ -217,9 +219,9 @@ where log::info!("Loading primitives..."); let primitives = { - let primitives = load_primitives::

(&scene_conf, &lookup)?; - //let block_size = std::mem::size_of::

() * primitives.len(); - //let block = region.allocate_block(block_size); + let mut primitives = load_primitives::

(&scene_conf, &lookup)?; + log::info!("Loading meshes..."); + primitives.extend(load_meshes::

(&scene_conf, &lookup)?); region.alloc_slice(&primitives) }; @@ -240,6 +242,7 @@ where P: Primitive + Load + Clone, C: Camera + Load, S: NoHit + Load, + Vec

: Load, { let scene_conf = match parser::from_str(data) { Ok(c) => c, @@ -260,9 +263,9 @@ where log::info!("Loading primitives..."); let primitives = { - let primitives = load_primitives::

(&scene_conf, &lookup)?; - //let block_size = std::mem::size_of::

() * primitives.len(); - //let block = region.allocate_block(block_size); + let mut primitives = load_primitives::

(&scene_conf, &lookup)?; + log::info!("Loading meshes..."); + primitives.extend(load_meshes::

(&scene_conf, &lookup)?); region.alloc_slice(&primitives) }; @@ -388,6 +391,21 @@ fn load_primitives( Ok(primitives) } +fn load_meshes( + objects: &[parser::Object], + lookup: &Lookup, +) -> Result, LoadErr> +where + Vec

: Load, +{ + let mut primitives = Vec::new(); + for obj in objects.iter().filter(|o| o.kind.is_mesh()) { + let props = Properties::new(lookup, obj); + primitives.extend( as Load>::load(props)?.1); + } + Ok(primitives) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/loader/src/meshes.rs b/crates/loader/src/meshes.rs new file mode 100644 index 0000000..7964e82 --- /dev/null +++ b/crates/loader/src/meshes.rs @@ -0,0 +1,103 @@ +use crate::Properties; +use crate::*; +use implementations::triangle::MeshData; +use implementations::triangle::MeshTriangle; +use implementations::*; + +impl Load for Vec> { + fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + let kind = match props.text("type") { + Some(k) => k, + None => return Err(LoadErr::MissingRequiredVariantType), + }; + match kind { + "mesh" => { + unimplemented!() + } + "aacuboid" => cuboid(props), + o => { + return Err(LoadErr::MissingRequired(format!( + "required a known value for mesh type, found '{o}'" + ))) + } + } + } +} + +fn cuboid<'a, M: Scatter>( + props: Properties, +) -> Result<(Option, Vec>), LoadErr> { + let mat: region::RegionRes = props + .scatter("material") + .unwrap_or_else(|| props.default_scatter()); + let point_one = match props.vec3("point_one") { + Some(c) => c, + None => { + return Err(LoadErr::MissingRequired( + "expected point_one on aacubiod, found nothing".to_string(), + )) + } + }; + let point_two = match props.vec3("point_two") { + Some(c) => c, + None => { + return Err(LoadErr::MissingRequired( + "expected point_two on aacubiod, found nothing".to_string(), + )) + } + }; + + let min = point_one.min_by_component(point_two); + let max = point_one.max_by_component(point_two); + + let points = vec![ + min, // 0 + Vec3::new(max.x, min.y, min.z), // 1 + Vec3::new(max.x, max.y, min.z), // 2 + Vec3::new(min.x, max.y, min.z), // 3 + Vec3::new(min.x, min.y, max.z), // 4 + Vec3::new(max.x, min.y, max.z), // 5 + max, // 6 + Vec3::new(min.x, max.y, max.z), // 7 + ]; + + let normals = vec![ + Vec3::x(), // 0 + -Vec3::x(), // 1 + Vec3::y(), // 2 + -Vec3::y(), // 3 + Vec3::z(), // 4 + -Vec3::z(), // 5 + ]; + + let mesh_data = std::sync::Arc::new(MeshData::new(points, normals)); + std::mem::forget(mesh_data.clone()); // prevent drop when primitives get moved to region + + macro_rules! mesh_tri { + ($p:expr, $normal:expr) => { + AllPrimitives::MeshTriangle(MeshTriangle::new( + $p, + [$normal; 3], + unsafe { &*(&*mat as *const _) }, + mesh_data.clone(), + )) + }; + } + + let triangles = vec![ + mesh_tri!([0, 1, 2], 5), + mesh_tri!([0, 2, 3], 5), + mesh_tri!([0, 1, 5], 3), + mesh_tri!([0, 5, 4], 3), + mesh_tri!([1, 2, 5], 0), + mesh_tri!([2, 5, 6], 0), + mesh_tri!([2, 3, 7], 2), + mesh_tri!([2, 6, 7], 2), + mesh_tri!([0, 3, 4], 1), + mesh_tri!([3, 4, 7], 1), + mesh_tri!([4, 5, 6], 4), + mesh_tri!([4, 6, 7], 4), + ]; + + Ok((None, triangles)) +} diff --git a/crates/loader/src/parser.rs b/crates/loader/src/parser.rs index f11d496..98cc1d9 100644 --- a/crates/loader/src/parser.rs +++ b/crates/loader/src/parser.rs @@ -12,6 +12,7 @@ pub enum ObjectKind { Primitive, Sky, Texture, + Mesh, Other, } @@ -54,6 +55,10 @@ impl ObjectKind { pub fn is_texture(&self) -> bool { matches!(self, ObjectKind::Texture) } + + pub fn is_mesh(&self) -> bool { + matches!(self, ObjectKind::Mesh) + } } impl<'a> Object<'a> { @@ -146,6 +151,7 @@ mod ver1 { map(tag("primitive"), |_| ObjectKind::Primitive), map(tag("sky"), |_| ObjectKind::Sky), map(tag("texture"), |_| ObjectKind::Texture), + map(tag("mesh"), |_| ObjectKind::Mesh), ))(i) } diff --git a/crates/rt_core/src/vec.rs b/crates/rt_core/src/vec.rs index bd7d6ab..a8ca370 100644 --- a/crates/rt_core/src/vec.rs +++ b/crates/rt_core/src/vec.rs @@ -136,6 +136,21 @@ impl Vec3 { Vec3::new(0.0, 0.0, 0.0) } + #[inline] + pub fn x() -> Self { + Vec3::new(1.0, 0.0, 0.0) + } + + #[inline] + pub fn y() -> Self { + Vec3::new(0.0, 1.0, 0.0) + } + + #[inline] + pub fn z() -> Self { + Vec3::new(0.0, 0.0, 1.0) + } + #[inline] pub fn from_spherical( sin_theta: Float, @@ -248,6 +263,16 @@ impl Vec2 { Vec2::new(0.0, 0.0) } + #[inline] + pub fn x() -> Self { + Vec2::new(1.0, 0.0) + } + + #[inline] + pub fn y() -> Self { + Vec2::new(0.0, 1.0) + } + #[inline] pub fn dot(&self, other: Self) -> Float { self.x * other.x + self.y * other.y diff --git a/scenes/scene.ssml b/scenes/scene.ssml index e26c3d2..0d491c2 100644 --- a/scenes/scene.ssml +++ b/scenes/scene.ssml @@ -52,9 +52,9 @@ primitive ( radius 0.5 ) -primitive ( - type sphere +mesh ( + type aacuboid material ground - centre -0.45 0.15 -0.45 - radius 0.05 -) + point_one -0.5 0.1 -0.5 + point_two -0.4 0.2 -0.4 +) \ No newline at end of file From 786d00933ba6e0fbdea97de1dc49e84a73f383b9 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Thu, 16 Mar 2023 17:56:25 +1100 Subject: [PATCH 31/39] Added model loading --- Cargo.toml | 1 - .../src/samplers/random_sampler.rs | 4 +- crates/loader/Cargo.toml | 1 + crates/loader/src/lib.rs | 6 +- crates/loader/src/materials.rs | 22 ++++++++ crates/loader/src/meshes.rs | 20 ++++++- src/load_model.rs => crates/loader/src/obj.rs | 56 +++++++------------ crates/loader/src/primitives.rs | 8 +-- 8 files changed, 71 insertions(+), 47 deletions(-) rename src/load_model.rs => crates/loader/src/obj.rs (51%) diff --git a/Cargo.toml b/Cargo.toml index 8b9bdaa..709f8a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ region = { path = "./crates/region" } vulkano = { version = "0.28.0", optional = true } vulkano-shaders = { version = "0.28.0", optional = true } vulkano-win = { version = "0.28.0", optional = true } -wavefront_obj = "10.0.0" winit = { version = "0.26.1", optional = true } [features] diff --git a/crates/implementations/src/samplers/random_sampler.rs b/crates/implementations/src/samplers/random_sampler.rs index 02e185d..324b355 100644 --- a/crates/implementations/src/samplers/random_sampler.rs +++ b/crates/implementations/src/samplers/random_sampler.rs @@ -54,10 +54,10 @@ impl Sampler for RandomSampler { let x = pixel_i % render_options.width; let y = (pixel_i - x) / render_options.width; let u = (rng.gen_range(0.0..1.0) + x as Float) - / render_options.width as Float; + / (render_options.width - 1) as Float; let v = 1.0 - (rng.gen_range(0.0..1.0) + y as Float) - / render_options.height as Float; + / (render_options.height - 1) as Float; let mut ray = camera.get_ray(u, v); // remember to add le DOF let result = match render_options.render_method { diff --git a/crates/loader/Cargo.toml b/crates/loader/Cargo.toml index c22b6f0..59ca43e 100644 --- a/crates/loader/Cargo.toml +++ b/crates/loader/Cargo.toml @@ -9,6 +9,7 @@ nom = "7" region = { path = "../region" } implementations = { path = "../implementations" } thiserror = "1.0" +wavefront_obj = "10.0.0" [features] f64 = ["implementations/f64"] diff --git a/crates/loader/src/lib.rs b/crates/loader/src/lib.rs index e254a89..f2c765b 100644 --- a/crates/loader/src/lib.rs +++ b/crates/loader/src/lib.rs @@ -1,6 +1,7 @@ pub mod materials; pub mod meshes; pub mod misc; +pub mod obj; pub mod parser; pub mod primitives; pub mod textures; @@ -125,6 +126,9 @@ impl<'a> Properties<'a> { pub fn scatter(&self, name: &str) -> Option> { self.lookup.scatter_lookup(self.text(name)?) } + pub fn lookup_material(&self, name: &str) -> Option> { + self.lookup.scatter_lookup(name) + } pub fn vec3(&self, name: &str) -> Option { match self.props.get(name) { Some(PropertiesValue::Vec3(x)) => Some(*x), @@ -369,7 +373,7 @@ fn load_materials( values: [ ("type", ObjectValue::Text("lambertian")), ("texture", ObjectValue::Text("__DEFAULT_TEX")), - ("albedo", ObjectValue::Num1(1.0)), + ("albedo", ObjectValue::Num1(0.25)), ] .into(), }; diff --git a/crates/loader/src/materials.rs b/crates/loader/src/materials.rs index 88626b8..1fb8945 100644 --- a/crates/loader/src/materials.rs +++ b/crates/loader/src/materials.rs @@ -27,6 +27,10 @@ impl Load for AllMaterials<'_, T> { let x = Refract::load(props)?; (x.0, Self::Refract(x.1)) } + "trowbridge_reitz" => { + let x = TrowbridgeReitz::load(props)?; + (x.0, Self::TrowbridgeReitz(x.1)) + } o => { return Err(LoadErr::MissingRequired(format!( "required a known value for material type, found '{o}'" @@ -88,6 +92,24 @@ impl Load for Refract<'_, T> { } } +impl Load for TrowbridgeReitz<'_, T> { + fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + let tex = props + .texture("texture") + .unwrap_or_else(|| props.default_texture()); + let alpha = props.float("alpha").unwrap_or(0.5); + let ior = props.vec3("ior").unwrap_or(Vec3::one()); + let metallic = props.float("metallic").unwrap_or(0.0); + + let name = props.name(); + + Ok(( + name, + Self::new(unsafe { &*(&*tex as *const _) }, alpha, ior, metallic), + )) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/loader/src/meshes.rs b/crates/loader/src/meshes.rs index 7964e82..164e9fd 100644 --- a/crates/loader/src/meshes.rs +++ b/crates/loader/src/meshes.rs @@ -1,3 +1,4 @@ +use crate::obj::load_obj; use crate::Properties; use crate::*; use implementations::triangle::MeshData; @@ -11,9 +12,7 @@ impl Load for Vec> { None => return Err(LoadErr::MissingRequiredVariantType), }; match kind { - "mesh" => { - unimplemented!() - } + "mesh" => mesh(props), "aacuboid" => cuboid(props), o => { return Err(LoadErr::MissingRequired(format!( @@ -101,3 +100,18 @@ fn cuboid<'a, M: Scatter>( Ok((None, triangles)) } + +fn mesh<'a, M: Scatter>( + props: Properties, +) -> Result<(Option, Vec>), LoadErr> { + let filepath = match props.text("obj") { + Some(c) => c.to_owned(), + None => { + return Err(LoadErr::MissingRequired( + "expected obj on mesh, found nothing".to_string(), + )) + } + }; + let prims = load_obj(&filepath, props); + Ok((None, prims)) +} diff --git a/src/load_model.rs b/crates/loader/src/obj.rs similarity index 51% rename from src/load_model.rs rename to crates/loader/src/obj.rs index 785b891..4f5659b 100644 --- a/src/load_model.rs +++ b/crates/loader/src/obj.rs @@ -1,17 +1,17 @@ -use scene::implementations::{ - rt_core::*, +use crate::Float; +use crate::Properties; +use crate::Scatter; +use crate::Vec3; +use implementations::{ triangle::{MeshData, MeshTriangle}, - AllMaterials, AllPrimitives, AllTextures, + AllPrimitives, }; use std::sync::Arc; -pub fn load_model_with_materials( - filepath: &str, - materials: &[(Arc>, &str)], -) -> Vec>> { +pub fn load_obj<'a, M: Scatter>(filepath: &str, props: Properties) -> Vec> { let model = wavefront_obj::obj::parse(&std::fs::read_to_string(filepath).unwrap()).unwrap(); - let mut primitives: Vec>> = Vec::new(); + let mut primitives: Vec> = Vec::new(); for object in model.objects { let mesh_data: Arc = Arc::new(MeshData::new( @@ -34,26 +34,28 @@ pub fn load_model_with_materials( panic!("Please export obj file with vertex normals!"); } - let mat = get_material( - materials, - geometric_object - .material_name - .as_ref() - .unwrap_or(&"default".to_owned()), - ); + let mat: region::RegionRes = props + .lookup_material( + geometric_object + .material_name + .as_ref() + .unwrap_or(&"default".to_owned()), + ) + .unwrap_or_else(|| props.default_scatter()); - let triangle: AllPrimitives> = + let triangle: AllPrimitives<'a, M> = AllPrimitives::MeshTriangle(MeshTriangle::new( [i1.0, i2.0, i3.0], [i1.2.unwrap(), i2.2.unwrap(), i3.2.unwrap()], - &mat, - &mesh_data, + unsafe { &*(&*mat as *const _) }, + mesh_data.clone(), )); primitives.push(triangle) } } } + std::mem::forget(mesh_data); } primitives } @@ -61,21 +63,3 @@ pub fn load_model_with_materials( fn vertex_to_vec3(vertex: wavefront_obj::obj::Vertex) -> Vec3 { Vec3::new(vertex.x as Float, vertex.y as Float, vertex.z as Float) } - -fn get_material( - materials: &[(Arc>, &str)], - name: &str, -) -> Arc> { - let mat = materials.iter().find(|&v| v.1 == name); - - match mat { - Some(mat) => mat.0.clone(), - None => { - let mat = materials.iter().find(|&v| v.1 == "default"); - if let Some(mat) = mat { - return mat.0.clone(); - } - panic!("{name} material not found"); - } - } -} diff --git a/crates/loader/src/primitives.rs b/crates/loader/src/primitives.rs index d131baa..145071f 100644 --- a/crates/loader/src/primitives.rs +++ b/crates/loader/src/primitives.rs @@ -39,17 +39,17 @@ impl Load for AllPrimitives<'_, M> { let x = Sphere::load(props)?; (x.0, Self::Sphere(x.1)) } - _ => todo!(), - /*o => { + "triangle" => todo!(), + o => { return Err(LoadErr::MissingRequired(format!( "required a known value for material type, found '{o}'" ))) - }*/ + } }) } } -// TODO LOAD FOR TRIANGLE & MESH +// TODO LOAD FOR TRIANGLE #[cfg(test)] mod tests { From ed94fa1269e2b5e7f9fbb103bfac2d4b825e46a2 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Tue, 21 Mar 2023 20:56:07 +1100 Subject: [PATCH 32/39] Fixed TrowbridgeReitz --- .../implementations/src/materials/refract.rs | 2 +- .../src/materials/trowbridge_reitz.rs | 24 ++++----- .../src/statistics/bxdfs/trowbridge_reitz.rs | 54 +++++++++++++++++-- scenes/{scene.ssml => overshadowed.ssml} | 0 4 files changed, 64 insertions(+), 16 deletions(-) rename scenes/{scene.ssml => overshadowed.ssml} (100%) diff --git a/crates/implementations/src/materials/refract.rs b/crates/implementations/src/materials/refract.rs index 9a1650f..0b30e99 100644 --- a/crates/implementations/src/materials/refract.rs +++ b/crates/implementations/src/materials/refract.rs @@ -30,7 +30,7 @@ where eta_fraction = self.eta; } - let cos_theta = ((-1.0 * ray.direction).dot(hit.normal)).min(1.0); + let cos_theta = ((-ray.direction).dot(hit.normal)).min(1.0); let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); let cannot_refract = eta_fraction * sin_theta > 1.0; diff --git a/crates/implementations/src/materials/trowbridge_reitz.rs b/crates/implementations/src/materials/trowbridge_reitz.rs index 990e75c..62ababc 100644 --- a/crates/implementations/src/materials/trowbridge_reitz.rs +++ b/crates/implementations/src/materials/trowbridge_reitz.rs @@ -29,7 +29,7 @@ where let f0 = ((1.0 - self.ior) / (1.0 + self.ior)).abs(); let f0 = f0 * f0; let f0 = lerp(f0, self.texture.colour_value(wi, hit.point), self.metallic); - refract::fresnel(wo.dot(h), f0) + refract::fresnel((-wo).dot(h), f0) } } @@ -59,29 +59,29 @@ where } } fn eval(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Vec3 { - let wo = -wo; - let h = (wi + wo).normalised(); + let h = (wi - wo).normalised(); - if wi.dot(hit.normal) < 0.0 || h.dot(wo) < 0.0 { + if wi.dot(hit.normal) < 0.0 || h.dot(wo) > 0.0 { return Vec3::zero(); } - self.fresnel(hit, wo, wi, h) - * trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi) - * trowbridge_reitz::d(self.alpha, hit.normal.dot(h)) - / (4.0 * wo.dot(hit.normal).abs()) + let f = self.fresnel(hit, wo, wi, h); + let g = trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi); + let d = trowbridge_reitz::d(self.alpha, hit.normal.dot(h)); + + f * g * d / (4.0 * (-wo).dot(hit.normal).abs() * wi.dot(hit.normal)) } fn eval_over_scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Vec3 { - let wo = -wo; - let h = (wi + wo).normalised(); + let h = (wi - wo).normalised(); - if wo.dot(h) < 0.0 || wi.dot(hit.normal) < 0.0 { + if wo.dot(h) > 0.0 || wi.dot(hit.normal) < 0.0 { return Vec3::zero(); } self.fresnel(hit, wo, wi, h) * trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi) - * wo.dot(h) / (wo.dot(hit.normal) * h.dot(hit.normal)).abs() + * (-wo).dot(h) + / (wo.dot(hit.normal) * h.dot(hit.normal)).abs() } } diff --git a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs index 25e1702..c8264bc 100644 --- a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs +++ b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs @@ -41,7 +41,9 @@ pub fn pdf_local(alpha: Float, incoming: Vec3, outgoing: Vec3) -> Float { pub fn sample(alpha: Float, incoming: Vec3, normal: Vec3, rng: &mut R) -> Vec3 { let coord = Coordinate::new_from_z(normal); - let h = coord.to_coord(sample_h(alpha, rng)); + let local_h = sample_h(alpha, rng); + let h = coord.to_coord(local_h); + incoming.reflected(h) } @@ -58,9 +60,13 @@ pub fn pdf(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float } pub fn g2(alpha: Float, normal: Vec3, h: Vec3, incoming: Vec3, outgoing: Vec3) -> Float { - if incoming.dot(h) / incoming.z <= 0.0 || outgoing.dot(h) / outgoing.z <= 0.0 { + let incoming = -incoming; + if incoming.dot(h) / incoming.dot(normal) <= 0.0 + || outgoing.dot(h) / outgoing.dot(normal) <= 0.0 + { return 0.0; } + let alpha_sq = alpha * alpha; let one_minus_alpha_sq = 1.0 - alpha_sq; let cos_i = normal.dot(incoming); @@ -73,7 +79,7 @@ pub fn g2(alpha: Float, normal: Vec3, h: Vec3, incoming: Vec3, outgoing: Vec3) - } pub fn g1(alpha: Float, normal: Vec3, h: Vec3, v: Vec3) -> Float { - if v.dot(h) / v.z <= 0.0 { + if v.dot(h) / v.dot(normal) <= 0.0 { return 0.0; } let cos = normal.dot(v); @@ -136,6 +142,25 @@ mod tests { assert!((integral - cos_theta).abs() < 0.0001); } + #[test] + fn projected_area_test_local() { + let mut rng = thread_rng(); + let alpha = rng.gen(); + let test = |h: Vec3| d(alpha, h.z) * h.z; + let integral = integrate_over_sphere(&test); + assert!((integral - 1.0).abs() < 0.0001); + } + + #[test] + fn projected_area_test_non_local() { + let mut rng = thread_rng(); + let normal = random_unit_vector(&mut rng); + let alpha = rng.gen(); + let test = |h: Vec3| d(alpha, h.dot(normal)) * h.dot(normal); + let integral = integrate_over_sphere(&test); + assert!((integral - 1.0).abs() < 0.0001); + } + #[test] fn weak_furnace_test() { let mut rng = thread_rng(); @@ -181,4 +206,27 @@ mod tests { let integral = integrate_over_sphere(&test); assert!(integral <= 1.0); } + + #[test] + fn g2_test_non_local() { + let mut rng = thread_rng(); + let a = -generate_wi(&mut rng); + let normal = random_unit_vector(&mut rng); + let alpha = rng.gen(); + let test = |b: Vec3| { + let mut h = (a + b).normalised(); + if h.dot(normal) < 0.0 { + h = -h; + } + let denom = 4.0 * a.dot(-normal).abs(); + if denom < 0.000000001 { + 0.0 + } else { + g2(alpha, normal, h, a, b) * d(alpha, a.dot(-normal)) / denom + } + }; + + let integral = integrate_over_sphere(&test); + assert!(integral <= 1.0); + } } diff --git a/scenes/scene.ssml b/scenes/overshadowed.ssml similarity index 100% rename from scenes/scene.ssml rename to scenes/overshadowed.ssml From 014c5b71fe29c1bb1820390b43a2794a11b22d3b Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 22 Mar 2023 22:25:16 +1100 Subject: [PATCH 33/39] Changed statistics::bxdfs to expect wo = -wo --- .../src/materials/lambertian.rs | 2 +- .../implementations/src/materials/reflect.rs | 2 +- .../src/materials/trowbridge_reitz.rs | 26 +++++++------ .../src/statistics/bxdfs/mod.rs | 2 +- .../src/statistics/bxdfs/trowbridge_reitz.rs | 14 +++---- .../statistics/bxdfs/trowbridge_reitz_vndf.rs | 39 +++++++++---------- crates/rt_core/src/vec.rs | 6 +-- 7 files changed, 47 insertions(+), 44 deletions(-) diff --git a/crates/implementations/src/materials/lambertian.rs b/crates/implementations/src/materials/lambertian.rs index 0521f96..d4b2a9e 100644 --- a/crates/implementations/src/materials/lambertian.rs +++ b/crates/implementations/src/materials/lambertian.rs @@ -29,7 +29,7 @@ where { fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { let direction = crate::statistics::bxdfs::lambertian::sample( - ray.direction, + ray.direction, // no negation since lambertian::sample doesn't use ray.direction hit.normal, &mut SmallRng::from_rng(thread_rng()).unwrap(), ); diff --git a/crates/implementations/src/materials/reflect.rs b/crates/implementations/src/materials/reflect.rs index 880b3dc..b22052d 100644 --- a/crates/implementations/src/materials/reflect.rs +++ b/crates/implementations/src/materials/reflect.rs @@ -24,7 +24,7 @@ where T: Texture, { fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { - let mut direction = ray.direction; + let mut direction = -ray.direction; direction.reflect(hit.normal); let point = offset_ray(hit.point, hit.normal, hit.error, true); *ray = Ray::new( diff --git a/crates/implementations/src/materials/trowbridge_reitz.rs b/crates/implementations/src/materials/trowbridge_reitz.rs index 62ababc..8a33d07 100644 --- a/crates/implementations/src/materials/trowbridge_reitz.rs +++ b/crates/implementations/src/materials/trowbridge_reitz.rs @@ -29,7 +29,7 @@ where let f0 = ((1.0 - self.ior) / (1.0 + self.ior)).abs(); let f0 = f0 * f0; let f0 = lerp(f0, self.texture.colour_value(wi, hit.point), self.metallic); - refract::fresnel((-wo).dot(h), f0) + refract::fresnel(wo.dot(h), f0) } } @@ -40,7 +40,7 @@ where fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { let direction = trowbridge_reitz::sample( self.alpha, - ray.direction, + -ray.direction, hit.normal, &mut SmallRng::from_rng(thread_rng()).unwrap(), ); @@ -51,6 +51,7 @@ where false } fn scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Float { + let wo = -wo; let a = trowbridge_reitz::pdf(self.alpha, wo, wi, hit.normal); if a == 0.0 { INFINITY @@ -59,9 +60,10 @@ where } } fn eval(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Vec3 { - let h = (wi - wo).normalised(); + let wo = -wo; + let h = (wi + wo).normalised(); - if wi.dot(hit.normal) < 0.0 || h.dot(wo) > 0.0 { + if wi.dot(hit.normal) < 0.0 || h.dot(wo) < 0.0 { return Vec3::zero(); } @@ -69,19 +71,21 @@ where let g = trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi); let d = trowbridge_reitz::d(self.alpha, hit.normal.dot(h)); - f * g * d / (4.0 * (-wo).dot(hit.normal).abs() * wi.dot(hit.normal)) + f * g * d / (4.0 * wo.dot(hit.normal).abs() * wi.dot(hit.normal)) } fn eval_over_scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Vec3 { - let h = (wi - wo).normalised(); + let wo = -wo; + let h = (wi + wo).normalised(); - if wo.dot(h) > 0.0 || wi.dot(hit.normal) < 0.0 { + if wo.dot(h) < 0.0 || wi.dot(hit.normal) < 0.0 { return Vec3::zero(); } - self.fresnel(hit, wo, wi, h) - * trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi) - * (-wo).dot(h) - / (wo.dot(hit.normal) * h.dot(hit.normal)).abs() + let f = self.fresnel(hit, wo, wi, h); + + let g = trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi); + + f * g * wo.dot(h) / (wo.dot(hit.normal) * h.dot(hit.normal)).abs() } } diff --git a/crates/implementations/src/statistics/bxdfs/mod.rs b/crates/implementations/src/statistics/bxdfs/mod.rs index 8281b26..bbb40db 100644 --- a/crates/implementations/src/statistics/bxdfs/mod.rs +++ b/crates/implementations/src/statistics/bxdfs/mod.rs @@ -8,6 +8,6 @@ pub mod trowbridge_reitz_vndf; // pdf(incoming, outgoing, normal, ...) // If implementations of the above are provided in local space as well // they must adhere to the same naming expect with _local and no normal parameter -// For the forementioned functions incoming will be pointing towards the surface +// For the forementioned functions incoming will be pointing away from the surface // outgoing will be pointing away from the surface and is the sampled direction // Note that auxillary function do not have to adhere to the above diff --git a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs index c8264bc..946a651 100644 --- a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs +++ b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs @@ -31,9 +31,9 @@ pub fn sample_local(alpha: Float, incoming: Vec3, rng: &mut R) -> Vec3 { } pub fn pdf_local(alpha: Float, incoming: Vec3, outgoing: Vec3) -> Float { - let mut h = (outgoing - incoming).normalised(); + let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = (incoming - outgoing).normalised(); + h = -h; } let d = d(alpha, h.z); d * h.z.abs() / (4.0 * outgoing.dot(h).abs()) @@ -51,16 +51,16 @@ pub fn pdf(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float let inverse = Coordinate::new_from_z(normal).create_inverse(); let incoming = inverse.to_coord(incoming); let outgoing = inverse.to_coord(outgoing); - let mut h = (outgoing - incoming).normalised(); + let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = (incoming - outgoing).normalised(); + h = -h; } let d = d(alpha, h.z); d * h.z.abs() / (4.0 * outgoing.dot(h).abs()) } pub fn g2(alpha: Float, normal: Vec3, h: Vec3, incoming: Vec3, outgoing: Vec3) -> Float { - let incoming = -incoming; + //let incoming = -incoming; if incoming.dot(h) / incoming.dot(normal) <= 0.0 || outgoing.dot(h) / outgoing.dot(normal) <= 0.0 { @@ -107,7 +107,7 @@ mod tests { #[test] fn tr() { let mut rng = thread_rng(); - let incoming = generate_wi(&mut rng); + let incoming = -generate_wi(&mut rng); let alpha = rng.gen(); let pdf = |outgoing: Vec3| pdf_local(alpha, incoming, outgoing); let sample = |rng: &mut ThreadRng| sample_local(alpha, incoming, rng); @@ -119,7 +119,7 @@ mod tests { let mut rng = thread_rng(); let normal = random_unit_vector(&mut rng); let to_local = Coordinate::new_from_z(normal); - let incoming = to_local.to_coord(generate_wi(&mut rng)); + let incoming = to_local.to_coord(-generate_wi(&mut rng)); let alpha = rng.gen(); let pdf = |outgoing: Vec3| pdf(alpha, incoming, outgoing, normal); let sample = |rng: &mut ThreadRng| sample(alpha, incoming, normal, rng); diff --git a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs index b1dd45a..80af5cb 100644 --- a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs +++ b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs @@ -20,16 +20,15 @@ pub mod isotropic { } pub fn sample_local(a: Float, incoming: Vec3, rng: &mut R) -> Vec3 { - let h = sample_vndf(a, -incoming, rng); + let h = sample_vndf(a, incoming, rng); incoming.reflected(h) } pub fn pdf_local(alpha: Float, incoming: Vec3, outgoing: Vec3) -> Float { - let mut h = (outgoing - incoming).normalised(); + let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = (incoming - outgoing).normalised(); + h = -h; //(incoming + outgoing).normalised(); } - let incoming = -incoming; let vndf = vndf(alpha, h, incoming); vndf / (4.0 * incoming.dot(h)) } @@ -37,7 +36,7 @@ pub mod isotropic { pub fn sample(a: Float, incoming: Vec3, normal: Vec3, rng: &mut R) -> Vec3 { let coord = Coordinate::new_from_z(normal); let inverse = coord.create_inverse(); - let h = coord.to_coord(sample_vndf(a, -inverse.to_coord(incoming), rng)); + let h = coord.to_coord(sample_vndf(a, inverse.to_coord(incoming), rng)); incoming.reflected(h) } @@ -45,11 +44,11 @@ pub mod isotropic { let inverse = Coordinate::new_from_z(normal).create_inverse(); let incoming = inverse.to_coord(incoming); let outgoing = inverse.to_coord(outgoing); - let mut h = (outgoing - incoming).normalised(); + let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = (incoming - outgoing).normalised(); + h = -h; //(incoming - outgoing).normalised(); } - let incoming = -incoming; + //let incoming = -incoming; let vndf = vndf(alpha, h, incoming); vndf / (4.0 * incoming.dot(h)) } @@ -111,16 +110,16 @@ pub mod ansiotropic { } pub fn sample_local(a_x: Float, a_y: Float, incoming: Vec3, rng: &mut R) -> Vec3 { - let h = sample_vndf(a_x, a_y, -incoming, rng); + let h = sample_vndf(a_x, a_y, incoming, rng); incoming.reflected(h) } pub fn pdf_local(a_x: Float, a_y: Float, incoming: Vec3, outgoing: Vec3) -> Float { - let mut h = (outgoing - incoming).normalised(); + let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = (incoming - outgoing).normalised(); + h = -h; //(incoming - outgoing).normalised(); } - let incoming = -incoming; + //let incoming = -incoming; let vndf = vndf(a_x, a_y, h, incoming); vndf / (4.0 * incoming.dot(h)) } @@ -134,7 +133,7 @@ pub mod ansiotropic { ) -> Vec3 { let coord = Coordinate::new_from_z(normal); let inverse = coord.create_inverse(); - let h = coord.to_coord(sample_vndf(a_x, a_y, -inverse.to_coord(incoming), rng)); + let h = coord.to_coord(sample_vndf(a_x, a_y, inverse.to_coord(incoming), rng)); incoming.reflected(h) } @@ -142,11 +141,11 @@ pub mod ansiotropic { let inverse = Coordinate::new_from_z(normal).create_inverse(); let incoming = inverse.to_coord(incoming); let outgoing = inverse.to_coord(outgoing); - let mut h = (outgoing - incoming).normalised(); + let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = (incoming - outgoing).normalised(); + h = -h; //(incoming - outgoing).normalised(); } - let incoming = -incoming; + //let incoming = -incoming; let vndf = vndf(a_x, a_y, h, incoming); vndf / (4.0 * incoming.dot(h)) } @@ -171,7 +170,7 @@ mod tests { #[test] fn isotropic() { let mut rng = thread_rng(); - let incoming = generate_wi(&mut rng); + let incoming = -generate_wi(&mut rng); let alpha = rng.gen(); let pdf = |outgoing: Vec3| isotropic::pdf_local(alpha, incoming, outgoing); let sample = |rng: &mut ThreadRng| isotropic::sample_local(alpha, incoming, rng); @@ -183,7 +182,7 @@ mod tests { let mut rng = thread_rng(); let normal = random_unit_vector(&mut rng); let to_local = Coordinate::new_from_z(normal); - let incoming = to_local.to_coord(generate_wi(&mut rng)); + let incoming = to_local.to_coord(-generate_wi(&mut rng)); let alpha = rng.gen(); let pdf = |outgoing: Vec3| isotropic::pdf(alpha, incoming, outgoing, normal); let sample = |rng: &mut ThreadRng| isotropic::sample(alpha, incoming, normal, rng); @@ -203,7 +202,7 @@ mod tests { #[test] fn ansiotropic() { let mut rng = thread_rng(); - let incoming = generate_wi(&mut rng); + let incoming = -generate_wi(&mut rng); let (a_x, a_y) = (rng.gen(), rng.gen()); let pdf = |outgoing: Vec3| ansiotropic::pdf_local(a_x, a_y, incoming, outgoing); let sample = |rng: &mut ThreadRng| ansiotropic::sample_local(a_x, a_y, incoming, rng); @@ -215,7 +214,7 @@ mod tests { let mut rng = thread_rng(); let normal = random_unit_vector(&mut rng); let to_local = Coordinate::new_from_z(normal); - let incoming = to_local.to_coord(generate_wi(&mut rng)); + let incoming = to_local.to_coord(-generate_wi(&mut rng)); let (a_x, a_y) = (rng.gen(), rng.gen()); let pdf = |outgoing: Vec3| ansiotropic::pdf(a_x, a_y, incoming, outgoing, normal); let sample = |rng: &mut ThreadRng| ansiotropic::sample(a_x, a_y, incoming, normal, rng); diff --git a/crates/rt_core/src/vec.rs b/crates/rt_core/src/vec.rs index a8ca370..d085665 100644 --- a/crates/rt_core/src/vec.rs +++ b/crates/rt_core/src/vec.rs @@ -198,15 +198,15 @@ impl Vec3 { pub fn abs(self) -> Self { Vec3::new(self.x.abs(), self.y.abs(), self.z.abs()) } - // note: self is pointing towards surface and normal away + // note: self is pointing away from surface #[inline] pub fn reflect(&mut self, normal: Self) { - *self -= 2.0 * self.dot(normal) * normal + *self = self.reflected(normal) } #[inline] pub fn reflected(&self, normal: Self) -> Self { - *self - 2.0 * self.dot(normal) * normal + 2.0 * self.dot(normal) * normal - *self } #[inline] From b9f28a5ab20595033d0171e20dece354ff058252 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Wed, 22 Mar 2023 22:44:10 +1100 Subject: [PATCH 34/39] Changed to using VNDF for TR --- .../src/materials/trowbridge_reitz.rs | 16 +++++++--------- .../src/statistics/bxdfs/trowbridge_reitz.rs | 1 - .../statistics/bxdfs/trowbridge_reitz_vndf.rs | 14 +++++--------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/crates/implementations/src/materials/trowbridge_reitz.rs b/crates/implementations/src/materials/trowbridge_reitz.rs index 8a33d07..df74abe 100644 --- a/crates/implementations/src/materials/trowbridge_reitz.rs +++ b/crates/implementations/src/materials/trowbridge_reitz.rs @@ -1,6 +1,4 @@ -use crate::{ - materials::refract, statistics::bxdfs::trowbridge_reitz, textures::Texture, utility::offset_ray, -}; +use crate::{materials::refract, statistics::bxdfs::*, textures::Texture, utility::offset_ray}; use rand::{rngs::SmallRng, thread_rng, SeedableRng}; use rt_core::*; @@ -38,7 +36,7 @@ where T: Texture, { fn scatter_ray(&self, ray: &mut Ray, hit: &Hit) -> bool { - let direction = trowbridge_reitz::sample( + let direction = trowbridge_reitz_vndf::isotropic::sample( self.alpha, -ray.direction, hit.normal, @@ -52,7 +50,7 @@ where } fn scattering_pdf(&self, hit: &Hit, wo: Vec3, wi: Vec3) -> Float { let wo = -wo; - let a = trowbridge_reitz::pdf(self.alpha, wo, wi, hit.normal); + let a = trowbridge_reitz_vndf::isotropic::pdf(self.alpha, wo, wi, hit.normal); if a == 0.0 { INFINITY } else { @@ -68,8 +66,8 @@ where } let f = self.fresnel(hit, wo, wi, h); - let g = trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi); - let d = trowbridge_reitz::d(self.alpha, hit.normal.dot(h)); + let g = trowbridge_reitz_vndf::isotropic::g2(self.alpha, hit.normal, h, wo, wi); + let d = trowbridge_reitz_vndf::isotropic::d(self.alpha, hit.normal.dot(h)); f * g * d / (4.0 * wo.dot(hit.normal).abs() * wi.dot(hit.normal)) } @@ -83,9 +81,9 @@ where let f = self.fresnel(hit, wo, wi, h); - let g = trowbridge_reitz::g2(self.alpha, hit.normal, h, wo, wi); + let g = trowbridge_reitz_vndf::isotropic::g2(self.alpha, hit.normal, h, wo, wi); - f * g * wo.dot(h) / (wo.dot(hit.normal) * h.dot(hit.normal)).abs() + f * g / trowbridge_reitz_vndf::isotropic::g1(self.alpha, hit.normal, h, wo) } } diff --git a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs index 946a651..9acb44a 100644 --- a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs +++ b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz.rs @@ -60,7 +60,6 @@ pub fn pdf(alpha: Float, incoming: Vec3, outgoing: Vec3, normal: Vec3) -> Float } pub fn g2(alpha: Float, normal: Vec3, h: Vec3, incoming: Vec3, outgoing: Vec3) -> Float { - //let incoming = -incoming; if incoming.dot(h) / incoming.dot(normal) <= 0.0 || outgoing.dot(h) / outgoing.dot(normal) <= 0.0 { diff --git a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs index 80af5cb..583e36d 100644 --- a/crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs +++ b/crates/implementations/src/statistics/bxdfs/trowbridge_reitz_vndf.rs @@ -4,8 +4,7 @@ use rt_core::*; pub mod isotropic { use super::*; - use crate::bxdfs::trowbridge_reitz::d; - use crate::bxdfs::trowbridge_reitz::g1; + pub use crate::bxdfs::trowbridge_reitz::{d, g1, g2}; pub fn vndf(a: Float, h: Vec3, incoming: Vec3) -> Float { if h.z < 0.0 { @@ -27,7 +26,7 @@ pub mod isotropic { pub fn pdf_local(alpha: Float, incoming: Vec3, outgoing: Vec3) -> Float { let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = -h; //(incoming + outgoing).normalised(); + h = -h; } let vndf = vndf(alpha, h, incoming); vndf / (4.0 * incoming.dot(h)) @@ -46,9 +45,8 @@ pub mod isotropic { let outgoing = inverse.to_coord(outgoing); let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = -h; //(incoming - outgoing).normalised(); + h = -h; } - //let incoming = -incoming; let vndf = vndf(alpha, h, incoming); vndf / (4.0 * incoming.dot(h)) } @@ -117,9 +115,8 @@ pub mod ansiotropic { pub fn pdf_local(a_x: Float, a_y: Float, incoming: Vec3, outgoing: Vec3) -> Float { let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = -h; //(incoming - outgoing).normalised(); + h = -h; } - //let incoming = -incoming; let vndf = vndf(a_x, a_y, h, incoming); vndf / (4.0 * incoming.dot(h)) } @@ -143,9 +140,8 @@ pub mod ansiotropic { let outgoing = inverse.to_coord(outgoing); let mut h = (outgoing + incoming).normalised(); if h.z < 0.0 { - h = -h; //(incoming - outgoing).normalised(); + h = -h; } - //let incoming = -incoming; let vndf = vndf(a_x, a_y, h, incoming); vndf / (4.0 * incoming.dot(h)) } From b129785008e235a8aed7b39887a9b115023cb9a0 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Sat, 25 Mar 2023 20:17:04 +1100 Subject: [PATCH 35/39] Added sky sampling (for real) --- .../implementations/src/acceleration/mod.rs | 51 +++- .../implementations/src/materials/emissive.rs | 7 +- .../implementations/src/primitives/sphere.rs | 1 - crates/implementations/src/samplers/mod.rs | 6 +- .../src/samplers/random_sampler.rs | 8 +- crates/implementations/src/sky.rs | 39 ++- crates/loader/src/lib.rs | 95 +++--- crates/loader/src/materials.rs | 26 +- crates/loader/src/meshes.rs | 8 +- crates/loader/src/misc.rs | 16 +- crates/loader/src/primitives.rs | 16 +- crates/loader/src/textures.rs | 28 +- crates/rt_core/src/acceleration.rs | 13 +- crates/rt_core/src/ray.rs | 272 ++++++++---------- crates/rt_core/src/sampler.rs | 7 +- src/main.rs | 8 +- src/parameters.rs | 11 +- src/scene.rs | 37 +-- 18 files changed, 364 insertions(+), 285 deletions(-) diff --git a/crates/implementations/src/acceleration/mod.rs b/crates/implementations/src/acceleration/mod.rs index 3f4a549..71d0a2d 100644 --- a/crates/implementations/src/acceleration/mod.rs +++ b/crates/implementations/src/acceleration/mod.rs @@ -40,25 +40,30 @@ impl PrimitiveInfo { } } -pub struct Bvh { +pub struct Bvh> { split_type: SplitType, nodes: Vec, + sky: S, pub primitives: RegionResSlice

, pub lights: Vec, phantom: PhantomData, } -//unsafe impl<'a, P: Primitive + AABound + Send, M: Scatter + Send> Sync for Bvh<'a, P, M> {} - -impl Bvh +impl Bvh where P: Primitive + AABound, M: Scatter, + S: NoHit, { - pub fn new(mut primitives: region::RegionUniqSlice<'_, P>, split_type: SplitType) -> Self { + pub fn new( + mut primitives: region::RegionUniqSlice<'_, P>, + sky: S, + split_type: SplitType, + ) -> Self { let mut bvh = Self { split_type, nodes: Vec::new(), + sky, primitives: primitives.zero_slice(), lights: Vec::new(), phantom: PhantomData, @@ -82,7 +87,7 @@ where } } - bvh.primitives = primitives.shared(); //primitives.into_bump_slice(); + bvh.primitives = primitives.shared(); bvh } @@ -182,13 +187,15 @@ where } } -impl AccelerationStructure for Bvh +impl AccelerationStructure for Bvh where P: Primitive, M: Scatter, + S: NoHit, { type Object = P; type Material = M; + type Sky = S; fn get_intersection_candidates(&self, ray: &Ray) -> Vec<(usize, usize)> { let mut offset_len = Vec::new(); @@ -255,7 +262,7 @@ where intersection } - fn check_hit(&self, ray: &Ray) -> Option<(SurfaceIntersection, usize)> { + fn check_hit(&self, ray: &Ray) -> (SurfaceIntersection, usize) { let offset_lens = self.get_intersection_candidates(ray); let mut hit: Option<(SurfaceIntersection, usize)> = None; @@ -284,7 +291,30 @@ where } } } - hit + match hit { + None => (self.sky.get_si(ray), usize::MAX), + Some(hit) => hit, + } + } + fn get_pdf_from_index( + &self, + last_hit: &Hit, + light_hit: &Hit, + sampled_dir: Vec3, + index: usize, + ) -> Float { + let sky_samplable = self.sky.can_sample(); + let divisor = if sky_samplable { + self.lights.len() + 1 + } else { + self.lights.len() + } as Float; + + if index == usize::MAX { + self.sky.pdf(sampled_dir) / divisor + } else { + self.primitives[index].scattering_pdf(last_hit.point, sampled_dir, light_hit) / divisor + } } fn get_samplable(&self) -> &[usize] { &self.lights @@ -292,6 +322,9 @@ where fn get_object(&self, index: usize) -> Option<&P> { self.primitives.get(index) } + fn sky(&self) -> &S { + &self.sky + } } #[derive(Debug)] diff --git a/crates/implementations/src/materials/emissive.rs b/crates/implementations/src/materials/emissive.rs index e19d471..5736af3 100644 --- a/crates/implementations/src/materials/emissive.rs +++ b/crates/implementations/src/materials/emissive.rs @@ -20,9 +20,12 @@ impl<'a, T> Scatter for Emit<'a, T> where T: Texture, { - fn get_emission(&self, hit: &Hit, _: Vec3) -> Vec3 { + fn get_emission(&self, hit: &Hit, wo: Vec3) -> Vec3 { let point = offset_ray(hit.point, hit.normal, hit.error, true); - self.strength * self.texture.colour_value(Vec3::zero(), point) + self.strength * self.texture.colour_value(wo, point) + } + fn scattering_pdf(&self, _hit: &Hit, _wo: Vec3, _wi: Vec3) -> Float { + unreachable!() } fn is_light(&self) -> bool { true diff --git a/crates/implementations/src/primitives/sphere.rs b/crates/implementations/src/primitives/sphere.rs index eab75b7..3fee377 100644 --- a/crates/implementations/src/primitives/sphere.rs +++ b/crates/implementations/src/primitives/sphere.rs @@ -150,7 +150,6 @@ where self.center + self.radius * vec }; - (point - in_point).normalised() } fn scattering_pdf(&self, hit_point: Vec3, wi: Vec3, sampled_hit: &Hit) -> Float { diff --git a/crates/implementations/src/samplers/mod.rs b/crates/implementations/src/samplers/mod.rs index 687082a..67e4e40 100644 --- a/crates/implementations/src/samplers/mod.rs +++ b/crates/implementations/src/samplers/mod.rs @@ -5,11 +5,10 @@ pub mod random_sampler; use clap::ValueEnum; pub trait Sampler: Sync { - fn sample_image( + fn sample_image( &self, _render_options: RenderOptions, _camera: &C, - _sky: &S, _acceleration_structure: &A, _update_function: Option<(&mut T, F)>, ) where @@ -17,8 +16,7 @@ pub trait Sampler: Sync { P: Primitive, M: Scatter, F: Fn(&mut T, &SamplerProgress, u64) -> bool, - A: AccelerationStructure, - S: NoHit; + A: AccelerationStructure; } #[derive(Copy, Clone, Debug)] diff --git a/crates/implementations/src/samplers/random_sampler.rs b/crates/implementations/src/samplers/random_sampler.rs index 324b355..a0fca44 100644 --- a/crates/implementations/src/samplers/random_sampler.rs +++ b/crates/implementations/src/samplers/random_sampler.rs @@ -6,11 +6,10 @@ use rt_core::*; pub struct RandomSampler; impl Sampler for RandomSampler { - fn sample_image( + fn sample_image( &self, render_options: RenderOptions, camera: &C, - sky: &S, acceleration_structure: &A, mut presentation_update: Option<(&mut T, F)>, ) where @@ -19,7 +18,6 @@ impl Sampler for RandomSampler { M: Scatter, F: Fn(&mut T, &SamplerProgress, u64) -> bool, A: AccelerationStructure, - S: NoHit, { let channels = 3; let pixel_num = render_options.width * render_options.height; @@ -62,10 +60,10 @@ impl Sampler for RandomSampler { let mut ray = camera.get_ray(u, v); // remember to add le DOF let result = match render_options.render_method { RenderMethod::Naive => { - Ray::get_colour_naive(&mut ray, sky, acceleration_structure) + Ray::get_colour_naive(&mut ray, acceleration_structure) } RenderMethod::MIS => { - Ray::get_colour(&mut ray, sky, acceleration_structure) + Ray::get_colour(&mut ray, acceleration_structure) } }; diff --git a/crates/implementations/src/sky.rs b/crates/implementations/src/sky.rs index c3b2225..1434630 100644 --- a/crates/implementations/src/sky.rs +++ b/crates/implementations/src/sky.rs @@ -9,15 +9,16 @@ use rt_core::*; use crate::Texture; -#[derive(Debug)] -pub struct Sky<'a, T: Texture> { +#[derive(Debug, Clone)] +pub struct Sky<'a, T: Texture, M: Scatter> { texture: &'a T, + mat: &'a M, pub distribution: Option, sampler_res: (usize, usize), } -impl<'a, T: Texture> Sky<'a, T> { - pub fn new(texture: &'a T, sampler_res: (usize, usize)) -> Self { +impl<'a, T: Texture, M: Scatter> Sky<'a, T, M> { + pub fn new(texture: &'a T, mat: &'a M, sampler_res: (usize, usize)) -> Self { let values = generate_values(texture, sampler_res); let distribution = if sampler_res.0 | sampler_res.1 != 0 { @@ -28,13 +29,14 @@ impl<'a, T: Texture> Sky<'a, T> { Sky { texture, + mat, distribution, sampler_res, } } } -impl<'a, T: Texture> NoHit for Sky<'a, T> { +impl<'a, T: Texture, M: Scatter> NoHit for Sky<'a, T, M> { fn get_colour(&self, ray: &Ray) -> Vec3 { self.texture.colour_value(ray.direction, ray.origin) } @@ -74,24 +76,41 @@ impl<'a, T: Texture> NoHit for Sky<'a, T> { Vec3::from_spherical(theta.sin(), theta.cos(), phi.sin(), phi.cos()) } + fn get_si(&self, _ray: &Ray) -> SurfaceIntersection { + SurfaceIntersection { + hit: Hit { + t: 0.0, + point: Vec3::zero(), + error: Vec3::zero(), + normal: Vec3::zero(), + uv: None, + out: false, + }, + material: self.mat, + } + } } #[cfg(test)] mod tests { use super::*; - use crate::spherical_sampling::test_spherical_pdf; + //use crate::spherical_sampling::test_spherical_pdf; + use crate::AllMaterials; use crate::AllTextures; + use crate::Emit; use crate::Lerp; - use rand::rngs::ThreadRng; + //use rand::rngs::ThreadRng; #[test] fn sky_sampling() { let tex = AllTextures::Lerp(Lerp::new(Vec3::zero(), Vec3::one())); + let mat = AllMaterials::Emit(Emit::new(&tex, 1.0)); - let sky = Sky::new(&tex, (60, 30)); + let _sky = Sky::new(&tex, &mat, (60, 30)); - let pdf = |outgoing: Vec3| sky.pdf(outgoing); + /*let pdf = |outgoing: Vec3| sky.pdf(outgoing); let sample = |_: &mut ThreadRng| sky.sample(); - test_spherical_pdf("lerp sky sampling", &pdf, &sample, false); + test_spherical_pdf("lerp sky sampling", &pdf, &sample, false);*/ + todo!() } } diff --git a/crates/loader/src/lib.rs b/crates/loader/src/lib.rs index f2c765b..e5006f8 100644 --- a/crates/loader/src/lib.rs +++ b/crates/loader/src/lib.rs @@ -7,16 +7,22 @@ pub mod primitives; pub mod textures; use implementations::rt_core::{Float, NoHit, Primitive, Scatter, Vec2, Vec3}; -use implementations::{Camera, Texture}; +use implementations::*; use region::{Region, RegionRes, RegionUniqSlice}; use std::{collections::HashMap, fmt}; use thiserror::Error; +type TextureType = AllTextures; +type MaterialType<'a> = AllMaterials<'a, TextureType>; +type PrimitiveType<'a> = AllPrimitives<'a, MaterialType<'a>>; +type SkyType<'a> = Sky<'a, TextureType, AllMaterials<'a, TextureType>>; + pub trait Load: Sized { /// Take a set of properties and load an object from, optionally also /// provide a resource name for this object. Such as a texture name /// or material ID. - fn load(props: Properties) -> Result<(Option, Self), LoadErr>; + /// Also take a region if load needs to allocate + fn load(props: Properties, region: &mut Region) -> Result<(Option, Self), LoadErr>; } #[derive(Default)] @@ -196,7 +202,7 @@ where M: Scatter + Load, P: Primitive + Load + Clone, C: Camera + Load, - S: NoHit + Load, + S: NoHit + Load, Vec

: Load, { let scene_file = match std::fs::read_to_string(file) { @@ -212,41 +218,41 @@ where let mut lookup = Lookup::new(); log::info!("Loading textures..."); - let textures = load_textures::(&scene_conf, &lookup)?; + let textures = load_textures::(&scene_conf, &lookup, region)?; region_insert_with_lookup(region, textures, |n, t| lookup.texture_insert(n, t)); log::info!("Loading materials..."); - let materials = load_materials::(&scene_conf, &lookup)?; + let materials = load_materials::(&scene_conf, &lookup, region)?; region_insert_with_lookup(region, materials, |n, s| lookup.scatter_insert(n, s)); + log::info!("Loading other objects..."); + let camera = load_scene_camera(&scene_conf, &lookup, region)?; + let sky = load_scene_sky(&scene_conf, &lookup, region)?; + log::info!("Loading primitives..."); let primitives = { - let mut primitives = load_primitives::

(&scene_conf, &lookup)?; + let mut primitives = load_primitives::

(&scene_conf, &lookup, region)?; log::info!("Loading meshes..."); - primitives.extend(load_meshes::

(&scene_conf, &lookup)?); + primitives.extend(load_meshes::

(&scene_conf, &lookup, region)?); region.alloc_slice(&primitives) }; - log::info!("Loading other objects..."); - let camera = load_scene_camera(&scene_conf, &lookup)?; - let sky = load_scene_sky(&scene_conf, &lookup)?; - Ok((primitives, camera, sky)) } pub fn load_str_full<'a, T, M, P, C, S>( region: &'a mut Region, data: &str, -) -> Result<(RegionUniqSlice<'a, P>, C, S), LoadErr> +) -> Result<(RegionUniqSlice<'a, PrimitiveType<'a>>, C, SkyType<'a>), LoadErr> where T: Texture + Load, M: Scatter + Load, - P: Primitive + Load + Clone, C: Camera + Load, - S: NoHit + Load, + S: NoHit + Load, Vec

: Load, + SkyType<'a>: implementations::rt_core::NoHit, { let scene_conf = match parser::from_str(data) { Ok(c) => c, @@ -256,31 +262,35 @@ where let mut lookup = Lookup::new(); log::info!("Loading textures..."); - let textures = load_textures::(&scene_conf, &lookup)?; + let textures = load_textures::(&scene_conf, &lookup, region)?; region_insert_with_lookup(region, textures, |n, t| lookup.texture_insert(n, t)); log::info!("Loading materials..."); - let materials = load_materials::(&scene_conf, &lookup)?; + let materials = load_materials::(&scene_conf, &lookup, region)?; region_insert_with_lookup(region, materials, |n, s| lookup.scatter_insert(n, s)); + log::info!("Loading other objects..."); + let camera = load_scene_camera(&scene_conf, &lookup, region)?; + let sky = load_scene_sky::(&scene_conf, &lookup, region)?; + log::info!("Loading primitives..."); let primitives = { - let mut primitives = load_primitives::

(&scene_conf, &lookup)?; + let mut primitives = load_primitives::(&scene_conf, &lookup, region)?; log::info!("Loading meshes..."); - primitives.extend(load_meshes::

(&scene_conf, &lookup)?); + primitives.extend(load_meshes::(&scene_conf, &lookup, region)?); region.alloc_slice(&primitives) }; - log::info!("Loading other objects..."); - let camera = load_scene_camera(&scene_conf, &lookup)?; - let sky = load_scene_sky(&scene_conf, &lookup)?; - Ok((primitives, camera, sky)) } -pub fn load_scene_camera(objects: &[parser::Object], lookup: &Lookup) -> Result +pub fn load_scene_camera( + objects: &[parser::Object], + lookup: &Lookup, + region: &mut Region, +) -> Result where C: Camera + Load, { @@ -292,12 +302,17 @@ where .find(|o| o.kind.is_camera()) .ok_or(LoadErr::MissingCamera)?, ); - Ok(C::load(props)?.1) + Ok(C::load(props, region)?.1) } -pub fn load_scene_sky(objects: &[parser::Object], lookup: &Lookup) -> Result +pub fn load_scene_sky( + objects: &[parser::Object], + lookup: &Lookup, + region: &mut Region, +) -> Result where - S: NoHit + Load, + S: NoHit + Load, + M: Scatter, { // Find a sky object, if none warn and use a default let obj = objects.iter().find(|o| o.kind.is_sky()); @@ -308,7 +323,7 @@ where Properties::new(lookup, &Default::default()) } }; - Ok(S::load(props)?.1) + Ok(S::load(props, region)?.1) } fn region_insert_with_lookup( @@ -316,8 +331,6 @@ fn region_insert_with_lookup( items: Vec<(Option, T)>, mut insert_fn: impl FnMut(&str, RegionRes) -> Option>, ) { - //let block_size = std::alloc::Layout::array::(items.len()).unwrap().size(); - //let block = region.allocate_block(block_size); for (name, item) in items.into_iter() { let uniq = region.alloc(item); if let Some(name) = name { @@ -331,11 +344,12 @@ fn region_insert_with_lookup( fn load_textures( objects: &[parser::Object], lookup: &Lookup, + region: &mut Region, ) -> Result, T)>, LoadErr> { let mut textures = Vec::new(); for obj in objects.iter().filter(|o| o.kind.is_texture()) { let props = Properties::new(lookup, obj); - textures.push(::load(props)?); + textures.push(::load(props, region)?); } // Load default texture, assumes that T contains SolidColor { @@ -350,7 +364,7 @@ fn load_textures( .into(), }; let props = Properties::new(lookup, &def_obj); - textures.push(::load(props)?); + textures.push(::load(props, region)?); } Ok(textures) } @@ -358,11 +372,12 @@ fn load_textures( fn load_materials( objects: &[parser::Object], lookup: &Lookup, + region: &mut Region, ) -> Result, S)>, LoadErr> { let mut materials = Vec::new(); for obj in objects.iter().filter(|o| o.kind.is_material()) { let props = Properties::new(lookup, obj); - materials.push(::load(props)?); + materials.push(::load(props, region)?); } // Load default material, assumes that S contains Lambertian { @@ -378,7 +393,7 @@ fn load_materials( .into(), }; let props = Properties::new(lookup, &def_obj); - materials.push(::load(props)?); + materials.push(::load(props, region)?); } Ok(materials) } @@ -386,11 +401,12 @@ fn load_materials( fn load_primitives( objects: &[parser::Object], lookup: &Lookup, + region: &mut Region, ) -> Result, LoadErr> { let mut primitives = Vec::new(); for obj in objects.iter().filter(|o| o.kind.is_primitive()) { let props = Properties::new(lookup, obj); - primitives.push(

::load(props)?.1); + primitives.push(

::load(props, region)?.1); } Ok(primitives) } @@ -398,6 +414,7 @@ fn load_primitives( fn load_meshes( objects: &[parser::Object], lookup: &Lookup, + region: &mut Region, ) -> Result, LoadErr> where Vec

: Load, @@ -405,7 +422,7 @@ where let mut primitives = Vec::new(); for obj in objects.iter().filter(|o| o.kind.is_mesh()) { let props = Properties::new(lookup, obj); - primitives.extend( as Load>::load(props)?.1); + primitives.extend( as Load>::load(props, region)?.1); } Ok(primitives) } @@ -414,8 +431,6 @@ where mod tests { use super::*; - use implementations::*; - const DATA: &str = "camera ( origin -5 3 -3 lookat 0 0.5 0 @@ -483,11 +498,11 @@ primitive ( type Tex = AllTextures; type Mat<'a> = AllMaterials<'a, Tex>; type Prim<'a> = AllPrimitives<'a, Mat<'a>>; - type SkyType<'a> = Sky<'a, Tex>; + type SkyType<'a> = Sky<'a, Tex, Mat<'a>>; let stuff = load_str_full::(&mut region, DATA).unwrap(); - let (p, _, _) = stuff; - let _: Bvh = Bvh::new(p, split::SplitType::Sah); + let (p, _, s) = stuff; + let _: Bvh = Bvh::new(p, s, split::SplitType::Sah); } } diff --git a/crates/loader/src/materials.rs b/crates/loader/src/materials.rs index 1fb8945..cf6dab4 100644 --- a/crates/loader/src/materials.rs +++ b/crates/loader/src/materials.rs @@ -4,7 +4,7 @@ use implementations::emissive::Emit; use implementations::*; impl Load for AllMaterials<'_, T> { - fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(props: Properties, region: &mut Region) -> Result<(Option, Self), LoadErr> { let kind = match props.text("type") { Some(k) => k, None => return Err(LoadErr::MissingRequiredVariantType), @@ -12,23 +12,23 @@ impl Load for AllMaterials<'_, T> { Ok(match kind { "emissive" => { - let x = Emit::load(props)?; + let x = Emit::load(props, region)?; (x.0, Self::Emit(x.1)) } "lambertian" => { - let x = Lambertian::load(props)?; + let x = Lambertian::load(props, region)?; (x.0, Self::Lambertian(x.1)) } "reflect" => { - let x = Reflect::load(props)?; + let x = Reflect::load(props, region)?; (x.0, Self::Reflect(x.1)) } "refract" => { - let x = Refract::load(props)?; + let x = Refract::load(props, region)?; (x.0, Self::Refract(x.1)) } "trowbridge_reitz" => { - let x = TrowbridgeReitz::load(props)?; + let x = TrowbridgeReitz::load(props, region)?; (x.0, Self::TrowbridgeReitz(x.1)) } o => { @@ -41,7 +41,7 @@ impl Load for AllMaterials<'_, T> { } impl Load for Lambertian<'_, T> { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let tex = props .texture("texture") .unwrap_or_else(|| props.default_texture()); @@ -54,7 +54,7 @@ impl Load for Lambertian<'_, T> { } impl Load for Emit<'_, T> { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let tex = props .texture("texture") .unwrap_or_else(|| props.default_texture()); @@ -67,7 +67,7 @@ impl Load for Emit<'_, T> { } impl Load for Reflect<'_, T> { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let tex = props .texture("texture") .unwrap_or_else(|| props.default_texture()); @@ -80,7 +80,7 @@ impl Load for Reflect<'_, T> { } impl Load for Refract<'_, T> { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let tex = props .texture("texture") .unwrap_or_else(|| props.default_texture()); @@ -93,7 +93,7 @@ impl Load for Refract<'_, T> { } impl Load for TrowbridgeReitz<'_, T> { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let tex = props .texture("texture") .unwrap_or_else(|| props.default_texture()); @@ -129,8 +129,8 @@ material ground ( albedo 0.5 )"; let data = parser::from_str(file).unwrap(); - let textures = load_textures::(&data, &lookup).unwrap(); + let textures = load_textures::(&data, &lookup, &mut region).unwrap(); region_insert_with_lookup(&mut region, textures, |n, t| lookup.texture_insert(n, t)); - let _ = load_materials::>(&data, &lookup).unwrap(); + let _ = load_materials::>(&data, &lookup, &mut region).unwrap(); } } diff --git a/crates/loader/src/meshes.rs b/crates/loader/src/meshes.rs index 164e9fd..1f86f2c 100644 --- a/crates/loader/src/meshes.rs +++ b/crates/loader/src/meshes.rs @@ -6,14 +6,14 @@ use implementations::triangle::MeshTriangle; use implementations::*; impl Load for Vec> { - fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(props: Properties, region: &mut Region) -> Result<(Option, Self), LoadErr> { let kind = match props.text("type") { Some(k) => k, None => return Err(LoadErr::MissingRequiredVariantType), }; match kind { - "mesh" => mesh(props), - "aacuboid" => cuboid(props), + "mesh" => mesh(props, region), + "aacuboid" => cuboid(props, region), o => { return Err(LoadErr::MissingRequired(format!( "required a known value for mesh type, found '{o}'" @@ -25,6 +25,7 @@ impl Load for Vec> { fn cuboid<'a, M: Scatter>( props: Properties, + _: &mut Region, ) -> Result<(Option, Vec>), LoadErr> { let mat: region::RegionRes = props .scatter("material") @@ -103,6 +104,7 @@ fn cuboid<'a, M: Scatter>( fn mesh<'a, M: Scatter>( props: Properties, + _: &mut Region, ) -> Result<(Option, Vec>), LoadErr> { let filepath = match props.text("obj") { Some(c) => c.to_owned(), diff --git a/crates/loader/src/misc.rs b/crates/loader/src/misc.rs index 552ba26..18597da 100644 --- a/crates/loader/src/misc.rs +++ b/crates/loader/src/misc.rs @@ -4,7 +4,7 @@ use crate::*; use implementations::*; impl Load for SimpleCamera { - fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let origin = props.vec3("origin").unwrap_or(Vec3::new(3., 0., 0.)); let lookat = props.vec3("lookat").unwrap_or(Vec3::zero()); let vup = props.vec3("vup").unwrap_or(Vec3::new(0., 1., 0.)); @@ -17,14 +17,22 @@ impl Load for SimpleCamera { } } -impl Load for Sky<'_, T> { - fn load(props: Properties) -> Result<(Option, Self), LoadErr> { +impl Load for Sky<'_, T, AllMaterials<'_, T>> { + fn load(props: Properties, region: &mut Region) -> Result<(Option, Self), LoadErr> { let tex = props .texture("texture") .unwrap_or_else(|| props.default_texture()); let res = props.vec2("sampler_res").unwrap_or(Vec2::new(100., 100.)); - let sky = Self::new(unsafe { &*(&*tex as *const _) }, (res.x as _, res.y as _)); + let mat = AllMaterials::Emit(Emit::new(unsafe { &*(&*tex as *const _) }, 1.0)); + + let mat = region.alloc(mat).shared(); + + let sky = Self::new( + unsafe { &*(&*tex as *const _) }, + unsafe { &*(&*mat as *const _) }, + (res.x as _, res.y as _), + ); Ok((None, sky)) } } diff --git a/crates/loader/src/primitives.rs b/crates/loader/src/primitives.rs index 145071f..d213e9a 100644 --- a/crates/loader/src/primitives.rs +++ b/crates/loader/src/primitives.rs @@ -6,7 +6,7 @@ use implementations::*; use rt_core::Scatter; impl Load for Sphere<'_, M> { - fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let mat: region::RegionRes = props .scatter("material") .unwrap_or_else(|| props.default_scatter()); @@ -28,7 +28,7 @@ impl Load for Sphere<'_, M> { } impl Load for AllPrimitives<'_, M> { - fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(props: Properties, region: &mut Region) -> Result<(Option, Self), LoadErr> { let kind = match props.text("type") { Some(k) => k, None => return Err(LoadErr::MissingRequiredVariantType), @@ -36,13 +36,13 @@ impl Load for AllPrimitives<'_, M> { Ok(match kind { "sphere" => { - let x = Sphere::load(props)?; + let x = Sphere::load(props, region)?; (x.0, Self::Sphere(x.1)) } "triangle" => todo!(), o => { return Err(LoadErr::MissingRequired(format!( - "required a known value for material type, found '{o}'" + "required a known value for primitive type, found '{o}'" ))) } }) @@ -76,14 +76,16 @@ primitive ( radius 1000 )"; let data = parser::from_str(file).unwrap(); - let textures = load_textures::(&data, &lookup).unwrap(); + let textures = load_textures::(&data, &lookup, &mut region).unwrap(); region_insert_with_lookup(&mut region, textures, |n, t| lookup.texture_insert(n, t)); - let materials = load_materials::>(&data, &lookup).unwrap(); + let materials = + load_materials::>(&data, &lookup, &mut region).unwrap(); region_insert_with_lookup(&mut region, materials, |n, t| lookup.scatter_insert(n, t)); - load_primitives::>>(&data, &lookup).unwrap(); + load_primitives::>>(&data, &lookup, &mut region) + .unwrap(); } } diff --git a/crates/loader/src/textures.rs b/crates/loader/src/textures.rs index adc3ae9..0aada14 100644 --- a/crates/loader/src/textures.rs +++ b/crates/loader/src/textures.rs @@ -3,7 +3,7 @@ use crate::*; use implementations::*; impl Load for AllTextures { - fn load(props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(props: Properties, region: &mut Region) -> Result<(Option, Self), LoadErr> { let kind = match props.text("type") { Some(k) => k, None => return Err(LoadErr::MissingRequiredVariantType), @@ -11,23 +11,23 @@ impl Load for AllTextures { Ok(match kind { "checkered" => { - let x = CheckeredTexture::load(props)?; + let x = CheckeredTexture::load(props, region)?; (x.0, Self::CheckeredTexture(x.1)) } "solid" => { - let x = SolidColour::load(props)?; + let x = SolidColour::load(props, region)?; (x.0, Self::SolidColour(x.1)) } "image" => { - let x = ImageTexture::load(props)?; + let x = ImageTexture::load(props, region)?; (x.0, Self::ImageTexture(x.1)) } "lerp" => { - let x = Lerp::load(props)?; + let x = Lerp::load(props, region)?; (x.0, Self::Lerp(x.1)) } "perlin" => { - let x = Perlin::load(props)?; + let x = Perlin::load(props, region)?; (x.0, Self::Perlin(Box::new(x.1))) } o => { @@ -40,7 +40,7 @@ impl Load for AllTextures { } impl Load for CheckeredTexture { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let primary = props.vec3("primary").unwrap_or(Vec3::one()); let secondary = props.vec3("secondary").unwrap_or(Vec3::zero()); let name = props.name(); @@ -49,7 +49,7 @@ impl Load for CheckeredTexture { } impl Load for ImageTexture { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let name = props.name(); let filename = match props.text("filename") { Some(f) => f, @@ -60,14 +60,14 @@ impl Load for ImageTexture { } impl Load for Perlin { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let name = props.name(); Ok((name, Self::new())) } } impl Load for Lerp { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let primary = props.vec3("primary").unwrap_or(Vec3::one()); let secondary = props.vec3("secondary").unwrap_or(Vec3::zero()); let name = props.name(); @@ -76,7 +76,7 @@ impl Load for Lerp { } impl Load for SolidColour { - fn load(mut props: Properties) -> Result<(Option, Self), LoadErr> { + fn load(mut props: Properties, _: &mut Region) -> Result<(Option, Self), LoadErr> { let colour = props.vec3("colour").unwrap_or(0.5 * Vec3::one()); let name = props.name(); Ok((name, Self::new(colour))) @@ -89,6 +89,7 @@ mod tests { #[test] fn coloured_texture() { + let mut region = Region::new(); let lookup = Lookup::new(); let thing = "texture grey ( type solid @@ -96,12 +97,13 @@ mod tests { )"; let a = parser::from_str(thing).unwrap(); let props = Properties::new(&lookup, &a[0]); - let b = ::load(props).unwrap(); + let b = ::load(props, &mut region).unwrap(); println!("{b:?}"); } #[test] fn checkered_texture() { + let mut region = Region::new(); let lookup = Lookup::new(); let thing = "texture checkered ( type checkered @@ -110,7 +112,7 @@ mod tests { )"; let a = parser::from_str(thing).unwrap(); let props = Properties::new(&lookup, &a[0]); - let b = ::load(props).unwrap(); + let b = ::load(props, &mut region).unwrap(); println!("{b:?}"); } } diff --git a/crates/rt_core/src/acceleration.rs b/crates/rt_core/src/acceleration.rs index 0da56b4..6663745 100644 --- a/crates/rt_core/src/acceleration.rs +++ b/crates/rt_core/src/acceleration.rs @@ -1,8 +1,9 @@ -use crate::{Primitive, Ray, Scatter, SurfaceIntersection}; +use crate::*; pub trait AccelerationStructure: Sync { type Object: Primitive; type Material: Scatter; + type Sky: NoHit; fn get_intersection_candidates(&self, ray: &Ray) -> Vec<(usize, usize)>; fn check_hit_index( @@ -11,7 +12,7 @@ pub trait AccelerationStructure: Sync { object_index: usize, ) -> Option>; - fn check_hit(&self, ray: &Ray) -> Option<(SurfaceIntersection, usize)>; + fn check_hit(&self, ray: &Ray) -> (SurfaceIntersection, usize); fn get_samplable(&self) -> &[usize] { unimplemented!() @@ -20,4 +21,12 @@ pub trait AccelerationStructure: Sync { fn get_object(&self, _index: usize) -> Option<&Self::Object> { unimplemented!() } + fn get_pdf_from_index( + &self, + last_hit: &Hit, + light_hit: &Hit, + sampled_dir: Vec3, + index: usize, + ) -> Float; + fn sky(&self) -> &Self::Sky; } diff --git a/crates/rt_core/src/ray.rs b/crates/rt_core/src/ray.rs index 759e601..16038be 100644 --- a/crates/rt_core/src/ray.rs +++ b/crates/rt_core/src/ray.rs @@ -1,5 +1,5 @@ use crate::{power_heuristic, AccelerationStructure, Float, Hit, NoHit, Primitive, Scatter, Vec3}; -use rand::{prelude::SliceRandom, rngs::SmallRng, thread_rng, Rng, SeedableRng}; +use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; const RUSSIAN_ROULETTE_THRESHOLD: u32 = 3; const MAX_DEPTH: u32 = 50; @@ -55,70 +55,71 @@ impl Ray { self.origin + self.direction * t } - fn sample_lights_test< + fn sample_lights< A: AccelerationStructure, P: Primitive, M: Scatter, - S: NoHit, >( bvh: &A, hit: &Hit, - _sky: &S, - _mat: &M, - _wo: Vec3, ) -> Option<(Vec3, Vec3, Float)> { - //l_pos, le, l_pdf - let light_index = match bvh - .get_samplable() - .choose(&mut SmallRng::from_rng(thread_rng()).unwrap()) - { - Some(&index) => index, - None => return None, + //l_wi, le, l_pdf + let sky = bvh.sky(); + let samplable_len = bvh.get_samplable().len(); + let sky_can_sample = sky.can_sample(); + + let sample_sky = |pdf_multiplier: Float| { + let l_wi = sky.sample(); + let ray = Ray::new(hit.point + 0.0001 * hit.normal, l_wi, 0.0); + + let (sa, index) = bvh.check_hit(&ray); + if index == usize::MAX { + let le = sa.material.get_emission(hit, l_wi); + let l_pdf = sky.pdf(l_wi); + return Some((l_wi, le, l_pdf * pdf_multiplier)); + } + None }; - let samplable = bvh.get_object(light_index).unwrap(); - - let sampled_wi = samplable.sample_visible_from_point(hit.point); - - if let Some(sampled_si) = bvh.check_hit_index( - &Ray::new(hit.point + 0.0001 * hit.normal, sampled_wi, 0.0), - light_index, - ) { - let sampled_hit = &sampled_si.hit; - - let sampled_pdf = samplable.scattering_pdf(hit.point, sampled_wi, sampled_hit); + let sample_light = |pdf_multiplier: Float, index: usize| { + let index = bvh.get_samplable()[index]; + let light = bvh.get_object(index).unwrap(); - if sampled_pdf > 0.0 { - let li = sampled_si.material.get_emission(sampled_hit, sampled_wi); + let l_wi = light.sample_visible_from_point(hit.point); - let num_lights = bvh.get_samplable().len() as Float; - - Some((sampled_si.hit.point, li, sampled_pdf / num_lights)) - } else { - None + if let Some(si) = + bvh.check_hit_index(&Ray::new(hit.point + 0.0001 * hit.normal, l_wi, 0.0), index) + { + let l_pdf = light.scattering_pdf(hit.point, l_wi, &si.hit); + if l_pdf > 0.0 { + let le = si.material.get_emission(&si.hit, l_wi); + return Some((l_wi, le, l_pdf * pdf_multiplier)); + } } - } else { None - } - } + }; - fn sample_material_test< - A: AccelerationStructure, - P: Primitive, - M: Scatter, - S: NoHit, - >( - _bvh: &A, - hit: &Hit, - _sky: &S, - mat: &M, - ray: &mut Ray, - ) -> Option<(Vec3, Float)> { - let wo = ray.direction; - if mat.scatter_ray(ray, hit) { - None - } else { - Some((ray.direction, mat.scattering_pdf(hit, wo, ray.direction))) + match (samplable_len, sky_can_sample) { + (0, false) => None, + (0, true) => sample_sky(1.0), + (_, false) => { + let multipler = 1.0 / samplable_len as Float; + let light_index = SmallRng::from_rng(thread_rng()) + .unwrap() + .gen_range(0..samplable_len); + sample_light(multipler, light_index) + } + (_, true) => { + let multipler = 1.0 / (samplable_len + 1) as Float; + let light_index = SmallRng::from_rng(thread_rng()) + .unwrap() + .gen_range(0..=samplable_len); + if light_index == samplable_len { + sample_sky(multipler) + } else { + sample_light(multipler, light_index) + } + } } } @@ -126,100 +127,84 @@ impl Ray { A: AccelerationStructure, P: Primitive, M: Scatter, - S: NoHit, >( ray: &mut Ray, - sky: &S, bvh: &A, ) -> (Colour, u64) { let (mut throughput, mut output) = (Colour::one(), Colour::zero()); + let mut ray_count = 0; + let mut wo; let mut hit; let mut mat; - let mut ray_count = 0; + let (surface_intersection, _index) = bvh.check_hit(ray); - // depth 0 - if let Some((surface_intersection, _index)) = bvh.check_hit(ray) { - (hit, mat) = (surface_intersection.hit, surface_intersection.material); + (hit, mat) = (surface_intersection.hit, surface_intersection.material); - wo = ray.direction; + wo = ray.direction; - let emission = mat.get_emission(&hit, wo); + let emission = mat.get_emission(&hit, wo); - let exit = mat.scatter_ray(&mut ray.clone(), &hit); + let exit = mat.scatter_ray(&mut ray.clone(), &hit); - output += emission; + output += emission; - if exit { - return (output, ray_count); - } - } else { - output += sky.get_colour(ray); + if exit { return (output, ray_count); } let mut depth = 1; - while depth < MAX_DEPTH { - if !mat.is_delta() { - // light sampling - if let Some((l_pos, le, l_pdf)) = Ray::sample_lights_test(bvh, &hit, sky, mat, wo) { - let l_wi = (l_pos - hit.point).normalised(); - let m_pdf = mat.scattering_pdf(&hit, wo, l_wi); - let mis_weight = power_heuristic(l_pdf, m_pdf); - output += throughput * mat.eval(&hit, wo, l_wi) * mis_weight * le / l_pdf; - } - ray_count += 1; + while depth < MAX_DEPTH { + // light sampling + let sample_lights = Ray::sample_lights(bvh, &hit); + ray_count += 1; + if let Some((l_wi, le, l_pdf)) = sample_lights { + let m_pdf = mat.scattering_pdf(&hit, wo, l_wi); + let mis_weight = power_heuristic(l_pdf, m_pdf); + output += throughput * mat.eval(&hit, wo, l_wi) * mis_weight * le / l_pdf; } - // material sample and bounce - let (m_wi, m_pdf) = match Ray::sample_material_test(bvh, &hit, sky, mat, ray) { - Some((m_wi, m_pdf)) => (m_wi, m_pdf), - None => break, - }; - - throughput *= if mat.is_delta() { - mat.eval(&hit, wo, m_wi) - } else { - mat.eval_over_scattering_pdf(&hit, wo, m_wi) - }; - - let (surface_intersection, index) = match bvh.check_hit(ray) { - Some((surface_intersection, index)) => (surface_intersection, index), - None => { - output += throughput * sky.get_colour(ray); - break; - } // no sky sampling support yet - }; - - if surface_intersection.material.get_emission(&hit, wo) != Vec3::zero() { - let le = surface_intersection.material.get_emission(&hit, wo); - - if mat.is_delta() { - output += throughput * le; + // material sampling and bounce + let exit = mat.scatter_ray(ray, &hit); + if exit { + break; + } + let m_wi = ray.direction; + + let (intersection, index) = bvh.check_hit(ray); + + let m_pdf = mat.scattering_pdf(&hit, wo, m_wi); + let le = intersection.material.get_emission(&hit, m_wi); + throughput *= mat.eval_over_scattering_pdf(&hit, wo, m_wi); + if le != Vec3::zero() { + if (bvh.get_samplable().contains(&index) && !mat.is_delta()) + || (index == usize::MAX && bvh.sky().can_sample()) + { + let l_pdf = bvh.get_pdf_from_index(&hit, &intersection.hit, m_wi, index); + let mis_weight = power_heuristic(m_pdf, l_pdf); + output += throughput * le * mis_weight; } else { - let light_pdf = if bvh.get_samplable().contains(&index) { - bvh.get_object(index).unwrap().scattering_pdf( - hit.point, - m_wi, - &surface_intersection.hit, - ) / bvh.get_samplable().len() as Float - } else { - 0.0 - }; - let mis_weight = power_heuristic(m_pdf, light_pdf); - - output += throughput * mis_weight * le; + output += throughput * le; } + } - if surface_intersection.material.is_light() { + if intersection.material.is_light() { + break; + } + + if depth > RUSSIAN_ROULETTE_THRESHOLD { + let p = throughput.component_max(); + let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + if rng.gen::() > p { break; } + throughput /= p; } - hit = surface_intersection.hit; - mat = surface_intersection.material; wo = m_wi; + hit = intersection.hit; + mat = intersection.material; depth += 1; } @@ -233,10 +218,8 @@ impl Ray { A: AccelerationStructure, P: Primitive, M: Scatter, - S: NoHit, >( ray: &mut Ray, - sky: &S, bvh: &A, ) -> (Colour, u64) { let (mut throughput, mut output) = (Colour::one(), Colour::zero()); @@ -248,44 +231,43 @@ impl Ray { ray_count += 1; - if let Some((surface_intersection, _index)) = hit_info { - let (hit, mat) = (&surface_intersection.hit, &surface_intersection.material); - - let wo = ray.direction; + let (surface_intersection, _index) = hit_info; + let (hit, mat) = (&surface_intersection.hit, &surface_intersection.material); - let emission = mat.get_emission(hit, wo); + let wo = ray.direction; - let exit = mat.scatter_ray(ray, hit); + let emission = mat.get_emission(hit, wo); - if depth == 0 { - output += throughput * emission; - } + let exit = mat.scatter_ray(ray, hit); + if depth == 0 { + output += emission; if exit { - output += throughput * emission; break; } + } - if !mat.is_delta() { - throughput *= mat.eval_over_scattering_pdf(hit, wo, ray.direction); - } else { - throughput *= mat.eval(hit, wo, ray.direction); - } - - if depth > RUSSIAN_ROULETTE_THRESHOLD { - let p = throughput.component_max(); - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - if rng.gen::() > p { - break; - } - throughput /= p; - } + if exit { + output += throughput * emission; + break; + } - depth += 1; + if !mat.is_delta() { + throughput *= mat.eval_over_scattering_pdf(hit, wo, ray.direction); } else { - output += throughput * sky.get_colour(ray); - break; + throughput *= mat.eval(hit, wo, ray.direction); + } + + if depth > RUSSIAN_ROULETTE_THRESHOLD { + let p = throughput.component_max(); + let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + if rng.gen::() > p { + break; + } + throughput /= p; } + + depth += 1; } if output.contains_nan() || !output.is_finite() { return (Vec3::zero(), ray_count); diff --git a/crates/rt_core/src/sampler.rs b/crates/rt_core/src/sampler.rs index c0699d8..6754747 100644 --- a/crates/rt_core/src/sampler.rs +++ b/crates/rt_core/src/sampler.rs @@ -1,6 +1,6 @@ -use crate::{Float, Ray, Vec3}; +use crate::{Float, Ray, Scatter, SurfaceIntersection, Vec3}; -pub trait NoHit: Sync { +pub trait NoHit: Sync { fn get_colour(&self, ray: &Ray) -> Vec3; fn pdf(&self, _: Vec3) -> Float { unimplemented!() @@ -11,4 +11,7 @@ pub trait NoHit: Sync { fn sample(&self) -> Vec3 { unimplemented!() } + fn get_si(&self, _: &Ray) -> SurfaceIntersection { + unimplemented!() + } } diff --git a/src/main.rs b/src/main.rs index c34abbe..622a5ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,8 +28,8 @@ fn render_gui( M: Scatter + 'static, P: Primitive + 'static, C: Camera + 'static, - S: NoHit + 'static, - A: AccelerationStructure + 'static, + S: NoHit + 'static, + A: AccelerationStructure + 'static, { let required_extensions = vulkano_win::required_extensions(); let instance = Instance::new( @@ -141,8 +141,8 @@ fn render_tui( M: Scatter, P: Primitive, C: Camera, - S: NoHit, - A: AccelerationStructure, + S: NoHit, + A: AccelerationStructure, { let start = print_render_start( render_options.width, diff --git a/src/parameters.rs b/src/parameters.rs index 4b9cc4f..1afead2 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -6,9 +6,10 @@ use region::Region; type MaterialType<'a> = AllMaterials<'a, AllTextures>; type PrimitiveType<'a> = AllPrimitives<'a, MaterialType<'a>>; -type BvhType<'a> = Bvh, MaterialType<'a>>; +type SkyType<'a> = Sky<'a, AllTextures, MaterialType<'a>>; +type BvhType<'a> = Bvh, MaterialType<'a>, SkyType<'a>>; pub type SceneType<'a> = - Scene, PrimitiveType<'a>, SimpleCamera, Sky<'a, AllTextures>, BvhType<'a>>; + Scene, PrimitiveType<'a>, SimpleCamera, SkyType<'a>, BvhType<'a>>; pub struct Parameters { pub render_options: RenderOptions, @@ -48,16 +49,16 @@ pub fn process_args() -> Option<(SceneType<'static>, Parameters)> { MaterialType, PrimitiveType, SimpleCamera, - Sky<'_, AllTextures>, + SkyType, >(&mut region, &cli.filepath) { Ok(a) => a, Err(e) => panic!("{e:?}"), }; - let bvh = Bvh::new(primitives, cli.bvh_type); + let bvh = Bvh::new(primitives, sky, cli.bvh_type); - let scene = Scene::new(bvh, camera, sky, region); + let scene = Scene::new(bvh, camera, region); let render_ops = RenderOptions { width: cli.width, diff --git a/src/scene.rs b/src/scene.rs index ba60d3c..242facf 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -9,12 +9,11 @@ where M: Scatter, P: Primitive, C: Camera, - S: NoHit, - A: AccelerationStructure, + S: NoHit, + A: AccelerationStructure, { acceleration: A, camera: C, - sky: S, _region: ManuallyDrop, } @@ -23,14 +22,13 @@ where M: Scatter, P: Primitive, C: Camera, - S: NoHit, - A: AccelerationStructure, + S: NoHit, + A: AccelerationStructure, { - pub fn new(acceleration: A, camera: C, sky: S, region: ManuallyDrop) -> Self { + pub fn new(acceleration: A, camera: C, region: ManuallyDrop) -> Self { Self { acceleration, camera, - sky, _region: region, } } @@ -40,21 +38,21 @@ where update: Option<(&mut T, impl Fn(&mut T, &SamplerProgress, u64) -> bool)>, ) { let sampler = RandomSampler {}; - sampler.sample_image(opts, &self.camera, &self.sky, &self.acceleration, update); + sampler.sample_image(opts, &self.camera, &self.acceleration, update); } } -unsafe impl Send for Scene +unsafe impl Send for Scene where M: Scatter, P: Primitive, C: Camera, - S: NoHit, - A: AccelerationStructure, + S: NoHit, + A: AccelerationStructure, { } -#[cfg(test)] +/*#[cfg(test)] mod tests { use super::*; use loader::load_str_full; @@ -126,14 +124,21 @@ primitive ( type Tex = AllTextures; type Mat<'a> = AllMaterials<'a, Tex>; type Prim<'a> = AllPrimitives<'a, Mat<'a>>; - type SkyType<'a> = Sky<'a, Tex>; + type SkyType<'a> = Sky<'a, Tex, Mat<'a>>; + type SceneType<'a> = Scene< + Mat<'a>, + Prim<'a>, + SimpleCamera, + SkyType<'a>, + Bvh, Mat<'a>, SkyType<'a>>, + >; let stuff = load_str_full::(&mut region, DATA).unwrap(); let (p, camera, sky) = stuff; - let bvh: Bvh = Bvh::new(p, split::SplitType::Sah); + let bvh: Bvh = Bvh::new(p, sky.clone(), split::SplitType::Sah); - let scene = Scene::new(bvh, camera, sky, region); + let scene: SceneType<'static> = Scene::new(bvh, camera, sky, region); scene.render::<()>( RenderOptions { @@ -145,4 +150,4 @@ primitive ( None as Option<(&mut (), fn(&mut (), &SamplerProgress, u64) -> bool)>, ); } -} +}*/ From aef085708030a7828b232c02c8f3aa53fc99f90a Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Sat, 25 Mar 2023 21:25:39 +1100 Subject: [PATCH 36/39] Added gamma control --- crates/implementations/src/samplers/mod.rs | 4 +++- crates/output/src/lib.rs | 4 ++-- src/main.rs | 8 ++++++-- src/parameters.rs | 11 +++++++---- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/implementations/src/samplers/mod.rs b/crates/implementations/src/samplers/mod.rs index 67e4e40..c4e6953 100644 --- a/crates/implementations/src/samplers/mod.rs +++ b/crates/implementations/src/samplers/mod.rs @@ -25,15 +25,17 @@ pub struct RenderOptions { pub render_method: RenderMethod, pub width: u64, pub height: u64, + pub gamma: Float, } impl Default for RenderOptions { fn default() -> Self { Self { - samples_per_pixel: 100, + samples_per_pixel: 128, render_method: RenderMethod::MIS, width: 1920, height: 1080, + gamma: 2.2, } } } diff --git a/crates/output/src/lib.rs b/crates/output/src/lib.rs index cbc447c..e806bb3 100644 --- a/crates/output/src/lib.rs +++ b/crates/output/src/lib.rs @@ -117,10 +117,10 @@ pub fn print_final_statistics(start: Instant, ray_count: u64, samples: u64) { ) } -pub fn print_render_start(width: u64, height: u64, samples: Option) -> Instant { +pub fn print_render_start(width: u64, height: u64, gamma: f64, samples: Option) -> Instant { match samples { Some(samples) => log::info!( - "Render started:\n\tWidth:\t\t{width}\n\tHeight:\t\t{height}\n\tSamples:\t{samples}" + "Render started:\n\tWidth:\t\t{width}\n\tHeight:\t\t{height}\n\tGamma:\t\t{gamma:.3}\n\tSamples:\t{samples}" ), None => { log::info!("Render started:\n\tWidth:\t\t{width}\n\tHeight:\t\t{height}\n\tSamples:\t∞") diff --git a/src/main.rs b/src/main.rs index 622a5ae..401e94f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,6 +89,7 @@ fn render_gui( let start = print_render_start( render_options.width, render_options.height, + render_options.gamma as f64, Some(render_options.samples_per_pixel), ); @@ -124,6 +125,7 @@ fn render_gui( moved_filename, render_options.width, render_options.height, + render_options.gamma, &*buffer.read().unwrap(), to_sc, ); @@ -147,6 +149,7 @@ fn render_tui( let start = print_render_start( render_options.width, render_options.height, + render_options.gamma as f64, Some(render_options.samples_per_pixel), ); @@ -193,7 +196,7 @@ fn render_tui( .sampler_progress .current_image .iter() - .map(|val| (val.sqrt() * 255.999) as u8) + .map(|val| (val.powf(1.0 / render_options.gamma) * 255.999) as u8) .collect(); if let Some(filename) = filename { @@ -235,6 +238,7 @@ fn save_file( filename: Option, width: u64, height: u64, + gamma: Float, buffer: &[f32], image_fence: Future, ) { @@ -252,7 +256,7 @@ fn save_file( height, buffer .iter() - .map(|val| (val.sqrt() * 255.999) as u8) + .map(|val| (val.powf(1.0 / gamma) * 255.999) as u8) .collect(), filename, true, diff --git a/src/parameters.rs b/src/parameters.rs index 1afead2..193bd98 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -1,4 +1,4 @@ -use crate::scene::Scene; +use crate::{scene::Scene, Float}; use clap::Parser; use implementations::{split::SplitType, *}; @@ -20,15 +20,15 @@ pub struct Parameters { #[derive(Parser, Debug)] #[command(about, long_about=None)] #[command(name = "Pathtracer")] -#[command(about = "An expiremental pathtracer written in Rust")] +#[command(about = "An experimental pathtracer written in Rust")] struct Cli { #[arg(short, long, default_value_t = false)] gui: bool, #[arg(short, long, default_value_t = 128)] samples: u64, - #[arg(short, long, default_value_t = 1920)] + #[arg(short = 'x', long, default_value_t = 1920)] width: u64, - #[arg(short, long, default_value_t = 1080)] + #[arg(short = 'y', long, default_value_t = 1080)] height: u64, #[arg(short, long)] filepath: String, @@ -38,6 +38,8 @@ struct Cli { render_method: RenderMethod, #[arg(short, long)] output: Option, + #[arg(long, default_value_t = 2.2)] + gamma: Float, } pub fn process_args() -> Option<(SceneType<'static>, Parameters)> { @@ -65,6 +67,7 @@ pub fn process_args() -> Option<(SceneType<'static>, Parameters)> { height: cli.height, samples_per_pixel: cli.samples, render_method: cli.render_method, + gamma: cli.gamma, }; let params = Parameters { render_options: render_ops, From 84a9ca1e31508a40970d237db363d1f02f6bb3cc Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Sat, 25 Mar 2023 22:01:26 +1100 Subject: [PATCH 37/39] Optimised coordinate inversion --- crates/implementations/src/utility/coord.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/crates/implementations/src/utility/coord.rs b/crates/implementations/src/utility/coord.rs index 7d742f8..6791a6d 100644 --- a/crates/implementations/src/utility/coord.rs +++ b/crates/implementations/src/utility/coord.rs @@ -20,21 +20,9 @@ impl Coordinate { } } pub fn create_inverse(&self) -> Self { - let a = self.x.x; - let b = self.y.x; - let c = self.z.x; - let d = self.x.y; - let e = self.y.y; - let f = self.z.y; - let g = self.x.z; - let h = self.y.z; - let i = self.z.z; - - let tmp = Vec3::new(e * i - f * h, f * g - d * i, d * h - e * g); - let one_over_det = 1.0 / (a * tmp.x + b * tmp.y + c * tmp.z); - let x = one_over_det * tmp; - let y = one_over_det * Vec3::new(c * h - b * i, a * i - c * g, b * g - a * h); - let z = one_over_det * Vec3::new(b * f - c * e, c * d - a * f, a * e - b * d); + let x = Vec3::new(self.x.x, self.y.x, self.z.x); + let y = Vec3::new(self.x.y, self.y.y, self.z.y); + let z = Vec3::new(self.x.z, self.y.z, self.z.z); Coordinate { x, y, z } } pub fn to_coord(&self, vec: Vec3) -> Vec3 { From 8512e743d96d147154bba0bc28de26c1daced0dc Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Mon, 27 Mar 2023 20:18:08 +1100 Subject: [PATCH 38/39] Change image saving, exr now supported --- crates/output/Cargo.toml | 9 ++++- crates/output/src/lib.rs | 66 ++++++++++++++++++--------------- src/main.rs | 80 ++++++++++++---------------------------- 3 files changed, 66 insertions(+), 89 deletions(-) diff --git a/crates/output/Cargo.toml b/crates/output/Cargo.toml index a4d8a45..ee9146d 100644 --- a/crates/output/Cargo.toml +++ b/crates/output/Cargo.toml @@ -9,5 +9,10 @@ edition = "2021" indicatif = "0.17.3" log = { version = "^0.4.14", features = ["std"] } chrono = "0.4.19" -image = "0.23.14" -fern = { version = "0.6", features = ["colored"] } \ No newline at end of file +image = "0.24" +fern = { version = "0.6", features = ["colored"] } +rt_core = { path = "../rt_core" } + + +[features] +f64 = ["rt_core/f64"] \ No newline at end of file diff --git a/crates/output/src/lib.rs b/crates/output/src/lib.rs index e806bb3..bc3729a 100644 --- a/crates/output/src/lib.rs +++ b/crates/output/src/lib.rs @@ -1,9 +1,9 @@ use fern::colors::{Color, ColoredLevelConfig}; +use rt_core::Float; use std::process; use std::time::Instant; -use std::io::Write; use std::time::Duration; pub fn create_logger() { @@ -62,7 +62,22 @@ pub fn get_readable_duration(duration: Duration) -> String { days_string + &hours_string + &minutes_string + &seconds_string } -pub fn save_u8_to_image(width: u64, height: u64, image: Vec, filename: String, alpha: bool) { +pub fn rgba_to_rgb(data: &[Float]) -> Vec { + data.iter() + .enumerate() + .filter(|(i, _)| i % 4 != 0) + .map(|(_, v)| *v) + .collect::>() +} + +#[allow(clippy::unnecessary_cast)] +pub fn save_data_to_image( + filename: String, + width: u32, + height: u32, + image: Vec, + gamma: Float, +) { let split = filename.split('.').collect::>(); if split.len() != 2 { println!("Invalid filename: {filename}"); @@ -72,38 +87,29 @@ pub fn save_u8_to_image(width: u64, height: u64, image: Vec, filename: Strin let extension = split[1]; match extension { - "png" | "jpg" | "jpeg" | "exr" | "tiff" => { - image::save_buffer( - filename, - &image, - width.try_into().unwrap(), - height.try_into().unwrap(), - if alpha { - image::ColorType::Rgba8 - } else { - image::ColorType::Rgb8 - }, - ) - .unwrap(); + // TODO HDR + "png" | "jpg" | "jpeg" | "tiff" | "ppm" | "bmp" => { + let data: Vec = image + .into_iter() + .map(|val| (val.powf(1.0 / gamma) * 255.999) as u8) + .collect(); + + image::save_buffer(&filename, &data, width, height, image::ColorType::Rgb8).unwrap(); } - "ppm" => { - let mut data = format!("P3\n{width} {height}\n255\n").as_bytes().to_owned(); - - image.iter().enumerate().for_each(|(i, &v)| { - if i % 3 == 0 { - data.extend_from_slice(format!("{v}\n").as_bytes()) - } else { - data.extend_from_slice(format!("{v} ").as_bytes()) - } - }); - - let mut file = std::fs::File::create(filename).unwrap(); - file.write_all(&data).unwrap(); + "exr" => { + // gamma is ignored because of exr + let data: Vec = image.into_iter().map(|val| (val as f32)).collect(); + + let image_buf: image::Rgb32FImage = + image::ImageBuffer::from_raw(width, height, data).unwrap(); + image_buf.save(&filename).unwrap(); } _ => { - println!("Unknown filetype: .{extension}"); + log::error!("Unable to save file: (unknown filetype .{extension})"); + return; } - } + }; + log::info!("Image {filename} saved"); } pub fn print_final_statistics(start: Instant, ray_count: u64, samples: u64) { diff --git a/src/main.rs b/src/main.rs index 401e94f..6bc0518 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,14 +121,22 @@ fn render_gui( moved_render_canceled.store(false, Ordering::Relaxed); - save_file( - moved_filename, - render_options.width, - render_options.height, - render_options.gamma, - &*buffer.read().unwrap(), - to_sc, - ); + if let Some(filename) = moved_filename { + match &*to_sc.lock().unwrap() { + Some(future) => { + future.wait(None).unwrap(); + } + None => {} + } + + save_data_to_image( + filename, + render_options.width as u32, + render_options.height as u32, + rgba_to_rgb(&*buffer.read().unwrap()), + render_options.gamma, + ); + } }); gui.run(); @@ -186,26 +194,17 @@ fn render_tui( scene.render(render_options, Some((&mut image, progress_bar_output))); - let output = ℑ + let ray_count = image.sampler_progress.rays_shot; - let ray_count = output.sampler_progress.rays_shot; - - print_final_statistics(start, ray_count, output.sampler_progress.samples_completed); - - let output: Vec = output - .sampler_progress - .current_image - .iter() - .map(|val| (val.powf(1.0 / render_options.gamma) * 255.999) as u8) - .collect(); + print_final_statistics(start, ray_count, image.sampler_progress.samples_completed); if let Some(filename) = filename { - save_u8_to_image( - render_options.width, - render_options.height, - output, + save_data_to_image( filename, - false, + render_options.width as u32, + render_options.height as u32, + image.sampler_progress.current_image, + render_options.gamma, ); } } @@ -232,36 +231,3 @@ fn main() { println!("feature: gui not enabled"); } } - -#[cfg(feature = "gui")] -fn save_file( - filename: Option, - width: u64, - height: u64, - gamma: Float, - buffer: &[f32], - image_fence: Future, -) { - match filename { - Some(filename) => { - match &*image_fence.lock().unwrap() { - Some(future) => { - future.wait(None).unwrap(); - } - None => {} - } - - save_u8_to_image( - width, - height, - buffer - .iter() - .map(|val| (val.powf(1.0 / gamma) * 255.999) as u8) - .collect(), - filename, - true, - ) - } - None => {} - } -} From 6d431e5de39128dcefbf88bd7d407493ea2359e7 Mon Sep 17 00:00:00 2001 From: nonl4331 Date: Mon, 27 Mar 2023 21:15:51 +1100 Subject: [PATCH 39/39] Added integrators trait --- crates/implementations/src/integrators/mis.rs | 157 +++ crates/implementations/src/integrators/mod.rs | 79 ++ crates/implementations/src/lib.rs | 1 + .../src/samplers/random_sampler.rs | 10 +- crates/rt_core/src/ray.rs | 228 +---- src/macros.rs | 893 ------------------ src/main.rs | 2 - src/utility.rs | 59 -- 8 files changed, 244 insertions(+), 1185 deletions(-) create mode 100644 crates/implementations/src/integrators/mis.rs create mode 100644 crates/implementations/src/integrators/mod.rs delete mode 100644 src/macros.rs delete mode 100644 src/utility.rs diff --git a/crates/implementations/src/integrators/mis.rs b/crates/implementations/src/integrators/mis.rs new file mode 100644 index 0000000..ed4e46a --- /dev/null +++ b/crates/implementations/src/integrators/mis.rs @@ -0,0 +1,157 @@ +use crate::integrators::*; +use rt_core::*; + +pub struct MisIntegrator; + +impl Integrator for MisIntegrator { + fn get_colour, P: Primitive, M: Scatter>( + ray: &mut Ray, + bvh: &A, + ) -> (Vec3, u64) { + let (mut throughput, mut output) = (Vec3::one(), Vec3::zero()); + let mut ray_count = 0; + + let mut wo; + let mut hit; + let mut mat; + let (surface_intersection, _index) = bvh.check_hit(ray); + + (hit, mat) = (surface_intersection.hit, surface_intersection.material); + + wo = ray.direction; + + let emission = mat.get_emission(&hit, wo); + + let exit = mat.scatter_ray(&mut ray.clone(), &hit); + + output += emission; + + if exit { + return (output, ray_count); + } + + let mut depth = 1; + + while depth < MAX_DEPTH { + // light sampling + let sample_lights = sample_lights(bvh, &hit); + ray_count += 1; + if let Some((l_wi, le, l_pdf)) = sample_lights { + let m_pdf = mat.scattering_pdf(&hit, wo, l_wi); + let mis_weight = power_heuristic(l_pdf, m_pdf); + output += throughput * mat.eval(&hit, wo, l_wi) * mis_weight * le / l_pdf; + } + + // material sampling and bounce + let exit = mat.scatter_ray(ray, &hit); + if exit { + break; + } + let m_wi = ray.direction; + + let (intersection, index) = bvh.check_hit(ray); + + let m_pdf = mat.scattering_pdf(&hit, wo, m_wi); + let le = intersection.material.get_emission(&hit, m_wi); + throughput *= mat.eval_over_scattering_pdf(&hit, wo, m_wi); + if le != Vec3::zero() { + if (bvh.get_samplable().contains(&index) && !mat.is_delta()) + || (index == usize::MAX && bvh.sky().can_sample()) + { + let l_pdf = bvh.get_pdf_from_index(&hit, &intersection.hit, m_wi, index); + let mis_weight = power_heuristic(m_pdf, l_pdf); + output += throughput * le * mis_weight; + } else { + output += throughput * le; + } + } + + if intersection.material.is_light() { + break; + } + + if depth > RUSSIAN_ROULETTE_THRESHOLD { + let p = throughput.component_max(); + let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + if rng.gen::() > p { + break; + } + throughput /= p; + } + + wo = m_wi; + hit = intersection.hit; + mat = intersection.material; + + depth += 1; + } + if output.contains_nan() || !output.is_finite() { + return (Vec3::zero(), ray_count); + } + (output, ray_count) + } +} + +fn sample_lights, P: Primitive, M: Scatter>( + bvh: &A, + hit: &Hit, +) -> Option<(Vec3, Vec3, Float)> { + //l_wi, le, l_pdf + let sky = bvh.sky(); + let samplable_len = bvh.get_samplable().len(); + let sky_can_sample = sky.can_sample(); + + let sample_sky = |pdf_multiplier: Float| { + let l_wi = sky.sample(); + let ray = Ray::new(hit.point + 0.0001 * hit.normal, l_wi, 0.0); + + let (sa, index) = bvh.check_hit(&ray); + if index == usize::MAX { + let le = sa.material.get_emission(hit, l_wi); + let l_pdf = sky.pdf(l_wi); + return Some((l_wi, le, l_pdf * pdf_multiplier)); + } + None + }; + + let sample_light = |pdf_multiplier: Float, index: usize| { + let index = bvh.get_samplable()[index]; + let light = bvh.get_object(index).unwrap(); + + let l_wi = light.sample_visible_from_point(hit.point); + + if let Some(si) = + bvh.check_hit_index(&Ray::new(hit.point + 0.0001 * hit.normal, l_wi, 0.0), index) + { + let l_pdf = light.scattering_pdf(hit.point, l_wi, &si.hit); + if l_pdf > 0.0 { + let le = si.material.get_emission(&si.hit, l_wi); + return Some((l_wi, le, l_pdf * pdf_multiplier)); + } + } + None + }; + + match (samplable_len, sky_can_sample) { + (0, false) => None, + (0, true) => sample_sky(1.0), + (_, false) => { + let multipler = 1.0 / samplable_len as Float; + let light_index = SmallRng::from_rng(thread_rng()) + .unwrap() + .gen_range(0..samplable_len); + sample_light(multipler, light_index) + } + (_, true) => { + let multipler = 1.0 / (samplable_len + 1) as Float; + let light_index = SmallRng::from_rng(thread_rng()) + .unwrap() + .gen_range(0..=samplable_len); + if light_index == samplable_len { + sample_sky(multipler) + } else { + sample_light(multipler, light_index) + } + } + } +} diff --git a/crates/implementations/src/integrators/mod.rs b/crates/implementations/src/integrators/mod.rs new file mode 100644 index 0000000..72fc932 --- /dev/null +++ b/crates/implementations/src/integrators/mod.rs @@ -0,0 +1,79 @@ +use crate::rt_core::*; +use rand::rngs::SmallRng; +use rand::thread_rng; +use rand::Rng; +use rand::SeedableRng; + +const MAX_DEPTH: u32 = 50; +const RUSSIAN_ROULETTE_THRESHOLD: u32 = 3; + +pub mod mis; +pub use mis::*; + +pub trait Integrator { + fn get_colour, P: Primitive, M: Scatter>( + ray: &mut Ray, + bvh: &A, + ) -> (Vec3, u64); +} + +pub struct NaiveIntegrator; + +impl Integrator for NaiveIntegrator { + fn get_colour, P: Primitive, M: Scatter>( + ray: &mut Ray, + bvh: &A, + ) -> (Vec3, u64) { + let (mut throughput, mut output) = (Vec3::one(), Vec3::zero()); + let mut depth = 0; + let mut ray_count = 0; + + while depth < MAX_DEPTH { + let hit_info = bvh.check_hit(ray); + + ray_count += 1; + + let (surface_intersection, _index) = hit_info; + let (hit, mat) = (&surface_intersection.hit, &surface_intersection.material); + + let wo = ray.direction; + + let emission = mat.get_emission(hit, wo); + + let exit = mat.scatter_ray(ray, hit); + + if depth == 0 { + output += emission; + if exit { + break; + } + } + + if exit { + output += throughput * emission; + break; + } + + if !mat.is_delta() { + throughput *= mat.eval_over_scattering_pdf(hit, wo, ray.direction); + } else { + throughput *= mat.eval(hit, wo, ray.direction); + } + + if depth > RUSSIAN_ROULETTE_THRESHOLD { + let p = throughput.component_max(); + let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + if rng.gen::() > p { + break; + } + throughput /= p; + } + + depth += 1; + } + if output.contains_nan() || !output.is_finite() { + return (Vec3::zero(), ray_count); + } + (output, ray_count) + } +} diff --git a/crates/implementations/src/lib.rs b/crates/implementations/src/lib.rs index 02202d8..d8c243a 100644 --- a/crates/implementations/src/lib.rs +++ b/crates/implementations/src/lib.rs @@ -1,5 +1,6 @@ mod acceleration; mod camera; +mod integrators; mod materials; mod primitives; mod samplers; diff --git a/crates/implementations/src/samplers/random_sampler.rs b/crates/implementations/src/samplers/random_sampler.rs index a0fca44..a9aed17 100644 --- a/crates/implementations/src/samplers/random_sampler.rs +++ b/crates/implementations/src/samplers/random_sampler.rs @@ -1,3 +1,4 @@ +use crate::integrators::*; use crate::*; use rand::Rng; use rayon::prelude::*; @@ -59,11 +60,12 @@ impl Sampler for RandomSampler { let mut ray = camera.get_ray(u, v); // remember to add le DOF let result = match render_options.render_method { - RenderMethod::Naive => { - Ray::get_colour_naive(&mut ray, acceleration_structure) - } + RenderMethod::Naive => NaiveIntegrator::get_colour( + &mut ray, + acceleration_structure, + ), RenderMethod::MIS => { - Ray::get_colour(&mut ray, acceleration_structure) + MisIntegrator::get_colour(&mut ray, acceleration_structure) } }; diff --git a/crates/rt_core/src/ray.rs b/crates/rt_core/src/ray.rs index 16038be..9170cd5 100644 --- a/crates/rt_core/src/ray.rs +++ b/crates/rt_core/src/ray.rs @@ -1,10 +1,4 @@ -use crate::{power_heuristic, AccelerationStructure, Float, Hit, NoHit, Primitive, Scatter, Vec3}; -use rand::{rngs::SmallRng, thread_rng, Rng, SeedableRng}; - -const RUSSIAN_ROULETTE_THRESHOLD: u32 = 3; -const MAX_DEPTH: u32 = 50; - -pub type Colour = Vec3; +use crate::{Float, Vec3}; #[derive(Debug, Clone, Copy, PartialEq)] pub struct Ray { @@ -54,224 +48,4 @@ impl Ray { pub fn at(&self, t: Float) -> Vec3 { self.origin + self.direction * t } - - fn sample_lights< - A: AccelerationStructure, - P: Primitive, - M: Scatter, - >( - bvh: &A, - hit: &Hit, - ) -> Option<(Vec3, Vec3, Float)> { - //l_wi, le, l_pdf - let sky = bvh.sky(); - let samplable_len = bvh.get_samplable().len(); - let sky_can_sample = sky.can_sample(); - - let sample_sky = |pdf_multiplier: Float| { - let l_wi = sky.sample(); - let ray = Ray::new(hit.point + 0.0001 * hit.normal, l_wi, 0.0); - - let (sa, index) = bvh.check_hit(&ray); - if index == usize::MAX { - let le = sa.material.get_emission(hit, l_wi); - let l_pdf = sky.pdf(l_wi); - return Some((l_wi, le, l_pdf * pdf_multiplier)); - } - None - }; - - let sample_light = |pdf_multiplier: Float, index: usize| { - let index = bvh.get_samplable()[index]; - let light = bvh.get_object(index).unwrap(); - - let l_wi = light.sample_visible_from_point(hit.point); - - if let Some(si) = - bvh.check_hit_index(&Ray::new(hit.point + 0.0001 * hit.normal, l_wi, 0.0), index) - { - let l_pdf = light.scattering_pdf(hit.point, l_wi, &si.hit); - if l_pdf > 0.0 { - let le = si.material.get_emission(&si.hit, l_wi); - return Some((l_wi, le, l_pdf * pdf_multiplier)); - } - } - None - }; - - match (samplable_len, sky_can_sample) { - (0, false) => None, - (0, true) => sample_sky(1.0), - (_, false) => { - let multipler = 1.0 / samplable_len as Float; - let light_index = SmallRng::from_rng(thread_rng()) - .unwrap() - .gen_range(0..samplable_len); - sample_light(multipler, light_index) - } - (_, true) => { - let multipler = 1.0 / (samplable_len + 1) as Float; - let light_index = SmallRng::from_rng(thread_rng()) - .unwrap() - .gen_range(0..=samplable_len); - if light_index == samplable_len { - sample_sky(multipler) - } else { - sample_light(multipler, light_index) - } - } - } - } - - pub fn get_colour< - A: AccelerationStructure, - P: Primitive, - M: Scatter, - >( - ray: &mut Ray, - bvh: &A, - ) -> (Colour, u64) { - let (mut throughput, mut output) = (Colour::one(), Colour::zero()); - let mut ray_count = 0; - - let mut wo; - let mut hit; - let mut mat; - let (surface_intersection, _index) = bvh.check_hit(ray); - - (hit, mat) = (surface_intersection.hit, surface_intersection.material); - - wo = ray.direction; - - let emission = mat.get_emission(&hit, wo); - - let exit = mat.scatter_ray(&mut ray.clone(), &hit); - - output += emission; - - if exit { - return (output, ray_count); - } - - let mut depth = 1; - - while depth < MAX_DEPTH { - // light sampling - let sample_lights = Ray::sample_lights(bvh, &hit); - ray_count += 1; - if let Some((l_wi, le, l_pdf)) = sample_lights { - let m_pdf = mat.scattering_pdf(&hit, wo, l_wi); - let mis_weight = power_heuristic(l_pdf, m_pdf); - output += throughput * mat.eval(&hit, wo, l_wi) * mis_weight * le / l_pdf; - } - - // material sampling and bounce - let exit = mat.scatter_ray(ray, &hit); - if exit { - break; - } - let m_wi = ray.direction; - - let (intersection, index) = bvh.check_hit(ray); - - let m_pdf = mat.scattering_pdf(&hit, wo, m_wi); - let le = intersection.material.get_emission(&hit, m_wi); - throughput *= mat.eval_over_scattering_pdf(&hit, wo, m_wi); - if le != Vec3::zero() { - if (bvh.get_samplable().contains(&index) && !mat.is_delta()) - || (index == usize::MAX && bvh.sky().can_sample()) - { - let l_pdf = bvh.get_pdf_from_index(&hit, &intersection.hit, m_wi, index); - let mis_weight = power_heuristic(m_pdf, l_pdf); - output += throughput * le * mis_weight; - } else { - output += throughput * le; - } - } - - if intersection.material.is_light() { - break; - } - - if depth > RUSSIAN_ROULETTE_THRESHOLD { - let p = throughput.component_max(); - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - if rng.gen::() > p { - break; - } - throughput /= p; - } - - wo = m_wi; - hit = intersection.hit; - mat = intersection.material; - - depth += 1; - } - if output.contains_nan() || !output.is_finite() { - return (Vec3::zero(), ray_count); - } - (output, ray_count) - } - - pub fn get_colour_naive< - A: AccelerationStructure, - P: Primitive, - M: Scatter, - >( - ray: &mut Ray, - bvh: &A, - ) -> (Colour, u64) { - let (mut throughput, mut output) = (Colour::one(), Colour::zero()); - let mut depth = 0; - let mut ray_count = 0; - - while depth < MAX_DEPTH { - let hit_info = bvh.check_hit(ray); - - ray_count += 1; - - let (surface_intersection, _index) = hit_info; - let (hit, mat) = (&surface_intersection.hit, &surface_intersection.material); - - let wo = ray.direction; - - let emission = mat.get_emission(hit, wo); - - let exit = mat.scatter_ray(ray, hit); - - if depth == 0 { - output += emission; - if exit { - break; - } - } - - if exit { - output += throughput * emission; - break; - } - - if !mat.is_delta() { - throughput *= mat.eval_over_scattering_pdf(hit, wo, ray.direction); - } else { - throughput *= mat.eval(hit, wo, ray.direction); - } - - if depth > RUSSIAN_ROULETTE_THRESHOLD { - let p = throughput.component_max(); - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - if rng.gen::() > p { - break; - } - throughput /= p; - } - - depth += 1; - } - if output.contains_nan() || !output.is_finite() { - return (Vec3::zero(), ray_count); - } - (output, ray_count) - } } diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index c36811d..0000000 --- a/src/macros.rs +++ /dev/null @@ -1,893 +0,0 @@ -//------ -// MISC -//------ -#[macro_export] -macro_rules! position { - ($x:expr, $y:expr, $z:expr) => { - implementations::rt_core::vec::Vec3::new( - $x as implementations::rt_core::Float, - $y as implementations::rt_core::Float, - $z as implementations::rt_core::Float, - ) - }; - ($x:expr, $y:expr) => { - implementations::rt_core::vec::Vec2::new( - $x as implementations::rt_core::Float, - $y as implementations::rt_core::Float, - ) - }; -} - -#[macro_export] -macro_rules! colour { - ($r:expr,$g:expr,$b:expr) => { - implementations::rt_core::Vec3::new( - $r as implementations::rt_core::Float, - $g as implementations::rt_core::Float, - $b as implementations::rt_core::Float, - ) - }; - ($value:expr) => { - implementations::rt_core::Vec3::new( - $value as implementations::rt_core::Float, - $value as implementations::rt_core::Float, - $value as implementations::rt_core::Float, - ) - }; -} - -#[macro_export] -macro_rules! rotation { - ($x:expr, $y:expr, $z:expr, D) => { - implementations::rt_core::vec::Vec3::new( - $x as implementations::rt_core::Float * implementations::rt_core::PI / 180.0, - $y as implementations::rt_core::Float * implementations::rt_core::PI / 180.0, - $z as implementations::rt_core::Float * implementations::rt_core::PI / 180.0, - ) - }; - ($x:expr, $y:expr, $z:expr, R) => { - implementations::rt_core::vec::Vec3::new( - $x as implementations::rt_core::Float, - $y as implementations::rt_core::Float, - $z as implementations::rt_core::Float, - ) - }; -} - -#[macro_export] -macro_rules! axis { - (X) => { - implementations::Axis::X - }; - (Y) => { - implementations::Axis::Y - }; - (Z) => { - implementations::Axis::Z - }; -} - -//----- -// TEXTURES -//----- -#[macro_export] -macro_rules! solid_colour { - ($r:expr, $g:expr, $b:expr) => { - std::sync::Arc::new(implementations::AllTextures::SolidColour( - implementations::SolidColour::new(colour!($r, $g, $b)), - )) - }; - ($colour:expr) => { - std::sync::Arc::new(implementations::AllTextures::SolidColour( - implementations::SolidColour::new($colour), - )) - }; -} - -#[macro_export] -macro_rules! image { - ($filepath:expr) => { - std::sync::Arc::new(implementations::AllTextures::ImageTexture( - implementations::ImageTexture::new($filepath), - )) - }; -} - -#[macro_export] -macro_rules! checkered { - ($colour_one:expr, $colour_two:expr) => { - std::sync::Arc::new(implementations::AllTextures::CheckeredTexture( - implementations::rt_core::CheckeredTexture::new($colour_one, $colour_two), - )) - }; - ($r1:expr, $g1:expr, $b1:expr, $r2:expr, $g2:expr, $b2:expr) => { - std::sync::Arc::new(implementations::AllTextures::CheckeredTexture( - implementations::rt_core::CheckeredTexture::new( - colour!($r1, $g1, $b1), - colour!($r2, $g2, $b2), - ), - )) - }; -} - -#[macro_export] -macro_rules! texture_lerp { - ($colour_one:expr, $colour_two:expr) => { - std::sync::Arc::new(implementations::AllTextures::Lerp( - implementations::Lerp::new($colour_one, $colour_two), - )) - }; - ($r1:expr, $g1:expr, $b1:expr, $r2:expr, $g2:expr, $b2:expr) => { - std::sync::Arc::new(implementations::AllTextures::Lerp( - implementations::rt_core::Lerp::new(colour!($r1, $g1, $b1), colour!($r2, $g2, $b2)), - )) - }; -} - -#[macro_export] -macro_rules! perlin { - () => { - std::sync::Arc::new(implementations::AllTextures::Perlin(Box::new( - implementations::rt_core::Perlin::new(), - ))) - }; -} - -//----- -// MATERIALS -//----- -#[macro_export] -macro_rules! diffuse { - ($r:expr,$g:expr,$b:expr, $absorption:expr) => { - std::sync::Arc::new(implementations::AllMaterials::Lambertian( - implementations::Lambertian::new( - &std::sync::Arc::new(implementations::AllTextures::SolidColour( - implementations::SolidColour::new(colour!($r, $g, $b)), - )), - $absorption as implementations::rt_core::Float, - ), - )) - }; - ($texture:expr,$absorption:expr) => { - std::sync::Arc::new(implementations::AllMaterials::Lambertian( - implementations::Lambertian::new( - $texture, - $absorption as implementations::rt_core::Float, - ), - )) - }; -} - -#[macro_export] -macro_rules! reflect { - ($r:expr,$g:expr,$b:expr, $fuzz:expr) => { - std::sync::Arc::new(implementations::AllMaterials::Reflect( - implementations::rt_core::Reflect::new( - &Arc::new(implementations::AllTextures::SolidColour( - implementations::rt_core::SolidColour::new(colour!($r, $g, $b)), - )), - $fuzz as implementations::rt_core::Float, - ), - )); - }; - ($texture:expr,$fuzz:expr) => { - std::sync::Arc::new(implementations::AllMaterials::Reflect( - implementations::Reflect::new($texture, $fuzz as implementations::rt_core::Float), - )) - }; -} - -#[macro_export] -macro_rules! refract { - ($r:expr,$g:expr,$b:expr, $eta:expr) => { - std::sync::Arc::new(implementations::AllMaterials::Refract( - implementations::rt_core::Refract::new( - &std::sync::Arc::new(implementations::AllTextures::SolidColour( - implementations::rt_core::SolidColour::new(colour!($r, $g, $b)), - )), - $eta as implementations::rt_core::Float, - ), - )) - }; - ($texture:expr,$eta:expr) => { - std::sync::Arc::new(implementations::AllMaterials::Refract( - implementations::Refract::new($texture, $eta as implementations::rt_core::Float), - )) - }; -} - -#[macro_export] -macro_rules! emit { - ($r:expr,$g:expr,$b:expr, $strength:expr) => { - std::sync::Arc::new(implementations::AllMaterials::Emit( - implementations::rt_core::Emit::new( - &std::sync::Arc::new(Texture::SolidColour(SolidColour::new(colour!($r, $g, $b)))), - $strength as implementations::rt_core::Float, - ), - )); - }; - ($texture:expr,$strength:expr) => { - std::sync::Arc::new(implementations::AllMaterials::Emit( - implementations::Emit::new($texture, $strength as implementations::rt_core::Float), - )) - }; -} - -//----- -// PRIMITIVES -//----- -#[macro_export] -macro_rules! sphere { - ($x:expr, $y:expr, $z:expr, $radius:expr, $material:expr) => { - implementations::AllPrimitives::Sphere(implementations::sphere::Sphere::new( - position!($x, $y, $z), - $radius as Float, - $material, - )) - }; - ($position:expr, $radius:expr, $material:expr) => { - implementations::AllPrimitives::Sphere(implementations::sphere::Sphere::new( - $position, - $radius as implementations::rt_core::Float, - $material, - )) - }; -} - -#[macro_export] -macro_rules! aarect { - ($point_one:expr, $point_two:expr, $axis_value:expr, $axis:expr, $material:expr) => {{ - let point_three = implementations::Axis::point_from_2d( - implementations::rt_core::Vec2::new($point_one.x, $point_two.y), - $axis, - $axis_value, - ); - let point_four = implementations::Axis::point_from_2d( - implementations::rt_core::Vec2::new($point_two.x, $point_one.y), - $axis, - $axis_value, - ); - let point_one = implementations::Axis::point_from_2d($point_one, $axis, $axis_value); - let point_two = implementations::Axis::point_from_2d($point_two, $axis, $axis_value); - let mut vec = Vec::new(); - vec.push(triangle!(point_one, point_two, point_three, $material)); - vec.push(triangle!(point_one, point_two, point_four, $material)); - - vec - }}; - ($x1:expr, $y1:expr, $x2:expr, $y2:expr, $axis_value:expr, $axis:expr, $material:expr, $cp:expr) => {{ - let point_three = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x1, $y2), - $axis, - $axis_value, - ); - let point_four = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x2, $y1), - $axis, - $axis_value, - ); - let point_one = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x1, $y1), - $axis, - $axis_value, - ); - let point_two = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x2, $y2), - $axis, - $axis_value, - ); - let mut vec = Vec::new(); - let nr = 0.5 * (point_one + point_two) - $cp; - vec.push(triangle!(point_one, point_two, point_three, $material, nr)); - vec.push(triangle!(point_one, point_two, point_four, $material, nr)); - - vec - }}; - ($x1:expr, $y1:expr, $x2:expr, $y2:expr, $axis_value:expr, $axis:expr, $material:expr) => {{ - let point_three = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x1, $y2), - $axis, - $axis_value, - ); - let point_four = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x2, $y1), - $axis, - $axis_value, - ); - let point_one = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x1, $y1), - $axis, - $axis_value, - ); - let point_two = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x2, $y2), - $axis, - $axis_value, - ); - - let mut vec = Vec::new(); - vec.push(triangle!(point_one, point_two, point_three, $material)); - vec.push(triangle!(point_one, point_two, point_four, $material)); - - vec - }}; -} - -#[macro_export] -macro_rules! rect { - ($point_one:expr, $point_two:expr, $axis_value:expr, $axis:expr, $rotation:expr, $material:expr) => {{ - let center_point = 0.5 * ($point_one + $point_two); - let point_three = implementations::Axis::point_from_2d( - implementations::rt_core::Vec2::new($point_one.x, $point_two.y), - $axis, - $axis_value, - ); - let point_four = implementations::Axis::point_from_2d( - implementations::rt_core::Vec2::new($point_two.x, $point_one.y), - $axis, - $axis_value, - ); - let point_one = implementations::Axis::point_from_2d($point_one, $axis, $axis_value); - let point_two = implementations::Axis::point_from_2d($point_two, $axis, $axis_value); - - let sin_rot = implementations::rt_core::Vec3::new( - $rotation.x.sin(), - $rotation.y.sin(), - $rotation.z.sin(), - ); - let cos_rot = implementations::rt_core::Vec3::new( - $rotation.x.cos(), - $rotation.y.cos(), - $rotation.z.cos(), - ); - - let point_one = - $crate::utility::rotate_around_point(point_one, center_point, sin_rot, cos_rot); - let point_two = - $crate::utility::rotate_around_point(point_two, center_point, sin_rot, cos_rot); - let point_three = - $crate::utility::rotate_around_point(point_three, center_point, sin_rot, cos_rot); - let point_four = - $crate::utility::rotate_around_point(point_four, center_point, sin_rot, cos_rot); - - let vec = Vec::new(); - vec.push(triangle!(point_one, point_two, point_three, $material)); - vec.push(triangle!(point_one, point_two, point_four, $material)); - - vec - }}; - ($x1:expr, $y1:expr, $x2:expr, $y2:expr, $axis_value:expr, $axis:expr, $rotation:expr, $material:expr) => {{ - let mut point_three = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x1, $y2), - $axis, - $axis_value, - ); - let mut point_four = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x2, $y1), - $axis, - $axis_value, - ); - let mut point_one = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x1, $y1), - $axis, - $axis_value, - ); - let mut point_two = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x2, $y2), - $axis, - $axis_value, - ); - - let sin_rot = implementations::rt_core::Vec3::new( - $rotation.x.sin(), - $rotation.y.sin(), - $rotation.z.sin(), - ); - let cos_rot = implementations::rt_core::Vec3::new( - $rotation.x.cos(), - $rotation.y.cos(), - $rotation.z.cos(), - ); - - $crate::utility::rotate_around_point(&mut point_one, center_point, sin_rot, cos_rot); - - $crate::utility::rotate_around_point(&mut point_two, center_point, sin_rot, cos_rot); - - $crate::utility::rotate_around_point(&mut point_three, center_point, sin_rot, cos_rot); - - $crate::utility::rotate_around_point(&mut point_four, center_point, sin_rot, cos_rot); - - let mut vec = Vec::new(); - vec.push(triangle!(point_one, point_two, point_three, $material)); - vec.push(triangle!(point_one, point_two, point_four, $material)); - - vec - }}; - ($x1:expr, $y1:expr, $x2:expr, $y2:expr, $axis_value:expr, $axis:expr, $center_point:expr, $sin_rot:expr, $cos_rot:expr, $material:expr) => {{ - let mut point_three = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x1, $y2), - $axis, - $axis_value, - ); - let mut point_four = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x2, $y1), - $axis, - $axis_value, - ); - let mut point_one = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x1, $y1), - $axis, - $axis_value, - ); - let mut point_two = implementations::Axis::point_from_2d( - &implementations::rt_core::Vec2::new($x2, $y2), - $axis, - $axis_value, - ); - - $crate::utility::rotate_around_point(&mut point_one, $center_point, $sin_rot, $cos_rot); - - $crate::utility::rotate_around_point(&mut point_two, $center_point, $sin_rot, $cos_rot); - - $crate::utility::rotate_around_point(&mut point_three, $center_point, $sin_rot, $cos_rot); - - $crate::utility::rotate_around_point(&mut point_four, $center_point, $sin_rot, $cos_rot); - - let mut vec = Vec::new(); - vec.push(triangle!(point_one, point_two, point_three, $material)); - vec.push(triangle!(point_one, point_two, point_four, $material)); - - vec - }}; -} - -#[macro_export] -macro_rules! aacuboid { - ($point_one:expr, $point_two:expr, $material:expr) => {{ - let center_point = 0.5 * ($point_one + $point_two); - let mut vec = Vec::new(); - vec.extend(aarect!( - $point_one.x, - $point_one.y, - $point_two.x, - $point_two.y, - $point_one.z, - implementations::Axis::Z, - $material - center_point - )); - vec.extend(aarect!( - $point_one.x, - $point_one.y, - $point_two.x, - $point_two.y, - $point_two.z, - implementations::Axis::Z, - $material - center_point - )); - - vec.extend(aarect!( - $point_one.x, - $point_one.z, - $point_two.x, - $point_two.z, - $point_one.y, - implementations::Axis::Y, - $material - center_point - )); - vec.extend(aarect!( - $point_one.x, - $point_one.z, - $point_two.x, - $point_two.z, - $point_two.y, - implementations::Axis::Y, - $material - )); - - vec.extend(aarect!( - $point_one.y, - $point_one.z, - $point_two.y, - $point_two.z, - $point_one.x, - implementations::Axis::X, - $material - center_point - )); - vec.extend(aarect!( - $point_one.y, - $point_one.z, - $point_two.y, - $point_two.z, - $point_two.x, - implementations::Axis::X, - $material - center_point - )); - vec - }}; - ($x1:expr, $y1:expr, $z1:expr, $x2:expr, $y2:expr, $z2:expr, $material:expr) => {{ - let mut vec = Vec::new(); - let center_point = 0.5 * (implementations::rt_core::Vec3::new($x1, $y1, $z1) + implementations::rt_core::Vec3::new($x2, $y2, $z2)); - vec.extend(aarect!( - $x1, - $y1, - $x2, - $y2, - $z1, - &implementations::Axis::Z, - $material, - center_point - )); - vec.extend(aarect!( - $x1, - $y1, - $x2, - $y2, - $z2, - &implementations::Axis::Z, - $material, - center_point - )); - - vec.extend(aarect!( - $x1, - $z1, - $x2, - $z2, - $y1, - &implementations::Axis::Y, - $material, - center_point - )); - vec.extend(aarect!( - $x1, - $z1, - $x2, - $z2, - $y2, - &implementations::Axis::Y, - $material, - center_point - )); - - vec.extend(aarect!( - $y1, - $z1, - $y2, - $z2, - $x1, - &implementations::Axis::X, - $material, - center_point - )); - vec.extend(aarect!( - $y1, - $z1, - $y2, - $z2, - $x2, - &implementations::Axis::X, - $material, - center_point - )); - vec - }}; -} - -#[macro_export] -macro_rules! cuboid { - ($point_one:expr, $point_two:expr, $rotation:expr, $material:expr) => {{ - let center_point = 0.5 * ($point_one + $point_two); - let sin_rot = ($rotation.x.sin(), $rotation.y.sin(), $rotation.z.sin()); - let cos_rot = ($rotation.x.cos(), $rotation.y.cos(), $rotation.z.cos()); - - let mut vec = Vec::new(); - vec.extend(rect!( - $point_one.x, - $point_one.y, - $point_two.x, - $point_two.y, - $point_one.z, - implementations::Axis::Z, - $center_point, - $sin_rot, - $cos_rot, - $material - )); - vec.extend(rect!( - $point_one.x, - $point_one.y, - $point_two.x, - $point_two.y, - $point_two.z, - implementations::Axis::Z, - $center_point, - $sin_rot, - $cos_rot, - $material - )); - - vec.extend(rect!( - $point_one.x, - $point_one.z, - $point_two.x, - $point_two.z, - $point_one.y, - implementations::Axis::Y, - $center_point, - $sin_rot, - $cos_rot, - $material - )); - vec.extend(rect!( - $point_one.x, - $point_one.z, - $point_two.x, - $point_two.z, - $point_two.y, - implementations::Axis::Y, - $center_point, - $sin_rot, - $cos_rot, - $material - )); - - vec.extend(rect!( - $point_one.y, - $point_one.z, - $point_two.y, - $point_two.z, - $point_one.x, - implementations::Axis::X, - $center_point, - $sin_rot, - $cos_rot, - $material - )); - vec.extend(rect!( - $point_one.y, - $point_one.z, - $point_two.y, - $point_two.z, - $point_two.x, - implementations::Axis::X, - $center_point, - $sin_rot, - $cos_rot, - $material - )); - vec - }}; - ($x1:expr, $y1:expr, $z1:expr, $x2:expr, $y2:expr, $z2:expr, $rotation:expr, $material:expr) => {{ - let point_one = implementations::rt_core::Vec3::new($x1, $y1, $z1); - let point_two = implementations::rt_core::Vec3::new($x2, $y2, $z2); - let center_point = 0.5 * (point_one + point_two); - let sin_rot = implementations::rt_core::Vec3::new( - $rotation.x.sin(), - $rotation.y.sin(), - $rotation.z.sin(), - ); - let cos_rot = implementations::rt_core::Vec3::new( - $rotation.x.cos(), - $rotation.y.cos(), - $rotation.z.cos(), - ); - - let mut vec = Vec::new(); - vec.extend(rect!( - point_one.x, - point_one.y, - point_two.x, - point_two.y, - point_one.z, - &implementations::Axis::Z, - center_point, - sin_rot, - cos_rot, - $material - )); - vec.extend(rect!( - point_one.x, - point_one.y, - point_two.x, - point_two.y, - point_two.z, - &implementations::Axis::Z, - center_point, - sin_rot, - cos_rot, - $material - )); - - vec.extend(rect!( - point_one.x, - point_one.z, - point_two.x, - point_two.z, - point_one.y, - &implementations::Axis::Y, - center_point, - sin_rot, - cos_rot, - $material - )); - vec.extend(rect!( - point_one.x, - point_one.z, - point_two.x, - point_two.z, - point_two.y, - &implementations::Axis::Y, - center_point, - sin_rot, - cos_rot, - $material - )); - - vec.extend(rect!( - point_one.y, - point_one.z, - point_two.y, - point_two.z, - point_one.x, - &implementations::Axis::X, - center_point, - sin_rot, - cos_rot, - $material - )); - vec.extend(rect!( - point_one.y, - point_one.z, - point_two.y, - point_two.z, - point_two.x, - &implementations::Axis::X, - center_point, - sin_rot, - cos_rot, - $material - )); - vec - }}; -} - -#[macro_export] -macro_rules! model { - ($filepath:expr, $material:expr) => { - $crate::ray_tracing::load_model::load_model($filepath, $material) - }; -} - -// assumes orientation -#[macro_export] -macro_rules! triangle { - ($point_one:expr, $point_two:expr, $point_three:expr, $material:expr) => {{ - let normal = { - let a = $point_two - $point_one; - let b = $point_three - $point_one; - a.cross(b) - } - .normalised(); - - implementations::AllPrimitives::Triangle(implementations::Triangle::new( - [$point_one, $point_two, $point_three], - [normal; 3], - $material, - )) - }}; - ($point_one:expr, $point_two:expr, $point_three:expr, $material:expr, $nr:expr) => {{ - let mut normal = { - let a = $point_two - $point_one; - let b = $point_three - $point_one; - a.cross(b) - } - .normalised(); - - if normal.dot($nr) < 0.0 { - normal = -1.0 * normal; - } - - implementations::AllPrimitives::Triangle(implementations::Triangle::new( - [$point_one, $point_two, $point_three], - [normal; 3], - $material, - )) - }}; - ($point_one:expr, $point_two:expr, $point_three:expr, $normal_ray:expr, $material:expr) => {{ - let mut normal = { - let a = $point_two - $point_one; - let b = $point_three - $point_one; - a.cross(b) - } - .normalised(); - - if normal.dot($normal_ray) < 0.0 { - normal = -normal; - } - - implementations::AllPrimitives::Triangle(implementations::Triangle::new( - [$point_one, $point_two, $point_three], - [normal; 3], - $material, - )) - }}; - - ($p1x:expr, $p1y:expr, $p1z:expr, $p2x:expr, $p2y:expr, $p2z:expr, $p3x:expr, $p3y:expr, $p3z:expr, $material:expr) => {{ - let point_one = position!($p1x, $p1y, $p1z); - let point_two = position!($p2x, $p2y, $p2z); - let point_three = position!($p3x, $p3y, $p3z); - let normal = { - let a = point_two - point_one; - let b = point_three - point_one; - a.cross(b) - } - .normalized(); - - implementations::rt_core::AllPrimitives::Triangle( - implementations::rt_core::Triangle::new_from_arc( - [point_one, point_two, point_three], - [normal; 3], - $material, - ), - ) - }}; -} - -//----- -// OTHER STRUCTURES -//----- -#[macro_export] -macro_rules! camera { - ($origin:expr, $lookat:expr, $vup:expr, $fov:expr, $aspect_ratio:expr, $aperture:expr, $focus_dist:expr) => { - std::sync::Arc::new(implementations::SimpleCamera::new( - $origin, - $lookat, - $vup, - $fov as implementations::rt_core::Float, - $aspect_ratio as implementations::rt_core::Float, - $aperture as implementations::rt_core::Float, - $focus_dist as implementations::rt_core::Float, - )) - }; -} - -#[macro_export] -macro_rules! random_sampler { - () => { - std::sync::Arc::new(implementations::random_sampler::RandomSampler {}) - }; -} - -#[macro_export] -macro_rules! sky { - () => { - std::sync::Arc::new(implementations::Sky::new( - &std::sync::Arc::new(implementations::AllTextures::SolidColour( - implementations::SolidColour::new(implementations::rt_core::Vec3::zero()), - )), - (100, 100), - )) - }; - ($sky_texture:expr) => { - std::sync::Arc::new(implementations::Sky::new($sky_texture, (100, 100))) - }; -} - -#[macro_export] -macro_rules! bvh { - ($primitives:expr, $split_type:expr) => { - std::sync::Arc::new(implementations::Bvh::new($primitives, $split_type)) - }; -} - -#[macro_export] -macro_rules! scene { - ($camera:expr, $sky:expr, $sampler:expr, $bvh:expr) => { - scene::Scene::new($camera, $sky, $sampler, $bvh) - }; -} diff --git a/src/main.rs b/src/main.rs index 6bc0518..31e6512 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,10 +14,8 @@ use { winit::event_loop::EventLoopProxy, }; -mod macros; mod parameters; mod scene; -mod utility; #[cfg(feature = "gui")] fn render_gui( diff --git a/src/utility.rs b/src/utility.rs deleted file mode 100644 index 6b3604d..0000000 --- a/src/utility.rs +++ /dev/null @@ -1,59 +0,0 @@ -use implementations::rt_core::*; -use implementations::*; - -pub fn rotate_around_point( - point: &mut Vec3, - center_point: Vec3, - sin_angles: Vec3, - cos_angles: Vec3, -) { - *point -= center_point; - - rotate_around_axis(point, Axis::X, sin_angles.x, cos_angles.x); - rotate_around_axis(point, Axis::Y, sin_angles.y, cos_angles.y); - rotate_around_axis(point, Axis::Z, sin_angles.z, cos_angles.z); - - *point += center_point; -} - -pub fn rotate_around_axis(point: &mut Vec3, axis: Axis, sin: Float, cos: Float) { - match axis { - Axis::X => { - let old_y = point.y; - point.y = cos * point.y - sin * point.z; - point.z = sin * old_y + cos * point.z; - } - Axis::Y => { - let old_x = point.x; - point.x = cos * point.x - sin * point.z; - point.z = sin * old_x + cos * point.z; - } - Axis::Z => { - let old_y = point.y; - point.y = cos * point.y - sin * point.x; - point.x = sin * old_y + cos * point.x; - } - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn rotation() { - let center_point = Vec3::new(1.0, 0.0, 0.0); - - let mut point = Vec3::new(2.0, 0.0, 0.0); - - let angles = Vec3::new(0.0, 45.0 * PI / 180.0, 0.0); - - let cos_angles = Vec3::new(angles.x.cos(), angles.y.cos(), angles.z.cos()); - let sin_angles = Vec3::new(angles.x.sin(), angles.y.sin(), angles.z.sin()); - - rotate_around_point(&mut point, center_point, sin_angles, cos_angles); - - assert!((point - Vec3::new(1.707107, 0.0, 0.7071069)).abs().mag() < 0.000001); - } -}