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
}
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
F: FnMut(&T, &mut [AccelRay]),
{

View File

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

View File

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

View File

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

View File

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

View File

@ -7,8 +7,8 @@ use crate::{
boundable::Boundable,
color::{Color, SpectralSample},
lerp::lerp_slice,
math::{coordinate_system_from_vector, dot, Normal, Point, Vector, Xform, XformFull},
ray::{RayBatch, RayStack},
math::{coordinate_system_from_vector, dot, Normal, Point, Vector, XformFull},
ray::{LocalRay, Ray},
sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphere},
shading::surface_closure::SurfaceClosure,
shading::SurfaceShader,
@ -201,33 +201,19 @@ impl<'a> SurfaceLight for SphereLight<'a> {
}
impl<'a> Surface for SphereLight<'a> {
fn intersect_rays(
fn intersect_ray(
&self,
rays: &mut RayBatch,
ray_stack: &mut RayStack,
isects: &mut [SurfaceIntersection],
shader: &dyn SurfaceShader,
space: &[Xform],
ray: &mut Ray,
local_ray: &LocalRay,
space: &XformFull,
isect: &mut SurfaceIntersection,
_shader: &dyn SurfaceShader,
) {
let _ = shader; // Silence 'unused' warning
ray_stack.pop_do_next_task(|ray_idx| {
let time = rays.time(ray_idx);
// Get the transform space
let xform = if let Some(xform) = lerp_slice(space, time).to_full() {
xform
} else {
return;
};
let time = ray.time;
// Get the radius of the sphere at the ray's time
let radius = lerp_slice(self.radii, time); // Radius of the sphere
// Get the ray origin and direction in local space
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
@ -236,9 +222,9 @@ impl<'a> Surface for SphereLight<'a> {
// 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 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 {
@ -258,7 +244,7 @@ impl<'a> Surface for SphereLight<'a> {
// Get our final parametric values
let mut t0 = q / a;
let mut t1 = if q != 0.0 { c / q } else { rays.max_t(ray_idx) };
let mut t1 = if q != 0.0 { c / q } else { ray.max_t };
// Swap them so they are ordered right
if t0 > t1 {
@ -267,14 +253,14 @@ impl<'a> Surface for SphereLight<'a> {
}
// Check our intersection for validity against this ray's extents
if t0 > rays.max_t(ray_idx) || t1 <= 0.0 {
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 <= rays.max_t(ray_idx) {
} else if t1 <= ray.max_t {
t1
} else {
// Didn't hit because ray is entirely within the sphere, and
@ -283,57 +269,54 @@ impl<'a> Surface for SphereLight<'a> {
};
// We hit the sphere, so calculate intersection info.
if rays.is_occlusion(ray_idx) {
isects[ray_idx] = SurfaceIntersection::Occlude;
rays.mark_done(ray_idx);
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 = orig + (dir * t);
let unit_pos = t_pos.normalized();
let pos = (unit_pos * radius).xform(&xform).into_point();
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(&xform);
let normal = unit_pos.into_normal().xform_fast(space);
let intersection_data = SurfaceIntersectionData {
incoming: rays.dir(ray_idx),
incoming: ray.dir,
t: t,
pos: pos,
pos_err: pos_err,
nor: normal,
nor_g: normal,
local_space: xform,
local_space: *space,
sample_pdf: self.sample_pdf(
&xform,
rays.orig(ray_idx),
rays.dir(ray_idx),
space,
ray.orig,
ray.dir,
0.0,
0.0,
rays.wavelength(ray_idx),
ray.wavelength,
time,
),
};
let closure = {
let inv_surface_area =
(1.0 / (4.0 * PI_64 * radius as f64 * radius as f64)) as f32;
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 {
*isect = SurfaceIntersection::Hit {
intersection_data: intersection_data,
closure: closure,
};
// Set ray's max t
rays.set_max_t(ray_idx, t);
ray.max_t = t;
}
});
}
}

View File

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

View File

@ -1,16 +1,11 @@
#![allow(dead_code)]
use rmath::wide4::Bool4;
use crate::math::{Point, Vector, XformFull};
type RayIndexType = u16;
type FlagType = u8;
const OCCLUSION_FLAG: FlagType = 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)]
pub struct Ray {
pub orig: Point,
@ -18,380 +13,85 @@ pub struct Ray {
pub time: f32,
pub wavelength: 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)]
struct RayHot {
orig_local: Point, // Local-space ray origin
dir_inv_local: Vector, // Local-space 1.0/ray direction
max_t: f32,
pub struct LocalRay {
pub orig: Point,
pub dir: Vector,
pub dir_inv: Vector,
}
impl Ray {
pub fn new(
orig: Point,
dir: Vector,
time: f32,
flags: FlagType,
}
/// The cold (infrequently accessed) parts of ray data.
#[derive(Debug, Copy, Clone)]
struct RayCold {
orig: Point, // World-space ray origin
dir: Vector, // World-space ray direction
wavelength: f32,
}
/// A batch of rays, separated into hot and cold parts.
#[derive(Debug)]
pub struct RayBatch {
hot: Vec<RayHot>,
cold: Vec<RayCold>,
}
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,
max_t: f32,
is_occlusion: bool,
) -> Self {
Self {
orig: orig,
dir: dir,
time: time,
wavelength: wavelength,
max_t: max_t,
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) {
self.hot.swap(a, b);
self.cold.swap(a, b);
/// Creates a local ray from the given transform.
pub fn to_local_xform(&self, xform: &XformFull) -> LocalRay {
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) {
self.hot[idx].orig_local = ray.orig;
self.hot[idx].dir_inv_local = Vector(ray.dir.0.recip());
self.hot[idx].max_t = ray.max_t;
self.hot[idx].time = ray.time;
self.hot[idx].flags = if is_occlusion { OCCLUSION_FLAG } else { 0 };
self.cold[idx].orig = ray.orig;
self.cold[idx].dir = ray.dir;
self.cold[idx].wavelength = ray.wavelength;
/// Creates a local ray with no transform applied.
pub fn to_local(&self) -> LocalRay {
LocalRay {
orig: self.orig,
dir: self.dir,
dir_inv: self.dir.recip(),
}
}
pub fn truncate(&mut self, len: usize) {
self.hot.truncate(len);
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
//---------------------------------------------------------
// Flags.
/// Returns whether this is an occlusion ray.
#[inline(always)]
pub fn orig(&self, idx: usize) -> Point {
self.cold[idx].orig
pub fn is_occlusion(&self) -> bool {
(self.flags & OCCLUSION_FLAG) != 0
}
/// Returns whether this ray has finished traversal.
#[inline(always)]
pub fn dir(&self, idx: usize) -> Vector {
self.cold[idx].dir
pub fn is_done(&self) -> bool {
(self.flags & DONE_FLAG) != 0
}
/// Marks this as an occlusion ray.
#[inline(always)]
pub fn orig_local(&self, idx: usize) -> Point {
self.hot[idx].orig_local
pub fn mark_occlusion(&mut self) {
self.flags |= OCCLUSION_FLAG
}
/// Marks this as having finished traversal.
#[inline(always)]
pub fn dir_inv_local(&self, idx: usize) -> Vector {
self.hot[idx].dir_inv_local
}
#[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
pub fn mark_done(&mut self) {
self.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},
fp_utils::robust_ray_origin,
image::Image,
math::{probit, upper_power_of_two, Float4},
math::{probit, upper_power_of_two, Float4, XformFull},
mis::power_heuristic,
ray::{Ray, RayBatch},
ray::Ray,
scene::{Scene, SceneLightSample},
scramble::owen4,
space_fill::{hilbert, morton},
surface,
timer::Timer,
tracer::Tracer,
transform_stack::TransformStack,
};
#[derive(Debug)]
@ -203,10 +202,7 @@ impl<'a> Renderer<'a> {
let mut 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 xform_stack = TransformStack::new();
// Pre-calculate some useful values related to the image plane
let cmpx = 1.0 / self.resolution.0 as f32;
@ -220,9 +216,6 @@ impl<'a> Renderer<'a> {
// Render
'render_loop: loop {
paths.clear();
rays.clear();
// Get bucket, or exit if no more jobs left
let bucket: BucketJob;
loop {
@ -235,9 +228,14 @@ impl<'a> Renderer<'a> {
}
timer.tick();
// Generate light paths and initial rays
for y in bucket.y..(bucket.y + bucket.h) {
for x in bucket.x..(bucket.x + bucket.w) {
let bucket_min = (bucket.x, bucket.y);
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
// spirit of the paper "Screen-Space Blue-Noise Diffusion of Monte
// 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)
};
// Create the light path and initial ray for this sample
let (path, ray) = LightPath::new(
// Create the light path and initial ray for this sample.
let (mut path, mut ray) = LightPath::new(
&self.scene,
self.seed,
(x, y),
@ -277,46 +275,27 @@ impl<'a> Renderer<'a> {
map_0_1_to_wavelength(d0),
si as u32,
);
paths.push(path);
rays.push(ray, false);
}
}
}
stats.initial_ray_generation_time += timer.tick() as f64;
// Trace the paths!
let mut pi = paths.len();
while pi > 0 {
// Test rays against scene
let isects = tracer.trace(&mut rays);
stats.trace_time += timer.tick() as f64;
let mut isect = surface::SurfaceIntersection::Miss;
// Determine next rays to shoot based on result
let mut new_end = 0;
for i in 0..pi {
if paths[i].next(&mut xform_stack, &self.scene, &isects[i], &mut rays, i) {
paths.swap(new_end, i);
rays.swap(new_end, i);
new_end += 1;
}
}
rays.truncate(new_end);
pi = new_end;
stats.ray_generation_time += timer.tick() as f64;
// Trace light path.
while path.next(&self.scene, &isect, &mut ray) {
isect = surface::SurfaceIntersection::Miss;
tracer.trace(&mut ray, &mut isect);
}
{
// 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 {
// Accummulate light path color to pixel.
let path_col = SpectralSample::from_parts(path.color, path.wavelength);
let mut col = img_bucket.get(path.pixel_co.0, path.pixel_co.1);
let mut col = img_bucket.get(x, y);
col += XYZ::from_spectral_sample(&path_col) / self.spp as f32;
img_bucket.set(path.pixel_co.0, path.pixel_co.1, col);
img_bucket.set(x, y, col);
}
stats.sample_writing_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;
// Pre-calculate base64 encoding if needed
let base64_enc = if do_blender_output {
@ -342,7 +321,10 @@ impl<'a> Renderer<'a> {
// If doing Blender output
println!("DIV");
println!("{}", new_string);
println!("{} {} {} {}", min.0, min.1, max.0, max.1);
println!(
"{} {} {} {}",
bucket_min.0, bucket_min.1, bucket_max.0, bucket_max.1
);
println!("{}", bucket_data);
println!("BUCKET_END");
println!("DIV");
@ -354,7 +336,6 @@ impl<'a> Renderer<'a> {
}
let _ = io::stdout().flush();
}
}
stats.total_time += total_timer.tick() as f64;
stats.ray_count = tracer.rays_traced();
@ -450,14 +431,7 @@ impl LightPath {
get_sample_4d(self.sample_number, dimension, self.sampling_seed)
}
fn next(
&mut self,
xform_stack: &mut TransformStack,
scene: &Scene,
isect: &surface::SurfaceIntersection,
rays: &mut RayBatch,
ray_idx: usize,
) -> bool {
fn next(&mut self, scene: &Scene, isect: &surface::SurfaceIntersection, ray: &mut Ray) -> bool {
match self.event {
//--------------------------------------------------------------------
// Result of Camera or bounce ray, prepare next bounce and light rays
@ -493,13 +467,12 @@ impl LightPath {
self.next_lds_sequence();
let (light_n, d2, d3, d4) = self.next_lds_samp();
let light_uvw = (d2, d3, d4);
xform_stack.clear();
let light_info = scene.sample_lights(
xform_stack,
light_n,
light_uvw,
self.wavelength,
self.time,
&XformFull::identity(),
isect,
);
let found_light = if light_info.is_none()
@ -518,7 +491,7 @@ impl LightPath {
// Distant light
SceneLightSample::Distant { direction, .. } => {
let (attenuation, closure_pdf) = closure.evaluate(
rays.dir(ray_idx),
ray.dir,
direction,
idata.nor,
idata.nor_g,
@ -533,13 +506,14 @@ impl LightPath {
idata.nor_g.normalized(),
direction,
);
Ray {
orig: offset_pos,
dir: direction,
time: self.time,
wavelength: self.wavelength,
max_t: std::f32::INFINITY,
}
Ray::new(
offset_pos,
direction,
self.time,
self.wavelength,
std::f32::INFINITY,
true,
)
};
(attenuation, closure_pdf, shadow_ray)
}
@ -548,7 +522,7 @@ impl LightPath {
SceneLightSample::Surface { sample_geo, .. } => {
let dir = sample_geo.0 - idata.pos;
let (attenuation, closure_pdf) = closure.evaluate(
rays.dir(ray_idx),
ray.dir,
dir,
idata.nor,
idata.nor_g,
@ -569,13 +543,14 @@ impl LightPath {
sample_geo.1.normalized(),
-dir,
);
Ray {
orig: offset_pos,
dir: offset_end - offset_pos,
time: self.time,
wavelength: self.wavelength,
max_t: 1.0,
}
Ray::new(
offset_pos,
offset_end - offset_pos,
self.time,
self.wavelength,
1.0,
true,
)
};
(attenuation, closure_pdf, shadow_ray)
}
@ -593,7 +568,7 @@ impl LightPath {
light_info.color().e * attenuation.e * self.light_attenuation
/ (light_mis_pdf * light_sel_pdf);
rays.set_from_ray(&shadow_ray, true, ray_idx);
*ray = shadow_ray;
true
}
@ -630,13 +605,14 @@ impl LightPath {
idata.nor_g.normalized(),
dir,
);
self.next_bounce_ray = Some(Ray {
orig: offset_pos,
dir: dir,
time: self.time,
wavelength: self.wavelength,
max_t: std::f32::INFINITY,
});
self.next_bounce_ray = Some(Ray::new(
offset_pos,
dir,
self.time,
self.wavelength,
std::f32::INFINITY,
false,
));
true
} else {
@ -652,7 +628,7 @@ impl LightPath {
self.event = LightPathEvent::ShadowRay;
return true;
} 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.light_attenuation *= self.next_attenuation_fac;
return true;
@ -683,7 +659,7 @@ impl LightPath {
// Set up for the next bounce, if any
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.event = LightPathEvent::BounceRay;
return true;

View File

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

View File

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

View File

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

View File

@ -7,8 +7,8 @@ use crate::{
bbox::BBox,
boundable::Boundable,
lerp::lerp_slice,
math::{cross, dot, Normal, Point, Xform},
ray::{RayBatch, RayStack},
math::{cross, dot, Normal, Point, XformFull},
ray::{LocalRay, Ray},
shading::SurfaceShader,
};
@ -122,86 +122,25 @@ impl<'a> Boundable for TriangleMesh<'a> {
}
impl<'a> Surface for TriangleMesh<'a> {
fn intersect_rays(
fn intersect_ray(
&self,
rays: &mut RayBatch,
ray_stack: &mut RayStack,
isects: &mut [SurfaceIntersection],
ray: &mut Ray,
local_ray: &LocalRay,
space: &XformFull,
isect: &mut SurfaceIntersection,
shader: &dyn SurfaceShader,
space: &[Xform],
) {
// Precalculate transform for non-motion blur cases
let static_mat_space = if space.len() == 1 {
space[0]
} else {
Xform::identity()
};
self.accel
.traverse(rays, ray_stack, |idx_range, rays, ray_stack| {
let tri_count = idx_range.end - idx_range.start;
// Build the triangle cache if we can!
let is_cached = ray_stack.ray_count_in_next_task() >= tri_count
&& self.time_sample_count == 1
&& 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.
unsafe {
*tri_cache[i].as_mut_ptr() = (
self.vertices[tri_indices.0 as usize],
self.vertices[tri_indices.1 as usize],
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.
ray_stack.do_next_task(|ray_idx| {
let ray_idx = ray_idx as usize;
if rays.is_done(ray_idx) {
return;
}
let ray_time = rays.time(ray_idx);
// Calculate the ray space, if necessary.
let mat_space = if space.len() > 1 {
// Per-ray transform, for motion blur
lerp_slice(space, ray_time)
} else {
static_mat_space
};
self.accel.traverse(ray, local_ray, |idx_range, ray| {
// 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));
let ray_pre = triangle::RayTriPrecompute::new(ray.dir);
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 {
// Get triangle.
let mut tri = if self.time_sample_count == 1 {
// No deformation motion blur, so fast-path it.
(
@ -211,46 +150,36 @@ impl<'a> Surface for TriangleMesh<'a> {
)
} else {
// Deformation motion blur, need to interpolate.
let p0_slice = &self.vertices[(tri_indices.0 as usize
* self.time_sample_count)
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)
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)
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);
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
};
// 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(
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);
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;
rays.set_max_t(ray_idx, t);
ray.max_t = t;
unsafe {
*hit_tri.as_mut_ptr() = tri;
*hit_tri_indices.as_mut_ptr() = tri_indices;
@ -262,13 +191,6 @@ impl<'a> Surface for TriangleMesh<'a> {
// Calculate intersection data if necessary.
if non_shadow_hit {
// Get the full space data.
let mat_space = if let Some(space) = mat_space.to_full() {
space
} else {
return;
};
let hit_tri = unsafe { hit_tri.assume_init() };
let (t, b0, b1, b2) = unsafe { hit_tri_data.assume_init() };
@ -276,27 +198,23 @@ impl<'a> Surface for TriangleMesh<'a> {
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();
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)
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)
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)
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 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);
let s_nor = ((n0 * b0) + (n1 * b1) + (n2 * b2)).xform_fast(&space);
if dot(s_nor, geo_normal) >= 0.0 {
s_nor
} else {
@ -307,24 +225,22 @@ impl<'a> Surface for TriangleMesh<'a> {
};
let intersection_data = SurfaceIntersectionData {
incoming: rays.dir(ray_idx),
incoming: ray.dir,
t: t,
pos: pos,
pos_err: pos_err,
nor: shading_normal,
nor_g: geo_normal,
local_space: mat_space,
local_space: *space,
sample_pdf: 0.0,
};
// Fill in intersection data
isects[ray_idx] = SurfaceIntersection::Hit {
*isect = SurfaceIntersection::Hit {
intersection_data: intersection_data,
closure: shader.shade(&intersection_data, ray_time),
closure: shader.shade(&intersection_data, ray.time),
};
}
});
ray_stack.pop_task();
});
}
}

View File

@ -1,198 +1,110 @@
use std::iter;
use crate::{
accel::ray_code,
color::{rec709_to_xyz, Color},
lerp::lerp_slice,
math::XformFull,
ray::{RayBatch, RayStack},
ray::{LocalRay, Ray},
scene::{Assembly, InstanceType, Object},
shading::{SimpleSurfaceShader, SurfaceShader},
surface::SurfaceIntersection,
transform_stack::TransformStack,
};
pub struct Tracer<'a> {
root: &'a Assembly<'a>,
ray_trace_count: u64,
ray_stack: RayStack,
inner: TracerInner<'a>,
}
impl<'a> Tracer<'a> {
pub fn from_assembly(assembly: &'a Assembly) -> Tracer<'a> {
Tracer {
ray_trace_count: 0,
ray_stack: RayStack::new(),
inner: TracerInner {
root: assembly,
xform_stack: TransformStack::new(),
isects: Vec::new(),
},
ray_trace_count: 0,
}
}
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 {
self.ray_trace_count
}
pub fn trace(&mut self, ray: &mut Ray, isect: &mut SurfaceIntersection) {
self.ray_trace_count += 1;
let local_ray = ray.to_local();
let space = XformFull::identity();
self.trace_assembly(self.root, ray, &local_ray, &space, isect);
}
struct TracerInner<'a> {
root: &'a Assembly<'a>,
xform_stack: TransformStack,
isects: Vec<SurfaceIntersection>,
}
impl<'a> TracerInner<'a> {
fn trace<'b>(
&'b mut self,
rays: &mut RayBatch,
ray_stack: &mut RayStack,
) -> &'b [SurfaceIntersection] {
ray_stack.clear();
// Ready the isects
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>(
&'b mut self,
fn trace_assembly(
&mut self,
assembly: &Assembly,
rays: &mut RayBatch,
ray_stack: &mut RayStack,
ray: &mut Ray,
local_ray: &LocalRay,
space: &XformFull,
isect: &mut SurfaceIntersection,
) {
assembly
.object_accel
.traverse(rays, ray_stack, |idx_range, rays, ray_stack| {
let inst = &assembly.instances[idx_range.start];
.traverse(ray, local_ray, |idx_range, ray| {
for inst_idx in idx_range {
let inst = &assembly.instances[inst_idx];
// Transform rays if needed
if let Some((xstart, xend)) = inst.transform_indices {
// Push transforms to stack
self.xform_stack.push(&assembly.xforms[xstart..xend]);
// Handle transforms if needed.
let (local_space, local_ray) = if let Some((xstart, xend)) =
inst.transform_indices
{
let instance_xform = lerp_slice(&assembly.xforms[xstart..xend], ray.time);
let combined_xform = instance_xform.compose(&space.fwd);
// Do transforms
// TODO: re-divide rays based on direction (maybe?).
let xforms = self.xform_stack.top();
let static_xform = if xforms.len() == 1 {
if let Some(xform) = xforms[0].to_full() {
Some(xform)
if let Some(xform) = combined_xform.to_full() {
(xform, ray.to_local_xform(&xform))
} else {
return;
// Invalid transform, so skip traversing into this instance.
continue;
}
} else if xforms.len() == 0 {
Some(XformFull::identity())
} 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 {
InstanceType::Object => {
self.trace_object(
&assembly.objects[inst.data_index],
inst.surface_shader_index
.map(|i| assembly.surface_shaders[i]),
rays,
ray_stack,
ray,
&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(),
)
}),
self.trace_assembly(
&assembly.assemblies[inst.data_index],
ray,
&local_ray,
&local_space,
isect,
);
});
} else {
let ident = XformFull::identity();
ray_stack.pop_do_next_task(|ray_idx| {
rays.update_local(ray_idx, &ident);
});
}
}
if ray.is_done() {
return;
}
}
});
}
fn trace_object<'b>(
&'b mut self,
&mut self,
obj: &Object,
surface_shader: Option<&dyn SurfaceShader>,
rays: &mut RayBatch,
ray_stack: &mut RayStack,
ray: &mut Ray,
local_ray: &LocalRay,
space: &XformFull,
isect: &mut SurfaceIntersection,
) {
match *obj {
Object::Surface(surface) => {
@ -201,13 +113,7 @@ impl<'a> TracerInner<'a> {
};
let shader = surface_shader.unwrap_or(&unassigned_shader);
surface.intersect_rays(
rays,
ray_stack,
&mut self.isects,
shader,
self.xform_stack.top(),
);
surface.intersect_ray(ray, local_ray, space, isect, shader);
}
Object::SurfaceLight(surface) => {
@ -216,13 +122,7 @@ impl<'a> TracerInner<'a> {
color: Color::new_xyz(rec709_to_xyz((1.0, 0.0, 1.0))),
};
surface.intersect_rays(
rays,
ray_stack,
&mut self.isects,
&bogus_shader,
self.xform_stack.top(),
);
surface.intersect_ray(ray, local_ray, space, isect, &bogus_shader);
}
}
}

View File

@ -1,83 +1,30 @@
use std::{
cmp,
mem::{transmute, MaybeUninit},
};
use crate::{algorithm::merge_slices_to, math::Xform};
use crate::math::Xform;
pub struct TransformStack {
stack: Vec<MaybeUninit<Xform>>,
stack_indices: Vec<usize>,
stack: Vec<Xform>,
}
impl TransformStack {
pub fn new() -> TransformStack {
let mut ts = TransformStack {
stack: Vec::new(),
stack_indices: Vec::new(),
};
ts.stack_indices.push(0);
ts.stack_indices.push(0);
ts
TransformStack { stack: Vec::new() }
}
pub fn clear(&mut self) {
self.stack.clear();
self.stack_indices.clear();
self.stack_indices.push(0);
self.stack_indices.push(0);
}
pub fn push(&mut self, xforms: &[Xform]) {
assert!(!xforms.is_empty());
if self.stack.is_empty() {
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) {
assert!(self.stack_indices.len() > 2);
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] {
let sil = self.stack_indices.len();
let i1 = self.stack_indices[sil - 2];
let i2 = self.stack_indices[sil - 1];
unsafe { transmute(&self.stack[i1..i2]) }
pub fn push(&mut self, xform: Xform) {
match self.stack.last() {
None => self.stack.push(xform),
Some(prev_xform) => self.stack.push(xform.compose(prev_xform)),
}
}
pub fn pop(&mut self) -> Option<Xform> {
self.stack.pop()
}
pub fn top(&self) -> Option<&Xform> {
self.stack.last()
}
}

View File

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