From 9e81acfda993b046a050b23240b21be1ec288b32 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Tue, 17 Jan 2023 21:32:28 -0800 Subject: [PATCH] Working basic triangle intersection. --- rtiow/renderer/src/scenes/stltest.rs | 21 +-- rtiow/renderer/src/triangles.rs | 230 ++++++++++++++++++++------- 2 files changed, 185 insertions(+), 66 deletions(-) diff --git a/rtiow/renderer/src/scenes/stltest.rs b/rtiow/renderer/src/scenes/stltest.rs index 816d103..6f18395 100644 --- a/rtiow/renderer/src/scenes/stltest.rs +++ b/rtiow/renderer/src/scenes/stltest.rs @@ -11,13 +11,14 @@ use crate::{ renderer::{Opt, Scene}, sphere::Sphere, texture::ConstantTexture, + translate::Translate, triangles::Triangles, vec3::Vec3, }; pub fn new(opt: &Opt) -> Scene { - let lookfrom = Vec3::new(-100., 200., 200.); - let lookat = Vec3::new(0., 1., 0.); + let lookfrom = Vec3::new(-20., 100., -100.); + let lookat = Vec3::new(0., 10., 0.); let dist_to_focus = 10.0; let aperture = 0.0; let time_min = 0.; @@ -46,12 +47,6 @@ pub fn new(opt: &Opt) -> Scene { .expect("failed to parse cube"); let light_size = 50.; let light_height = 200.; - let tri_mesh = Triangles::new( - &stl_cube, - Lambertian::new(ConstantTexture::new(Vec3::new(1., 1., 1.))), - 1., - ); - dbg!(tri_mesh.triangles.len(), tri_mesh.bbox); let objects: Vec> = vec![ // Light from above Box::new(XZRect::new( @@ -98,7 +93,15 @@ pub fn new(opt: &Opt) -> Scene { 20., Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2), )), - Box::new(tri_mesh), + // STL Mesh + Box::new(Translate::new( + Triangles::new( + &stl_cube, + Lambertian::new(ConstantTexture::new(Vec3::new(1., 1.0, 1.0))), + 1., + ), + [-10., 0., 0.], + )), ]; let world: Box = if opt.use_accel { Box::new(KDTree::new(objects, time_min, time_max)) diff --git a/rtiow/renderer/src/triangles.rs b/rtiow/renderer/src/triangles.rs index 57827d7..62c2b8f 100644 --- a/rtiow/renderer/src/triangles.rs +++ b/rtiow/renderer/src/triangles.rs @@ -1,3 +1,5 @@ +use std::f32::EPSILON; + use stl::STL; use crate::{ @@ -13,11 +15,6 @@ pub struct Triangle { normal: Vec3, verts: [Vec3; 3], // Precomputed data - // TODO(wathiede): precompute `d` on load. - d: f32, - edge0: Vec3, - edge1: Vec3, - edge2: Vec3, } #[derive(Debug)] @@ -42,13 +39,17 @@ where let v0 = t.verts[0] * scale_factor; let v1 = t.verts[1] * scale_factor; let v2 = t.verts[2] * scale_factor; + assert_eq!( + t.normal, + cross(v1 - v0, v2 - v0).unit_vector(), + "v1 {} v2 {} v3 {}", + v0, + v1, + v2 + ); Triangle { normal: t.normal, verts: [v0, v1, v2], - d: -dot(t.normal, t.verts[0]), - edge0: v1 - v0, - edge1: v2 - v1, - edge2: v0 - v2, } }) .collect(); @@ -77,62 +78,172 @@ where } } +/// Based on https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection.html +fn ray_triangle_intersect_moller_trumbore1(r: Ray, tri: &Triangle) -> Option { + // #ifdef MOLLER_TRUMBORE + // Vec3f v0v1 = v1 - v0; + // Vec3f v0v2 = v2 - v0; + // Vec3f pvec = dir.crossProduct(v0v2); + // float det = v0v1.dotProduct(pvec); + // #ifdef CULLING + // // if the determinant is negative, the triangle is 'back facing' + // // if the determinant is close to 0, the ray misses the triangle + // if (det < kEpsilon) return false; + // #else + // // ray and triangle are parallel if det is close to 0 + // if (fabs(det) < kEpsilon) return false; + // #endif + // float invDet = 1 / det; + // + // Vec3f tvec = orig - v0; + // u = tvec.dotProduct(pvec) * invDet; + // if (u < 0 || u > 1) return false; + // + // Vec3f qvec = tvec.crossProduct(v0v1); + // v = dir.dotProduct(qvec) * invDet; + // if (v < 0 || u + v > 1) return false; + // + // t = v0v2.dotProduct(qvec) * invDet; + // + let v0 = tri.verts[0]; + let v1 = tri.verts[1]; + let v2 = tri.verts[2]; + + let v0v1 = v1 - v0; + let v0v2 = v2 - v0; + let p = cross(r.direction, v0v2); + let det = dot(v0v1, p); + if det < EPSILON { + return None; + } + + let inv_det = 1. / det; + + let t = r.origin - v0; + let u = dot(t, p) * inv_det; + if u < 0. || u > 1. { + return None; + } + + let q = cross(t, v0v1); + let v = dot(r.direction, q) * inv_det; + if v < 0. || u + v > 1. { + return None; + } + let t = dot(v0v2, q) * inv_det; + + if t > EPSILON { + return Some(RayTriangleResult { + t, + p: r.origin + r.direction * t, + }); + } + None +} + +// From https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm +fn ray_triangle_intersect_moller_trumbore2(r: Ray, tri: &Triangle) -> Option { + let v0 = tri.verts[0]; + let v1 = tri.verts[1]; + let v2 = tri.verts[2]; + + let edge1 = v1 - v0; + let edge2 = v2 - v0; + let h = cross(r.direction, edge2); + let a = dot(edge1, h); + if a < EPSILON { + return None; + } + + let f = 1. / a; + let s = r.origin - v0; + let u = f * dot(s, h); + if u < 0. || u > 1. { + return None; + } + let q = cross(s, edge1); + let v = f * dot(r.direction, q); + if v < 0. || u + v > 1. { + return None; + } + // At this stage we can compute t to find out where the intersection point is on the line. + let t = f * dot(edge2, q); + // ray intersection + if t > EPSILON { + return Some(RayTriangleResult { + t, + p: r.origin + r.direction * t, + }); + } + // This means that there is a line intersection but not a ray intersection. + None +} +/// Based on https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/ray-triangle-intersection-geometric-solution.html +fn ray_triangle_intersect_geometric(r: Ray, tri: &Triangle) -> Option { + let n = tri.normal; + + // close enough to parallel, won't hit + let n_dot_dir = dot(n, r.direction); + if n_dot_dir < EPSILON { + return None; + } + + let v0 = tri.verts[0]; + let v1 = tri.verts[1]; + let v2 = tri.verts[2]; + let d = -dot(n, v0); + let t = -(dot(n, r.origin) + d) / n_dot_dir; + // check if the triangle is behind the ray + if t < 0. { + return None; + } + + let p = r.origin + t * r.direction; + + let edge0 = v1 - v0; + let edge1 = v2 - v1; + let edge2 = v0 - v2; + let vp0 = p - v0; + let c = cross(edge0, vp0); + if dot(n, c) < 0. { + return None; + } + + let vp1 = p - v1; + let c = cross(edge1, vp1); + if dot(n, c) < 0. { + return None; + } + + let vp2 = p - v2; + let c = cross(edge2, vp2); + if dot(n, c) < 0. { + return None; + } + Some(RayTriangleResult { t, p }) +} + impl Hit for Triangles where M: Material, { - // Based on https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/ray-triangle-intersection-geometric-solution.html fn hit(&self, r: Ray, _t_min: f32, _t_max: f32) -> Option { // TODO(wathiede): add an acceleration structure to more cheaply skip some triangles. for tri in &self.triangles { - let n = tri.normal; - - // close enough to parallel, won't hit - // TODO(wathiede): verify EPSILON - const EPSILON: f32 = 0.00001; - let n_dot_dir = dot(n, r.direction); - if n_dot_dir < EPSILON { - continue; + if let Some(RayTriangleResult { t, p }) = + ray_triangle_intersect_moller_trumbore1(r, tri) + { + //if let Some(RayTriangleResult { t, p }) = ray_triangle_intersect_geometric(r, tri) { + // We don't support UV (yet?). + let uv = (0.5, 0.5); + return Some(HitRecord { + t, + uv, + p, + normal: tri.normal, + material: &self.material, + }); } - - let t = -(dot(n, r.origin) + tri.d) / n_dot_dir; - // check if the triangle is behind the ray - if t < 0. { - continue; - } - - let p = r.origin + t * r.direction; - let v0 = tri.verts[0]; - let v1 = tri.verts[1]; - let v2 = tri.verts[2]; - - let vp0 = p - v0; - let c = cross(tri.edge0, vp0); - if dot(n, c) < 0. { - continue; - } - - let vp1 = p - v1; - let c = cross(tri.edge1, vp1); - if dot(n, c) < 0. { - continue; - } - - let vp2 = p - v2; - let c = cross(tri.edge2, vp2); - if dot(n, c) < 0. { - continue; - } - - // We don't support UV (yet?). - let uv = (0., 0.); - return Some(HitRecord { - t, - uv, - p, - normal: n, - material: &self.material, - }); } None } @@ -141,3 +252,8 @@ where Some(self.bbox) } } + +struct RayTriangleResult { + t: f32, + p: Vec3, +}