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:
Nathan Vegdahl 2017-06-19 22:28:44 -07:00
parent 71bdf203aa
commit 011405e131
6 changed files with 135 additions and 15 deletions

93
src/fp_utils.rs Normal file
View 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) }
}

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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())