WIP do depth-first instead of breadth-first ray tracing.

Currently totally broken.
This commit is contained in:
Nathan Vegdahl 2022-07-31 17:50:54 -07:00
parent 98a9aeb374
commit 8ca6e27f39
16 changed files with 684 additions and 1223 deletions

View File

@ -67,7 +67,91 @@ impl<'a> BVH<'a> {
self.depth self.depth
} }
pub fn traverse<T, F>(&self, rays: &mut [AccelRay], objects: &[T], mut obj_ray_test: F) pub fn traverse<T, F>(&self, ray: &mut AccelRay, objects: &[T], mut obj_ray_test: F)
where
F: FnMut(&T, &mut AccelRay),
{
if self.root.is_none() {
return;
}
let mut timer = Timer::new();
let mut trav_time: f64 = 0.0;
let mut node_tests: u64 = 0;
let ray_sign = [
ray.dir_inv.x() >= 0.0,
ray.dir_inv.y() >= 0.0,
ray.dir_inv.z() >= 0.0,
];
// +2 of max depth for root and last child
let mut node_stack = [self.root.unwrap(); BVH_MAX_DEPTH + 2];
let mut stack_ptr = 1;
while stack_ptr > 0 && !ray.is_done {
node_tests += 1;
match *node_stack[stack_ptr] {
BVHNode::Internal {
children,
bounds_start,
bounds_len,
split_axis,
} => {
let bounds =
unsafe { std::slice::from_raw_parts(bounds_start, bounds_len as usize) };
let is_hit = lerp_slice(bounds, ray.time).intersect_accel_ray(&ray);
if is_hit {
if ray_sign[split_axis as usize] {
node_stack[stack_ptr] = children.1;
node_stack[stack_ptr + 1] = children.0;
} else {
node_stack[stack_ptr] = children.0;
node_stack[stack_ptr + 1] = children.1;
}
stack_ptr += 1;
} else {
stack_ptr -= 1;
}
}
BVHNode::Leaf {
object_range,
bounds_start,
bounds_len,
} => {
let bounds =
unsafe { std::slice::from_raw_parts(bounds_start, bounds_len as usize) };
let is_hit = lerp_slice(bounds, ray.time).intersect_accel_ray(&ray);
trav_time += timer.tick() as f64;
if is_hit {
for obj in &objects[object_range.0..object_range.1] {
obj_ray_test(obj, ray);
}
}
timer.tick();
stack_ptr -= 1;
}
}
}
trav_time += timer.tick() as f64;
ACCEL_TRAV_TIME.with(|att| {
let v = att.get();
att.set(v + trav_time);
});
ACCEL_NODE_RAY_TESTS.with(|anv| {
let v = anv.get();
anv.set(v + node_tests);
});
}
pub fn traverse_multi<T, F>(&self, rays: &mut [AccelRay], objects: &[T], mut obj_ray_test: F)
where where
F: FnMut(&T, &mut [AccelRay]), F: FnMut(&T, &mut [AccelRay]),
{ {

View File

@ -6,8 +6,6 @@
use std::mem::{transmute, MaybeUninit}; use std::mem::{transmute, MaybeUninit};
use rmath::wide4::Bool4;
use kioku::Arena; use kioku::Arena;
use crate::{ use crate::{
@ -16,7 +14,7 @@ use crate::{
boundable::Boundable, boundable::Boundable,
lerp::lerp_slice, lerp::lerp_slice,
math::Vector, math::Vector,
ray::{RayBatch, RayStack}, ray::{LocalRay, Ray},
}; };
use super::{ use super::{
@ -25,6 +23,7 @@ use super::{
}; };
use bvh_order::{calc_traversal_code, SplitAxes, TRAVERSAL_TABLE}; use bvh_order::{calc_traversal_code, SplitAxes, TRAVERSAL_TABLE};
use rmath::wide4::Float4;
pub fn ray_code(dir: Vector) -> usize { pub fn ray_code(dir: Vector) -> usize {
let ray_sign_is_neg = [dir.x() < 0.0, dir.y() < 0.0, dir.z() < 0.0]; let ray_sign_is_neg = [dir.x() < 0.0, dir.y() < 0.0, dir.z() < 0.0];
@ -33,6 +32,8 @@ pub fn ray_code(dir: Vector) -> usize {
+ ((ray_sign_is_neg[2] as usize) << 2) + ((ray_sign_is_neg[2] as usize) << 2)
} }
//-------------------------------------------------------------
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct BVH4<'a> { pub struct BVH4<'a> {
root: Option<&'a BVH4Node<'a>>, root: Option<&'a BVH4Node<'a>>,
@ -98,9 +99,9 @@ impl<'a> BVH4<'a> {
self.depth self.depth
} }
pub fn traverse<F>(&self, rays: &mut RayBatch, ray_stack: &mut RayStack, mut obj_ray_test: F) pub fn traverse<F>(&self, ray: &mut Ray, local_ray: &LocalRay, mut obj_ray_test: F)
where where
F: FnMut(std::ops::Range<usize>, &mut RayBatch, &mut RayStack), F: FnMut(std::ops::Range<usize>, &mut Ray),
{ {
if self.root.is_none() { if self.root.is_none() {
return; return;
@ -108,55 +109,48 @@ impl<'a> BVH4<'a> {
let mut node_tests: u64 = 0; let mut node_tests: u64 = 0;
let traversal_table = // SIMD-ready ray data.
&TRAVERSAL_TABLE[ray_code(rays.dir_inv_local(ray_stack.next_task_ray_idx(0)))]; let orig4 = [
local_ray.orig.0.aaaa(),
local_ray.orig.0.bbbb(),
local_ray.orig.0.cccc(),
];
let dir_inv4 = [
local_ray.dir_inv.0.aaaa(),
local_ray.dir_inv.0.bbbb(),
local_ray.dir_inv.0.cccc(),
];
let mut max_t4 = Float4::splat(ray.max_t);
// +2 of max depth for root and last child // +2 of max depth for root and last child
let mut node_stack = [self.root.unwrap(); (BVH_MAX_DEPTH * 3) + 2]; let mut node_stack = [self.root.unwrap(); (BVH_MAX_DEPTH * 3) + 2];
let mut stack_ptr = 1; let mut stack_ptr = 1;
while stack_ptr > 0 { let traversal_table = &TRAVERSAL_TABLE[ray_code(local_ray.dir_inv)];
while stack_ptr > 0 && !ray.is_done() {
match *node_stack[stack_ptr] { match *node_stack[stack_ptr] {
BVH4Node::Internal { BVH4Node::Internal {
bounds, bounds,
children, children,
traversal_code, traversal_code,
} => { } => {
node_tests += ray_stack.ray_count_in_next_task() as u64; node_tests += 1;
let mut all_hits = Bool4::new_false();
// Ray testing let hits = if bounds.len() == 1 {
ray_stack.pop_do_next_task_and_push_rays(children.len(), |ray_idx| { bounds[0].intersect_ray(orig4, dir_inv4, max_t4)
if rays.is_done(ray_idx) { } else {
Bool4::new_false() lerp_slice(bounds, ray.time).intersect_ray(orig4, dir_inv4, max_t4)
} else { };
let hits = if bounds.len() == 1 {
bounds[0].intersect_ray(
rays.orig_local(ray_idx),
rays.dir_inv_local(ray_idx),
rays.max_t(ray_idx),
)
} else {
lerp_slice(bounds, rays.time(ray_idx)).intersect_ray(
rays.orig_local(ray_idx),
rays.dir_inv_local(ray_idx),
rays.max_t(ray_idx),
)
};
all_hits |= hits;
hits
}
});
// If there were any intersections, create tasks. // Push child nodes onto the stack if there were any hits.
if all_hits.any() { if hits.any() {
let order_code = traversal_table[traversal_code as usize]; let order_code = traversal_table[traversal_code as usize];
let hits = hits.to_bools();
let mut lane_count = 0; let mut lane_count = 0;
let mut i = children.len() as u8; for i in (0..children.len() as u8).rev() {
while i > 0 {
i -= 1;
let child_i = ((order_code >> (i * 2)) & 3) as usize; let child_i = ((order_code >> (i * 2)) & 3) as usize;
if ray_stack.push_lane_to_task(child_i) { if hits[child_i] {
node_stack[stack_ptr + lane_count] = &children[child_i]; node_stack[stack_ptr + lane_count] = &children[child_i];
lane_count += 1; lane_count += 1;
} }
@ -169,8 +163,10 @@ impl<'a> BVH4<'a> {
} }
BVH4Node::Leaf { object_range } => { BVH4Node::Leaf { object_range } => {
// Do the ray tests. obj_ray_test(object_range.0..object_range.1, ray);
obj_ray_test(object_range.0..object_range.1, rays, ray_stack);
// Update SIMD max_t in case there was a hit.
max_t4 = Float4::splat(ray.max_t);
stack_ptr -= 1; stack_ptr -= 1;
} }

View File

@ -6,7 +6,6 @@ use std::ops::{BitOr, BitOrAssign};
use crate::{ use crate::{
bbox::BBox, bbox::BBox,
lerp::{lerp, Lerp}, lerp::{lerp, Lerp},
math::{Point, Vector},
}; };
use rmath::wide4::{Bool4, Float4}; use rmath::wide4::{Bool4, Float4};
@ -60,23 +59,15 @@ impl BBox4 {
} }
// Returns whether the given ray intersects with the bboxes. // Returns whether the given ray intersects with the bboxes.
pub fn intersect_ray(&self, orig: Point, dir_inv: Vector, max_t: f32) -> Bool4 { #[inline(always)]
// Get the ray data into SIMD format. pub fn intersect_ray(&self, orig: [Float4; 3], dir_inv: [Float4; 3], max_t: Float4) -> Bool4 {
let ro_x = orig.0.aaaa();
let ro_y = orig.0.bbbb();
let ro_z = orig.0.cccc();
let rdi_x = dir_inv.0.aaaa();
let rdi_y = dir_inv.0.bbbb();
let rdi_z = dir_inv.0.cccc();
let max_t = Float4::splat(max_t);
// Slab tests // Slab tests
let t1_x = (self.x.0 - ro_x) * rdi_x; let t1_x = (self.x.0 - orig[0]) * dir_inv[0];
let t1_y = (self.y.0 - ro_y) * rdi_y; let t1_y = (self.y.0 - orig[1]) * dir_inv[1];
let t1_z = (self.z.0 - ro_z) * rdi_z; let t1_z = (self.z.0 - orig[2]) * dir_inv[2];
let t2_x = (self.x.1 - ro_x) * rdi_x; let t2_x = (self.x.1 - orig[0]) * dir_inv[0];
let t2_y = (self.y.1 - ro_y) * rdi_y; let t2_y = (self.y.1 - orig[1]) * dir_inv[1];
let t2_z = (self.z.1 - ro_z) * rdi_z; let t2_z = (self.z.1 - orig[2]) * dir_inv[2];
// Get the far and near t hits for each axis. // Get the far and near t hits for each axis.
let t_far_x = t1_x.max(t2_x); let t_far_x = t1_x.max(t2_x);

View File

@ -92,12 +92,13 @@ impl<'a> Camera<'a> {
) )
.normalized(); .normalized();
Ray { Ray::new(
orig: orig.xform_inv_fast(&transform), orig.xform_inv_fast(&transform),
dir: dir.xform_inv_fast(&transform), dir.xform_inv_fast(&transform),
time: time, time,
wavelength: wavelength, wavelength,
max_t: std::f32::INFINITY, std::f32::INFINITY,
} false,
)
} }
} }

View File

@ -5,8 +5,8 @@ use crate::{
boundable::Boundable, boundable::Boundable,
color::{Color, SpectralSample}, color::{Color, SpectralSample},
lerp::lerp_slice, lerp::lerp_slice,
math::{cross, dot, Normal, Point, Vector, Xform, XformFull}, math::{cross, dot, Normal, Point, Vector, XformFull},
ray::{RayBatch, RayStack}, ray::{LocalRay, Ray},
sampling::{ sampling::{
spherical_triangle_solid_angle, triangle_surface_area, uniform_sample_spherical_triangle, spherical_triangle_solid_angle, triangle_surface_area, uniform_sample_spherical_triangle,
uniform_sample_triangle, uniform_sample_triangle,
@ -251,89 +251,77 @@ impl<'a> SurfaceLight for RectangleLight<'a> {
} }
impl<'a> Surface for RectangleLight<'a> { impl<'a> Surface for RectangleLight<'a> {
fn intersect_rays( fn intersect_ray(
&self, &self,
rays: &mut RayBatch, ray: &mut Ray,
ray_stack: &mut RayStack, _local_ray: &LocalRay,
isects: &mut [SurfaceIntersection], space: &XformFull,
shader: &dyn SurfaceShader, isect: &mut SurfaceIntersection,
space: &[Xform], _shader: &dyn SurfaceShader,
) { ) {
let _ = shader; // Silence 'unused' warning let time = ray.time;
ray_stack.pop_do_next_task(|ray_idx| { // Calculate time interpolated values.
let time = rays.time(ray_idx); let dim = lerp_slice(self.dimensions, time);
let orig = rays.orig(ray_idx);
let dir = rays.dir(ray_idx);
let max_t = rays.max_t(ray_idx);
// Calculate time interpolated values // Get the four corners of the rectangle, transformed into world space.
let dim = lerp_slice(self.dimensions, time); let p1 = Point::new(dim.0 * 0.5, dim.1 * 0.5, 0.0).xform(space);
let xform = lerp_slice(space, time); let p2 = Point::new(dim.0 * -0.5, dim.1 * 0.5, 0.0).xform(space);
let p3 = Point::new(dim.0 * -0.5, dim.1 * -0.5, 0.0).xform(space);
let p4 = Point::new(dim.0 * 0.5, dim.1 * -0.5, 0.0).xform(space);
let space = if let Some(xform) = xform.to_full() { // Test against two triangles that make up the light.
xform let ray_pre = triangle::RayTriPrecompute::new(ray.dir);
} else { for tri in &[(p1, p2, p3), (p3, p4, p1)] {
return; if let Some((t, b0, b1, b2)) =
}; triangle::intersect_ray(ray.orig, ray_pre, ray.max_t, *tri)
{
if t < ray.max_t {
if ray.is_occlusion() {
*isect = SurfaceIntersection::Occlude;
ray.mark_done();
return;
} else {
let (pos, pos_err) = triangle::surface_point(*tri, (b0, b1, b2));
let normal = cross(tri.0 - tri.1, tri.0 - tri.2).into_normal();
// Get the four corners of the rectangle, transformed into world space let intersection_data = SurfaceIntersectionData {
let p1 = Point::new(dim.0 * 0.5, dim.1 * 0.5, 0.0).xform(&space); incoming: ray.dir,
let p2 = Point::new(dim.0 * -0.5, dim.1 * 0.5, 0.0).xform(&space); t: t,
let p3 = Point::new(dim.0 * -0.5, dim.1 * -0.5, 0.0).xform(&space); pos: pos,
let p4 = Point::new(dim.0 * 0.5, dim.1 * -0.5, 0.0).xform(&space); pos_err: pos_err,
nor: normal,
nor_g: normal,
local_space: *space,
sample_pdf: self.sample_pdf(
space,
ray.orig,
ray.dir,
pos,
ray.wavelength,
time,
),
};
// Test against two triangles that make up the light let closure = {
let ray_pre = triangle::RayTriPrecompute::new(dir); let inv_surface_area = (1.0 / (dim.0 as f64 * dim.1 as f64)) as f32;
for tri in &[(p1, p2, p3), (p3, p4, p1)] { let color = lerp_slice(self.colors, time) * inv_surface_area;
if let Some((t, b0, b1, b2)) = triangle::intersect_ray(orig, ray_pre, max_t, *tri) { SurfaceClosure::Emit(color)
if t < max_t { };
if rays.is_occlusion(ray_idx) {
isects[ray_idx] = SurfaceIntersection::Occlude;
rays.mark_done(ray_idx);
} else {
let (pos, pos_err) = triangle::surface_point(*tri, (b0, b1, b2));
let normal = cross(tri.0 - tri.1, tri.0 - tri.2).into_normal();
let intersection_data = SurfaceIntersectionData { // Fill in intersection.
incoming: dir, *isect = SurfaceIntersection::Hit {
t: t, intersection_data: intersection_data,
pos: pos, closure: closure,
pos_err: pos_err, };
nor: normal,
nor_g: normal,
local_space: space,
sample_pdf: self.sample_pdf(
&space,
orig,
dir,
pos,
rays.wavelength(ray_idx),
time,
),
};
let closure = { ray.max_t = t;
let inv_surface_area = (1.0 / (dim.0 as f64 * dim.1 as f64)) as f32;
let color = lerp_slice(self.colors, time) * inv_surface_area;
SurfaceClosure::Emit(color)
};
// Fill in intersection
isects[ray_idx] = SurfaceIntersection::Hit {
intersection_data: intersection_data,
closure: closure,
};
// Set ray's max t
rays.set_max_t(ray_idx, t);
}
break;
} }
break;
} }
} }
}); }
} }
} }

View File

@ -7,8 +7,8 @@ use crate::{
boundable::Boundable, boundable::Boundable,
color::{Color, SpectralSample}, color::{Color, SpectralSample},
lerp::lerp_slice, lerp::lerp_slice,
math::{coordinate_system_from_vector, dot, Normal, Point, Vector, Xform, XformFull}, math::{coordinate_system_from_vector, dot, Normal, Point, Vector, XformFull},
ray::{RayBatch, RayStack}, ray::{LocalRay, Ray},
sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphere}, sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphere},
shading::surface_closure::SurfaceClosure, shading::surface_closure::SurfaceClosure,
shading::SurfaceShader, shading::SurfaceShader,
@ -201,139 +201,122 @@ impl<'a> SurfaceLight for SphereLight<'a> {
} }
impl<'a> Surface for SphereLight<'a> { impl<'a> Surface for SphereLight<'a> {
fn intersect_rays( fn intersect_ray(
&self, &self,
rays: &mut RayBatch, ray: &mut Ray,
ray_stack: &mut RayStack, local_ray: &LocalRay,
isects: &mut [SurfaceIntersection], space: &XformFull,
shader: &dyn SurfaceShader, isect: &mut SurfaceIntersection,
space: &[Xform], _shader: &dyn SurfaceShader,
) { ) {
let _ = shader; // Silence 'unused' warning let time = ray.time;
ray_stack.pop_do_next_task(|ray_idx| { // Get the radius of the sphere at the ray's time
let time = rays.time(ray_idx); let radius = lerp_slice(self.radii, time); // Radius of the sphere
// Get the transform space // Code adapted to Rust from https://github.com/Tecla/Rayito
let xform = if let Some(xform) = lerp_slice(space, time).to_full() { // Ray-sphere intersection can result in either zero, one or two points
xform // of intersection. It turns into a quadratic equation, so we just find
} else { // the solution using the quadratic formula. Note that there is a
return; // slightly more stable form of it when computing it on a computer, and
// we use that method to keep everything accurate.
// Calculate quadratic coeffs
let a = local_ray.dir.length2();
let b = 2.0 * dot(local_ray.dir, local_ray.orig.into_vector());
let c = local_ray.orig.into_vector().length2() - (radius * radius);
let discriminant = (b * b) - (4.0 * a * c);
if discriminant < 0.0 {
// Discriminant less than zero? No solution => no intersection.
return;
}
let discriminant = discriminant.sqrt();
// Compute a more stable form of our param t (t0 = q/a, t1 = c/q)
// q = -0.5 * (b - sqrt(b * b - 4.0 * a * c)) if b < 0, or
// q = -0.5 * (b + sqrt(b * b - 4.0 * a * c)) if b >= 0
let q = if b < 0.0 {
-0.5 * (b - discriminant)
} else {
-0.5 * (b + discriminant)
};
// Get our final parametric values
let mut t0 = q / a;
let mut t1 = if q != 0.0 { c / q } else { ray.max_t };
// Swap them so they are ordered right
if t0 > t1 {
use std::mem::swap;
swap(&mut t0, &mut t1);
}
// Check our intersection for validity against this ray's extents
if t0 > ray.max_t || t1 <= 0.0 {
// Didn't hit because sphere is entirely outside of ray's extents
return;
}
let t = if t0 > 0.0 {
t0
} else if t1 <= ray.max_t {
t1
} else {
// Didn't hit because ray is entirely within the sphere, and
// therefore doesn't hit its surface.
return;
};
// We hit the sphere, so calculate intersection info.
if ray.is_occlusion() {
*isect = SurfaceIntersection::Occlude;
ray.mark_done();
} else {
// Position is calculated from the local-space ray and t, and then
// re-projected onto the surface of the sphere.
let t_pos = local_ray.orig + (local_ray.dir * t);
let unit_pos = t_pos.into_vector().normalized();
let pos = (unit_pos * radius).xform(space).into_point();
// TODO: proper error bounds.
let pos_err = 0.001;
let normal = unit_pos.into_normal().xform_fast(space);
let intersection_data = SurfaceIntersectionData {
incoming: ray.dir,
t: t,
pos: pos,
pos_err: pos_err,
nor: normal,
nor_g: normal,
local_space: *space,
sample_pdf: self.sample_pdf(
space,
ray.orig,
ray.dir,
0.0,
0.0,
ray.wavelength,
time,
),
}; };
// Get the radius of the sphere at the ray's time let closure = {
let radius = lerp_slice(self.radii, time); // Radius of the sphere let inv_surface_area = (1.0 / (4.0 * PI_64 * radius as f64 * radius as f64)) as f32;
let color = lerp_slice(self.colors, time) * inv_surface_area;
// Get the ray origin and direction in local space SurfaceClosure::Emit(color)
let orig = rays.orig_local(ray_idx).into_vector();
let dir = rays.dir(ray_idx).xform_inv(&xform);
// Code adapted to Rust from https://github.com/Tecla/Rayito
// Ray-sphere intersection can result in either zero, one or two points
// of intersection. It turns into a quadratic equation, so we just find
// the solution using the quadratic formula. Note that there is a
// slightly more stable form of it when computing it on a computer, and
// we use that method to keep everything accurate.
// Calculate quadratic coeffs
let a = dir.length2();
let b = 2.0 * dot(dir, orig);
let c = orig.length2() - (radius * radius);
let discriminant = (b * b) - (4.0 * a * c);
if discriminant < 0.0 {
// Discriminant less than zero? No solution => no intersection.
return;
}
let discriminant = discriminant.sqrt();
// Compute a more stable form of our param t (t0 = q/a, t1 = c/q)
// q = -0.5 * (b - sqrt(b * b - 4.0 * a * c)) if b < 0, or
// q = -0.5 * (b + sqrt(b * b - 4.0 * a * c)) if b >= 0
let q = if b < 0.0 {
-0.5 * (b - discriminant)
} else {
-0.5 * (b + discriminant)
}; };
// Get our final parametric values // Fill in intersection
let mut t0 = q / a; *isect = SurfaceIntersection::Hit {
let mut t1 = if q != 0.0 { c / q } else { rays.max_t(ray_idx) }; intersection_data: intersection_data,
closure: closure,
// Swap them so they are ordered right
if t0 > t1 {
use std::mem::swap;
swap(&mut t0, &mut t1);
}
// Check our intersection for validity against this ray's extents
if t0 > rays.max_t(ray_idx) || t1 <= 0.0 {
// Didn't hit because sphere is entirely outside of ray's extents
return;
}
let t = if t0 > 0.0 {
t0
} else if t1 <= rays.max_t(ray_idx) {
t1
} else {
// Didn't hit because ray is entirely within the sphere, and
// therefore doesn't hit its surface.
return;
}; };
// We hit the sphere, so calculate intersection info. ray.max_t = t;
if rays.is_occlusion(ray_idx) { }
isects[ray_idx] = SurfaceIntersection::Occlude;
rays.mark_done(ray_idx);
} else {
// Position is calculated from the local-space ray and t, and then
// re-projected onto the surface of the sphere.
let t_pos = orig + (dir * t);
let unit_pos = t_pos.normalized();
let pos = (unit_pos * radius).xform(&xform).into_point();
// TODO: proper error bounds.
let pos_err = 0.001;
let normal = unit_pos.into_normal().xform_fast(&xform);
let intersection_data = SurfaceIntersectionData {
incoming: rays.dir(ray_idx),
t: t,
pos: pos,
pos_err: pos_err,
nor: normal,
nor_g: normal,
local_space: xform,
sample_pdf: self.sample_pdf(
&xform,
rays.orig(ray_idx),
rays.dir(ray_idx),
0.0,
0.0,
rays.wavelength(ray_idx),
time,
),
};
let closure = {
let inv_surface_area =
(1.0 / (4.0 * PI_64 * radius as f64 * radius as f64)) as f32;
let color = lerp_slice(self.colors, time) * inv_surface_area;
SurfaceClosure::Emit(color)
};
// Fill in intersection
isects[ray_idx] = SurfaceIntersection::Hit {
intersection_data: intersection_data,
closure: closure,
};
// Set ray's max t
rays.set_max_t(ray_idx, t);
}
});
} }
} }

View File

@ -39,7 +39,7 @@ mod space_fill;
mod surface; mod surface;
mod timer; mod timer;
mod tracer; mod tracer;
mod transform_stack; // mod transform_stack;
use std::{fs::File, io, io::Read, mem, path::Path, str::FromStr}; use std::{fs::File, io, io::Read, mem, path::Path, str::FromStr};

View File

@ -1,16 +1,11 @@
#![allow(dead_code)] #![allow(dead_code)]
use rmath::wide4::Bool4;
use crate::math::{Point, Vector, XformFull}; use crate::math::{Point, Vector, XformFull};
type RayIndexType = u16;
type FlagType = u8; type FlagType = u8;
const OCCLUSION_FLAG: FlagType = 1; const OCCLUSION_FLAG: FlagType = 1;
const DONE_FLAG: FlagType = 1 << 1; const DONE_FLAG: FlagType = 1 << 1;
/// This is never used directly in ray tracing--it's only used as a convenience
/// for filling the RayBatch structure.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Ray { pub struct Ray {
pub orig: Point, pub orig: Point,
@ -18,380 +13,85 @@ pub struct Ray {
pub time: f32, pub time: f32,
pub wavelength: f32, pub wavelength: f32,
pub max_t: f32, pub max_t: f32,
pub flags: FlagType,
} }
/// The hot (frequently accessed) parts of ray data. /// A specifically local-space ray, for passing to functions when we've
/// already calculated the local-space version of a ray for the object
/// in question.
///
/// Also includes `dir_inv`, which is generally useful to have as well.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct RayHot { pub struct LocalRay {
orig_local: Point, // Local-space ray origin pub orig: Point,
dir_inv_local: Vector, // Local-space 1.0/ray direction pub dir: Vector,
max_t: f32, pub dir_inv: Vector,
time: f32,
flags: FlagType,
} }
/// The cold (infrequently accessed) parts of ray data. impl Ray {
#[derive(Debug, Copy, Clone)] pub fn new(
struct RayCold { orig: Point,
orig: Point, // World-space ray origin dir: Vector,
dir: Vector, // World-space ray direction time: f32,
wavelength: f32, wavelength: f32,
} max_t: f32,
is_occlusion: bool,
/// A batch of rays, separated into hot and cold parts. ) -> Self {
#[derive(Debug)] Self {
pub struct RayBatch { orig: orig,
hot: Vec<RayHot>, dir: dir,
cold: Vec<RayCold>, time: time,
} wavelength: wavelength,
max_t: max_t,
impl RayBatch {
/// Creates a new empty ray batch.
pub fn new() -> RayBatch {
RayBatch {
hot: Vec::new(),
cold: Vec::new(),
}
}
/// Creates a new empty ray batch, with pre-allocated capacity for
/// `n` rays.
pub fn with_capacity(n: usize) -> RayBatch {
RayBatch {
hot: Vec::with_capacity(n),
cold: Vec::with_capacity(n),
}
}
pub fn push(&mut self, ray: Ray, is_occlusion: bool) {
self.hot.push(RayHot {
orig_local: ray.orig, // Bogus, to place-hold.
dir_inv_local: ray.dir, // Bogus, to place-hold.
max_t: ray.max_t,
time: ray.time,
flags: if is_occlusion { OCCLUSION_FLAG } else { 0 }, flags: if is_occlusion { OCCLUSION_FLAG } else { 0 },
}); }
self.cold.push(RayCold {
orig: ray.orig,
dir: ray.dir,
wavelength: ray.wavelength,
});
} }
pub fn swap(&mut self, a: usize, b: usize) { /// Creates a local ray from the given transform.
self.hot.swap(a, b); pub fn to_local_xform(&self, xform: &XformFull) -> LocalRay {
self.cold.swap(a, b); let orig = self.orig.xform_inv(xform);
let dir = self.dir.xform_inv(xform);
LocalRay {
orig: orig,
dir: dir,
dir_inv: dir.recip(),
}
} }
pub fn set_from_ray(&mut self, ray: &Ray, is_occlusion: bool, idx: usize) { /// Creates a local ray with no transform applied.
self.hot[idx].orig_local = ray.orig; pub fn to_local(&self) -> LocalRay {
self.hot[idx].dir_inv_local = Vector(ray.dir.0.recip()); LocalRay {
self.hot[idx].max_t = ray.max_t; orig: self.orig,
self.hot[idx].time = ray.time; dir: self.dir,
self.hot[idx].flags = if is_occlusion { OCCLUSION_FLAG } else { 0 }; dir_inv: self.dir.recip(),
}
self.cold[idx].orig = ray.orig;
self.cold[idx].dir = ray.dir;
self.cold[idx].wavelength = ray.wavelength;
} }
pub fn truncate(&mut self, len: usize) { //---------------------------------------------------------
self.hot.truncate(len); // Flags.
self.cold.truncate(len);
}
/// Clear all rays, settings the size of the batch back to zero.
///
/// Capacity is maintained.
pub fn clear(&mut self) {
self.hot.clear();
self.cold.clear();
}
pub fn len(&self) -> usize {
self.hot.len()
}
/// Updates the accel data of the given ray (at index `idx`) with the
/// given transform.
///
/// This should be called when entering (and exiting) traversal of a
/// new transform space.
pub fn update_local(&mut self, idx: usize, xform: &XformFull) {
self.hot[idx].orig_local = self.cold[idx].orig.xform_inv(xform);
self.hot[idx].dir_inv_local = Vector((self.cold[idx].dir.xform_inv(xform)).0.recip());
}
//==========================================================
// Data access
/// Returns whether this is an occlusion ray.
#[inline(always)] #[inline(always)]
pub fn orig(&self, idx: usize) -> Point { pub fn is_occlusion(&self) -> bool {
self.cold[idx].orig (self.flags & OCCLUSION_FLAG) != 0
} }
/// Returns whether this ray has finished traversal.
#[inline(always)] #[inline(always)]
pub fn dir(&self, idx: usize) -> Vector { pub fn is_done(&self) -> bool {
self.cold[idx].dir (self.flags & DONE_FLAG) != 0
} }
/// Marks this as an occlusion ray.
#[inline(always)] #[inline(always)]
pub fn orig_local(&self, idx: usize) -> Point { pub fn mark_occlusion(&mut self) {
self.hot[idx].orig_local self.flags |= OCCLUSION_FLAG
} }
/// Marks this as having finished traversal.
#[inline(always)] #[inline(always)]
pub fn dir_inv_local(&self, idx: usize) -> Vector { pub fn mark_done(&mut self) {
self.hot[idx].dir_inv_local self.flags |= DONE_FLAG
}
#[inline(always)]
pub fn time(&self, idx: usize) -> f32 {
self.hot[idx].time
}
#[inline(always)]
pub fn max_t(&self, idx: usize) -> f32 {
self.hot[idx].max_t
}
#[inline(always)]
pub fn set_max_t(&mut self, idx: usize, new_max_t: f32) {
self.hot[idx].max_t = new_max_t;
}
#[inline(always)]
pub fn wavelength(&self, idx: usize) -> f32 {
self.cold[idx].wavelength
}
/// Returns whether the given ray (at index `idx`) is an occlusion ray.
#[inline(always)]
pub fn is_occlusion(&self, idx: usize) -> bool {
(self.hot[idx].flags & OCCLUSION_FLAG) != 0
}
/// Returns whether the given ray (at index `idx`) has finished traversal.
#[inline(always)]
pub fn is_done(&self, idx: usize) -> bool {
(self.hot[idx].flags & DONE_FLAG) != 0
}
/// Marks the given ray (at index `idx`) as an occlusion ray.
#[inline(always)]
pub fn mark_occlusion(&mut self, idx: usize) {
self.hot[idx].flags |= OCCLUSION_FLAG
}
/// Marks the given ray (at index `idx`) as having finished traversal.
#[inline(always)]
pub fn mark_done(&mut self, idx: usize) {
self.hot[idx].flags |= DONE_FLAG
} }
} }
/// A structure used for tracking traversal of a ray batch through a scene.
#[derive(Debug)]
pub struct RayStack {
lanes: Vec<Lane>,
tasks: Vec<RayTask>,
}
impl RayStack {
pub fn new() -> RayStack {
RayStack {
lanes: Vec::new(),
tasks: Vec::new(),
}
}
/// Returns whether the stack is empty of tasks or not.
pub fn is_empty(&self) -> bool {
self.tasks.is_empty()
}
/// Makes sure there are at least `count` lanes.
pub fn ensure_lane_count(&mut self, count: usize) {
while self.lanes.len() < count {
self.lanes.push(Lane {
idxs: Vec::new(),
end_len: 0,
})
}
}
pub fn ray_count_in_next_task(&self) -> usize {
let task = self.tasks.last().unwrap();
let end = self.lanes[task.lane].end_len;
end - task.start_idx
}
pub fn next_task_ray_idx(&self, i: usize) -> usize {
let task = self.tasks.last().unwrap();
let i = i + task.start_idx;
debug_assert!(i < self.lanes[task.lane].end_len);
self.lanes[task.lane].idxs[i] as usize
}
/// Clears the lanes and tasks of the RayStack.
///
/// Note: this is (importantly) different than calling clear individually
/// on the `lanes` and `tasks` members. Specifically, we don't want to
/// clear `lanes` itself, as that would also free all the memory of the
/// individual lanes. Instead, we want to iterate over the individual
/// lanes and clear them, but leave `lanes` itself untouched.
pub fn clear(&mut self) {
for lane in self.lanes.iter_mut() {
lane.idxs.clear();
lane.end_len = 0;
}
self.tasks.clear();
}
/// Pushes the given ray index onto the end of the specified lane.
pub fn push_ray_index(&mut self, ray_idx: usize, lane: usize) {
assert!(self.lanes.len() > lane);
self.lanes[lane].idxs.push(ray_idx as RayIndexType);
}
/// Pushes any excess indices on the given lane to a new task on the
/// task stack.
///
/// Returns whether a task was pushed or not. No task will be pushed
/// if there are no excess indices on the end of the lane.
pub fn push_lane_to_task(&mut self, lane_idx: usize) -> bool {
if self.lanes[lane_idx].end_len < self.lanes[lane_idx].idxs.len() {
self.tasks.push(RayTask {
lane: lane_idx,
start_idx: self.lanes[lane_idx].end_len,
});
self.lanes[lane_idx].end_len = self.lanes[lane_idx].idxs.len();
true
} else {
false
}
}
/// Takes the given list of lane indices, and pushes any excess indices on
/// the end of each into a new task, in the order provided.
pub fn push_lanes_to_tasks(&mut self, lane_idxs: &[usize]) {
for &l in lane_idxs {
self.push_lane_to_task(l);
}
}
pub fn duplicate_next_task(&mut self) {
let task = self.tasks.last().unwrap();
let l = task.lane;
let start = task.start_idx;
let end = self.lanes[l].end_len;
// Extend the indices vector
self.lanes[l].idxs.reserve(end - start);
let old_len = self.lanes[l].idxs.len();
let new_len = old_len + end - start;
unsafe {
self.lanes[l].idxs.set_len(new_len);
}
// Copy elements
copy_in_place::copy_in_place(&mut self.lanes[l].idxs, start..end, end);
// Push the new task onto the stack
self.tasks.push(RayTask {
lane: l,
start_idx: end,
});
self.lanes[l].end_len = self.lanes[l].idxs.len();
}
// Pops the next task off the stack.
pub fn pop_task(&mut self) {
let task = self.tasks.pop().unwrap();
self.lanes[task.lane].end_len = task.start_idx;
self.lanes[task.lane].idxs.truncate(task.start_idx);
}
// Executes a task without popping it from the task stack.
pub fn do_next_task<F>(&mut self, mut handle_ray: F)
where
F: FnMut(usize),
{
let task = self.tasks.last().unwrap();
let task_range = (task.start_idx, self.lanes[task.lane].end_len);
// Execute task.
for i in task_range.0..task_range.1 {
let ray_idx = self.lanes[task.lane].idxs[i];
handle_ray(ray_idx as usize);
}
}
/// Pops the next task off the stack, and executes the provided closure for
/// each ray index in the task.
#[inline(always)]
pub fn pop_do_next_task<F>(&mut self, handle_ray: F)
where
F: FnMut(usize),
{
self.do_next_task(handle_ray);
self.pop_task();
}
/// Pops the next task off the stack, executes the provided closure for
/// each ray index in the task, and pushes the ray indices back onto the
/// indicated lanes.
pub fn pop_do_next_task_and_push_rays<F>(&mut self, output_lane_count: usize, mut handle_ray: F)
where
F: FnMut(usize) -> Bool4,
{
// Pop the task and do necessary bookkeeping.
let task = self.tasks.pop().unwrap();
let task_range = (task.start_idx, self.lanes[task.lane].end_len);
self.lanes[task.lane].end_len = task.start_idx;
// SAFETY: this is probably evil, and depends on behavior of Vec that
// are not actually promised. But we're essentially truncating the lane
// to the start of our task range, but will continue to access it's
// elements beyond that range via `get_unchecked()` below. Because the
// memory is not freed nor altered, this is safe. However, again, the
// Vec apis don't promise this behavior. So:
//
// TODO: build a slightly different lane abstraction to get this same
// efficiency without depending on implicit Vec behavior.
unsafe {
self.lanes[task.lane].idxs.set_len(task.start_idx);
}
// Execute task.
for i in task_range.0..task_range.1 {
let ray_idx = *unsafe { self.lanes[task.lane].idxs.get_unchecked(i) };
let push_mask = handle_ray(ray_idx as usize).bitmask();
for l in 0..output_lane_count {
if (push_mask & (1 << l)) != 0 {
self.lanes[l as usize].idxs.push(ray_idx);
}
}
}
}
}
/// A lane within a RayStack.
#[derive(Debug)]
struct Lane {
idxs: Vec<RayIndexType>,
end_len: usize,
}
/// A task within a RayStack.
//
// Specifies the lane that the relevant ray pointers are in, and the
// starting index within that lane. The relevant pointers are always
// `&[start_idx..]` within the given lane.
#[derive(Debug)]
struct RayTask {
lane: usize,
start_idx: usize,
}

View File

@ -14,16 +14,15 @@ use crate::{
color::{map_0_1_to_wavelength, SpectralSample, XYZ}, color::{map_0_1_to_wavelength, SpectralSample, XYZ},
fp_utils::robust_ray_origin, fp_utils::robust_ray_origin,
image::Image, image::Image,
math::{probit, upper_power_of_two, Float4}, math::{probit, upper_power_of_two, Float4, XformFull},
mis::power_heuristic, mis::power_heuristic,
ray::{Ray, RayBatch}, ray::Ray,
scene::{Scene, SceneLightSample}, scene::{Scene, SceneLightSample},
scramble::owen4, scramble::owen4,
space_fill::{hilbert, morton}, space_fill::{hilbert, morton},
surface, surface,
timer::Timer, timer::Timer,
tracer::Tracer, tracer::Tracer,
transform_stack::TransformStack,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -203,10 +202,7 @@ impl<'a> Renderer<'a> {
let mut timer = Timer::new(); let mut timer = Timer::new();
let mut total_timer = Timer::new(); let mut total_timer = Timer::new();
let mut paths = Vec::new();
let mut rays = RayBatch::new();
let mut tracer = Tracer::from_assembly(&self.scene.root); let mut tracer = Tracer::from_assembly(&self.scene.root);
let mut xform_stack = TransformStack::new();
// Pre-calculate some useful values related to the image plane // Pre-calculate some useful values related to the image plane
let cmpx = 1.0 / self.resolution.0 as f32; let cmpx = 1.0 / self.resolution.0 as f32;
@ -220,9 +216,6 @@ impl<'a> Renderer<'a> {
// Render // Render
'render_loop: loop { 'render_loop: loop {
paths.clear();
rays.clear();
// Get bucket, or exit if no more jobs left // Get bucket, or exit if no more jobs left
let bucket: BucketJob; let bucket: BucketJob;
loop { loop {
@ -235,9 +228,14 @@ impl<'a> Renderer<'a> {
} }
timer.tick(); timer.tick();
// Generate light paths and initial rays
for y in bucket.y..(bucket.y + bucket.h) { let bucket_min = (bucket.x, bucket.y);
for x in bucket.x..(bucket.x + bucket.w) { let bucket_max = (bucket.x + bucket.w, bucket.y + bucket.h);
let mut img_bucket = image.get_bucket(bucket_min, bucket_max);
// Trace each sample in each pixel.
for y in bucket_min.1..bucket_max.1 {
for x in bucket_min.0..bucket_max.0 {
// `si_offset` is for screen-space blue-noise sampling in the // `si_offset` is for screen-space blue-noise sampling in the
// spirit of the paper "Screen-Space Blue-Noise Diffusion of Monte // spirit of the paper "Screen-Space Blue-Noise Diffusion of Monte
// Carlo Sampling Error via Hierarchical Ordering of Pixels" by // Carlo Sampling Error via Hierarchical Ordering of Pixels" by
@ -266,8 +264,8 @@ impl<'a> Renderer<'a> {
((samp_x - 0.5) * x_extent, (0.5 - samp_y) * y_extent) ((samp_x - 0.5) * x_extent, (0.5 - samp_y) * y_extent)
}; };
// Create the light path and initial ray for this sample // Create the light path and initial ray for this sample.
let (path, ray) = LightPath::new( let (mut path, mut ray) = LightPath::new(
&self.scene, &self.scene,
self.seed, self.seed,
(x, y), (x, y),
@ -277,83 +275,66 @@ impl<'a> Renderer<'a> {
map_0_1_to_wavelength(d0), map_0_1_to_wavelength(d0),
si as u32, si as u32,
); );
paths.push(path);
rays.push(ray, false); let mut isect = surface::SurfaceIntersection::Miss;
// Trace light path.
while path.next(&self.scene, &isect, &mut ray) {
isect = surface::SurfaceIntersection::Miss;
tracer.trace(&mut ray, &mut isect);
}
// Accummulate light path color to pixel.
let path_col = SpectralSample::from_parts(path.color, path.wavelength);
let mut col = img_bucket.get(x, y);
col += XYZ::from_spectral_sample(&path_col) / self.spp as f32;
img_bucket.set(x, y, col);
} }
} }
} }
stats.initial_ray_generation_time += timer.tick() as f64; // stats.initial_ray_generation_time += timer.tick() as f64;
// stats.ray_generation_time += timer.tick() as f64;
// stats.trace_time += timer.tick() as f64;
// stats.sample_writing_time += timer.tick() as f64;
// Trace the paths! // Pre-calculate base64 encoding if needed
let mut pi = paths.len(); let base64_enc = if do_blender_output {
while pi > 0 { use crate::color::xyz_to_rec709_e;
// Test rays against scene Some(img_bucket.rgba_base64(xyz_to_rec709_e))
let isects = tracer.trace(&mut rays); } else {
stats.trace_time += timer.tick() as f64; None
};
// Determine next rays to shoot based on result // Print render progress, and image data if doing blender output
let mut new_end = 0; let guard = pixels_rendered.lock().unwrap();
for i in 0..pi { let mut pr = (*guard).get();
if paths[i].next(&mut xform_stack, &self.scene, &isects[i], &mut rays, i) { let percentage_old = pr as f64 / total_pixels as f64 * 100.0;
paths.swap(new_end, i);
rays.swap(new_end, i); pr += bucket.w as usize * bucket.h as usize;
new_end += 1; (*guard).set(pr);
} let percentage_new = pr as f64 / total_pixels as f64 * 100.0;
let old_string = format!("{:.2}%", percentage_old);
let new_string = format!("{:.2}%", percentage_new);
if let Some(bucket_data) = base64_enc {
// If doing Blender output
println!("DIV");
println!("{}", new_string);
println!(
"{} {} {} {}",
bucket_min.0, bucket_min.1, bucket_max.0, bucket_max.1
);
println!("{}", bucket_data);
println!("BUCKET_END");
println!("DIV");
} else {
// If doing console output
if new_string != old_string {
print!("\r{}", new_string);
} }
rays.truncate(new_end);
pi = new_end;
stats.ray_generation_time += timer.tick() as f64;
}
{
// Calculate color based on ray hits and save to image
let min = (bucket.x, bucket.y);
let max = (bucket.x + bucket.w, bucket.y + bucket.h);
let mut img_bucket = image.get_bucket(min, max);
for path in &paths {
let path_col = SpectralSample::from_parts(path.color, path.wavelength);
let mut col = img_bucket.get(path.pixel_co.0, path.pixel_co.1);
col += XYZ::from_spectral_sample(&path_col) / self.spp as f32;
img_bucket.set(path.pixel_co.0, path.pixel_co.1, col);
}
stats.sample_writing_time += timer.tick() as f64;
// Pre-calculate base64 encoding if needed
let base64_enc = if do_blender_output {
use crate::color::xyz_to_rec709_e;
Some(img_bucket.rgba_base64(xyz_to_rec709_e))
} else {
None
};
// Print render progress, and image data if doing blender output
let guard = pixels_rendered.lock().unwrap();
let mut pr = (*guard).get();
let percentage_old = pr as f64 / total_pixels as f64 * 100.0;
pr += bucket.w as usize * bucket.h as usize;
(*guard).set(pr);
let percentage_new = pr as f64 / total_pixels as f64 * 100.0;
let old_string = format!("{:.2}%", percentage_old);
let new_string = format!("{:.2}%", percentage_new);
if let Some(bucket_data) = base64_enc {
// If doing Blender output
println!("DIV");
println!("{}", new_string);
println!("{} {} {} {}", min.0, min.1, max.0, max.1);
println!("{}", bucket_data);
println!("BUCKET_END");
println!("DIV");
} else {
// If doing console output
if new_string != old_string {
print!("\r{}", new_string);
}
}
let _ = io::stdout().flush();
} }
let _ = io::stdout().flush();
} }
stats.total_time += total_timer.tick() as f64; stats.total_time += total_timer.tick() as f64;
@ -450,14 +431,7 @@ impl LightPath {
get_sample_4d(self.sample_number, dimension, self.sampling_seed) get_sample_4d(self.sample_number, dimension, self.sampling_seed)
} }
fn next( fn next(&mut self, scene: &Scene, isect: &surface::SurfaceIntersection, ray: &mut Ray) -> bool {
&mut self,
xform_stack: &mut TransformStack,
scene: &Scene,
isect: &surface::SurfaceIntersection,
rays: &mut RayBatch,
ray_idx: usize,
) -> bool {
match self.event { match self.event {
//-------------------------------------------------------------------- //--------------------------------------------------------------------
// Result of Camera or bounce ray, prepare next bounce and light rays // Result of Camera or bounce ray, prepare next bounce and light rays
@ -493,13 +467,12 @@ impl LightPath {
self.next_lds_sequence(); self.next_lds_sequence();
let (light_n, d2, d3, d4) = self.next_lds_samp(); let (light_n, d2, d3, d4) = self.next_lds_samp();
let light_uvw = (d2, d3, d4); let light_uvw = (d2, d3, d4);
xform_stack.clear();
let light_info = scene.sample_lights( let light_info = scene.sample_lights(
xform_stack,
light_n, light_n,
light_uvw, light_uvw,
self.wavelength, self.wavelength,
self.time, self.time,
&XformFull::identity(),
isect, isect,
); );
let found_light = if light_info.is_none() let found_light = if light_info.is_none()
@ -518,7 +491,7 @@ impl LightPath {
// Distant light // Distant light
SceneLightSample::Distant { direction, .. } => { SceneLightSample::Distant { direction, .. } => {
let (attenuation, closure_pdf) = closure.evaluate( let (attenuation, closure_pdf) = closure.evaluate(
rays.dir(ray_idx), ray.dir,
direction, direction,
idata.nor, idata.nor,
idata.nor_g, idata.nor_g,
@ -533,13 +506,14 @@ impl LightPath {
idata.nor_g.normalized(), idata.nor_g.normalized(),
direction, direction,
); );
Ray { Ray::new(
orig: offset_pos, offset_pos,
dir: direction, direction,
time: self.time, self.time,
wavelength: self.wavelength, self.wavelength,
max_t: std::f32::INFINITY, std::f32::INFINITY,
} true,
)
}; };
(attenuation, closure_pdf, shadow_ray) (attenuation, closure_pdf, shadow_ray)
} }
@ -548,7 +522,7 @@ impl LightPath {
SceneLightSample::Surface { sample_geo, .. } => { SceneLightSample::Surface { sample_geo, .. } => {
let dir = sample_geo.0 - idata.pos; let dir = sample_geo.0 - idata.pos;
let (attenuation, closure_pdf) = closure.evaluate( let (attenuation, closure_pdf) = closure.evaluate(
rays.dir(ray_idx), ray.dir,
dir, dir,
idata.nor, idata.nor,
idata.nor_g, idata.nor_g,
@ -569,13 +543,14 @@ impl LightPath {
sample_geo.1.normalized(), sample_geo.1.normalized(),
-dir, -dir,
); );
Ray { Ray::new(
orig: offset_pos, offset_pos,
dir: offset_end - offset_pos, offset_end - offset_pos,
time: self.time, self.time,
wavelength: self.wavelength, self.wavelength,
max_t: 1.0, 1.0,
} true,
)
}; };
(attenuation, closure_pdf, shadow_ray) (attenuation, closure_pdf, shadow_ray)
} }
@ -593,7 +568,7 @@ impl LightPath {
light_info.color().e * attenuation.e * self.light_attenuation light_info.color().e * attenuation.e * self.light_attenuation
/ (light_mis_pdf * light_sel_pdf); / (light_mis_pdf * light_sel_pdf);
rays.set_from_ray(&shadow_ray, true, ray_idx); *ray = shadow_ray;
true true
} }
@ -630,13 +605,14 @@ impl LightPath {
idata.nor_g.normalized(), idata.nor_g.normalized(),
dir, dir,
); );
self.next_bounce_ray = Some(Ray { self.next_bounce_ray = Some(Ray::new(
orig: offset_pos, offset_pos,
dir: dir, dir,
time: self.time, self.time,
wavelength: self.wavelength, self.wavelength,
max_t: std::f32::INFINITY, std::f32::INFINITY,
}); false,
));
true true
} else { } else {
@ -652,7 +628,7 @@ impl LightPath {
self.event = LightPathEvent::ShadowRay; self.event = LightPathEvent::ShadowRay;
return true; return true;
} else if do_bounce { } else if do_bounce {
rays.set_from_ray(&self.next_bounce_ray.unwrap(), false, ray_idx); *ray = self.next_bounce_ray.unwrap();
self.event = LightPathEvent::BounceRay; self.event = LightPathEvent::BounceRay;
self.light_attenuation *= self.next_attenuation_fac; self.light_attenuation *= self.next_attenuation_fac;
return true; return true;
@ -683,7 +659,7 @@ impl LightPath {
// Set up for the next bounce, if any // Set up for the next bounce, if any
if let Some(ref nbr) = self.next_bounce_ray { if let Some(ref nbr) = self.next_bounce_ray {
rays.set_from_ray(nbr, false, ray_idx); *ray = *nbr;
self.light_attenuation *= self.next_attenuation_fac; self.light_attenuation *= self.next_attenuation_fac;
self.event = LightPathEvent::BounceRay; self.event = LightPathEvent::BounceRay;
return true; return true;

View File

@ -13,7 +13,6 @@ use crate::{
math::{Normal, Point, Xform, XformFull}, math::{Normal, Point, Xform, XformFull},
shading::SurfaceShader, shading::SurfaceShader,
surface::{Surface, SurfaceIntersection}, surface::{Surface, SurfaceIntersection},
transform_stack::TransformStack,
}; };
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -45,11 +44,11 @@ impl<'a> Assembly<'a> {
// Returns (light_color, (sample_point, normal, point_err), pdf, selection_pdf) // Returns (light_color, (sample_point, normal, point_err), pdf, selection_pdf)
pub fn sample_lights( pub fn sample_lights(
&self, &self,
xform_stack: &mut TransformStack,
n: f32, n: f32,
uvw: (f32, f32, f32), uvw: (f32, f32, f32),
wavelength: f32, wavelength: f32,
time: f32, time: f32,
space: &XformFull,
intr: &SurfaceIntersection, intr: &SurfaceIntersection,
) -> Option<(SpectralSample, (Point, Normal, f32), f32, f32)> { ) -> Option<(SpectralSample, (Point, Normal, f32), f32, f32)> {
if let SurfaceIntersection::Hit { if let SurfaceIntersection::Hit {
@ -57,58 +56,46 @@ impl<'a> Assembly<'a> {
closure, closure,
} = *intr } = *intr
{ {
let sel_xform = if !xform_stack.top().is_empty() {
if let Some(xform) = lerp_slice(xform_stack.top(), time).to_full() {
xform
} else {
return None;
}
} else {
XformFull::identity()
};
if let Some((light_i, sel_pdf, whittled_n)) = self.light_accel.select( if let Some((light_i, sel_pdf, whittled_n)) = self.light_accel.select(
idata.incoming.xform_inv(&sel_xform), idata.incoming.xform_inv(space),
idata.pos.xform_inv(&sel_xform), idata.pos.xform_inv(space),
idata.nor.xform_inv_fast(&sel_xform), idata.nor.xform_inv_fast(space),
idata.nor_g.xform_inv_fast(&sel_xform), idata.nor_g.xform_inv_fast(space),
&closure, &closure,
time, time,
n, n,
) { ) {
let inst = self.light_instances[light_i]; let inst = self.light_instances[light_i];
// Handle transforms.
let local_space = if let Some((a, b)) = inst.transform_indices {
if let Some(new_space) = lerp_slice(&self.xforms[a..b], time)
.compose(&space.fwd)
.to_full()
{
new_space
} else {
// Invalid transform. Give up.
return None;
}
} else {
*space
};
match inst.instance_type { match inst.instance_type {
InstanceType::Object => { InstanceType::Object => {
match self.objects[inst.data_index] { match self.objects[inst.data_index] {
Object::SurfaceLight(light) => { Object::SurfaceLight(light) => {
// Get the transform of the light.
let xform = if let Some((a, b)) = inst.transform_indices {
let pxforms = xform_stack.top();
let xform = lerp_slice(&self.xforms[a..b], time);
if !pxforms.is_empty() {
xform.compose(&lerp_slice(pxforms, time))
} else {
xform
}
} else {
let pxforms = xform_stack.top();
if !pxforms.is_empty() {
lerp_slice(pxforms, time)
} else {
Xform::identity()
}
}
.to_full();
// Sample the light // Sample the light
if let Some(xform) = xform { let (color, sample_geo, pdf) = light.sample_from_point(
let (color, sample_geo, pdf) = light.sample_from_point( &local_space,
&xform, idata.pos, uvw.0, uvw.1, wavelength, time, idata.pos,
); uvw.0,
return Some((color, sample_geo, pdf, sel_pdf)); uvw.1,
} else { wavelength,
return None; time,
} );
return Some((color, sample_geo, pdf, sel_pdf));
} }
_ => unimplemented!(), _ => unimplemented!(),
@ -116,27 +103,16 @@ impl<'a> Assembly<'a> {
} }
InstanceType::Assembly => { InstanceType::Assembly => {
// Push the transform 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 // Sample sub-assembly lights
let sample = self.assemblies[inst.data_index].sample_lights( let sample = self.assemblies[inst.data_index].sample_lights(
xform_stack,
whittled_n, whittled_n,
uvw, uvw,
wavelength, wavelength,
time, time,
&local_space,
intr, intr,
); );
// Pop the assembly's transforms off the transform stack.
if inst.transform_indices.is_some() {
xform_stack.pop();
}
// Return sample // Return sample
return sample.map(|(ss, v, pdf, spdf)| (ss, v, pdf, spdf * sel_pdf)); return sample.map(|(ss, v, pdf, spdf)| (ss, v, pdf, spdf * sel_pdf));
} }

View File

@ -6,9 +6,8 @@ use crate::{
algorithm::weighted_choice, algorithm::weighted_choice,
camera::Camera, camera::Camera,
color::SpectralSample, color::SpectralSample,
math::{Normal, Point, Vector}, math::{Normal, Point, Vector, XformFull},
surface::SurfaceIntersection, surface::SurfaceIntersection,
transform_stack::TransformStack,
}; };
pub use self::{ pub use self::{
@ -27,11 +26,11 @@ pub struct Scene<'a> {
impl<'a> Scene<'a> { impl<'a> Scene<'a> {
pub fn sample_lights( pub fn sample_lights(
&self, &self,
xform_stack: &mut TransformStack,
n: f32, n: f32,
uvw: (f32, f32, f32), uvw: (f32, f32, f32),
wavelength: f32, wavelength: f32,
time: f32, time: f32,
space: &XformFull,
intr: &SurfaceIntersection, intr: &SurfaceIntersection,
) -> SceneLightSample { ) -> SceneLightSample {
// TODO: this just selects between world lights and local lights // TODO: this just selects between world lights and local lights
@ -81,9 +80,9 @@ impl<'a> Scene<'a> {
// Local lights // Local lights
let n = (n - wl_prob) / (1.0 - wl_prob); let n = (n - wl_prob) / (1.0 - wl_prob);
if let Some((ss, sgeo, pdf, spdf)) = if let Some((ss, sgeo, pdf, spdf)) = self
self.root .root
.sample_lights(xform_stack, n, uvw, wavelength, time, intr) .sample_lights(n, uvw, wavelength, time, space, intr)
{ {
return SceneLightSample::Surface { return SceneLightSample::Surface {
color: ss, color: ss,

View File

@ -2,7 +2,6 @@
// pub mod micropoly_batch; // pub mod micropoly_batch;
pub mod bilinear_patch; pub mod bilinear_patch;
pub mod micropoly_batch;
pub mod triangle; pub mod triangle;
pub mod triangle_mesh; pub mod triangle_mesh;
@ -10,8 +9,8 @@ use std::fmt::Debug;
use crate::{ use crate::{
boundable::Boundable, boundable::Boundable,
math::{Normal, Point, Vector, Xform, XformFull}, math::{Normal, Point, Vector, XformFull},
ray::{RayBatch, RayStack}, ray::{LocalRay, Ray},
shading::surface_closure::SurfaceClosure, shading::surface_closure::SurfaceClosure,
shading::SurfaceShader, shading::SurfaceShader,
}; };
@ -19,13 +18,13 @@ use crate::{
const MAX_EDGE_DICE: u32 = 128; const MAX_EDGE_DICE: u32 = 128;
pub trait Surface: Boundable + Debug + Sync { pub trait Surface: Boundable + Debug + Sync {
fn intersect_rays( fn intersect_ray(
&self, &self,
rays: &mut RayBatch, ray: &mut Ray,
ray_stack: &mut RayStack, local_ray: &LocalRay,
isects: &mut [SurfaceIntersection], space: &XformFull,
isect: &mut SurfaceIntersection,
shader: &dyn SurfaceShader, shader: &dyn SurfaceShader,
space: &[Xform],
); );
} }

View File

@ -7,8 +7,8 @@ use crate::{
bbox::BBox, bbox::BBox,
boundable::Boundable, boundable::Boundable,
lerp::lerp_slice, lerp::lerp_slice,
math::{cross, dot, Normal, Point, Xform}, math::{cross, dot, Normal, Point, XformFull},
ray::{RayBatch, RayStack}, ray::{LocalRay, Ray},
shading::SurfaceShader, shading::SurfaceShader,
}; };
@ -122,209 +122,125 @@ impl<'a> Boundable for TriangleMesh<'a> {
} }
impl<'a> Surface for TriangleMesh<'a> { impl<'a> Surface for TriangleMesh<'a> {
fn intersect_rays( fn intersect_ray(
&self, &self,
rays: &mut RayBatch, ray: &mut Ray,
ray_stack: &mut RayStack, local_ray: &LocalRay,
isects: &mut [SurfaceIntersection], space: &XformFull,
isect: &mut SurfaceIntersection,
shader: &dyn SurfaceShader, shader: &dyn SurfaceShader,
space: &[Xform],
) { ) {
// Precalculate transform for non-motion blur cases self.accel.traverse(ray, local_ray, |idx_range, ray| {
let static_mat_space = if space.len() == 1 { // Iterate through the triangles and test the ray against them.
space[0] let mut non_shadow_hit = false;
} else { let mut hit_tri = std::mem::MaybeUninit::uninit();
Xform::identity() let mut hit_tri_indices = std::mem::MaybeUninit::uninit();
}; let mut hit_tri_data = std::mem::MaybeUninit::uninit();
let ray_pre = triangle::RayTriPrecompute::new(ray.dir);
for tri_idx in idx_range.clone() {
let tri_indices = self.indices[tri_idx];
self.accel // Get triangle.
.traverse(rays, ray_stack, |idx_range, rays, ray_stack| { let mut tri = if self.time_sample_count == 1 {
let tri_count = idx_range.end - idx_range.start; // No deformation motion blur, so fast-path it.
(
self.vertices[tri_indices.0 as usize],
self.vertices[tri_indices.1 as usize],
self.vertices[tri_indices.2 as usize],
)
} else {
// Deformation motion blur, need to interpolate.
let p0_slice = &self.vertices[(tri_indices.0 as usize * self.time_sample_count)
..((tri_indices.0 as usize + 1) * self.time_sample_count)];
let p1_slice = &self.vertices[(tri_indices.1 as usize * self.time_sample_count)
..((tri_indices.1 as usize + 1) * self.time_sample_count)];
let p2_slice = &self.vertices[(tri_indices.2 as usize * self.time_sample_count)
..((tri_indices.2 as usize + 1) * self.time_sample_count)];
// Build the triangle cache if we can! let p0 = lerp_slice(p0_slice, ray.time);
let is_cached = ray_stack.ray_count_in_next_task() >= tri_count let p1 = lerp_slice(p1_slice, ray.time);
&& self.time_sample_count == 1 let p2 = lerp_slice(p2_slice, ray.time);
&& space.len() <= 1;
let mut tri_cache = [std::mem::MaybeUninit::uninit(); MAX_LEAF_TRIANGLE_COUNT];
if is_cached {
for tri_idx in idx_range.clone() {
let i = tri_idx - idx_range.start;
let tri_indices = self.indices[tri_idx];
// For static triangles with static transforms, cache them. (p0, p1, p2)
};
// Transform triangle into world space.
tri.0 = tri.0.xform(space);
tri.1 = tri.1.xform(space);
tri.2 = tri.2.xform(space);
// Test ray against triangle
if let Some((t, b0, b1, b2)) =
triangle::intersect_ray(ray.orig, ray_pre, ray.max_t, tri)
{
if ray.is_occlusion() {
*isect = SurfaceIntersection::Occlude;
ray.mark_done();
break;
} else {
non_shadow_hit = true;
ray.max_t = t;
unsafe { unsafe {
*tri_cache[i].as_mut_ptr() = ( *hit_tri.as_mut_ptr() = tri;
self.vertices[tri_indices.0 as usize], *hit_tri_indices.as_mut_ptr() = tri_indices;
self.vertices[tri_indices.1 as usize], *hit_tri_data.as_mut_ptr() = (t, b0, b1, b2);
self.vertices[tri_indices.2 as usize],
);
if !space.is_empty() {
(*tri_cache[i].as_mut_ptr()).0 =
(*tri_cache[i].as_mut_ptr()).0.xform(&static_mat_space);
(*tri_cache[i].as_mut_ptr()).1 =
(*tri_cache[i].as_mut_ptr()).1.xform(&static_mat_space);
(*tri_cache[i].as_mut_ptr()).2 =
(*tri_cache[i].as_mut_ptr()).2.xform(&static_mat_space);
}
} }
} }
} }
}
// Test each ray against the triangles. // Calculate intersection data if necessary.
ray_stack.do_next_task(|ray_idx| { if non_shadow_hit {
let ray_idx = ray_idx as usize; let hit_tri = unsafe { hit_tri.assume_init() };
let (t, b0, b1, b2) = unsafe { hit_tri_data.assume_init() };
if rays.is_done(ray_idx) { // Calculate intersection point and error magnitudes
return; let (pos, pos_err) = triangle::surface_point(hit_tri, (b0, b1, b2));
}
let ray_time = rays.time(ray_idx); // Calculate geometric surface normal
let geo_normal = cross(hit_tri.0 - hit_tri.1, hit_tri.0 - hit_tri.2).into_normal();
// Calculate the ray space, if necessary. // Calculate interpolated surface normal, if any
let mat_space = if space.len() > 1 { let shading_normal = if let Some(normals) = self.normals {
// Per-ray transform, for motion blur let hit_tri_indices = unsafe { hit_tri_indices.assume_init() };
lerp_slice(space, ray_time) let n0_slice = &normals[(hit_tri_indices.0 as usize * self.time_sample_count)
..((hit_tri_indices.0 as usize + 1) * self.time_sample_count)];
let n1_slice = &normals[(hit_tri_indices.1 as usize * self.time_sample_count)
..((hit_tri_indices.1 as usize + 1) * self.time_sample_count)];
let n2_slice = &normals[(hit_tri_indices.2 as usize * self.time_sample_count)
..((hit_tri_indices.2 as usize + 1) * self.time_sample_count)];
let n0 = lerp_slice(n0_slice, ray.time).normalized();
let n1 = lerp_slice(n1_slice, ray.time).normalized();
let n2 = lerp_slice(n2_slice, ray.time).normalized();
let s_nor = ((n0 * b0) + (n1 * b1) + (n2 * b2)).xform_fast(&space);
if dot(s_nor, geo_normal) >= 0.0 {
s_nor
} else { } else {
static_mat_space -s_nor
};
// Iterate through the triangles and test the ray against them.
let mut non_shadow_hit = false;
let mut hit_tri = std::mem::MaybeUninit::uninit();
let mut hit_tri_indices = std::mem::MaybeUninit::uninit();
let mut hit_tri_data = std::mem::MaybeUninit::uninit();
let ray_pre = triangle::RayTriPrecompute::new(rays.dir(ray_idx));
for tri_idx in idx_range.clone() {
let tri_indices = self.indices[tri_idx];
// Get triangle if necessary
let tri = if is_cached {
let i = tri_idx - idx_range.start;
unsafe { tri_cache[i].assume_init() }
} else {
let mut tri = if self.time_sample_count == 1 {
// No deformation motion blur, so fast-path it.
(
self.vertices[tri_indices.0 as usize],
self.vertices[tri_indices.1 as usize],
self.vertices[tri_indices.2 as usize],
)
} else {
// Deformation motion blur, need to interpolate.
let p0_slice = &self.vertices[(tri_indices.0 as usize
* self.time_sample_count)
..((tri_indices.0 as usize + 1) * self.time_sample_count)];
let p1_slice = &self.vertices[(tri_indices.1 as usize
* self.time_sample_count)
..((tri_indices.1 as usize + 1) * self.time_sample_count)];
let p2_slice = &self.vertices[(tri_indices.2 as usize
* self.time_sample_count)
..((tri_indices.2 as usize + 1) * self.time_sample_count)];
let p0 = lerp_slice(p0_slice, ray_time);
let p1 = lerp_slice(p1_slice, ray_time);
let p2 = lerp_slice(p2_slice, ray_time);
(p0, p1, p2)
};
if !space.is_empty() {
tri.0 = tri.0.xform(&mat_space);
tri.1 = tri.1.xform(&mat_space);
tri.2 = tri.2.xform(&mat_space);
}
tri
};
// Test ray against triangle
if let Some((t, b0, b1, b2)) = triangle::intersect_ray(
rays.orig(ray_idx),
ray_pre,
rays.max_t(ray_idx),
tri,
) {
if rays.is_occlusion(ray_idx) {
isects[ray_idx] = SurfaceIntersection::Occlude;
rays.mark_done(ray_idx);
break;
} else {
non_shadow_hit = true;
rays.set_max_t(ray_idx, t);
unsafe {
*hit_tri.as_mut_ptr() = tri;
*hit_tri_indices.as_mut_ptr() = tri_indices;
*hit_tri_data.as_mut_ptr() = (t, b0, b1, b2);
}
}
}
} }
} else {
geo_normal
};
// Calculate intersection data if necessary. let intersection_data = SurfaceIntersectionData {
if non_shadow_hit { incoming: ray.dir,
// Get the full space data. t: t,
let mat_space = if let Some(space) = mat_space.to_full() { pos: pos,
space pos_err: pos_err,
} else { nor: shading_normal,
return; nor_g: geo_normal,
}; local_space: *space,
sample_pdf: 0.0,
};
let hit_tri = unsafe { hit_tri.assume_init() }; // Fill in intersection data
let (t, b0, b1, b2) = unsafe { hit_tri_data.assume_init() }; *isect = SurfaceIntersection::Hit {
intersection_data: intersection_data,
// Calculate intersection point and error magnitudes closure: shader.shade(&intersection_data, ray.time),
let (pos, pos_err) = triangle::surface_point(hit_tri, (b0, b1, b2)); };
}
// Calculate geometric surface normal });
let geo_normal =
cross(hit_tri.0 - hit_tri.1, hit_tri.0 - hit_tri.2).into_normal();
// Calculate interpolated surface normal, if any
let shading_normal = if let Some(normals) = self.normals {
let hit_tri_indices = unsafe { hit_tri_indices.assume_init() };
let n0_slice = &normals[(hit_tri_indices.0 as usize
* self.time_sample_count)
..((hit_tri_indices.0 as usize + 1) * self.time_sample_count)];
let n1_slice = &normals[(hit_tri_indices.1 as usize
* self.time_sample_count)
..((hit_tri_indices.1 as usize + 1) * self.time_sample_count)];
let n2_slice = &normals[(hit_tri_indices.2 as usize
* self.time_sample_count)
..((hit_tri_indices.2 as usize + 1) * self.time_sample_count)];
let n0 = lerp_slice(n0_slice, ray_time).normalized();
let n1 = lerp_slice(n1_slice, ray_time).normalized();
let n2 = lerp_slice(n2_slice, ray_time).normalized();
let s_nor = ((n0 * b0) + (n1 * b1) + (n2 * b2)).xform_fast(&mat_space);
if dot(s_nor, geo_normal) >= 0.0 {
s_nor
} else {
-s_nor
}
} else {
geo_normal
};
let intersection_data = SurfaceIntersectionData {
incoming: rays.dir(ray_idx),
t: t,
pos: pos,
pos_err: pos_err,
nor: shading_normal,
nor_g: geo_normal,
local_space: mat_space,
sample_pdf: 0.0,
};
// Fill in intersection data
isects[ray_idx] = SurfaceIntersection::Hit {
intersection_data: intersection_data,
closure: shader.shade(&intersection_data, ray_time),
};
}
});
ray_stack.pop_task();
});
} }
} }

View File

@ -1,198 +1,110 @@
use std::iter;
use crate::{ use crate::{
accel::ray_code,
color::{rec709_to_xyz, Color}, color::{rec709_to_xyz, Color},
lerp::lerp_slice, lerp::lerp_slice,
math::XformFull, math::XformFull,
ray::{RayBatch, RayStack}, ray::{LocalRay, Ray},
scene::{Assembly, InstanceType, Object}, scene::{Assembly, InstanceType, Object},
shading::{SimpleSurfaceShader, SurfaceShader}, shading::{SimpleSurfaceShader, SurfaceShader},
surface::SurfaceIntersection, surface::SurfaceIntersection,
transform_stack::TransformStack,
}; };
pub struct Tracer<'a> { pub struct Tracer<'a> {
root: &'a Assembly<'a>,
ray_trace_count: u64, ray_trace_count: u64,
ray_stack: RayStack,
inner: TracerInner<'a>,
} }
impl<'a> Tracer<'a> { impl<'a> Tracer<'a> {
pub fn from_assembly(assembly: &'a Assembly) -> Tracer<'a> { pub fn from_assembly(assembly: &'a Assembly) -> Tracer<'a> {
Tracer { Tracer {
root: assembly,
ray_trace_count: 0, ray_trace_count: 0,
ray_stack: RayStack::new(),
inner: TracerInner {
root: assembly,
xform_stack: TransformStack::new(),
isects: Vec::new(),
},
} }
} }
pub fn trace<'b>(&'b mut self, rays: &mut RayBatch) -> &'b [SurfaceIntersection] {
self.ray_trace_count += rays.len() as u64;
self.inner.trace(rays, &mut self.ray_stack)
}
pub fn rays_traced(&self) -> u64 { pub fn rays_traced(&self) -> u64 {
self.ray_trace_count self.ray_trace_count
} }
}
struct TracerInner<'a> { pub fn trace(&mut self, ray: &mut Ray, isect: &mut SurfaceIntersection) {
root: &'a Assembly<'a>, self.ray_trace_count += 1;
xform_stack: TransformStack,
isects: Vec<SurfaceIntersection>,
}
impl<'a> TracerInner<'a> { let local_ray = ray.to_local();
fn trace<'b>( let space = XformFull::identity();
&'b mut self,
rays: &mut RayBatch,
ray_stack: &mut RayStack,
) -> &'b [SurfaceIntersection] {
ray_stack.clear();
// Ready the isects self.trace_assembly(self.root, ray, &local_ray, &space, isect);
self.isects.clear();
self.isects.reserve(rays.len());
self.isects
.extend(iter::repeat(SurfaceIntersection::Miss).take(rays.len()));
// Prep the accel part of the rays.
{
let ident = XformFull::identity();
for i in 0..rays.len() {
rays.update_local(i, &ident);
}
}
// Divide the rays into 8 different lanes by direction.
ray_stack.ensure_lane_count(8);
for i in 0..rays.len() {
ray_stack.push_ray_index(i, ray_code(rays.dir(i)));
}
ray_stack.push_lanes_to_tasks(&[0, 1, 2, 3, 4, 5, 6, 7]);
// Trace each of the 8 lanes separately.
while !ray_stack.is_empty() {
self.trace_assembly(self.root, rays, ray_stack);
}
&self.isects
} }
fn trace_assembly<'b>( fn trace_assembly(
&'b mut self, &mut self,
assembly: &Assembly, assembly: &Assembly,
rays: &mut RayBatch, ray: &mut Ray,
ray_stack: &mut RayStack, local_ray: &LocalRay,
space: &XformFull,
isect: &mut SurfaceIntersection,
) { ) {
assembly assembly
.object_accel .object_accel
.traverse(rays, ray_stack, |idx_range, rays, ray_stack| { .traverse(ray, local_ray, |idx_range, ray| {
let inst = &assembly.instances[idx_range.start]; for inst_idx in idx_range {
let inst = &assembly.instances[inst_idx];
// Transform rays if needed // Handle transforms if needed.
if let Some((xstart, xend)) = inst.transform_indices { let (local_space, local_ray) = if let Some((xstart, xend)) =
// Push transforms to stack inst.transform_indices
self.xform_stack.push(&assembly.xforms[xstart..xend]); {
let instance_xform = lerp_slice(&assembly.xforms[xstart..xend], ray.time);
let combined_xform = instance_xform.compose(&space.fwd);
// Do transforms if let Some(xform) = combined_xform.to_full() {
// TODO: re-divide rays based on direction (maybe?). (xform, ray.to_local_xform(&xform))
let xforms = self.xform_stack.top();
let static_xform = if xforms.len() == 1 {
if let Some(xform) = xforms[0].to_full() {
Some(xform)
} else { } else {
return; // Invalid transform, so skip traversing into this instance.
continue;
} }
} else if xforms.len() == 0 {
Some(XformFull::identity())
} else { } else {
None (*space, *local_ray)
}; };
ray_stack.do_next_task(|ray_idx| {
let t = rays.time(ray_idx);
rays.update_local(
ray_idx,
&static_xform.unwrap_or_else(|| {
lerp_slice(xforms, t).to_full().unwrap_or(
// TODO: filter out ray instead.
XformFull::identity(),
)
}),
);
});
ray_stack.duplicate_next_task();
}
// Trace rays // Trace ray.
match inst.instance_type { match inst.instance_type {
InstanceType::Object => { InstanceType::Object => {
self.trace_object( self.trace_object(
&assembly.objects[inst.data_index], &assembly.objects[inst.data_index],
inst.surface_shader_index inst.surface_shader_index
.map(|i| assembly.surface_shaders[i]), .map(|i| assembly.surface_shaders[i]),
rays, ray,
ray_stack, &local_ray,
); &local_space,
} isect,
InstanceType::Assembly => {
self.trace_assembly(&assembly.assemblies[inst.data_index], rays, ray_stack);
}
}
// Un-transform rays if needed
if inst.transform_indices.is_some() {
// Pop transforms off stack
self.xform_stack.pop();
// Undo transforms
let xforms = self.xform_stack.top();
let static_xform = if xforms.len() == 1 {
if let Some(xform) = xforms[0].to_full() {
Some(xform)
} else {
return;
}
} else if xforms.len() == 0 {
Some(XformFull::identity())
} else {
None
};
if !xforms.is_empty() {
ray_stack.pop_do_next_task(|ray_idx| {
let t = rays.time(ray_idx);
rays.update_local(
ray_idx,
&static_xform.unwrap_or_else(|| {
lerp_slice(xforms, t).to_full().unwrap_or(
// TODO: filter out ray instead.
XformFull::identity(),
)
}),
); );
}); }
} else {
let ident = XformFull::identity(); InstanceType::Assembly => {
ray_stack.pop_do_next_task(|ray_idx| { self.trace_assembly(
rays.update_local(ray_idx, &ident); &assembly.assemblies[inst.data_index],
}); ray,
&local_ray,
&local_space,
isect,
);
}
}
if ray.is_done() {
return;
} }
} }
}); });
} }
fn trace_object<'b>( fn trace_object<'b>(
&'b mut self, &mut self,
obj: &Object, obj: &Object,
surface_shader: Option<&dyn SurfaceShader>, surface_shader: Option<&dyn SurfaceShader>,
rays: &mut RayBatch, ray: &mut Ray,
ray_stack: &mut RayStack, local_ray: &LocalRay,
space: &XformFull,
isect: &mut SurfaceIntersection,
) { ) {
match *obj { match *obj {
Object::Surface(surface) => { Object::Surface(surface) => {
@ -201,13 +113,7 @@ impl<'a> TracerInner<'a> {
}; };
let shader = surface_shader.unwrap_or(&unassigned_shader); let shader = surface_shader.unwrap_or(&unassigned_shader);
surface.intersect_rays( surface.intersect_ray(ray, local_ray, space, isect, shader);
rays,
ray_stack,
&mut self.isects,
shader,
self.xform_stack.top(),
);
} }
Object::SurfaceLight(surface) => { Object::SurfaceLight(surface) => {
@ -216,13 +122,7 @@ impl<'a> TracerInner<'a> {
color: Color::new_xyz(rec709_to_xyz((1.0, 0.0, 1.0))), color: Color::new_xyz(rec709_to_xyz((1.0, 0.0, 1.0))),
}; };
surface.intersect_rays( surface.intersect_ray(ray, local_ray, space, isect, &bogus_shader);
rays,
ray_stack,
&mut self.isects,
&bogus_shader,
self.xform_stack.top(),
);
} }
} }
} }

View File

@ -1,83 +1,30 @@
use std::{ use crate::math::Xform;
cmp,
mem::{transmute, MaybeUninit},
};
use crate::{algorithm::merge_slices_to, math::Xform};
pub struct TransformStack { pub struct TransformStack {
stack: Vec<MaybeUninit<Xform>>, stack: Vec<Xform>,
stack_indices: Vec<usize>,
} }
impl TransformStack { impl TransformStack {
pub fn new() -> TransformStack { pub fn new() -> TransformStack {
let mut ts = TransformStack { TransformStack { stack: Vec::new() }
stack: Vec::new(),
stack_indices: Vec::new(),
};
ts.stack_indices.push(0);
ts.stack_indices.push(0);
ts
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.stack.clear(); self.stack.clear();
self.stack_indices.clear();
self.stack_indices.push(0);
self.stack_indices.push(0);
} }
pub fn push(&mut self, xforms: &[Xform]) { pub fn push(&mut self, xform: Xform) {
assert!(!xforms.is_empty()); match self.stack.last() {
None => self.stack.push(xform),
if self.stack.is_empty() { Some(prev_xform) => self.stack.push(xform.compose(prev_xform)),
let xforms: &[MaybeUninit<Xform>] = unsafe { transmute(xforms) };
self.stack.extend(xforms);
} else {
let sil = self.stack_indices.len();
let i1 = self.stack_indices[sil - 2];
let i2 = self.stack_indices[sil - 1];
// Reserve stack space for the new transforms.
// Note this leaves exposed uninitialized memory. The subsequent call to
// merge_slices_to() fills that memory in.
{
let maxlen = cmp::max(xforms.len(), i2 - i1);
self.stack.reserve(maxlen);
let l = self.stack.len();
unsafe { self.stack.set_len(l + maxlen) };
}
let (xfs1, xfs2) = self.stack.split_at_mut(i2);
merge_slices_to(
unsafe { transmute(&xfs1[i1..i2]) },
xforms,
xfs2,
|xf1, xf2| xf2.compose(xf1),
);
} }
self.stack_indices.push(self.stack.len());
} }
pub fn pop(&mut self) { pub fn pop(&mut self) -> Option<Xform> {
assert!(self.stack_indices.len() > 2); self.stack.pop()
let sl = self.stack.len();
let sil = self.stack_indices.len();
let i1 = self.stack_indices[sil - 2];
let i2 = self.stack_indices[sil - 1];
self.stack.truncate(sl - (i2 - i1));
self.stack_indices.pop();
} }
pub fn top(&self) -> &[Xform] { pub fn top(&self) -> Option<&Xform> {
let sil = self.stack_indices.len(); self.stack.last()
let i1 = self.stack_indices[sil - 2];
let i2 = self.stack_indices[sil - 1];
unsafe { transmute(&self.stack[i1..i2]) }
} }
} }

View File

@ -42,6 +42,11 @@ impl Vector {
Self(self.0.abs()) Self(self.0.abs())
} }
#[inline(always)]
pub fn recip(self) -> Self {
Self(self.0.recip())
}
#[inline(always)] #[inline(always)]
pub fn into_point(self) -> Point { pub fn into_point(self) -> Point {
Point(self.0) Point(self.0)