diff --git a/src/assembly.rs b/src/assembly.rs index 4bb7ff8..a9aaf53 100644 --- a/src/assembly.rs +++ b/src/assembly.rs @@ -22,6 +22,9 @@ pub struct Assembly { // Object accel pub object_accel: BVH, + + // Light accel + pub light_accel: Vec, } impl Boundable for Assembly { @@ -126,20 +129,37 @@ impl AssemblyBuilder { self.objects.shrink_to_fit(); self.assemblies.shrink_to_fit(); - // Build object accel + // Calculate instance bounds, used for building object accel and + // (TODO) light accel. let (bis, bbs) = self.instance_bounds(); + + // Build object accel let object_accel = BVH::from_objects(&mut self.instances[..], 1, |inst| &bbs[bis[inst.id]..bis[inst.id + 1]]); - println!("Assembly BVH Depth: {}", object_accel.tree_depth()); + // Build light accel + // TODO: build light tree instead of stupid vec + let light_accel = { + let mut light_accel = Vec::new(); + for inst in self.instances.iter() { + if let InstanceType::Object = inst.instance_type { + if let Object::Light(_) = self.objects[inst.data_index] { + light_accel.push(*inst); + } + } + } + light_accel + }; + Assembly { instances: self.instances, xforms: self.xforms, objects: self.objects, assemblies: self.assemblies, object_accel: object_accel, + light_accel: light_accel, } } diff --git a/src/light/mod.rs b/src/light/mod.rs index 43baaa3..465b603 100644 --- a/src/light/mod.rs +++ b/src/light/mod.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; pub use self::sphere_light::SphereLight; -use math::{Vector, Point}; +use math::{Vector, Point, Matrix4x4}; use color::SpectralSample; use boundable::Boundable; @@ -20,6 +20,7 @@ pub trait LightSource: Boundable + Debug + Sync { /// Returns: The light arriving at the point arr, the vector to use for /// shadow testing, and the pdf of the sample. fn sample(&self, + space: &Matrix4x4, arr: Point, u: f32, v: f32, @@ -37,6 +38,7 @@ pub trait LightSource: Boundable + Debug + Sync { /// source). No guarantees are made about the correctness of the return /// value if they are not valid. fn sample_pdf(&self, + space: &Matrix4x4, arr: Point, sample_dir: Vector, sample_u: f32, @@ -54,7 +56,14 @@ pub trait LightSource: Boundable + Debug + Sync { /// - v: Random parameter V. /// - wavelength: The hero wavelength of light to sample at. /// - time: The time to sample at. - fn outgoing(&self, dir: Vector, u: f32, v: f32, wavelength: f32, time: f32) -> SpectralSample; + fn outgoing(&self, + space: &Matrix4x4, + dir: Vector, + u: f32, + v: f32, + wavelength: f32, + time: f32) + -> SpectralSample; diff --git a/src/light/sphere_light.rs b/src/light/sphere_light.rs index 06d51e9..6f58be2 100644 --- a/src/light/sphere_light.rs +++ b/src/light/sphere_light.rs @@ -1,4 +1,4 @@ -use math::{Vector, Point, coordinate_system_from_vector}; +use math::{Vector, Point, Matrix4x4, coordinate_system_from_vector}; use bbox::BBox; use boundable::Boundable; use color::{XYZ, SpectralSample, Color}; @@ -35,14 +35,16 @@ impl SphereLight { impl LightSource for SphereLight { fn sample(&self, + space: &Matrix4x4, arr: Point, u: f32, v: f32, wavelength: f32, time: f32) -> (SpectralSample, Vector, f32) { + // TODO: use transform space correctly + let pos = Point::new(0.0, 0.0, 0.0) * space.inverse(); // Calculate time interpolated values - let pos = Point::new(0.0, 0.0, 0.0); // Light position is always at origin let radius: f64 = lerp_slice(&self.radii, time) as f64; let col = lerp_slice(&self.colors, time); let surface_area_inv: f64 = 1.0 / (4.0 * PI_64 * radius * radius); @@ -104,6 +106,7 @@ impl LightSource for SphereLight { } fn sample_pdf(&self, + space: &Matrix4x4, arr: Point, sample_dir: Vector, sample_u: f32, @@ -111,7 +114,8 @@ impl LightSource for SphereLight { wavelength: f32, time: f32) -> f32 { - let pos = Point::new(0.0, 0.0, 0.0); // Light position is always at origin + // TODO: use transform space correctly + let pos = Point::new(0.0, 0.0, 0.0) * space.inverse(); let radius: f64 = lerp_slice(&self.radii, time) as f64; let d2: f64 = (pos - arr).length2() as f64; // Distance from center of sphere squared @@ -129,7 +133,15 @@ impl LightSource for SphereLight { } } - fn outgoing(&self, dir: Vector, u: f32, v: f32, wavelength: f32, time: f32) -> SpectralSample { + fn outgoing(&self, + space: &Matrix4x4, + dir: Vector, + u: f32, + v: f32, + wavelength: f32, + time: f32) + -> SpectralSample { + // TODO: use transform space correctly let radius = lerp_slice(&self.radii, time) as f64; let col = lerp_slice(&self.colors, time); let surface_area = 4.0 * PI_64 * radius * radius; diff --git a/src/math/normal.rs b/src/math/normal.rs index 9a9cb5d..006970d 100644 --- a/src/math/normal.rs +++ b/src/math/normal.rs @@ -7,7 +7,7 @@ use lerp::Lerp; use float4::Float4; use super::{DotProduct, CrossProduct}; -use super::Matrix4x4; +use super::{Matrix4x4, Vector}; /// A surface normal in 3d homogeneous space. #[derive(Debug, Copy, Clone)] @@ -31,6 +31,10 @@ impl Normal { pub fn normalized(&self) -> Normal { *self / self.length() } + + pub fn into_vector(self) -> Vector { + Vector::new(self.co[0], self.co[1], self.co[2]) + } } diff --git a/src/math/vector.rs b/src/math/vector.rs index 13ebdc0..669f578 100644 --- a/src/math/vector.rs +++ b/src/math/vector.rs @@ -7,7 +7,7 @@ use lerp::Lerp; use float4::Float4; use super::{DotProduct, CrossProduct}; -use super::Matrix4x4; +use super::{Matrix4x4, Normal}; /// A direction vector in 3d homogeneous space. #[derive(Debug, Copy, Clone)] @@ -31,6 +31,10 @@ impl Vector { pub fn normalized(&self) -> Vector { *self / self.length() } + + pub fn into_normal(self) -> Normal { + Normal::new(self.co[0], self.co[1], self.co[2]) + } } diff --git a/src/renderer.rs b/src/renderer.rs index f1393fc..40819b3 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -9,10 +9,12 @@ use scoped_threadpool::Pool; use crossbeam::sync::MsQueue; use algorithm::partition_pair; +use lerp::lerp_slice; use ray::Ray; +use assembly::Object; use tracer::Tracer; use halton; -use math::fast_logit; +use math::{Vector, Matrix4x4, dot, fast_logit}; use image::Image; use surface; use scene::Scene; @@ -125,9 +127,10 @@ impl Renderer { let isects = tracer.trace(&rays); // Determine next rays to shoot based on result - pi = partition_pair(&mut paths[..pi], - &mut rays[..pi], - |i, path, ray| path.next(&isects[i], &mut *ray)); + pi = + partition_pair(&mut paths[..pi], &mut rays[..pi], |i, path, ray| { + path.next(&self.scene, &isects[i], &mut *ray) + }); } // Calculate color based on ray hits @@ -185,6 +188,7 @@ pub struct LightPath { round: u32, time: f32, wavelength: f32, + light_attenuation: XYZ, color: XYZ, } @@ -201,9 +205,10 @@ impl LightPath { pixel_co: pixel_co, lds_offset: lds_offset, dim_offset: 6, - round: 1, + round: 0, time: time, wavelength: wavelength, + light_attenuation: XYZ::new(1.0, 1.0, 1.0), color: XYZ::new(0.0, 0.0, 0.0), }, @@ -214,19 +219,82 @@ impl LightPath { lens_uv.1)) } - fn next(&mut self, isect: &surface::SurfaceIntersection, ray: &mut Ray) -> bool { - if let &surface::SurfaceIntersection::Hit { t: _, pos: _, nor: _, local_space: _, uv } = - isect { - let rgbcol = (uv.0, uv.1, (1.0 - uv.0 - uv.1).max(0.0)); - let xyz = XYZ::from_tuple(rec709e_to_xyz(rgbcol)); - self.color += XYZ::from_spectral_sample(&xyz.to_spectral_sample(self.wavelength)); + fn next_lds_samp(&mut self) -> f32 { + let s = halton::sample(self.dim_offset, self.lds_offset); + self.dim_offset += 1; + s + } - } else { - let xyz = XYZ::new(0.02, 0.02, 0.02); - self.color += XYZ::from_spectral_sample(&xyz.to_spectral_sample(self.wavelength)); + fn next(&mut self, scene: &Scene, isect: &surface::SurfaceIntersection, ray: &mut Ray) -> bool { + match self.round { + // Result of camera rays, prepare light rays + 0 => { + self.round += 1; + if let &surface::SurfaceIntersection::Hit { t: _, + pos: pos, + nor: nor, + local_space: _, + uv } = isect { + // Hit something! Do lighting! + if scene.root.light_accel.len() > 0 { + // Get the light and the mapping to its local space + let (light, space) = { + let l1 = &scene.root.objects[scene.root.light_accel[0].data_index]; + let light = if let &Object::Light(ref light) = l1 { + light + } else { + panic!() + }; + let space = if let Some((start, end)) = scene.root.light_accel[0] + .transform_indices { + lerp_slice(&scene.root.xforms[start..end], self.time) + } else { + Matrix4x4::new() + }; + (light, space) + }; + + let lu = self.next_lds_samp(); + let lv = self.next_lds_samp(); + // TODO: store incident light info and pdf, and use them properly + let (_, shadow_vec, _) = + light.sample(&space, pos, lu, lv, self.wavelength, self.time); + + let la = dot(nor.normalized().into_vector(), shadow_vec.normalized()) + .max(0.0); + self.light_attenuation = XYZ::from_spectral_sample(&XYZ::new(la, la, la) + .to_spectral_sample(self.wavelength)); + *ray = Ray::new(pos + shadow_vec.normalized() * 0.0001, + shadow_vec, + self.time, + true); + + return true; + } else { + return false; + } + } else { + // Didn't hit anything, so background color + let xyz = XYZ::new(0.02, 0.02, 0.02); + self.color += + XYZ::from_spectral_sample(&xyz.to_spectral_sample(self.wavelength)); + return false; + } + + } + + // Result of light rays + 1 => { + self.round += 1; + if let &surface::SurfaceIntersection::Miss = isect { + self.color += self.light_attenuation; + } + return false; + } + + // TODO + _ => unimplemented!(), } - - return false; } } diff --git a/src/surface/triangle_mesh.rs b/src/surface/triangle_mesh.rs index 50dceaf..58bbe63 100644 --- a/src/surface/triangle_mesh.rs +++ b/src/surface/triangle_mesh.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use lerp::{lerp, lerp_slice, lerp_slice_with}; -use math::{Point, Normal, Matrix4x4}; +use math::{Point, Normal, Matrix4x4, cross}; use ray::{Ray, AccelRay}; use triangle; use bbox::BBox; @@ -85,7 +85,7 @@ impl Surface for TriangleMesh { isects[r.id as usize] = SurfaceIntersection::Hit { t: t, pos: wr.orig + (wr.dir * t), - nor: Normal::new(0.0, 0.0, 0.0), // TODO + nor: cross(tri.0 - tri.1, tri.0 - tri.2).into_normal(), local_space: mat_space, uv: (tri_u, tri_v), }; diff --git a/src/tracer.rs b/src/tracer.rs index a647e5e..4640535 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -116,7 +116,9 @@ impl<'a> Tracer<'a> { surface.intersect_rays(rays, wrays, &mut self.isects, self.xform_stack.top()); } - &Object::Light(_) => unimplemented!(), + &Object::Light(_) => { + // TODO + } } } } diff --git a/todo.txt b/todo.txt index b82b2dc..f925911 100644 --- a/todo.txt +++ b/todo.txt @@ -1,3 +1,3 @@ -- Move to spectral rendering. +//- Move to spectral rendering. - Implement basic direct lighting w/ spherical lights. - Unit tests for scene parsing. \ No newline at end of file