Cast rays for single light wavelengths instead of RGB values

Colours are wrong because Spectrum class isn't implemented.

Also there's a lot of other cleanup that needs to be done.
This commit is contained in:
Matthew Gordon 2020-09-05 22:45:43 -04:00
parent 8defc781b1
commit 2494a30aef
12 changed files with 223 additions and 109 deletions

View File

@ -2,7 +2,7 @@ use crate::colour::{ColourXyz, Photon};
use crate::image::{ImageRgbU8, ToneMapper};
use crate::util::{Array2D, Tile};
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct AccumulationBuffer {
colour_buffer: Array2D<ColourXyz>,
weight_buffer: Array2D<f64>,

View File

@ -1,7 +1,10 @@
use crate::math::Vec3;
use super::colour::{ColourRgbF, NamedColour};
use super::image::ImageRgbF;
use super::accumulation_buffer::AccumulationBuffer;
use super::colour::{
ColourRgbF, NamedColour, Photon, Spectrum, LONGEST_VISIBLE_WAVELENGTH,
SHORTEST_VISIBLE_WAVELENGTH,
};
use super::integrators::{DirectionalLight, Integrator, WhittedIntegrator};
use super::raycasting::Ray;
use super::sampler::Sampler;
@ -90,42 +93,70 @@ const RECURSION_LIMIT: u16 = 32;
/// // display and/or save tile_image
/// }
/// ```
pub fn partial_render_scene(scene: &Scene, tile: Tile, height: usize, width: usize) -> ImageRgbF {
let mut output_image_tile = ImageRgbF::new(tile.width(), tile.height());
pub fn partial_render_scene(
scene: &Scene,
tile: Tile,
height: usize,
width: usize,
) -> AccumulationBuffer {
let mut output_image_tile = AccumulationBuffer::new(tile.width(), tile.height());
let image_sampler = ImageSampler::new(width, height, scene.camera_location);
let ambient_intensity = 0.0;
let directional_intensity1 = 7.0;
let directional_intensity2 = 3.0;
let directional_intensity3 = 2.0;
let integrator = WhittedIntegrator {
ambient_light: ColourRgbF::from_named(NamedColour::White) * ambient_intensity,
ambient_light: Spectrum::from_linear_rgb(
&(ColourRgbF::from_named(NamedColour::White) * ambient_intensity),
),
lights: vec![
DirectionalLight {
direction: Vec3::new(1.0, 1.0, -1.0).normalize(),
colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity1,
spectrum: Spectrum::from_linear_rgb(
&(ColourRgbF::from_named(NamedColour::White) * directional_intensity1),
),
},
DirectionalLight {
direction: Vec3::new(-0.5, 2.0, -0.5).normalize(),
colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity2,
spectrum: Spectrum::from_linear_rgb(
&(ColourRgbF::from_named(NamedColour::White) * directional_intensity2),
),
},
DirectionalLight {
direction: Vec3::new(-3.0, 0.1, -0.5).normalize(),
colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity3,
spectrum: Spectrum::from_linear_rgb(
&(ColourRgbF::from_named(NamedColour::White) * directional_intensity3),
),
},
],
};
let sampler = Sampler { scene: &scene };
for column in 0..tile.width() {
for row in 0..tile.height() {
let ray = image_sampler.ray_for_pixel(tile.start_row + row, tile.start_column + column);
for wavelength_number in 0..8 {
let wavelength_ratio = wavelength_number as f64 / 8.0;
let wavelength = SHORTEST_VISIBLE_WAVELENGTH
+ wavelength_ratio * (LONGEST_VISIBLE_WAVELENGTH - SHORTEST_VISIBLE_WAVELENGTH);
let ray =
image_sampler.ray_for_pixel(tile.start_row + row, tile.start_column + column);
let hit = sampler.sample(&ray);
let colour = match hit {
None => ColourRgbF::from_named(NamedColour::Black),
Some(intersection_info) => {
integrator.integrate(&sampler, &intersection_info, RECURSION_LIMIT)
}
let photon = match hit {
None => Photon {
wavelength: 0.0,
intensity: 0.0,
},
Some(intersection_info) => integrator.integrate(
&sampler,
&intersection_info,
&Photon {
wavelength,
intensity: 0.0,
},
RECURSION_LIMIT,
),
};
output_image_tile.set_colour(row, column, colour);
output_image_tile.update_pixel(row, column, &photon, 1.0);
}
}
}
output_image_tile

View File

@ -6,3 +6,9 @@ pub use photon::Photon;
pub mod colour_xyz;
pub use colour_xyz::ColourXyz;
pub mod spectrum;
pub use spectrum::Spectrum;
pub const SHORTEST_VISIBLE_WAVELENGTH: f64 = 380.0;
pub const LONGEST_VISIBLE_WAVELENGTH: f64 = 740.0;

View File

@ -9,3 +9,19 @@ pub struct Photon {
/// radiant flux in W, irradiance in W/m^2, or radiance in W/(m^2sr).
pub intensity: f64,
}
impl Photon {
pub fn scale_intensity(&self, scale_factor: f64) -> Photon {
Photon {
wavelength: self.wavelength,
intensity: self.intensity * scale_factor,
}
}
pub fn set_intensity(&self, intensity: f64) -> Photon {
Photon {
wavelength: self.wavelength,
intensity: intensity,
}
}
}

24
src/colour/spectrum.rs Normal file
View File

@ -0,0 +1,24 @@
use crate::colour::{ColourRgbF, Photon};
#[derive(Debug)]
pub struct Spectrum {}
impl Spectrum {
pub fn from_linear_rgb(colour: &ColourRgbF) -> Spectrum {
Spectrum {}
}
pub fn intensity_at_wavelength(&self, wavelength: f64) -> f64 {
1.0
}
pub fn scale_photon(&self, photon: &Photon) -> Photon {
let wavelength = photon.wavelength;
photon.scale_intensity(self.intensity_at_wavelength(wavelength))
}
pub fn emit_photon(&self, photon: &Photon) -> Photon {
let wavelength = photon.wavelength;
photon.set_intensity(self.intensity_at_wavelength(wavelength))
}
}

View File

@ -3,9 +3,10 @@ use std::fs::File;
use std::io::BufWriter;
use std::path::Path;
use crate::colour::{ColourRgbF, ColourRgbU8};
use crate::colour::{ColourRgbF, ColourRgbU8, ColourXyz};
use crate::util::Array2D;
#[derive(Debug)]
pub struct ImageRgbU8 {
data: Array2D<[u8; 3]>,
}
@ -163,6 +164,29 @@ impl ToneMapper<ColourRgbF> for ClampingToneMapper {
}
}
impl ToneMapper<ColourXyz> for ClampingToneMapper {
fn apply_tone_mapping(&self, image_in: &Array2D<ColourXyz>, image_out: &mut ImageRgbU8) {
assert!(image_in.get_width() == image_out.get_width());
assert!(image_in.get_height() == image_out.get_height());
for column in 0..image_in.get_width() {
for row in 0..image_in.get_height() {
let colour = image_in[row][column].to_linear_rgb();
image_out.set_colour(
row,
column,
ColourRgbU8 {
values: [
Self::clamp(&colour.red()),
Self::clamp(&colour.green()),
Self::clamp(&colour.blue()),
],
},
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -1,6 +1,6 @@
use crate::math::Vec3;
use super::colour::ColourRgbF;
use super::colour::{Photon, Spectrum};
use super::raycasting::{IntersectionInfo, Ray};
use super::sampler::Sampler;
use super::util::algebra_utils::try_change_of_basis_matrix;
@ -10,29 +10,29 @@ pub trait Integrator {
&self,
sampler: &Sampler,
info: &IntersectionInfo,
photon: &Photon,
recursion_limit: u16,
) -> ColourRgbF;
) -> Photon;
}
pub struct DirectionalLight {
pub direction: Vec3,
pub colour: ColourRgbF,
pub spectrum: Spectrum,
}
pub struct WhittedIntegrator {
pub ambient_light: ColourRgbF,
pub ambient_light: Spectrum,
pub lights: Vec<DirectionalLight>,
}
// TODO: Get rid of the magic bias number, which should be calculated base on expected error
// bounds and tangent direction
impl Integrator for WhittedIntegrator {
fn integrate(
&self,
sampler: &Sampler,
info: &IntersectionInfo,
photon: &Photon,
recursion_limit: u16,
) -> ColourRgbF {
) -> Photon {
let world_to_bsdf_space =
try_change_of_basis_matrix(&info.tangent, &info.cotangent, &info.normal)
.expect("Normal, tangent and cotangent don't for a valid basis.");
@ -43,14 +43,15 @@ impl Integrator for WhittedIntegrator {
.iter()
.map(|light| {
match sampler.sample(&Ray::new(info.location, light.direction).bias(0.000_000_1)) {
Some(_) => self.ambient_light,
None => {
info.material.bsdf()(
world_to_bsdf_space * info.retro,
world_to_bsdf_space * light.direction,
light.colour,
) * light.direction.dot(&info.normal).abs()
}
Some(_) => self.ambient_light.emit_photon(&photon),
None => info.material.bsdf()(
&(world_to_bsdf_space * info.retro),
&(world_to_bsdf_space * light.direction),
&light
.spectrum
.emit_photon(&photon)
.scale_intensity(light.direction.dot(&info.normal).abs()),
),
}
})
.chain(
@ -64,23 +65,31 @@ impl Integrator for WhittedIntegrator {
) {
Some(recursive_hit) => {
if recursion_limit > 0 {
info.material.bsdf()(
world_to_bsdf_space * info.retro,
*direction,
self.integrate(
let photon = info.material.bsdf()(
&(world_to_bsdf_space * info.retro),
direction,
&self.integrate(
&sampler,
&recursive_hit,
&photon,
recursion_limit - 1,
),
) * world_space_direction.dot(&info.normal).abs()
);
photon.scale_intensity(
world_space_direction.dot(&info.normal).abs(),
)
} else {
ColourRgbF::new(0.0, 0.0, 0.0)
photon.scale_intensity(0.0)
}
}
None => ColourRgbF::new(0.0, 0.0, 0.0),
None => photon.scale_intensity(0.0),
}
}),
)
.fold(self.ambient_light, |a, b| a + b)
.fold(photon.clone(), |a, b| {
let mut result = a;
result.intensity += b.intensity;
result
})
}
}

View File

@ -13,15 +13,16 @@ use std::path::{Path, PathBuf};
use std::sync::{mpsc, Arc};
use std::time::Duration;
use vanrijn::colour::{ColourRgbF, NamedColour};
use vanrijn::image::{ClampingToneMapper, ImageRgbU8, ToneMapper};
use vanrijn::accumulation_buffer::AccumulationBuffer;
use vanrijn::colour::{ColourRgbF, NamedColour, Spectrum};
use vanrijn::image::{ClampingToneMapper, ImageRgbU8};
use vanrijn::materials::{LambertianMaterial, PhongMaterial, ReflectiveMaterial};
use vanrijn::math::Vec3;
use vanrijn::mesh::load_obj;
use vanrijn::partial_render_scene;
use vanrijn::raycasting::{Aggregate, BoundingVolumeHierarchy, Plane, Primitive, Sphere};
use vanrijn::scene::Scene;
use vanrijn::util::{Tile, TileIterator};
use vanrijn::util::TileIterator;
#[derive(Debug)]
struct CommandLineParameters {
@ -73,25 +74,16 @@ fn parse_args() -> CommandLineParameters {
}
}
fn update_texture(tile: &Tile, image: &ImageRgbU8, texture: &mut Texture) {
fn update_texture(image: &ImageRgbU8, texture: &mut Texture) {
texture
.update(
Rect::new(
tile.start_column as i32,
tile.start_row as i32,
tile.width() as u32,
tile.height() as u32,
),
Rect::new(0, 0, image.get_width() as u32, image.get_height() as u32),
image.get_pixel_data(),
(image.get_width() * ImageRgbU8::num_channels()) as usize,
)
.expect("Couldn't update texture.");
}
fn update_image(tile: &Tile, tile_image: &ImageRgbU8, image: &mut ImageRgbU8) {
image.update(tile.start_row, tile.start_column, tile_image);
}
fn init_canvas(
image_width: usize,
image_height: usize,
@ -114,7 +106,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let image_width = parameters.width;
let image_height = parameters.height;
let mut rendered_image = ImageRgbU8::new(image_width, image_height);
let mut rendered_image = AccumulationBuffer::new(image_width, image_height);
let (sdl_context, mut canvas) = init_canvas(image_width, image_height)?;
@ -131,7 +123,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut model_object = load_obj(
&model_file_path,
Arc::new(ReflectiveMaterial {
colour: ColourRgbF::from_named(NamedColour::Yellow),
colour: Spectrum::from_linear_rgb(&ColourRgbF::from_named(NamedColour::Yellow)),
diffuse_strength: 0.05,
reflection_strength: 0.9,
}),
@ -149,7 +141,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
Vec3::new(0.0, 1.0, 0.0),
-2.0,
Arc::new(LambertianMaterial {
colour: ColourRgbF::new(0.55, 0.27, 0.04),
colour: Spectrum::from_linear_rgb(&ColourRgbF::new(0.55, 0.27, 0.04)),
diffuse_strength: 0.1,
}),
)) as Box<dyn Primitive>,
@ -157,7 +149,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
Vec3::new(-6.25, -0.5, 1.0),
1.0,
Arc::new(LambertianMaterial {
colour: ColourRgbF::from_named(NamedColour::Green),
colour: Spectrum::from_linear_rgb(&ColourRgbF::from_named(
NamedColour::Green,
)),
diffuse_strength: 0.1,
}),
)),
@ -165,7 +159,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
Vec3::new(-4.25, -0.5, 2.0),
1.0,
Arc::new(ReflectiveMaterial {
colour: ColourRgbF::from_named(NamedColour::Blue),
colour: Spectrum::from_linear_rgb(&ColourRgbF::from_named(
NamedColour::Blue,
)),
diffuse_strength: 0.01,
reflection_strength: 0.99,
}),
@ -174,7 +170,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
Vec3::new(-5.0, 1.5, 1.0),
1.0,
Arc::new(PhongMaterial {
colour: ColourRgbF::from_named(NamedColour::Red),
colour: Spectrum::from_linear_rgb(&ColourRgbF::from_named(
NamedColour::Red,
)),
diffuse_strength: 0.05,
smoothness: 100.0,
specular_strength: 1.0,
@ -193,7 +191,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let worker_boss = std::thread::spawn(move || {
let end_tx = tile_tx.clone();
TileIterator::new(image_width as usize, image_height as usize, 32)
TileIterator::new(image_width as usize, image_height as usize, 256)
.map(move |tile| (tile, tile_tx.clone()))
.par_bridge()
.try_for_each(|(tile, tx)| {
@ -209,16 +207,16 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
'running: loop {
if let Some(ref tile_rx) = tile_rx {
for message in tile_rx.try_iter() {
if let Some((tile, tile_image)) = message {
let mut tile_image_rgbu8 = ImageRgbU8::new(tile.width(), tile.height());
ClampingToneMapper {}
.apply_tone_mapping(&tile_image.data, &mut tile_image_rgbu8);
update_texture(&tile, &tile_image_rgbu8, &mut rendered_image_texture);
update_image(&tile, &tile_image_rgbu8, &mut rendered_image);
if let Some((tile, tile_accumulation_buffer)) = message {
rendered_image.merge_tile(&tile, &tile_accumulation_buffer);
let rgb_image = rendered_image.to_image_rgb_u8(&ClampingToneMapper {});
update_texture(&rgb_image, &mut rendered_image_texture);
canvas.copy(&rendered_image_texture, None, None).unwrap();
canvas.present();
} else if let Some(image_filename) = parameters.output_file {
rendered_image.write_png(&image_filename)?;
rendered_image
.to_image_rgb_u8(&ClampingToneMapper {})
.write_png(&image_filename)?;
break 'running;
}
}

View File

@ -1,28 +1,31 @@
use crate::colour::ColourRgbF;
use crate::colour::{Photon, Spectrum};
use crate::math::Vec3;
use super::{Bsdf, Material};
use super::Material;
use std::fmt::Debug;
#[derive(Debug)]
pub struct LambertianMaterial {
pub colour: ColourRgbF,
pub colour: Spectrum,
pub diffuse_strength: f64,
}
impl LambertianMaterial {
pub fn new_dummy() -> LambertianMaterial {
LambertianMaterial {
colour: ColourRgbF::new(1.0, 1.0, 1.0),
colour: Spectrum {},
diffuse_strength: 1.0,
}
}
}
impl Material for LambertianMaterial {
fn bsdf(&self) -> Bsdf {
let colour = self.colour * self.diffuse_strength;
Box::new(move |_w_o: Vec3, _w_i: Vec3, colour_in: ColourRgbF| colour * colour_in)
fn bsdf<'a>(&'a self) -> Box<dyn Fn(&Vec3, &Vec3, &Photon) -> Photon + 'a> {
Box::new(move |_w_o: &Vec3, _w_i: &Vec3, photon_in: &Photon| {
let mut result = self.colour.scale_photon(photon_in);
result.intensity *= self.diffuse_strength;
result
})
}
}

View File

@ -1,11 +1,9 @@
use crate::math::Vec3;
use super::colour::ColourRgbF;
use super::colour::Photon;
use std::fmt::Debug;
type Bsdf = Box<dyn Fn(Vec3, Vec3, ColourRgbF) -> ColourRgbF>;
pub mod lambertian_material;
pub use lambertian_material::LambertianMaterial;
@ -15,11 +13,8 @@ pub use phong_material::PhongMaterial;
pub mod reflective_material;
pub use reflective_material::ReflectiveMaterial;
pub mod rgb_sampled_bsdf_material;
pub use rgb_sampled_bsdf_material::RgbSampledBsdfMaterial;
pub trait Material: Debug + Sync + Send {
fn bsdf(&self) -> Bsdf;
fn bsdf<'a>(&'a self) -> Box<dyn Fn(&Vec3, &Vec3, &Photon) -> Photon + 'a>;
fn sample(&self, _w_o: &Vec3) -> Vec<Vec3> {
vec![]

View File

@ -1,32 +1,36 @@
use crate::colour::{ColourRgbF, NamedColour};
use crate::colour::{Photon, Spectrum};
use crate::math::Vec3;
use std::fmt::Debug;
use super::{Bsdf, Material};
use super::Material;
#[derive(Debug)]
pub struct PhongMaterial {
pub colour: ColourRgbF,
pub colour: Spectrum,
pub diffuse_strength: f64,
pub specular_strength: f64,
pub smoothness: f64,
}
impl Material for PhongMaterial {
fn bsdf(&self) -> Bsdf {
let smoothness = self.smoothness;
let specular_strength = self.specular_strength;
let colour = self.colour * self.diffuse_strength;
Box::new(move |w_o: Vec3, w_i: Vec3, colour_in: ColourRgbF| {
fn bsdf<'a>(&'a self) -> Box<dyn Fn(&Vec3, &Vec3, &Photon) -> Photon + 'a> {
Box::new(move |w_o: &Vec3, w_i: &Vec3, photon_in: &Photon| {
if w_i.z() < 0.0 || w_o.z() < 0.0 {
ColourRgbF::from_vec3(&Vec3::zeros())
Photon {
wavelength: photon_in.wavelength,
intensity: 0.0,
}
} else {
let reflection_vector = Vec3::new(-w_i.x(), -w_i.y(), w_i.z());
colour * colour_in
+ ColourRgbF::from_named(NamedColour::White)
* w_o.dot(&reflection_vector).abs().powf(smoothness)
* (specular_strength / w_i.dot(&Vec3::unit_z()))
let intensity = self.colour.scale_photon(photon_in).intensity
* self.diffuse_strength
+ w_o.dot(&reflection_vector).abs().powf(self.smoothness)
* (self.specular_strength / w_i.dot(&Vec3::unit_z()));
Photon {
wavelength: photon_in.wavelength,
intensity,
}
}
})
}

View File

@ -1,36 +1,40 @@
use crate::colour::ColourRgbF;
use crate::colour::{Photon, Spectrum};
use crate::math::Vec3;
use std::fmt::Debug;
use super::{Bsdf, Material};
use super::Material;
#[derive(Debug)]
pub struct ReflectiveMaterial {
pub colour: ColourRgbF,
pub colour: Spectrum,
pub diffuse_strength: f64,
pub reflection_strength: f64,
}
impl Material for ReflectiveMaterial {
fn bsdf(&self) -> Bsdf {
let diffuse_colour_factor = self.colour * self.diffuse_strength;
let reflection_strength = self.reflection_strength;
Box::new(move |w_o: Vec3, w_i: Vec3, colour_in: ColourRgbF| {
fn bsdf<'a>(&'a self) -> Box<dyn Fn(&Vec3, &Vec3, &Photon) -> Photon + 'a> {
Box::new(move |w_o: &Vec3, w_i: &Vec3, photon_in: &Photon| {
if w_i.z() <= 0.0 || w_o.z() <= 0.0 {
ColourRgbF::new(0.0, 0.0, 0.0)
Photon {
wavelength: photon_in.wavelength,
intensity: 0.0,
}
} else {
let reflection_vector = Vec3::new(-w_o.x(), -w_o.y(), w_o.z());
let reflection_colour = colour_in * reflection_strength;
let diffuse_colour = diffuse_colour_factor * colour_in;
let mut photon_out = self.colour.scale_photon(photon_in);
photon_out.intensity *= self.diffuse_strength;
let sigma = 0.05;
let two = 2.0;
// These are normalized vectors, but sometimes rounding errors cause the
// dot product to be slightly above 1 or below 0. The call to clamp
// ensures the values stay within the domain of acos,
let theta = w_i.dot(&reflection_vector).clamp(0.0, 1.0).abs().acos();
let reflection_factor = (-(theta.powf(two)) / (two * sigma * sigma)).exp();
reflection_colour * reflection_factor + diffuse_colour * (1.0 - reflection_factor)
let reflection_factor =
self.reflection_strength * (-(theta.powf(two)) / (two * sigma * sigma)).exp();
photon_out.intensity =
photon_out.intensity * (1.0 - reflection_factor) + reflection_factor;
photon_out
}
})
}