diff --git a/examples/colorful.yaml b/examples/colorful.yaml index 7dcca98..6ed76fa 100644 --- a/examples/colorful.yaml +++ b/examples/colorful.yaml @@ -47,6 +47,9 @@ objects: r: 1.0 g: 1.0 b: 1.0 + # Optional fields (go together) + #transparency: 0.5 + #index: 1.5 texture: type: uniform color: @@ -71,7 +74,7 @@ objects: g: 1.0 b: 1.0 # Optional field - # reflectivity: 0.0 + #reflectivity: 0.0 texture: type: uniform color: diff --git a/examples/scene.yaml b/examples/scene.yaml index a6b4ab5..d65f02b 100644 --- a/examples/scene.yaml +++ b/examples/scene.yaml @@ -35,7 +35,6 @@ objects: type: uniform diffuse: {r: 0.5, g: 0.5, b: 0.5} specular: {r: 1., g: 1., b: 1.} - reflectivity: 0.5 texture: type: uniform color: {r: 0.25, g: 0.5, b: 1.} diff --git a/src/core/light_properties.rs b/src/core/light_properties.rs new file mode 100644 index 0000000..c76cab9 --- /dev/null +++ b/src/core/light_properties.rs @@ -0,0 +1,124 @@ +use super::color::LinearColor; +use serde::Deserialize; + +#[derive(Debug, PartialEq, Clone, Deserialize)] +#[serde(untagged)] +pub enum ReflTransEnum { + Transparency { + /// The transparency coefficient. + #[serde(rename = "transparency")] + coef: f32, + /// The diffraction index. + index: f32, + }, + Reflectivity { + /// The reflectivity coefficient. + #[serde(rename = "reflectivity")] + coef: f32, + }, +} + +/// A structure holding all the physical proprerties relating to light at a point. +#[derive(Debug, PartialEq, Clone, Deserialize)] +pub struct LightProperties { + /// The diffuse component. + pub diffuse: LinearColor, + /// The specular component. + pub specular: LinearColor, + /// The transparency or reflectivity properties. + #[serde(flatten)] + pub refl_trans: Option, +} + +impl LightProperties { + pub fn new( + diffuse: LinearColor, + specular: LinearColor, + refl_trans: Option, + ) -> Self { + LightProperties { + diffuse, + specular, + refl_trans, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn new_works() { + let diffuse = LinearColor::new(0.25, 0.5, 1.); + let specular = LinearColor::new(0.75, 0.375, 0.125); + let refl_trans = Some(ReflTransEnum::Reflectivity { coef: 0.5 }); + let properties = + LightProperties::new(diffuse.clone(), specular.clone(), refl_trans.clone()); + assert_eq!( + properties, + LightProperties { + diffuse, + specular, + refl_trans, + } + ) + } + + #[test] + fn deserialization_without_refl_trans_works() { + let yaml = r#" + diffuse: {r: 1.0, g: 0.5, b: 0.25} + specular: {r: 0.25, g: 0.125, b: 0.75} + "#; + let properties: LightProperties = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + properties, + LightProperties::new( + LinearColor::new(1., 0.5, 0.25), + LinearColor::new(0.25, 0.125, 0.75), + None + ) + ) + } + + #[test] + fn deserialization_with_reflection_works() { + let yaml = r#" + diffuse: {r: 1.0, g: 0.5, b: 0.25} + specular: {r: 0.25, g: 0.125, b: 0.75} + transparency: 0.5 + index: 1.5 + "#; + let properties: LightProperties = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + properties, + LightProperties::new( + LinearColor::new(1., 0.5, 0.25), + LinearColor::new(0.25, 0.125, 0.75), + Some(ReflTransEnum::Transparency { + coef: 0.5, + index: 1.5 + }) + ) + ) + } + + #[test] + fn deserialization_with_transparency_works() { + let yaml = r#" + diffuse: {r: 1.0, g: 0.5, b: 0.25} + specular: {r: 0.25, g: 0.125, b: 0.75} + reflectivity: 0.25 + "#; + let properties: LightProperties = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + properties, + LightProperties::new( + LinearColor::new(1., 0.5, 0.25), + LinearColor::new(0.25, 0.125, 0.75), + Some(ReflTransEnum::Reflectivity { coef: 0.25 }) + ) + ) + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 849639a..e516fb5 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,3 +6,6 @@ pub use color::*; pub mod film; pub use film::*; + +pub mod light_properties; +pub use light_properties::*; diff --git a/src/material/mod.rs b/src/material/mod.rs index f946891..8be6f84 100644 --- a/src/material/mod.rs +++ b/src/material/mod.rs @@ -1,18 +1,7 @@ -use super::core::color::LinearColor; +use super::core::LightProperties; use super::Point2D; use serde::Deserialize; -/// A structure holding all the physical proprerties relating to light at a point. -#[derive(Debug, PartialEq, Clone)] -pub struct LightProperties { - /// The diffuse component. - pub diffuse: LinearColor, - /// The specular component, - pub specular: LinearColor, - /// The reflectivity coefficient, - pub reflectivity: f32, -} - /// All the existing `Material` implementation. #[serde(tag = "type")] #[serde(rename_all = "lowercase")] diff --git a/src/material/uniform.rs b/src/material/uniform.rs index de999b9..73060b7 100644 --- a/src/material/uniform.rs +++ b/src/material/uniform.rs @@ -1,88 +1,53 @@ -use super::LightProperties; use super::Material; -use crate::core::color::LinearColor; +use crate::core::LightProperties; use crate::Point2D; use serde::Deserialize; /// A material with the same characteristics on all points. #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct UniformMaterial { - diffuse: LinearColor, - specular: LinearColor, - #[serde(default)] - reflectivity: f32, + #[serde(flatten)] + properties: LightProperties, } impl UniformMaterial { - pub fn new(diffuse: LinearColor, specular: LinearColor, reflectivity: f32) -> Self { - UniformMaterial { - diffuse, - specular, - reflectivity, - } + pub fn new(properties: LightProperties) -> Self { + UniformMaterial { properties } } } impl Material for UniformMaterial { fn properties(&self, _: Point2D) -> LightProperties { - LightProperties { - diffuse: self.diffuse.clone(), - specular: self.specular.clone(), - reflectivity: self.reflectivity, - } + self.properties.clone() } } #[cfg(test)] mod test { use super::*; + use crate::core::color::LinearColor; + use crate::core::ReflTransEnum; #[test] fn new_works() { - let diffuse = LinearColor::new(0., 0.5, 0.); - let specular = LinearColor::new(1., 1., 1.); - let reflectivity = 0.5; - let mat = UniformMaterial::new(diffuse.clone(), specular.clone(), reflectivity); - assert_eq!( - mat, - UniformMaterial { - diffuse, - specular, - reflectivity - } - ) + let properties = LightProperties { + diffuse: LinearColor::new(0., 0.5, 0.), + specular: LinearColor::new(1., 1., 1.), + refl_trans: None, + }; + let mat = UniformMaterial::new(properties.clone()); + assert_eq!(mat, UniformMaterial { properties }) } - fn simple_material() -> impl Material { - UniformMaterial::new( - LinearColor::new(0.5, 0.5, 0.5), + #[test] + fn properties_works() { + let properties = LightProperties::new( + LinearColor::new(0., 0.5, 0.), LinearColor::new(1., 1., 1.), - 0.5, - ) - } - - #[test] - fn diffuse_works() { - let mat = simple_material(); - assert_eq!( - mat.properties(Point2D::origin()).diffuse, - LinearColor::new(0.5, 0.5, 0.5) - ) - } - - #[test] - fn specular_works() { - let mat = simple_material(); - assert_eq!( - mat.properties(Point2D::origin()).specular, - LinearColor::new(1., 1., 1.) - ) - } - - #[test] - fn reflectivity_works() { - let mat = simple_material(); - assert!(mat.properties(Point2D::origin()).reflectivity - 0.5 < std::f32::EPSILON) + None, + ); + let mat = UniformMaterial::new(properties.clone()); + assert_eq!(mat.properties(Point2D::origin()), properties) } #[test] @@ -95,11 +60,11 @@ mod test { let material: UniformMaterial = serde_yaml::from_str(yaml).unwrap(); assert_eq!( material, - UniformMaterial::new( + UniformMaterial::new(LightProperties::new( LinearColor::new(1., 0.5, 0.25), LinearColor::new(0.25, 0.125, 0.75), - 0.25 - ) + Some(ReflTransEnum::Reflectivity { coef: 0.25 }) + )) ) } } diff --git a/src/render/object.rs b/src/render/object.rs index 61c9af5..c208c0e 100644 --- a/src/render/object.rs +++ b/src/render/object.rs @@ -45,6 +45,7 @@ impl BHShape for Object { mod test { use super::*; use crate::core::color::LinearColor; + use crate::core::LightProperties; use crate::material::UniformMaterial; use crate::shape::Sphere; use crate::texture::UniformTexture; @@ -52,11 +53,11 @@ mod test { fn simple_object() -> Object { let shape = Sphere::new(Point::new(5., 0., 0.), 1.); - let material = UniformMaterial::new( + let material = UniformMaterial::new(LightProperties::new( LinearColor::new(0.5, 0.5, 0.5), LinearColor::new(1., 1., 1.), - 0.5, - ); + None, + )); let texture = UniformTexture::new(LinearColor::new(0.25, 0.5, 1.)); Object::new(shape.into(), material.into(), texture.into()) } @@ -64,11 +65,11 @@ mod test { #[test] fn new_works() { let shape = Sphere::new(Point::new(5., 0., 0.), 1.); - let material = UniformMaterial::new( + let material = UniformMaterial::new(LightProperties::new( LinearColor::new(0.5, 0.5, 0.5), LinearColor::new(1., 1., 1.), - 0.5, - ); + None, + )); let texture = UniformTexture::new(LinearColor::new(0.25, 0.5, 1.)); assert_eq!( simple_object(), @@ -93,7 +94,6 @@ mod test { type: uniform diffuse: {r: 0.5, g: 0.5, b: 0.5} specular: {r: 1., g: 1., b: 1.} - reflectivity: 0.5 texture: type: uniform color: {r: 0.25, g: 0.5, b: 1.} diff --git a/src/render/scene.rs b/src/render/scene.rs index 9d113a3..b8322d5 100644 --- a/src/render/scene.rs +++ b/src/render/scene.rs @@ -1,7 +1,7 @@ use super::{light_aggregate::LightAggregate, object::Object}; use crate::{ - core::{Camera, LinearColor}, - material::{LightProperties, Material}, + core::{Camera, LightProperties, LinearColor, ReflTransEnum}, + material::Material, shape::Shape, texture::Texture, {Point, Vector}, @@ -20,6 +20,7 @@ pub struct Scene { bvh: BVH, aliasing_limit: u32, reflection_limit: u32, + diffraction_index: f32, } impl Scene { @@ -29,6 +30,7 @@ impl Scene { mut objects: Vec, aliasing_limit: u32, reflection_limit: u32, + diffraction_index: f32, ) -> Self { let bvh = BVH::build(&mut objects); Scene { @@ -38,6 +40,7 @@ impl Scene { bvh, aliasing_limit, reflection_limit, + diffraction_index, } } @@ -82,7 +85,13 @@ impl Scene { let direction = (pixel - self.camera.origin()).normalize(); self.cast_ray(Ray::new(pixel, direction)) .map_or_else(LinearColor::black, |(t, obj)| { - self.color_at(pixel + direction * t, obj, direction, self.reflection_limit) + self.color_at( + pixel + direction * t, + obj, + direction, + self.reflection_limit, + self.diffraction_index, + ) }) } @@ -105,6 +114,7 @@ impl Scene { // NOTE(Bruno): should be written using iterators let mut shot_obj: Option<&Object> = None; let mut t = std::f32::INFINITY; + // NOTE: we don't care about all objects... Only the closest one for object in self.bvh.traverse(&ray, &self.objects).iter() { match object.shape.intersect(&ray) { Some(dist) if dist < t => { @@ -123,31 +133,82 @@ impl Scene { object: &Object, incident_ray: Vector, reflection_limit: u32, + diffraction_index: f32, ) -> LinearColor { - let normal = object.shape.normal(&point); - let reflected = reflected(incident_ray, normal); let texel = object.shape.project_texel(&point); - let properties = object.material.properties(texel); let object_color = object.texture.texel_color(texel); - self.illuminate(point, object_color, &properties, normal, reflected) - + self.reflection(point, &properties, reflected, reflection_limit) + let normal = object.shape.normal(&point); + let reflected = reflected(incident_ray, normal); + + let lighting = self.illuminate(point, object_color, &properties, normal, reflected); + match properties.refl_trans { + None => lighting, + Some(ReflTransEnum::Transparency { coef, index }) => { + // Calculate the refracted ray, if it was refracted + refracted(incident_ray, normal, diffraction_index, index).map_or_else( + // Total reflection + || self.reflection(point, 1., reflected, reflection_limit, diffraction_index), + // Refraction + |r| { + self.refraction(point, coef, r, reflection_limit, index) + + lighting * (1. - coef) + }, + ) + } + Some(ReflTransEnum::Reflectivity { coef }) => { + self.reflection(point, coef, reflected, reflection_limit, diffraction_index) + + lighting * (1. - coef) + } + } + } + + fn refraction( + &self, + point: Point, + transparency: f32, + refracted: Vector, + reflection_limit: u32, + new_index: f32, + ) -> LinearColor { + if transparency > 1e-5 && reflection_limit > 0 { + let refraction_start = point + refracted * 0.001; + if let Some((t, obj)) = self.cast_ray(Ray::new(refraction_start, refracted)) { + let resulting_position = refraction_start + refracted * t; + let refracted = self.color_at( + resulting_position, + obj, + refracted, + reflection_limit - 1, + new_index, + ); + return refracted * transparency; + } + } + LinearColor::black() } fn reflection( &self, point: Point, - properties: &LightProperties, + reflectivity: f32, reflected: Vector, reflection_limit: u32, + diffraction_index: f32, ) -> LinearColor { - let reflectivity = properties.reflectivity; + // FIXME: use fresnel reflection too if reflectivity > 1e-5 && reflection_limit > 0 { let reflection_start = point + reflected * 0.001; if let Some((t, obj)) = self.cast_ray(Ray::new(reflection_start, reflected)) { let resulting_position = reflection_start + reflected * t; - let color = self.color_at(resulting_position, obj, reflected, reflection_limit - 1); + let color = self.color_at( + resulting_position, + obj, + reflected, + reflection_limit - 1, + diffraction_index, + ); return color * reflectivity; } }; @@ -208,6 +269,18 @@ fn reflected(incident: Vector, normal: Vector) -> Vector { incident - delt } +fn refracted(incident: Vector, normal: Vector, n_1: f32, n_2: f32) -> Option { + let cos = incident.dot(&normal); + let normal = if cos < 0. { normal } else { -normal }; + let eta = n_1 / n_2; + let k = 1. - eta * eta * (1. - cos * cos); + if k < 0. { + None + } else { + Some(eta * incident + (eta * cos.abs() - f32::sqrt(k)) * normal) + } +} + #[derive(Debug, PartialEq, Deserialize)] struct SerializedScene { camera: Camera, @@ -219,6 +292,8 @@ struct SerializedScene { aliasing_limit: u32, #[serde(default)] reflection_limit: u32, + #[serde(default = "crate::serialize::default_identity")] + starting_diffraction: f32, } impl From for Scene { @@ -229,6 +304,7 @@ impl From for Scene { scene.objects, scene.aliasing_limit, scene.reflection_limit, + scene.starting_diffraction, ) } } diff --git a/src/serialize/coefficient.rs b/src/serialize/coefficient.rs index e69de29..1b37875 100644 --- a/src/serialize/coefficient.rs +++ b/src/serialize/coefficient.rs @@ -0,0 +1,3 @@ +pub fn default_identity() -> f32 { + 1. +}