WIP do depth-first instead of breadth-first ray tracing.
Currently totally broken.
This commit is contained in:
parent
98a9aeb374
commit
8ca6e27f39
|
@ -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]),
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
25
src/bbox4.rs
25
src/bbox4.rs
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
414
src/ray.rs
414
src/ray.rs
|
@ -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,
|
||||
}
|
||||
|
|
146
src/renderer.rs
146
src/renderer.rs
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
202
src/tracer.rs
202
src/tracer.rs
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
struct TracerInner<'a> {
|
||||
root: &'a Assembly<'a>,
|
||||
xform_stack: TransformStack,
|
||||
isects: Vec<SurfaceIntersection>,
|
||||
}
|
||||
pub fn trace(&mut self, ray: &mut Ray, isect: &mut SurfaceIntersection) {
|
||||
self.ray_trace_count += 1;
|
||||
|
||||
impl<'a> TracerInner<'a> {
|
||||
fn trace<'b>(
|
||||
&'b mut self,
|
||||
rays: &mut RayBatch,
|
||||
ray_stack: &mut RayStack,
|
||||
) -> &'b [SurfaceIntersection] {
|
||||
ray_stack.clear();
|
||||
let local_ray = ray.to_local();
|
||||
let space = XformFull::identity();
|
||||
|
||||
// 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);
|
||||
}
|
||||
self.trace_assembly(self.root, ray, &local_ray, &space, isect);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) };
|
||||
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)),
|
||||
}
|
||||
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) -> Option<Xform> {
|
||||
self.stack.pop()
|
||||
}
|
||||
|
||||
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 top(&self) -> Option<&Xform> {
|
||||
self.stack.last()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user