From 50d38744670cf45a7190c7a64d81b9cb9d764d93 Mon Sep 17 00:00:00 2001 From: hal8174 Date: Sun, 29 Sep 2024 20:25:20 +0200 Subject: [PATCH] Draw first real image --- ray-tracing-core/src/aabb.rs | 49 ++++++ ray-tracing-core/src/color.rs | 38 ++++- ray-tracing-core/src/lib.rs | 2 + ray-tracing-core/src/material.rs | 47 +++++- ray-tracing-core/src/math/dir3.rs | 12 ++ ray-tracing-core/src/math/frame.rs | 46 ++++++ ray-tracing-core/src/math/mat3.rs | 24 +++ ray-tracing-core/src/math/mod.rs | 4 + ray-tracing-core/src/ray.rs | 4 + ray-tracing-core/src/renderer.rs | 77 ++++++++++ ray-tracing-core/src/scene.rs | 4 + ray-tracing-image/src/main.rs | 11 +- ray-tracing-scene/src/basic_scene.rs | 49 ++++++ ray-tracing-scene/src/lib.rs | 48 +----- ray-tracing-scene/src/sphere.rs | 0 ray-tracing-scene/src/triangle_bvh.rs | 206 ++++++++++++++++++++++++++ 16 files changed, 565 insertions(+), 56 deletions(-) create mode 100644 ray-tracing-core/src/aabb.rs create mode 100644 ray-tracing-core/src/math/frame.rs create mode 100644 ray-tracing-core/src/math/mat3.rs create mode 100644 ray-tracing-scene/src/basic_scene.rs delete mode 100644 ray-tracing-scene/src/sphere.rs create mode 100644 ray-tracing-scene/src/triangle_bvh.rs diff --git a/ray-tracing-core/src/aabb.rs b/ray-tracing-core/src/aabb.rs new file mode 100644 index 0000000..603b290 --- /dev/null +++ b/ray-tracing-core/src/aabb.rs @@ -0,0 +1,49 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, Copy)] +pub struct AABB { + min: Pos3, + max: Pos3, +} + +impl AABB { + pub fn new_unchecked(min: Pos3, max: Pos3) -> Self { + Self { min, max } + } + + pub fn new(a: Pos3, b: Pos3) -> Self { + Self { + min: Pos3::new( + Float::min(a.x, b.x), + Float::min(a.y, b.y), + Float::min(a.z, b.z), + ), + max: Pos3::new( + Float::max(a.x, b.x), + Float::max(a.y, b.y), + Float::max(a.z, b.z), + ), + } + } + + pub fn min(self) -> Pos3 { + self.min + } + + pub fn max(self) -> Pos3 { + self.max + } + + pub fn contains_point(self, pos: Pos3) -> bool { + self.min.x <= pos.x + && pos.x <= self.max.x + && self.min.y <= pos.y + && pos.y <= self.max.y + && self.min.z <= pos.z + && pos.z <= self.max.z + } + + pub fn intersect_ray(self, ray: Ray) -> Option { + todo!() + } +} diff --git a/ray-tracing-core/src/color.rs b/ray-tracing-core/src/color.rs index a0fd1a8..3756b77 100644 --- a/ray-tracing-core/src/color.rs +++ b/ray-tracing-core/src/color.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use std::ops::{Add, AddAssign, Div}; +use std::ops::{Add, AddAssign, Div, Mul, MulAssign}; #[derive(Debug, Clone, Copy)] pub struct Color { @@ -32,6 +32,10 @@ impl Color { pub fn black() -> Self { Self::gray(0.0) } + + pub fn white() -> Self { + Self::gray(1.0) + } } impl Add for Color { @@ -65,3 +69,35 @@ impl Div for Color { } } } + +impl Mul for Color { + type Output = Color; + + fn mul(self, rhs: Self) -> Self::Output { + Color::new(self.r * rhs.r, self.g * rhs.g, self.b * rhs.b) + } +} + +impl MulAssign for Color { + fn mul_assign(&mut self, rhs: Self) { + self.r *= rhs.r; + self.g *= rhs.g; + self.b *= rhs.b; + } +} + +impl Mul for Color { + type Output = Color; + + fn mul(self, rhs: Float) -> Self::Output { + Color::new(self.r * rhs, self.g * rhs, self.b * rhs) + } +} + +impl Mul for Float { + type Output = Color; + + fn mul(self, rhs: Color) -> Self::Output { + Color::new(self * rhs.r, self * rhs.g, self * rhs.b) + } +} diff --git a/ray-tracing-core/src/lib.rs b/ray-tracing-core/src/lib.rs index 34fc5c5..4a51959 100644 --- a/ray-tracing-core/src/lib.rs +++ b/ray-tracing-core/src/lib.rs @@ -1,3 +1,4 @@ +pub mod aabb; pub mod camera; pub mod color; pub mod material; @@ -8,6 +9,7 @@ pub mod scene; pub mod prelude { pub type Float = f32; + pub use crate::aabb::AABB; pub use crate::color::Color; pub use crate::material::Material; pub use crate::math::*; diff --git a/ray-tracing-core/src/material.rs b/ray-tracing-core/src/material.rs index f9b81df..ff22f87 100644 --- a/ray-tracing-core/src/material.rs +++ b/ray-tracing-core/src/material.rs @@ -2,14 +2,53 @@ use crate::prelude::*; use rand::Rng; /// All calculations for the material are done a tangent space of the intersection. -pub trait Material { - fn eval(&self, w_in: Dir3, w_out: Dir3, rng: &mut R) -> Color; +pub trait Material: Sync { + fn eval(&self, w_in: Dir3, w_out: Dir3, rng: &mut R) -> Option { + None + } + + fn emit(&self, w_in: Dir3, rng: &mut R) -> Option { + None + } } pub struct DefaultMaterial {} impl Material for DefaultMaterial { - fn eval(&self, _w_in: Dir3, _w_out: Dir3, rng: &mut R) -> Color { - Color::black() + // evaluates the bsdf + fn eval(&self, _w_in: Dir3, _w_out: Dir3, _rng: &mut R) -> Option { + None + } +} + +pub struct DiffuseMaterial { + color: Color, +} + +impl DiffuseMaterial { + pub fn new(color: Color) -> Self { + Self { color } + } +} + +impl Material for DiffuseMaterial { + fn eval(&self, _w_in: Dir3, _w_out: Dir3, _rng: &mut R) -> Option { + Some(self.color) + } +} + +pub struct AreaLight { + color: Color, +} + +impl AreaLight { + pub fn new(color: Color) -> Self { + Self { color } + } +} + +impl Material for AreaLight { + fn emit(&self, _w_in: Dir3, _rng: &mut R) -> Option { + Some(self.color) } } diff --git a/ray-tracing-core/src/math/dir3.rs b/ray-tracing-core/src/math/dir3.rs index c72cc07..7091e9c 100644 --- a/ray-tracing-core/src/math/dir3.rs +++ b/ray-tracing-core/src/math/dir3.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use rand::Rng; use std::ops::{Add, Div, Mul, Neg, Sub}; #[derive(Debug, Clone, Copy)] @@ -60,6 +61,17 @@ impl Dir3 { self.x * rhs.y - self.y * rhs.x, ) } + + pub fn generate_uniform_hemisphere(rng: &mut R) -> Self { + let theta = Float::acos(1.0 - rng.gen::()); + let phi = 2.0 * FloatConsts::PI * rng.gen::(); + let r = Float::sin(theta); + Dir3::new(Float::sin(phi) * r, Float::cos(theta), Float::cos(phi) * r) + } + + pub fn generate_cosine_hemisphere(rng: &mut R) -> Self { + todo!() + } } impl Add for Dir3 { diff --git a/ray-tracing-core/src/math/frame.rs b/ray-tracing-core/src/math/frame.rs new file mode 100644 index 0000000..159eec4 --- /dev/null +++ b/ray-tracing-core/src/math/frame.rs @@ -0,0 +1,46 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, Copy)] +pub struct Frame { + m: Mat3, +} + +impl Frame { + pub fn from_normal(n: Dir3) -> Self { + let n = n.normalize(); + let x = if n.x().abs() > n.y().abs() { + Dir3::new(n.z(), 0.0, -n.x()) + } else { + Dir3::new(0.0, n.z(), -n.y()) + } + .normalize(); + Self { + m: Mat3::new([x, n, Dir3::cross(x, n)]), + } + } + + pub fn to_frame(&self, dir: Dir3) -> Dir3 { + self.m.transform_transposed(dir) + } + + pub fn to_world(&self, dir: Dir3) -> Dir3 { + self.m.transform(dir) + } +} + +#[cfg(test)] +mod test { + use super::Frame; + use crate::prelude::*; + + #[test] + fn frame() { + let f = Frame::from_normal(Dir3::new(-1.0, 0.0, 0.0)); + + dbg!(f); + + dbg!(f.to_world(Dir3::new(0.0, 1.0, 0.0))); + + panic!() + } +} diff --git a/ray-tracing-core/src/math/mat3.rs b/ray-tracing-core/src/math/mat3.rs new file mode 100644 index 0000000..4128951 --- /dev/null +++ b/ray-tracing-core/src/math/mat3.rs @@ -0,0 +1,24 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, Copy)] +pub struct Mat3 { + v: [Dir3; 3], +} + +impl Mat3 { + pub fn new(v: [Dir3; 3]) -> Self { + Self { v } + } + + pub fn transform(&self, dir: Dir3) -> Dir3 { + self.v[0] * dir.x + self.v[1] * dir.y + self.v[2] * dir.z + } + + pub fn transform_transposed(&self, dir: Dir3) -> Dir3 { + Dir3::new( + Dir3::dot(self.v[0], dir), + Dir3::dot(self.v[1], dir), + Dir3::dot(self.v[2], dir), + ) + } +} diff --git a/ray-tracing-core/src/math/mod.rs b/ray-tracing-core/src/math/mod.rs index 6a89f06..12d3d28 100644 --- a/ray-tracing-core/src/math/mod.rs +++ b/ray-tracing-core/src/math/mod.rs @@ -1,5 +1,9 @@ pub mod dir3; +pub mod frame; +pub mod mat3; pub mod pos3; pub use dir3::Dir3; +pub use frame::Frame; +pub use mat3::Mat3; pub use pos3::Pos3; diff --git a/ray-tracing-core/src/ray.rs b/ray-tracing-core/src/ray.rs index 2af8d5e..ed5f000 100644 --- a/ray-tracing-core/src/ray.rs +++ b/ray-tracing-core/src/ray.rs @@ -23,4 +23,8 @@ impl Ray { pub fn time(self) -> Float { self.time } + + pub fn at(self, t: Float) -> Pos3 { + self.start + self.dir * t + } } diff --git a/ray-tracing-core/src/renderer.rs b/ray-tracing-core/src/renderer.rs index c34156d..d128386 100644 --- a/ray-tracing-core/src/renderer.rs +++ b/ray-tracing-core/src/renderer.rs @@ -9,6 +9,82 @@ pub trait ClassicalRenderer { fn height(&self) -> u32; } +pub struct PathTracer +where + S: Scene, + C: Camera, + R: Rng, +{ + scene: S, + camera: C, + rng: PhantomData, +} + +impl PathTracer +where + S: Scene, + C: Camera, + R: Rng, +{ + pub fn new(scene: S, camera: C) -> Self { + Self { + scene, + camera, + rng: PhantomData {}, + } + } +} + +impl ClassicalRenderer for PathTracer +where + S: Scene, + C: Camera, + R: Rng, +{ + fn render_pixel(&self, x: u32, y: u32, rng: &mut R) -> Color { + let mut sum = Color::black(); + let mut alpha = Color::white(); + + let mut r = self.camera.forward(x, y, rng); + + let mut count = 0; + + while let Some(i) = self.scene.intersect(r, 0.001, Float::INFINITY) { + if count > 4 { + break; + } + let frame = i.tangent_frame(); + let w_in = frame.to_frame(-r.dir()); + let w_out = Dir3::generate_uniform_hemisphere(rng); + + let mat = i.material(); + + if let Some(c) = mat.emit(w_in, rng) { + sum += alpha * c * w_out.y(); + } + + if let Some(c) = mat.eval(w_in, w_out, rng) { + alpha *= c; + } else { + return sum; + } + + r = Ray::new(r.at(i.t()), frame.to_world(w_out), r.time()); + count += 1; + } + + sum + } + + fn width(&self) -> u32 { + self.camera.width() + } + + fn height(&self) -> u32 { + self.camera.height() + } +} + pub struct DepthRenderer where S: Scene, @@ -47,6 +123,7 @@ where if let Some(i) = self.scene.intersect(r, 0.0, Float::INFINITY) { // Color::gray(1.0 / i.t()) let c = 0.5 * (i.normal() + Dir3::new(1.0, 1.0, 1.0)); + // let c = i.normal(); Color::new(c.x, c.y, c.z) } else { Color::black() diff --git a/ray-tracing-core/src/scene.rs b/ray-tracing-core/src/scene.rs index 66e05d9..524e2c9 100644 --- a/ray-tracing-core/src/scene.rs +++ b/ray-tracing-core/src/scene.rs @@ -31,4 +31,8 @@ impl<'sc, R: Rng> Intersection<'sc, R> { pub fn material(&self) -> &'sc dyn Material { self.material } + + pub fn tangent_frame(&self) -> Frame { + Frame::from_normal(self.normal) + } } diff --git a/ray-tracing-image/src/main.rs b/ray-tracing-image/src/main.rs index 6b5abb6..9bf2eff 100644 --- a/ray-tracing-image/src/main.rs +++ b/ray-tracing-image/src/main.rs @@ -3,9 +3,9 @@ use rand::{rngs::SmallRng, SeedableRng}; use ray_tracing_core::{ camera::BasicCamera, prelude::*, - renderer::{ClassicalRenderer, DepthRenderer}, + renderer::{ClassicalRenderer, DepthRenderer, PathTracer}, }; -use ray_tracing_scene::BasicScene; +use ray_tracing_scene::{basic_scene::BasicScene, triangle_bvh::examples::cornel}; use rayon::prelude::*; use std::path::Path; @@ -35,7 +35,8 @@ fn render_image + Sync>( } fn main() -> ImageResult<()> { - let s = BasicScene::new(); + // let s = BasicScene::new(); + let s = cornel(); let c = BasicCamera::new( 640, @@ -46,7 +47,7 @@ fn main() -> ImageResult<()> { Float::to_radians(90.0), ); - let r = DepthRenderer::new(s, c); + let r = PathTracer::new(s, c); - render_image(r, "test.exr", 16) + render_image(r, "test.exr", 1024) } diff --git a/ray-tracing-scene/src/basic_scene.rs b/ray-tracing-scene/src/basic_scene.rs new file mode 100644 index 0000000..7b315df --- /dev/null +++ b/ray-tracing-scene/src/basic_scene.rs @@ -0,0 +1,49 @@ +use rand::Rng; +use ray_tracing_core::material::DefaultMaterial; +use ray_tracing_core::prelude::*; +use ray_tracing_core::scene::{Intersection, Scene}; + +pub struct BasicScene { + pub(crate) spheres: Vec<(Pos3, Float)>, +} + +impl BasicScene { + pub fn new() -> Self { + Self { + spheres: vec![(Pos3::zero(), 1.0), (Pos3::new(0.0, 0.0, 2.5), 2.0)], + } + } +} + +impl Scene for BasicScene { + fn intersect(&self, ray: Ray, min: Float, max: Float) -> Option> { + let mut intersection: Option> = None; + + for &(c, r) in &self.spheres { + let offset = ray.start() - c; + let p = Dir3::dot(ray.dir(), offset); + let delta = p * p - (offset.length_squared() - r * r); + + if delta >= 0.0 { + let d = -p - Float::sqrt(delta); + + let int = Intersection::new( + d, + ((ray.start() + d * ray.dir()) - c).normalize(), + &DefaultMaterial {}, + ); + if d >= min && d <= max { + if let Some(i) = intersection.as_ref() { + if i.t() > d { + intersection.replace(int); + } + } else { + intersection = Some(int) + } + } + } + } + + intersection + } +} diff --git a/ray-tracing-scene/src/lib.rs b/ray-tracing-scene/src/lib.rs index 8db47da..db5f491 100644 --- a/ray-tracing-scene/src/lib.rs +++ b/ray-tracing-scene/src/lib.rs @@ -1,51 +1,7 @@ use rand::Rng; use ray_tracing_core::material::DefaultMaterial; use ray_tracing_core::prelude::*; -use ray_tracing_core::scene::{Intersection, Scene}; -pub mod sphere; +pub mod basic_scene; -pub struct BasicScene { - spheres: Vec<(Pos3, Float)>, -} - -impl BasicScene { - pub fn new() -> Self { - Self { - spheres: vec![(Pos3::zero(), 1.0), (Pos3::new(0.0, 0.0, 2.5), 2.0)], - } - } -} - -impl Scene for BasicScene { - fn intersect(&self, ray: Ray, min: Float, max: Float) -> Option> { - let mut intersection: Option> = None; - - for &(c, r) in &self.spheres { - let offset = ray.start() - c; - let p = Dir3::dot(ray.dir(), offset); - let delta = p * p - (offset.length_squared() - r * r); - - if delta >= 0.0 { - let d = -p - Float::sqrt(delta); - - let int = Intersection::new( - d, - ((ray.start() + d * ray.dir()) - c).normalize(), - &DefaultMaterial {}, - ); - if d >= min && d <= max { - if let Some(i) = intersection.as_ref() { - if i.t() > d { - intersection.replace(int); - } - } else { - intersection = Some(int) - } - } - } - } - - intersection - } -} +pub mod triangle_bvh; diff --git a/ray-tracing-scene/src/sphere.rs b/ray-tracing-scene/src/sphere.rs deleted file mode 100644 index e69de29..0000000 diff --git a/ray-tracing-scene/src/triangle_bvh.rs b/ray-tracing-scene/src/triangle_bvh.rs new file mode 100644 index 0000000..a5fae51 --- /dev/null +++ b/ray-tracing-scene/src/triangle_bvh.rs @@ -0,0 +1,206 @@ +use rand::Rng; +use ray_tracing_core::{ + prelude::*, + scene::{Intersection, Scene}, +}; + +type Index = u32; + +pub struct TriangleBVH { + vertices: Vec, + triangles: Vec, + materials: Vec>>, + bvh: Vec, +} + +#[derive(Debug, Clone, Copy)] +enum Node { + Inner { + left: Index, + left_aabb: AABB, + right: Index, + right_aabb: AABB, + }, + Leaf { + start: Index, + count: Index, + }, +} + +#[derive(Debug, Clone, Copy)] +pub struct Triangle { + vertices: [Index; 3], + material: Index, +} + +impl Triangle { + fn new(vertices: [Index; 3], material: Index) -> Self { + Triangle { vertices, material } + } +} + +fn triangle_intersection(ray: Ray, v: [Pos3; 3]) -> Option { + let e1 = v[1] - v[0]; + let e2 = v[2] - v[0]; + + let ray_cross_e2 = Dir3::cross(ray.dir(), e2); + let det = e1.dot(ray_cross_e2); + + if det > -f32::EPSILON && det < f32::EPSILON { + return None; // This ray is parallel to this triangle. + } + + let inv_det = 1.0 / det; + let s = ray.start() - v[0]; + let u = inv_det * s.dot(ray_cross_e2); + if u < 0.0 || u > 1.0 { + return None; + } + + let s_cross_e1 = s.cross(e1); + let v = inv_det * Dir3::dot(ray.dir(), s_cross_e1); + if v < 0.0 || u + v > 1.0 { + return None; + } + // At this stage we can compute t to find out where the intersection point is on the line. + let t = inv_det * e2.dot(s_cross_e1); + + if t > Float::EPSILON { + // ray intersection + Some(t) + } else { + // This means that there is a line intersection but not a ray intersection. + None + } +} + +fn triangle_normal(v: [Pos3; 3]) -> Dir3 { + let e1 = v[1] - v[0]; + let e2 = v[2] - v[0]; + + Dir3::cross(e1, e2) +} + +impl TriangleBVH { + fn new( + vertices: Vec, + triangles: Vec, + materials: Vec>>, + ) -> Self { + Self { + vertices, + bvh: vec![Node::Leaf { + start: 0, + count: triangles.len() as Index, + }], + triangles, + materials, + } + } + + fn get_vertices(&self, triangle: Index) -> [Pos3; 3] { + let t = self.triangles[triangle as usize]; + [ + self.vertices[t.vertices[0] as usize], + self.vertices[t.vertices[1] as usize], + self.vertices[t.vertices[2] as usize], + ] + } + + fn intersect_bvh( + &self, + node: Index, + ray: Ray, + min: Float, + max: Float, + ) -> Option<(Index, Float)> { + match self.bvh[node as usize] { + Node::Inner { + left, + left_aabb, + right, + right_aabb, + } => todo!(), + Node::Leaf { start, count } => { + let mut intersection = None; + for i in start..(start + count) { + if let Some(t) = triangle_intersection(ray, self.get_vertices(i)) { + if min <= t && t <= max && !intersection.is_some_and(|(_, old_t)| t > old_t) + { + intersection.replace((i, t)); + } + } + } + + intersection + } + } + } +} + +impl Scene for TriangleBVH { + fn intersect( + &self, + ray: Ray, + min: Float, + max: Float, + ) -> Option> { + let (i, t) = self.intersect_bvh(0, ray, min, max)?; + + let triangle = self.triangles[i as usize]; + let material = self.materials[triangle.material as usize].as_ref(); + + let n = triangle_normal(self.get_vertices(i)).normalize(); + + Some(Intersection::new(t, n, material)) + } +} + +pub mod examples { + use super::{Triangle, TriangleBVH}; + use rand::Rng; + use ray_tracing_core::material::{AreaLight, DiffuseMaterial}; + use ray_tracing_core::prelude::*; + + pub fn cornel() -> TriangleBVH { + let side_length = 1.5; + let light_size = 0.5; + let light_offset = 0.01; + TriangleBVH::new( + vec![ + Pos3::new(side_length, side_length, side_length), + Pos3::new(side_length, side_length, -side_length), + Pos3::new(side_length, -side_length, side_length), + Pos3::new(side_length, -side_length, -side_length), + Pos3::new(-side_length, side_length, side_length), + Pos3::new(-side_length, side_length, -side_length), + Pos3::new(-side_length, -side_length, side_length), + Pos3::new(-side_length, -side_length, -side_length), + Pos3::new(light_size, side_length - light_offset, light_size), + Pos3::new(light_size, side_length - light_offset, -light_size), + Pos3::new(-light_size, side_length - light_offset, light_size), + Pos3::new(-light_size, side_length - light_offset, -light_size), + ], + vec![ + Triangle::new([0, 1, 2], 0), + Triangle::new([1, 3, 2], 0), + Triangle::new([0, 4, 1], 0), + Triangle::new([1, 4, 5], 0), + Triangle::new([2, 3, 6], 0), + Triangle::new([3, 7, 6], 0), + Triangle::new([0, 2, 4], 1), + Triangle::new([6, 4, 2], 1), + Triangle::new([1, 5, 3], 2), + Triangle::new([7, 3, 5], 2), + Triangle::new([8, 10, 9], 3), + Triangle::new([11, 9, 10], 3), + ], + vec![ + Box::new(DiffuseMaterial::new(Color::new(0.8, 0.8, 0.8))), + Box::new(DiffuseMaterial::new(Color::new(0.9, 0.0, 0.0))), + Box::new(DiffuseMaterial::new(Color::new(0.0, 0.9, 0.0))), + Box::new(AreaLight::new(Color::new(5.0, 5.0, 5.0))), + ], + ) + } +}