Fix raytracer bugs: BVH traversal, AABB transforms, root selection, and shading
- BVH: transform AABB using all 8 corners, fix leaf node traversal to check all primitives - Node: reset AABB from primitive before transform, compute distance in world space - Primitives: correct quadratic root selection (pick smallest positive), fix t-guards for Circle/RectangleXY, fix Torus AABB orientation - Ray: fix random_unit_vec to cover all octants, compute reflection outside light loop, add indirect diffuse GI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
54
src/bvh.rs
54
src/bvh.rs
@@ -36,36 +36,46 @@ impl AABB {
|
||||
}
|
||||
//Apply a matrix transformation to a box
|
||||
pub fn transform_mut(&mut self, mat: &Matrix4<f64>) {
|
||||
let bln = &mut self.bln;
|
||||
let trf = &mut self.trf;
|
||||
let centroid = &mut self.centroid;
|
||||
self.bln = mat.transform_point(bln);
|
||||
self.trf = mat.transform_point(trf);
|
||||
self.centroid = mat.transform_point(centroid);
|
||||
let corners = [
|
||||
Point3::new(self.bln.x, self.bln.y, self.bln.z),
|
||||
Point3::new(self.trf.x, self.bln.y, self.bln.z),
|
||||
Point3::new(self.bln.x, self.trf.y, self.bln.z),
|
||||
Point3::new(self.trf.x, self.trf.y, self.bln.z),
|
||||
Point3::new(self.bln.x, self.bln.y, self.trf.z),
|
||||
Point3::new(self.trf.x, self.bln.y, self.trf.z),
|
||||
Point3::new(self.bln.x, self.trf.y, self.trf.z),
|
||||
Point3::new(self.trf.x, self.trf.y, self.trf.z),
|
||||
];
|
||||
let mut new_bln = Point3::new(f64::MAX, f64::MAX, f64::MAX);
|
||||
let mut new_trf = Point3::new(f64::MIN, f64::MIN, f64::MIN);
|
||||
for corner in &corners {
|
||||
let t = mat.transform_point(corner);
|
||||
new_bln = new_bln.inf(&t);
|
||||
new_trf = new_trf.sup(&t);
|
||||
}
|
||||
self.bln = new_bln;
|
||||
self.trf = new_trf;
|
||||
self.centroid = self.bln + (self.trf - self.bln) / 2.0;
|
||||
}
|
||||
// Intersect bounding box exactly
|
||||
pub fn intersect_ray(&self, ray: &Ray) -> bool {
|
||||
let bln = &self.bln;
|
||||
let trf = &self.trf;
|
||||
let t1 = (bln - ray.a).component_div(&ray.b);
|
||||
let t2 = (trf - ray.a).component_div(&ray.b);
|
||||
let t1 = (self.bln - ray.a).component_div(&ray.b);
|
||||
let t2 = (self.trf - ray.a).component_div(&ray.b);
|
||||
|
||||
let tmin = t1.inf(&t2).max();
|
||||
let tmax = t1.sup(&t2).min();
|
||||
|
||||
tmax >= tmin && tmax > 0.0
|
||||
}
|
||||
// Intersect ray with some epsilon tolerance
|
||||
// Intersect with some epsilon tolerance
|
||||
pub fn intersect_ray_aprox(&self, ray: &Ray) -> bool {
|
||||
let bln = &self.bln;
|
||||
let trf = &self.trf;
|
||||
let t1 = (bln - ray.a).component_div(&ray.b);
|
||||
let t2 = (trf - ray.a).component_div(&ray.b);
|
||||
let t1 = (self.bln - ray.a).component_div(&ray.b);
|
||||
let t2 = (self.trf - ray.a).component_div(&ray.b);
|
||||
|
||||
let tmin = t1.inf(&t2).max();
|
||||
let tmax = t1.sup(&t2).min();
|
||||
|
||||
tmax >= tmin - EPSILON && tmax > -EPSILON
|
||||
tmax >= tmin - EPSILON && tmax > 0.0
|
||||
}
|
||||
// Get the center of this bounding box
|
||||
fn get_centroid(&self) -> Point3<f64> {
|
||||
@@ -365,7 +375,7 @@ impl BVH {
|
||||
return None;
|
||||
}
|
||||
if bvh_node.prim_count != 0 {
|
||||
// Leaf node intersection — test all primitives in the leaf
|
||||
// Leaf node - check all primitives it contains
|
||||
let mut closest: Option<(&Node, Intersection)> = None;
|
||||
let mut closest_dist = f64::MAX;
|
||||
for i in 0..bvh_node.prim_count {
|
||||
@@ -374,10 +384,7 @@ impl BVH {
|
||||
continue;
|
||||
}
|
||||
if let Some(intersect) = node.intersect_ray(&ray) {
|
||||
if intersect.distance < EPSILON {
|
||||
continue;
|
||||
}
|
||||
if intersect.distance < closest_dist {
|
||||
if intersect.distance >= EPSILON && intersect.distance < closest_dist {
|
||||
closest_dist = intersect.distance;
|
||||
closest = Some((node, intersect));
|
||||
}
|
||||
@@ -424,10 +431,7 @@ impl BVH {
|
||||
}
|
||||
}
|
||||
let cost = l_count as f64 * l_aabb.area() + r_count as f64 * r_aabb.area();
|
||||
match cost > 0.0 {
|
||||
true => cost,
|
||||
false => 1e30,
|
||||
}
|
||||
if cost > 0.0 { cost } else { 1e30 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,19 +99,19 @@ impl Node {
|
||||
// Compute the inverse model matrix by inverting the model matrix
|
||||
self.inv_model = self.model.try_inverse().unwrap();
|
||||
self.inv_transpose_model = self.inv_model.transpose().remove_row(3).remove_column(3);
|
||||
// Reset AABB from primitive local space before transforming to world space
|
||||
self.aabb = self.primitive.get_aabb();
|
||||
self.aabb.transform_mut(&self.model);
|
||||
}
|
||||
// Intersection of a ray, will convert to model coords and check
|
||||
pub fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> {
|
||||
let world_origin = ray.a; // Save world-space origin before transform
|
||||
let ray = ray.transform(&self.inv_model); //Transform from world coordinates
|
||||
if let Some(mut intersect) = self.primitive.intersect_ray(&ray) {
|
||||
let local_ray = ray.transform(&self.inv_model); //Transform from world coordinates
|
||||
if let Some(mut intersect) = self.primitive.intersect_ray(&local_ray) {
|
||||
if intersect.distance < EPSILON {
|
||||
return None;
|
||||
}
|
||||
intersect.transform_mut(&self.model, &self.inv_transpose_model); //Transform to world coords
|
||||
intersect.distance = distance(&intersect.point, &world_origin);
|
||||
intersect.distance = distance(&intersect.point, &ray.a); // use world-space ray origin
|
||||
return Some(intersect);
|
||||
}
|
||||
return None;
|
||||
|
||||
@@ -47,12 +47,13 @@ impl Primitive for Sphere {
|
||||
Roots::No(_) => return None,
|
||||
Roots::One([x1]) => x1,
|
||||
Roots::Two([x1, x2]) => {
|
||||
if x1 > EPSILON {
|
||||
x1
|
||||
} else if x2 > EPSILON {
|
||||
// roots are returned in ascending order: x1 <= x2
|
||||
if x1 <= 0.0 && x2 <= 0.0 {
|
||||
return None;
|
||||
} else if x1 <= 0.0 {
|
||||
x2
|
||||
} else {
|
||||
return None;
|
||||
x1
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
@@ -122,9 +123,9 @@ impl Primitive for Circle {
|
||||
let n_dot_b = ray.b.dot(&self.normal);
|
||||
let t = (self.constant - n_dot_a) / n_dot_b;
|
||||
|
||||
if t < EPSILON || t > INFINITY {
|
||||
if t <= 0.0 || t > INFINITY {
|
||||
return None;
|
||||
};
|
||||
}
|
||||
|
||||
let intersect = ray.at_t(t);
|
||||
//Distance to center of circle
|
||||
@@ -195,12 +196,13 @@ impl Primitive for Cylinder {
|
||||
Roots::No(_) => return None,
|
||||
Roots::One([x1]) => Some(x1),
|
||||
Roots::Two([x1, x2]) => {
|
||||
if x1 > EPSILON {
|
||||
Some(x1)
|
||||
} else if x2 > EPSILON {
|
||||
// roots are returned in ascending order: x1 <= x2
|
||||
if x1 <= 0.0 && x2 <= 0.0 {
|
||||
return None;
|
||||
} else if x1 <= 0.0 {
|
||||
Some(x2)
|
||||
} else {
|
||||
return None;
|
||||
Some(x1)
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
@@ -321,12 +323,13 @@ impl Primitive for Cone {
|
||||
Roots::No(_) => None,
|
||||
Roots::One([x1]) => Some(x1),
|
||||
Roots::Two([x1, x2]) => {
|
||||
if x1 > EPSILON {
|
||||
Some(x1)
|
||||
} else if x2 > EPSILON {
|
||||
// roots are returned in ascending order: x1 <= x2
|
||||
if x1 <= 0.0 && x2 <= 0.0 {
|
||||
None
|
||||
} else if x1 <= 0.0 {
|
||||
Some(x2)
|
||||
} else {
|
||||
None
|
||||
Some(x1)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
@@ -354,9 +357,9 @@ impl Primitive for Cone {
|
||||
(Some(cone_intersect), None) => Some(cone_intersect),
|
||||
(None, Some(circle_intersect)) => Some(circle_intersect),
|
||||
(Some(cone_intersect), Some(circle_intersect)) => {
|
||||
let cone_distance = distance(&ray.a, &cone_intersect.point);
|
||||
let circle_distance = distance(&ray.a, &circle_intersect.point);
|
||||
if cone_distance < circle_distance {
|
||||
let cone_dist = distance(&ray.a, &cone_intersect.point);
|
||||
let circle_dist = distance(&ray.a, &circle_intersect.point);
|
||||
if cone_dist < circle_dist {
|
||||
Some(cone_intersect)
|
||||
} else {
|
||||
Some(circle_intersect)
|
||||
@@ -397,7 +400,7 @@ impl Primitive for RectangleXY {
|
||||
let az = ray.a.z;
|
||||
let bz = ray.b.z;
|
||||
let t = (z - az) / bz;
|
||||
if t < EPSILON || t > INFINITY {
|
||||
if t <= 0.0 || t > INFINITY {
|
||||
return None;
|
||||
}
|
||||
let intersect = ray.at_t(t);
|
||||
@@ -815,8 +818,8 @@ impl Primitive for Torus {
|
||||
|
||||
fn get_aabb(&self) -> AABB {
|
||||
let extent = self.inner_rad + self.outer_rad;
|
||||
let bln = Point3::new(-extent, -self.outer_rad, -extent);
|
||||
let trf = Point3::new(extent, self.outer_rad, extent);
|
||||
let bln = Point3::new(-extent, -extent, -self.outer_rad);
|
||||
let trf = Point3::new(extent, extent, self.outer_rad);
|
||||
AABB::new(bln, trf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ use nalgebra::{distance, Matrix3, Matrix4, Point3, Vector3};
|
||||
use rand;
|
||||
|
||||
fn random_vec() -> Vector3<f64> {
|
||||
Vector3::new(rand::random(), rand::random(), rand::random())
|
||||
Vector3::new(
|
||||
rand::random::<f64>() * 2.0 - 1.0,
|
||||
rand::random::<f64>() * 2.0 - 1.0,
|
||||
rand::random::<f64>() * 2.0 - 1.0,
|
||||
)
|
||||
}
|
||||
fn random_unit_vec() -> Vector3<f64> {
|
||||
random_vec().normalize()
|
||||
@@ -165,7 +169,6 @@ impl Ray {
|
||||
let incidence = &ray.b;
|
||||
let material = &node.material;
|
||||
|
||||
// Compute the ambient light component and set it as base colour
|
||||
let mut colour = Vector3::zeros();
|
||||
|
||||
// Reflection is view-dependent, not light-dependent — compute once
|
||||
@@ -257,7 +260,6 @@ impl Ray {
|
||||
if let Some((_, intersect)) = bvh.traverse(self, 0) {
|
||||
return intersect.distance < light_distance;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
None => {
|
||||
for (_, node) in &scene.nodes {
|
||||
|
||||
Reference in New Issue
Block a user