Implemented robust ray origin calculation for bounced rays.
We take a small performance hit for this, but given that it's making things meaningfully more correct I feel like it's more than worth it.
This commit is contained in:
parent
71bdf203aa
commit
011405e131
93
src/fp_utils.rs
Normal file
93
src/fp_utils.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
//! Utilities for handling floating point precision issues
|
||||||
|
//!
|
||||||
|
//! This is based on the work in section 3.9 of "Physically Based Rendering:
|
||||||
|
//! From Theory to Implementation" 3rd edition by Pharr et al.
|
||||||
|
|
||||||
|
use math::{Point, Vector, Normal, dot};
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn fp_gamma(n: u32) -> f32 {
|
||||||
|
use std::f32::EPSILON;
|
||||||
|
let e = EPSILON * 0.5;
|
||||||
|
(e * n as f32) / (1.0 - (e * n as f32))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn increment_ulp(v: f32) -> f32 {
|
||||||
|
// Handle special cases
|
||||||
|
if (v.is_infinite() && v > 0.0) || v.is_nan() {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
let v = if v == -0.0 { 0.0 } else { v };
|
||||||
|
|
||||||
|
// Increase ulp by 1
|
||||||
|
if v >= 0.0 {
|
||||||
|
bits_to_f32(f32_to_bits(v) + 1)
|
||||||
|
} else {
|
||||||
|
bits_to_f32(f32_to_bits(v) - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn decrement_ulp(v: f32) -> f32 {
|
||||||
|
// Handle special cases
|
||||||
|
if (v.is_infinite() && v < 0.0) || v.is_nan() {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
let v = if v == -0.0 { 0.0 } else { v };
|
||||||
|
|
||||||
|
// Decrease ulp by 1
|
||||||
|
if v >= 0.0 {
|
||||||
|
bits_to_f32(f32_to_bits(v) - 1)
|
||||||
|
} else {
|
||||||
|
bits_to_f32(f32_to_bits(v) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn robust_ray_origin(pos: Point, pos_err: Vector, nor: Normal, ray_dir: Vector) -> Point {
|
||||||
|
// Get surface normal pointing in the same
|
||||||
|
// direction as ray_dir.
|
||||||
|
let nor = {
|
||||||
|
let nor = nor.into_vector();
|
||||||
|
if dot(nor, ray_dir) >= 0.0 { nor } else { -nor }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate offset point
|
||||||
|
let d = dot(nor.abs(), pos_err);
|
||||||
|
let offset = nor * d;
|
||||||
|
let p = pos + offset;
|
||||||
|
|
||||||
|
// Calculate ulp offsets
|
||||||
|
let x = if offset.x() >= 0.0 {
|
||||||
|
increment_ulp(p.x())
|
||||||
|
} else {
|
||||||
|
decrement_ulp(p.x())
|
||||||
|
};
|
||||||
|
|
||||||
|
let y = if offset.y() >= 0.0 {
|
||||||
|
increment_ulp(p.y())
|
||||||
|
} else {
|
||||||
|
decrement_ulp(p.y())
|
||||||
|
};
|
||||||
|
|
||||||
|
let z = if offset.z() >= 0.0 {
|
||||||
|
increment_ulp(p.z())
|
||||||
|
} else {
|
||||||
|
decrement_ulp(p.z())
|
||||||
|
};
|
||||||
|
|
||||||
|
Point::new(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn f32_to_bits(v: f32) -> u32 {
|
||||||
|
use std::mem::transmute_copy;
|
||||||
|
unsafe { transmute_copy::<f32, u32>(&v) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn bits_to_f32(bits: u32) -> f32 {
|
||||||
|
use std::mem::transmute_copy;
|
||||||
|
unsafe { transmute_copy::<u32, f32>(&bits) }
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ mod bbox;
|
||||||
mod boundable;
|
mod boundable;
|
||||||
mod camera;
|
mod camera;
|
||||||
mod color;
|
mod color;
|
||||||
|
mod fp_utils;
|
||||||
mod hash;
|
mod hash;
|
||||||
mod hilbert;
|
mod hilbert;
|
||||||
mod image;
|
mod image;
|
||||||
|
|
|
@ -10,10 +10,11 @@ use scoped_threadpool::Pool;
|
||||||
|
|
||||||
use halton;
|
use halton;
|
||||||
|
|
||||||
use algorithm::partition_pair;
|
|
||||||
use accel::ACCEL_TRAV_TIME;
|
use accel::ACCEL_TRAV_TIME;
|
||||||
|
use algorithm::partition_pair;
|
||||||
use color::{Color, XYZ, SpectralSample, map_0_1_to_wavelength};
|
use color::{Color, XYZ, SpectralSample, map_0_1_to_wavelength};
|
||||||
use float4::Float4;
|
use float4::Float4;
|
||||||
|
use fp_utils::robust_ray_origin;
|
||||||
use hash::hash_u32;
|
use hash::hash_u32;
|
||||||
use hilbert;
|
use hilbert;
|
||||||
use image::Image;
|
use image::Image;
|
||||||
|
@ -477,14 +478,13 @@ impl LightPath {
|
||||||
|
|
||||||
// Calculate the shadow ray for testing if the light is
|
// Calculate the shadow ray for testing if the light is
|
||||||
// in shadow or not.
|
// in shadow or not.
|
||||||
// TODO: use proper ray offsets for avoiding self-shadowing
|
let offset_pos = robust_ray_origin(
|
||||||
// rather than this hacky stupid stuff.
|
idata.pos,
|
||||||
*ray = Ray::new(
|
idata.pos_err,
|
||||||
idata.pos + shadow_vec.normalized() * 0.001,
|
idata.nor_g.normalized(),
|
||||||
shadow_vec,
|
shadow_vec,
|
||||||
self.time,
|
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
|
*ray = Ray::new(offset_pos, shadow_vec, self.time, true);
|
||||||
|
|
||||||
// For distant lights
|
// For distant lights
|
||||||
if is_infinite {
|
if is_infinite {
|
||||||
|
@ -518,12 +518,14 @@ impl LightPath {
|
||||||
self.next_attentuation_fac = filter.e / pdf;
|
self.next_attentuation_fac = filter.e / pdf;
|
||||||
|
|
||||||
// Calculate the ray for this bounce
|
// Calculate the ray for this bounce
|
||||||
self.next_bounce_ray = Some(Ray::new(
|
let offset_pos = robust_ray_origin(
|
||||||
idata.pos + dir.normalized() * 0.0001,
|
idata.pos,
|
||||||
|
idata.pos_err,
|
||||||
|
idata.nor_g.normalized(),
|
||||||
dir,
|
dir,
|
||||||
self.time,
|
);
|
||||||
false,
|
self.next_bounce_ray =
|
||||||
));
|
Some(Ray::new(offset_pos, dir, self.time, false));
|
||||||
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -37,6 +37,7 @@ pub enum SurfaceIntersection {
|
||||||
pub struct SurfaceIntersectionData {
|
pub struct SurfaceIntersectionData {
|
||||||
pub incoming: Vector, // Direction of the incoming ray
|
pub incoming: Vector, // Direction of the incoming ray
|
||||||
pub pos: Point, // Position of the intersection
|
pub pos: Point, // Position of the intersection
|
||||||
|
pub pos_err: Vector, // Error magnitudes of the intersection position
|
||||||
pub nor: Normal, // Shading normal
|
pub nor: Normal, // Shading normal
|
||||||
pub nor_g: Normal, // True geometric normal
|
pub nor_g: Normal, // True geometric normal
|
||||||
pub local_space: Matrix4x4, // Matrix from global space to local space
|
pub local_space: Matrix4x4, // Matrix from global space to local space
|
||||||
|
|
|
@ -6,6 +6,7 @@ use accel::BVH;
|
||||||
use bbox::BBox;
|
use bbox::BBox;
|
||||||
use boundable::Boundable;
|
use boundable::Boundable;
|
||||||
use color::XYZ;
|
use color::XYZ;
|
||||||
|
use fp_utils::fp_gamma;
|
||||||
use lerp::{lerp, lerp_slice, lerp_slice_with};
|
use lerp::{lerp, lerp_slice, lerp_slice_with};
|
||||||
use math::{Point, Matrix4x4, cross};
|
use math::{Point, Matrix4x4, cross};
|
||||||
use ray::{Ray, AccelRay};
|
use ray::{Ray, AccelRay};
|
||||||
|
@ -92,17 +93,29 @@ impl<'a> Surface for TriangleMesh<'a> {
|
||||||
};
|
};
|
||||||
let mat_inv = mat_space.inverse();
|
let mat_inv = mat_space.inverse();
|
||||||
let tri = (tri.0 * mat_inv, tri.1 * mat_inv, tri.2 * mat_inv);
|
let tri = (tri.0 * mat_inv, tri.1 * mat_inv, tri.2 * mat_inv);
|
||||||
if let Some((t, _, _, _)) = triangle::intersect_ray(wr, tri) {
|
if let Some((t, b0, b1, b2)) = triangle::intersect_ray(wr, tri) {
|
||||||
if t < r.max_t {
|
if t < r.max_t {
|
||||||
if r.is_occlusion() {
|
if r.is_occlusion() {
|
||||||
isects[r.id as usize] = SurfaceIntersection::Occlude;
|
isects[r.id as usize] = SurfaceIntersection::Occlude;
|
||||||
r.mark_done();
|
r.mark_done();
|
||||||
} else {
|
} else {
|
||||||
|
// Calculate intersection point and error magnitudes
|
||||||
|
let pos = ((tri.0.into_vector() * b0)
|
||||||
|
+ (tri.1.into_vector() * b1)
|
||||||
|
+ (tri.2.into_vector() * b2)).into_point();
|
||||||
|
|
||||||
|
let pos_err = ((tri.0.into_vector().abs() * b0)
|
||||||
|
+ (tri.1.into_vector().abs() * b1)
|
||||||
|
+ (tri.2.into_vector().abs() * b2))
|
||||||
|
* fp_gamma(7);
|
||||||
|
|
||||||
|
// Fill in intersection data
|
||||||
isects[r.id as usize] = SurfaceIntersection::Hit {
|
isects[r.id as usize] = SurfaceIntersection::Hit {
|
||||||
intersection_data: SurfaceIntersectionData {
|
intersection_data: SurfaceIntersectionData {
|
||||||
incoming: wr.dir,
|
incoming: wr.dir,
|
||||||
t: t,
|
t: t,
|
||||||
pos: wr.orig + (wr.dir * t),
|
pos: pos,
|
||||||
|
pos_err: pos_err,
|
||||||
nor: cross(tri.0 - tri.1, tri.0 - tri.2)
|
nor: cross(tri.0 - tri.1, tri.0 - tri.2)
|
||||||
.into_normal(), // TODO
|
.into_normal(), // TODO
|
||||||
nor_g: cross(tri.0 - tri.1, tri.0 - tri.2)
|
nor_g: cross(tri.0 - tri.1, tri.0 - tri.2)
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::ops::{Add, Sub, Mul, Div, Neg};
|
||||||
use float4::Float4;
|
use float4::Float4;
|
||||||
|
|
||||||
use super::{DotProduct, CrossProduct};
|
use super::{DotProduct, CrossProduct};
|
||||||
use super::{Matrix4x4, Normal};
|
use super::{Matrix4x4, Point, Normal};
|
||||||
|
|
||||||
|
|
||||||
/// A direction vector in 3d homogeneous space.
|
/// A direction vector in 3d homogeneous space.
|
||||||
|
@ -36,6 +36,16 @@ impl Vector {
|
||||||
*self / self.length()
|
*self / self.length()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn abs(&self) -> Vector {
|
||||||
|
Vector::new(self.x().abs(), self.y().abs(), self.z().abs())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn into_point(self) -> Point {
|
||||||
|
Point::new(self.x(), self.y(), self.z())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn into_normal(self) -> Normal {
|
pub fn into_normal(self) -> Normal {
|
||||||
Normal::new(self.x(), self.y(), self.z())
|
Normal::new(self.x(), self.y(), self.z())
|
||||||
|
|
Loading…
Reference in New Issue
Block a user