LightTree now works with lights in hierarchical instancing.
This commit is contained in:
parent
97b5ef77f8
commit
c71b00ca31
|
@ -9,6 +9,7 @@ use light_accel::{LightAccel, LightTree};
|
|||
use light::LightSource;
|
||||
use math::{Matrix4x4, Vector};
|
||||
use surface::{Surface, SurfaceIntersection};
|
||||
use transform_stack::TransformStack;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -32,30 +33,56 @@ pub struct Assembly {
|
|||
}
|
||||
|
||||
impl Assembly {
|
||||
// Returns (light_color, shadow_vector, selection_pdf)
|
||||
// Returns (light_color, shadow_vector, pdf, selection_pdf)
|
||||
pub fn sample_lights(&self,
|
||||
xform_stack: &mut TransformStack,
|
||||
n: f32,
|
||||
uvw: (f32, f32, f32),
|
||||
wavelength: f32,
|
||||
time: f32,
|
||||
intr: &SurfaceIntersection)
|
||||
-> Option<(SpectralSample, Vector, f32)> {
|
||||
-> Option<(SpectralSample, Vector, f32, f32)> {
|
||||
if let &SurfaceIntersection::Hit { pos, incoming, nor, closure, .. } = intr {
|
||||
if let Some((light_i, sel_pdf, _)) = self.light_accel
|
||||
.select(incoming, pos, nor, closure.as_surface_closure(), time, n) {
|
||||
let sel_xform = if xform_stack.top().len() > 0 {
|
||||
lerp_slice(xform_stack.top(), time)
|
||||
} else {
|
||||
Matrix4x4::new()
|
||||
};
|
||||
if let Some((light_i, sel_pdf, whittled_n)) = self.light_accel
|
||||
.select(incoming * sel_xform,
|
||||
pos * sel_xform,
|
||||
nor * sel_xform,
|
||||
closure.as_surface_closure(),
|
||||
time,
|
||||
n) {
|
||||
let inst = self.light_instances[light_i];
|
||||
match inst.instance_type {
|
||||
|
||||
InstanceType::Object => {
|
||||
match &self.objects[inst.data_index] {
|
||||
&Object::Light(ref light) => {
|
||||
// Get the world-to-object space transform of the light
|
||||
let xform = if let Some((a, b)) = inst.transform_indices {
|
||||
lerp_slice(&self.xforms[a..b], time)
|
||||
let pxforms = xform_stack.top();
|
||||
let xform = lerp_slice(&self.xforms[a..b], time);
|
||||
if pxforms.len() > 0 {
|
||||
lerp_slice(pxforms, time) * xform
|
||||
} else {
|
||||
xform
|
||||
}
|
||||
} else {
|
||||
Matrix4x4::new()
|
||||
let pxforms = xform_stack.top();
|
||||
if pxforms.len() > 0 {
|
||||
lerp_slice(pxforms, time)
|
||||
} else {
|
||||
Matrix4x4::new()
|
||||
}
|
||||
};
|
||||
|
||||
// Sample the light
|
||||
let (color, shadow_vec, pdf) =
|
||||
light.sample(&xform, pos, uvw.0, uvw.1, wavelength, time);
|
||||
return Some((color, shadow_vec, pdf * sel_pdf));
|
||||
return Some((color, shadow_vec, pdf, sel_pdf));
|
||||
}
|
||||
|
||||
_ => unimplemented!(),
|
||||
|
@ -63,9 +90,23 @@ impl Assembly {
|
|||
}
|
||||
|
||||
InstanceType::Assembly => {
|
||||
// TODO: recursive light selection inside assemblies
|
||||
unimplemented!()
|
||||
// Push the world-to-object space transforms of the assembly onto
|
||||
// the transform stack.
|
||||
if let Some((a, b)) = inst.transform_indices {
|
||||
xform_stack.push(&self.xforms[a..b]);
|
||||
}
|
||||
|
||||
// Sample sub-assembly lights
|
||||
let sample = self.assemblies[inst.data_index]
|
||||
.sample_lights(xform_stack, whittled_n, uvw, wavelength, time, intr);
|
||||
|
||||
// Pop the assembly's transforms off the transform stack.
|
||||
if let Some(_) = inst.transform_indices {
|
||||
xform_stack.pop();
|
||||
}
|
||||
|
||||
// Return sample
|
||||
return sample.map(|(ss, v, pdf, spdf)| (ss, v, pdf, spdf * sel_pdf));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -190,8 +231,7 @@ impl AssemblyBuilder {
|
|||
self.objects.shrink_to_fit();
|
||||
self.assemblies.shrink_to_fit();
|
||||
|
||||
// Calculate instance bounds, used for building object accel and
|
||||
// (TODO) light accel.
|
||||
// Calculate instance bounds, used for building object accel and light accel.
|
||||
let (bis, bbs) = self.instance_bounds();
|
||||
|
||||
// Build object accel
|
||||
|
@ -199,8 +239,8 @@ impl AssemblyBuilder {
|
|||
1,
|
||||
|inst| &bbs[bis[inst.id]..bis[inst.id + 1]]);
|
||||
|
||||
// Get list of instances that are for light sources.
|
||||
// TODO: include assemblies that themselves contain light sources.
|
||||
// Get list of instances that are for light sources or assemblies that contain light
|
||||
// sources.
|
||||
let mut light_instances: Vec<_> = self.instances
|
||||
.iter()
|
||||
.filter(|inst| {
|
||||
|
@ -213,7 +253,9 @@ impl AssemblyBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
_ => false,
|
||||
InstanceType::Assembly => {
|
||||
self.assemblies[inst.data_index].light_accel.approximate_energy() > 0.0
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(|&a| a)
|
||||
|
@ -231,8 +273,9 @@ impl AssemblyBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: handle assemblies.
|
||||
_ => 0.0,
|
||||
InstanceType::Assembly => {
|
||||
self.assemblies[inst.data_index].light_accel.approximate_energy()
|
||||
}
|
||||
};
|
||||
(bounds, energy)
|
||||
});
|
||||
|
|
56
src/bvh.rs
56
src/bvh.rs
|
@ -1,15 +1,12 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use algorithm::{partition, quick_select, merge_slices_append};
|
||||
use algorithm::{partition, merge_slices_append};
|
||||
use bbox::BBox;
|
||||
use boundable::Boundable;
|
||||
use lerp::lerp_slice;
|
||||
use math::log2_64;
|
||||
use ray::AccelRay;
|
||||
use sah::sah_split;
|
||||
use objects_split::{sah_split, median_split};
|
||||
|
||||
|
||||
const BVH_MAX_DEPTH: usize = 64;
|
||||
|
@ -125,15 +122,6 @@ impl BVH {
|
|||
split_axis: 0,
|
||||
});
|
||||
|
||||
// Get combined object bounds
|
||||
let bounds = {
|
||||
let mut bb = BBox::new();
|
||||
for obj in &objects[..] {
|
||||
bb |= lerp_slice(bounder(obj), 0.5);
|
||||
}
|
||||
bb
|
||||
};
|
||||
|
||||
// Partition objects.
|
||||
// If we're too near the max depth, we do balanced building to
|
||||
// avoid exceeding max depth.
|
||||
|
@ -144,45 +132,7 @@ impl BVH {
|
|||
sah_split(objects, &bounder)
|
||||
} else {
|
||||
// Balanced splitting, when we don't have room to play
|
||||
let split_axis = {
|
||||
let mut axis = 0;
|
||||
let mut largest = std::f32::NEG_INFINITY;
|
||||
for i in 0..3 {
|
||||
let extent = bounds.max.get_n(i) - bounds.min.get_n(i);
|
||||
if extent > largest {
|
||||
largest = extent;
|
||||
axis = i;
|
||||
}
|
||||
}
|
||||
axis
|
||||
};
|
||||
|
||||
let place = {
|
||||
let place = objects.len() / 2;
|
||||
if place > 0 {
|
||||
place
|
||||
} else {
|
||||
1
|
||||
}
|
||||
};
|
||||
quick_select(objects, place, |a, b| {
|
||||
let tb_a = lerp_slice(bounder(a), 0.5);
|
||||
let tb_b = lerp_slice(bounder(b), 0.5);
|
||||
let centroid_a = (tb_a.min.get_n(split_axis) + tb_a.max.get_n(split_axis)) *
|
||||
0.5;
|
||||
let centroid_b = (tb_b.min.get_n(split_axis) + tb_b.max.get_n(split_axis)) *
|
||||
0.5;
|
||||
|
||||
if centroid_a < centroid_b {
|
||||
Ordering::Less
|
||||
} else if centroid_a == centroid_b {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
|
||||
(place, split_axis)
|
||||
median_split(objects, &bounder)
|
||||
};
|
||||
|
||||
// Create child nodes
|
||||
|
|
|
@ -44,8 +44,10 @@ impl LightSource for SphereLight {
|
|||
wavelength: f32,
|
||||
time: f32)
|
||||
-> (SpectralSample, Vector, f32) {
|
||||
// TODO: use transform space correctly
|
||||
let pos = Point::new(0.0, 0.0, 0.0) * space.inverse();
|
||||
// TODO: track fp error due to transforms
|
||||
let arr = arr * *space;
|
||||
let pos = Point::new(0.0, 0.0, 0.0);
|
||||
|
||||
// Calculate time interpolated values
|
||||
let radius: f64 = lerp_slice(&self.radii, time) as f64;
|
||||
let col = lerp_slice(&self.colors, time);
|
||||
|
@ -93,13 +95,14 @@ impl LightSource for SphereLight {
|
|||
(d - (cos_a * radius)) as f32);
|
||||
|
||||
// Calculate the final values and return everything.
|
||||
let shadow_vec = (x * sample.x()) + (y * sample.y()) + (z * sample.z());
|
||||
let shadow_vec = ((x * sample.x()) + (y * sample.y()) + (z * sample.z())) *
|
||||
space.inverse();
|
||||
let pdf = uniform_sample_cone_pdf(cos_theta_max);
|
||||
let spectral_sample = (col * surface_area_inv as f32).to_spectral_sample(wavelength);
|
||||
return (spectral_sample, shadow_vec, pdf as f32);
|
||||
} else {
|
||||
// If we're inside the sphere, there's light from every direction.
|
||||
let shadow_vec = uniform_sample_sphere(u, v);
|
||||
let shadow_vec = uniform_sample_sphere(u, v) * space.inverse();
|
||||
let pdf = 1.0 / (4.0 * PI_64);
|
||||
let spectral_sample = (col * surface_area_inv as f32).to_spectral_sample(wavelength);
|
||||
return (spectral_sample, shadow_vec, pdf as f32);
|
||||
|
@ -118,8 +121,8 @@ impl LightSource for SphereLight {
|
|||
// We're not using these, silence warnings
|
||||
let _ = (sample_dir, sample_u, sample_v, wavelength);
|
||||
|
||||
// TODO: use transform space correctly
|
||||
let pos = Point::new(0.0, 0.0, 0.0) * space.inverse();
|
||||
let arr = arr * *space;
|
||||
let pos = Point::new(0.0, 0.0, 0.0);
|
||||
let radius: f64 = lerp_slice(&self.radii, time) as f64;
|
||||
|
||||
let d2: f64 = (pos - arr).length2() as f64; // Distance from center of sphere squared
|
||||
|
|
|
@ -2,7 +2,7 @@ use algorithm::merge_slices_append;
|
|||
use bbox::BBox;
|
||||
use lerp::lerp_slice;
|
||||
use math::{Vector, Point, Normal};
|
||||
use sah::sah_split;
|
||||
use objects_split::sah_split;
|
||||
use shading::surface_closure::SurfaceClosure;
|
||||
|
||||
use super::LightAccel;
|
||||
|
@ -187,4 +187,12 @@ impl LightAccel for LightTree {
|
|||
// Found our light!
|
||||
Some((self.nodes[node_index].child_index, tot_prob, n))
|
||||
}
|
||||
|
||||
fn approximate_energy(&self) -> f32 {
|
||||
if self.nodes.len() > 0 {
|
||||
self.nodes[0].energy
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,14 @@ pub trait LightAccel {
|
|||
time: f32,
|
||||
n: f32)
|
||||
-> Option<(usize, f32, f32)>;
|
||||
|
||||
fn approximate_energy(&self) -> f32;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightArray {
|
||||
indices: Vec<usize>,
|
||||
aprx_energy: f32,
|
||||
}
|
||||
|
||||
impl LightArray {
|
||||
|
@ -30,15 +33,20 @@ impl LightArray {
|
|||
where F: 'a + Fn(&T) -> Option<(&'a [BBox], f32)>
|
||||
{
|
||||
let mut indices = Vec::new();
|
||||
let mut aprx_energy = 0.0;
|
||||
for (i, thing) in things.iter().enumerate() {
|
||||
if let Some((_, power)) = q(thing) {
|
||||
if power > 0.0 {
|
||||
indices.push(i);
|
||||
aprx_energy += power;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LightArray { indices: indices }
|
||||
LightArray {
|
||||
indices: indices,
|
||||
aprx_energy: aprx_energy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,4 +79,8 @@ impl LightAccel for LightArray {
|
|||
|
||||
Some((i, pdf, whittled_n))
|
||||
}
|
||||
|
||||
fn approximate_energy(&self) -> f32 {
|
||||
self.aprx_energy
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,10 +28,10 @@ mod lerp;
|
|||
mod light_accel;
|
||||
mod light;
|
||||
mod math;
|
||||
mod objects_split;
|
||||
mod parse;
|
||||
mod ray;
|
||||
mod renderer;
|
||||
mod sah;
|
||||
mod sampling;
|
||||
mod scene;
|
||||
mod shading;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use algorithm::partition;
|
||||
use algorithm::{partition, quick_select};
|
||||
use bbox::BBox;
|
||||
use lerp::lerp_slice;
|
||||
|
||||
|
@ -95,3 +96,105 @@ pub fn sah_split<'a, T, F>(objects: &mut [T], bounder: &F) -> (usize, usize)
|
|||
|
||||
(split_i, split_axis)
|
||||
}
|
||||
|
||||
/// Takes a slice of boundable objects and partitions them based on the bounds mean heuristic.
|
||||
///
|
||||
/// Returns the index of the partition boundary and the axis that it split on
|
||||
/// (0 = x, 1 = y, 2 = z).
|
||||
pub fn bounds_mean_split<'a, T, F>(objects: &mut [T], bounder: &F) -> (usize, usize)
|
||||
where F: Fn(&T) -> &'a [BBox]
|
||||
{
|
||||
// Get combined object bounds
|
||||
let bounds = {
|
||||
let mut bb = BBox::new();
|
||||
for obj in &objects[..] {
|
||||
bb |= lerp_slice(bounder(obj), 0.5);
|
||||
}
|
||||
bb
|
||||
};
|
||||
|
||||
let split_axis = {
|
||||
let mut axis = 0;
|
||||
let mut largest = std::f32::NEG_INFINITY;
|
||||
for i in 0..3 {
|
||||
let extent = bounds.max.get_n(i) - bounds.min.get_n(i);
|
||||
if extent > largest {
|
||||
largest = extent;
|
||||
axis = i;
|
||||
}
|
||||
}
|
||||
axis
|
||||
};
|
||||
|
||||
let div = (bounds.min.get_n(split_axis) + bounds.max.get_n(split_axis)) * 0.5;
|
||||
|
||||
// Partition
|
||||
let mut split_i = partition(&mut objects[..], |obj| {
|
||||
let tb = lerp_slice(bounder(obj), 0.5);
|
||||
let centroid = (tb.min.get_n(split_axis) + tb.max.get_n(split_axis)) * 0.5;
|
||||
centroid < div
|
||||
});
|
||||
if split_i < 1 {
|
||||
split_i = 1;
|
||||
} else if split_i >= objects.len() {
|
||||
split_i = objects.len() - 1;
|
||||
}
|
||||
|
||||
(split_i, split_axis)
|
||||
}
|
||||
|
||||
|
||||
/// Takes a slice of boundable objects and partitions them based on the median heuristic.
|
||||
///
|
||||
/// Returns the index of the partition boundary and the axis that it split on
|
||||
/// (0 = x, 1 = y, 2 = z).
|
||||
pub fn median_split<'a, T, F>(objects: &mut [T], bounder: &F) -> (usize, usize)
|
||||
where F: Fn(&T) -> &'a [BBox]
|
||||
{
|
||||
// Get combined object bounds
|
||||
let bounds = {
|
||||
let mut bb = BBox::new();
|
||||
for obj in &objects[..] {
|
||||
bb |= lerp_slice(bounder(obj), 0.5);
|
||||
}
|
||||
bb
|
||||
};
|
||||
|
||||
let split_axis = {
|
||||
let mut axis = 0;
|
||||
let mut largest = std::f32::NEG_INFINITY;
|
||||
for i in 0..3 {
|
||||
let extent = bounds.max.get_n(i) - bounds.min.get_n(i);
|
||||
if extent > largest {
|
||||
largest = extent;
|
||||
axis = i;
|
||||
}
|
||||
}
|
||||
axis
|
||||
};
|
||||
|
||||
let place = {
|
||||
let place = objects.len() / 2;
|
||||
if place > 0 {
|
||||
place
|
||||
} else {
|
||||
1
|
||||
}
|
||||
};
|
||||
quick_select(objects, place, |a, b| {
|
||||
let tb_a = lerp_slice(bounder(a), 0.5);
|
||||
let tb_b = lerp_slice(bounder(b), 0.5);
|
||||
let centroid_a = (tb_a.min.get_n(split_axis) + tb_a.max.get_n(split_axis)) * 0.5;
|
||||
let centroid_b = (tb_b.min.get_n(split_axis) + tb_b.max.get_n(split_axis)) * 0.5;
|
||||
|
||||
if centroid_a < centroid_b {
|
||||
Ordering::Less
|
||||
} else if centroid_a == centroid_b {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
|
||||
(place, split_axis)
|
||||
}
|
|
@ -18,6 +18,7 @@ use ray::Ray;
|
|||
use scene::Scene;
|
||||
use surface;
|
||||
use tracer::Tracer;
|
||||
use transform_stack::TransformStack;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -67,6 +68,7 @@ impl Renderer {
|
|||
let mut paths = Vec::new();
|
||||
let mut rays = Vec::new();
|
||||
let mut tracer = Tracer::from_assembly(&self.scene.root);
|
||||
let mut xform_stack = TransformStack::new();
|
||||
|
||||
loop {
|
||||
paths.clear();
|
||||
|
@ -130,7 +132,7 @@ impl Renderer {
|
|||
// Determine next rays to shoot based on result
|
||||
pi =
|
||||
partition_pair(&mut paths[..pi], &mut rays[..pi], |i, path, ray| {
|
||||
path.next(&self.scene, &isects[i], &mut *ray)
|
||||
path.next(&mut xform_stack, &self.scene, &isects[i], &mut *ray)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -274,7 +276,12 @@ impl LightPath {
|
|||
s
|
||||
}
|
||||
|
||||
fn next(&mut self, scene: &Scene, isect: &surface::SurfaceIntersection, ray: &mut Ray) -> bool {
|
||||
fn next(&mut self,
|
||||
xform_stack: &mut TransformStack,
|
||||
scene: &Scene,
|
||||
isect: &surface::SurfaceIntersection,
|
||||
ray: &mut Ray)
|
||||
-> bool {
|
||||
self.round += 1;
|
||||
|
||||
// Result of shading ray, prepare light ray
|
||||
|
@ -291,14 +298,20 @@ impl LightPath {
|
|||
// Prepare light ray
|
||||
let light_n = self.next_lds_samp();
|
||||
let light_uvw = (self.next_lds_samp(), self.next_lds_samp(), self.next_lds_samp());
|
||||
if let Some((light_color, shadow_vec, light_pdf)) = scene.root
|
||||
.sample_lights(light_n, light_uvw, self.wavelength, self.time, isect) {
|
||||
xform_stack.clear();
|
||||
if let Some((light_color, shadow_vec, light_pdf, light_sel_pdf)) = scene.root
|
||||
.sample_lights(xform_stack,
|
||||
light_n,
|
||||
light_uvw,
|
||||
self.wavelength,
|
||||
self.time,
|
||||
isect) {
|
||||
// Calculate and store the light that will be contributed
|
||||
// to the film plane if the light is not in shadow.
|
||||
self.pending_color_addition = {
|
||||
let material = closure.as_surface_closure();
|
||||
let la = material.evaluate(ray.dir, shadow_vec, nor, self.wavelength);
|
||||
light_color * la * self.light_attenuation / light_pdf
|
||||
light_color * la * self.light_attenuation / (light_pdf * light_sel_pdf)
|
||||
};
|
||||
|
||||
// Calculate the shadow ray for testing if the light is
|
||||
|
|
|
@ -323,7 +323,7 @@ impl SurfaceClosure for LambertClosure {
|
|||
}
|
||||
.into_vector();
|
||||
|
||||
let cos_nv = dot(nn, v);
|
||||
let cos_nv = dot(nn, v).max(-1.0).min(1.0);
|
||||
|
||||
return sphere_lambert(cos_nv, cos_theta);
|
||||
}
|
||||
|
|
4
todo.txt
4
todo.txt
|
@ -1,7 +1,7 @@
|
|||
//- Implement support for multiple light sources in a scene.
|
||||
//- Implement light tree.
|
||||
- Implement proper light source selection for hierarchical instancing.
|
||||
//- Implement proper light source selection for hierarchical instancing.
|
||||
- Implement shader parsing, and use in rendering.
|
||||
- Implement floating point accuracy stuff.
|
||||
- Add gui display of rendering in progress.
|
||||
- Unit tests for scene parsing.
|
||||
- Unit tests for scene parsing.
|
||||
|
|
Loading…
Reference in New Issue
Block a user