From 112f94c127cc24ce6f8903625bfc921beac7bd1a Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Fri, 28 Dec 2018 13:40:36 -0800 Subject: [PATCH] Implemented a "Micropolygon Batch" type. This is in prep for a shade-before-hit architecture. The type is currently unused and untested. architecture. --- src/light/rectangle_light.rs | 1 - src/light/sphere_light.rs | 1 - src/surface/micropoly_batch.rs | 262 +++++++++++++++++++++++++++++++++ src/surface/mod.rs | 2 +- src/surface/triangle_mesh.rs | 1 - 5 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 src/surface/micropoly_batch.rs diff --git a/src/light/rectangle_light.rs b/src/light/rectangle_light.rs index 5985bb8..98bae49 100644 --- a/src/light/rectangle_light.rs +++ b/src/light/rectangle_light.rs @@ -298,7 +298,6 @@ impl<'a> Surface for RectangleLight<'a> { pos_err: pos_err, nor: normal, nor_g: normal, - uv: (0.0, 0.0), // TODO local_space: xform, sample_pdf: self.sample_pdf( &xform, diff --git a/src/light/sphere_light.rs b/src/light/sphere_light.rs index d8e8208..2323902 100644 --- a/src/light/sphere_light.rs +++ b/src/light/sphere_light.rs @@ -306,7 +306,6 @@ impl<'a> Surface for SphereLight<'a> { pos_err: pos_err, nor: normal, nor_g: normal, - uv: (0.0, 0.0), // TODO local_space: xform, sample_pdf: self.sample_pdf( &xform, diff --git a/src/surface/micropoly_batch.rs b/src/surface/micropoly_batch.rs new file mode 100644 index 0000000..780d529 --- /dev/null +++ b/src/surface/micropoly_batch.rs @@ -0,0 +1,262 @@ +#![allow(dead_code)] + +use mem_arena::MemArena; + +use crate::{ + accel::BVH4, + bbox::BBox, + boundable::Boundable, + lerp::lerp_slice, + math::{cross, dot, Matrix4x4, Normal, Point}, + ray::{AccelRay, Ray}, + shading::{surface_closure::SurfaceClosure, SurfaceShader}, +}; + +use super::{triangle, Surface, SurfaceIntersection, SurfaceIntersectionData}; + +/// This is the core surface primitive for rendering: all surfaces are +/// ultimately processed into pre-shaded micropolygon batches for rendering. +/// +/// It is essentially a triangle soup that shares the same surface shader. +/// The parameters of that shader can vary over the triangles, but its +/// structure cannot. +#[derive(Copy, Clone, Debug)] +pub struct MicropolyBatch<'a> { + // Vertices and associated normals. Time samples for the same vertex are + // laid out next to each other in a contiguous slice. + time_sample_count: usize, + vertices: &'a [Point], + normals: &'a [Normal], + + // Per-vertex shading data. + vertex_closures: &'a [SurfaceClosure], + + // Micro-triangle indices. Each element of the tuple specifies the index + // of a vertex, which indexes into all of the arrays above. + indices: &'a [(u32, u32, u32)], + + // Acceleration structure for fast ray intersection testing. + accel: BVH4<'a>, +} + +impl<'a> MicropolyBatch<'a> { + pub fn from_verts_and_indices<'b>( + arena: &'b MemArena, + geo_time_sample_count: usize, + verts: &[Point], + vert_normals: &[Normal], + vert_closures: &[SurfaceClosure], + triangles: &[(u32, u32, u32)], + ) -> MicropolyBatch<'b> { + // Create bounds array for use during BVH construction + let bounds = { + let mut bounds = Vec::with_capacity(triangles.len() * geo_time_sample_count); + for tri in triangles { + for ti in 0..geo_time_sample_count { + let p0 = verts[(tri.0 as usize * geo_time_sample_count) + ti]; + let p1 = verts[(tri.1 as usize * geo_time_sample_count) + ti]; + let p2 = verts[(tri.2 as usize * geo_time_sample_count) + ti]; + let minimum = p0.min(p1.min(p2)); + let maximum = p0.max(p1.max(p2)); + bounds.push(BBox::from_points(minimum, maximum)); + } + } + bounds + }; + + // Create an array of triangle indices for use during the BVH build. + let mut tmp_indices: Vec<_> = (0u32..(triangles.len() as u32)).collect(); + + // Build BVH + let accel = BVH4::from_objects(arena, &mut tmp_indices[..], 3, |index| { + &bounds[(*index as usize * geo_time_sample_count) + ..((*index as usize + 1) * geo_time_sample_count)] + }); + + // Copy triangle vertex indices over in the post-bvh-build order. + let indices = { + let indices = unsafe { arena.alloc_array_uninitialized(triangles.len()) }; + for (i, tmp_i) in tmp_indices.iter().enumerate() { + indices[i] = triangles[*tmp_i as usize]; + } + indices + }; + + MicropolyBatch { + time_sample_count: geo_time_sample_count, + vertices: arena.copy_slice(verts), + normals: arena.copy_slice(vert_normals), + + vertex_closures: arena.copy_slice(vert_closures), + + indices: indices, + + accel: accel, + } + } +} + +impl<'a> Boundable for MicropolyBatch<'a> { + fn bounds(&self) -> &[BBox] { + self.accel.bounds() + } +} + +impl<'a> Surface for MicropolyBatch<'a> { + fn intersect_rays( + &self, + accel_rays: &mut [AccelRay], + wrays: &[Ray], + isects: &mut [SurfaceIntersection], + _shader: &SurfaceShader, + space: &[Matrix4x4], + ) { + // Precalculate transform for non-motion blur cases + let static_mat_space = if space.len() == 1 { + space[0].inverse() + } else { + Matrix4x4::new() + }; + + self.accel + .traverse(&mut accel_rays[..], self.indices, |tri_indices, rs| { + // For static triangles with static transforms, cache them. + let is_cached = self.time_sample_count == 1 && space.len() <= 1; + let mut tri = if is_cached { + let tri = ( + 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 + } else { + ( + tri.0 * static_mat_space, + tri.1 * static_mat_space, + tri.2 * static_mat_space, + ) + } + } else { + unsafe { std::mem::uninitialized() } + }; + + // Test each ray against the current triangle. + for r in rs { + let wr = &wrays[r.id as usize]; + + // Get triangle if necessary + if !is_cached { + tri = if self.time_sample_count == 1 { + // No deformation motion blur, so fast-path it. + ( + self.vertices[tri_indices.0 as usize], + self.vertices[tri_indices.1 as usize], + self.vertices[tri_indices.2 as usize], + ) + } else { + // Deformation motion blur, need to interpolate. + let p0_slice = &self.vertices[(tri_indices.0 as usize + * self.time_sample_count) + ..((tri_indices.0 as usize + 1) * self.time_sample_count)]; + let p1_slice = &self.vertices[(tri_indices.1 as usize + * self.time_sample_count) + ..((tri_indices.1 as usize + 1) * self.time_sample_count)]; + let p2_slice = &self.vertices[(tri_indices.2 as usize + * self.time_sample_count) + ..((tri_indices.2 as usize + 1) * self.time_sample_count)]; + + let p0 = lerp_slice(p0_slice, wr.time); + let p1 = lerp_slice(p1_slice, wr.time); + let p2 = lerp_slice(p2_slice, wr.time); + + (p0, p1, p2) + }; + } + + // Transform triangle if necessary, and get transform space. + let mat_space = if !space.is_empty() { + if space.len() > 1 { + // Per-ray transform, for motion blur + let mat_space = lerp_slice(space, wr.time).inverse(); + tri = (tri.0 * mat_space, tri.1 * mat_space, tri.2 * mat_space); + mat_space + } else { + // Same transform for all rays + if !is_cached { + tri = ( + tri.0 * static_mat_space, + tri.1 * static_mat_space, + tri.2 * static_mat_space, + ); + } + static_mat_space + } + } else { + // No transforms + Matrix4x4::new() + }; + + // Test ray against triangle + if let Some((t, b0, b1, b2)) = triangle::intersect_ray(wr, tri) { + if t < r.max_t { + if r.is_occlusion() { + isects[r.id as usize] = SurfaceIntersection::Occlude; + r.mark_done(); + } else { + // Calculate intersection point and error magnitudes + let (pos, pos_err) = triangle::surface_point(tri, (b0, b1, b2)); + + // Calculate geometric surface normal + let geo_normal = cross(tri.0 - tri.1, tri.0 - tri.2).into_normal(); + + // Calculate interpolated surface normal + let shading_normal = { + let n0_slice = &self.normals[(tri_indices.0 as usize + * self.time_sample_count) + ..((tri_indices.0 as usize + 1) * self.time_sample_count)]; + let n1_slice = &self.normals[(tri_indices.1 as usize + * self.time_sample_count) + ..((tri_indices.1 as usize + 1) * self.time_sample_count)]; + let n2_slice = &self.normals[(tri_indices.2 as usize + * self.time_sample_count) + ..((tri_indices.2 as usize + 1) * self.time_sample_count)]; + + let n0 = lerp_slice(n0_slice, wr.time).normalized(); + let n1 = lerp_slice(n1_slice, wr.time).normalized(); + let n2 = lerp_slice(n2_slice, wr.time).normalized(); + + let s_nor = ((n0 * b0) + (n1 * b1) + (n2 * b2)) * mat_space; + if dot(s_nor, geo_normal) >= 0.0 { + s_nor + } else { + -s_nor + } + }; + + // Calculate surface closure + // TODO: use interpolation between the vertices + let surface_closure = self.vertex_closures[tri_indices.0 as usize]; + + // Fill in intersection data + isects[r.id as usize] = SurfaceIntersection::Hit { + intersection_data: SurfaceIntersectionData { + incoming: wr.dir, + t: t, + pos: pos, + pos_err: pos_err, + nor: shading_normal, + nor_g: geo_normal, + local_space: mat_space, + sample_pdf: 0.0, + }, + closure: surface_closure, + }; + r.max_t = t; + } + } + } + } + }); + } +} diff --git a/src/surface/mod.rs b/src/surface/mod.rs index 0b3cfcd..9c2b761 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] +pub mod micropoly_batch; pub mod triangle; pub mod triangle_mesh; @@ -45,6 +46,5 @@ pub struct SurfaceIntersectionData { pub nor_g: Normal, // True geometric normal pub local_space: Matrix4x4, // Matrix from global space to local space pub t: f32, // Ray t-value at the intersection point - pub uv: (f32, f32), // 2d surface parameters pub sample_pdf: f32, // The PDF of getting this point by explicitly sampling the surface } diff --git a/src/surface/triangle_mesh.rs b/src/surface/triangle_mesh.rs index 1a4373f..a067416 100644 --- a/src/surface/triangle_mesh.rs +++ b/src/surface/triangle_mesh.rs @@ -255,7 +255,6 @@ impl<'a> Surface for TriangleMesh<'a> { pos_err: pos_err, nor: shading_normal, nor_g: geo_normal, - uv: (0.0, 0.0), // TODO local_space: mat_space, sample_pdf: 0.0, };