During BVH construction, merge BBox time samples based on a threshold.

If the average surface area of all the time samples is close enough
to the surface area of their union, just take the union and use that.
This both makes the BVH smaller in memory (time samples don't
propigate up the tree beyond their usefulness) and makes it
faster since traversal can avoid interpolating BBoxes when there's
only one BBox for a node.
This commit is contained in:
Nathan Vegdahl 2017-04-23 23:15:31 -07:00
parent 55c64a3392
commit c92a8c4da0
5 changed files with 58 additions and 29 deletions

View File

@ -128,9 +128,11 @@ impl<'a> BVH<'a> {
-> &'a mut BVHNode<'a> { -> &'a mut BVHNode<'a> {
match &base.nodes[node_index] { match &base.nodes[node_index] {
&BVHBaseNode::Internal { bounds_range, children_indices, split_axis } => { &BVHBaseNode::Internal { bounds_range, children_indices, split_axis } => {
let mut node = unsafe { arena.alloc_uninitialized::<BVHNode>() }; let mut node = unsafe { arena.alloc_uninitialized_with_alignment::<BVHNode>(32) };
let bounds = arena.copy_slice(&base.bounds[bounds_range.0..bounds_range.1]); let bounds =
arena.copy_slice_with_alignment(&base.bounds[bounds_range.0..bounds_range.1],
32);
let child1 = BVH::construct_from_base(arena, base, children_indices.0); let child1 = BVH::construct_from_base(arena, base, children_indices.0);
let child2 = BVH::construct_from_base(arena, base, children_indices.1); let child2 = BVH::construct_from_base(arena, base, children_indices.1);

View File

@ -10,6 +10,11 @@ use super::objects_split::{sah_split, median_split};
pub const BVH_MAX_DEPTH: usize = 42; pub const BVH_MAX_DEPTH: usize = 42;
// Amount bigger the union of all time samples can be
// and still use the union rather than preserve the
// individual time samples.
const USE_UNION_FACTOR: f32 = 1.4;
/// An intermediary structure for creating a BVH. /// An intermediary structure for creating a BVH.
#[derive(Debug)] #[derive(Debug)]
pub struct BVHBase { pub struct BVHBase {
@ -104,11 +109,24 @@ impl BVHBase {
return (0, (0, 0)); return (0, (0, 0));
} else if objects.len() <= objects_per_leaf { } else if objects.len() <= objects_per_leaf {
// Leaf node // Leaf node
self.acc_bounds(objects, bounder);
let bi = self.bounds.len(); let bi = self.bounds.len();
for b in self.bounds_cache.iter() { // Get bounds
self.bounds.push(*b); {
// We make sure that it's worth having multiple time samples, and if not
// we reduce to the union of the time samples.
self.acc_bounds(objects, bounder);
let union_bounds = self.bounds_cache.iter().fold(BBox::new(), |b1, b2| (b1 | *b2));
let average_area =
self.bounds_cache.iter().fold(0.0, |area, bb| area + bb.surface_area()) /
self.bounds_cache.len() as f32;
if union_bounds.surface_area() <= (average_area * USE_UNION_FACTOR) {
self.bounds.push(union_bounds);
} else {
self.bounds.extend(&self.bounds_cache);
}
} }
// Create node
self.nodes.push(BVHBaseNode::Leaf { self.nodes.push(BVHBaseNode::Leaf {
bounds_range: (bi, self.bounds.len()), bounds_range: (bi, self.bounds.len()),
object_range: (offset, offset + objects.len()), object_range: (offset, offset + objects.len()),
@ -155,12 +173,24 @@ impl BVHBase {
// Determine bounds // Determine bounds
// TODO: do merging without the temporary vec. // TODO: do merging without the temporary vec.
let bi = self.bounds.len(); let bi = self.bounds.len();
let mut merged = Vec::new(); {
merge_slices_append(&self.bounds[c1_bounds.0..c1_bounds.1], let mut merged = Vec::new();
&self.bounds[c2_bounds.0..c2_bounds.1], merge_slices_append(&self.bounds[c1_bounds.0..c1_bounds.1],
&mut merged, &self.bounds[c2_bounds.0..c2_bounds.1],
|b1, b2| *b1 | *b2); &mut merged,
self.bounds.extend(merged.drain(0..)); |b1, b2| *b1 | *b2);
// We make sure that it's worth having multiple time samples, and if not
// we reduce to the union of the time samples.
let union_bounds = merged.iter().fold(BBox::new(), |b1, b2| (b1 | *b2));
let average_area = merged.iter().fold(0.0, |area, bb| area + bb.surface_area()) /
merged.len() as f32;
if union_bounds.surface_area() <= (average_area * USE_UNION_FACTOR) {
self.bounds.push(union_bounds);
} else {
self.bounds.extend(merged.drain(0..));
}
}
// Set node // Set node
self.nodes[me] = BVHBaseNode::Internal { self.nodes[me] = BVHBaseNode::Internal {

View File

@ -80,11 +80,8 @@ impl BBox {
} }
pub fn surface_area(&self) -> f32 { pub fn surface_area(&self) -> f32 {
let x = self.max.x() - self.min.x(); let d = self.max - self.min;
let y = self.max.y() - self.min.y(); ((d.x() * d.y()) + (d.y() * d.z()) + (d.z() * d.x())) * 2.0
let z = self.max.z() - self.min.z();
((x * y) + (y * z) + (z * x)) * 2.0
} }
pub fn center(&self) -> Point { pub fn center(&self) -> Point {

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use mem_arena::MemArena; use mem_arena::MemArena;
use accel::{LightAccel, LightTree}; use accel::{LightAccel, LightTree};
use accel::BVH4; use accel::BVH;
use bbox::{BBox, transform_bbox_slice_from}; use bbox::{BBox, transform_bbox_slice_from};
use boundable::Boundable; use boundable::Boundable;
use color::SpectralSample; use color::SpectralSample;
@ -28,7 +28,7 @@ pub struct Assembly<'a> {
pub assemblies: &'a [Assembly<'a>], pub assemblies: &'a [Assembly<'a>],
// Object accel // Object accel
pub object_accel: BVH4<'a>, pub object_accel: BVH<'a>,
// Light accel // Light accel
pub light_accel: LightTree<'a>, pub light_accel: LightTree<'a>,
@ -231,10 +231,10 @@ impl<'a> AssemblyBuilder<'a> {
let (bis, bbs) = self.instance_bounds(); let (bis, bbs) = self.instance_bounds();
// Build object accel // Build object accel
let object_accel = BVH4::from_objects(self.arena, let object_accel = BVH::from_objects(self.arena,
&mut self.instances[..], &mut self.instances[..],
1, 1,
|inst| &bbs[bis[inst.id]..bis[inst.id + 1]]); |inst| &bbs[bis[inst.id]..bis[inst.id + 1]]);
// Get list of instances that are for light sources or assemblies that contain light // Get list of instances that are for light sources or assemblies that contain light
// sources. // sources.
@ -288,7 +288,7 @@ impl<'a> AssemblyBuilder<'a> {
/// Returns a pair of vectors with the bounds of all instances. /// Returns a pair of vectors with the bounds of all instances.
/// This is used for building the assembly's BVH4. /// This is used for building the assembly's BVH.
fn instance_bounds(&self) -> (Vec<usize>, Vec<BBox>) { fn instance_bounds(&self) -> (Vec<usize>, Vec<BBox>) {
let mut indices = vec![0]; let mut indices = vec![0];
let mut bounds = Vec::new(); let mut bounds = Vec::new();

View File

@ -2,7 +2,7 @@
use mem_arena::MemArena; use mem_arena::MemArena;
use accel::BVH4; use accel::BVH;
use bbox::BBox; use bbox::BBox;
use boundable::Boundable; use boundable::Boundable;
use color::XYZ; use color::XYZ;
@ -20,7 +20,7 @@ pub struct TriangleMesh<'a> {
time_samples: usize, time_samples: usize,
geo: &'a [(Point, Point, Point)], geo: &'a [(Point, Point, Point)],
indices: &'a [usize], indices: &'a [usize],
accel: BVH4<'a>, accel: BVH<'a>,
} }
impl<'a> TriangleMesh<'a> { impl<'a> TriangleMesh<'a> {
@ -44,10 +44,10 @@ impl<'a> TriangleMesh<'a> {
bounds bounds
}; };
let accel = BVH4::from_objects(arena, let accel = BVH::from_objects(arena,
&mut indices[..], &mut indices[..],
3, 3,
|tri_i| &bounds[*tri_i..(*tri_i + time_samples)]); |tri_i| &bounds[*tri_i..(*tri_i + time_samples)]);
TriangleMesh { TriangleMesh {
time_samples: time_samples, time_samples: time_samples,