Fix for ray origin offsets when intersection point is at 0.0.

For some reason the ulp incrementing is unreliable when starting
at zero.  It creates subnormal numbers, and that seems to be an
issue somewhere in the pipeline, ultimately leading to weird
render artifacts.  Not entirely sure why.

This fixes it by avoiding subnormal numbers in the final offset
ray origin.  Left a note suggesting investigating in more detail
at some point.
This commit is contained in:
Nathan Vegdahl 2017-06-21 01:36:08 -07:00
parent 927a86c1fc
commit 4e9bfd6e79

View File

@ -18,6 +18,8 @@ pub fn increment_ulp(v: f32) -> f32 {
if (v.is_infinite() && v > 0.0) || v.is_nan() { if (v.is_infinite() && v > 0.0) || v.is_nan() {
return v; return v;
} }
// Handle zero
let v = if v == -0.0 { 0.0 } else { v }; let v = if v == -0.0 { 0.0 } else { v };
// Increase ulp by 1 // Increase ulp by 1
@ -34,13 +36,15 @@ pub fn decrement_ulp(v: f32) -> f32 {
if (v.is_infinite() && v < 0.0) || v.is_nan() { if (v.is_infinite() && v < 0.0) || v.is_nan() {
return v; return v;
} }
let v = if v == -0.0 { 0.0 } else { v };
// Handle zero
let v = if v == 0.0 { -0.0 } else { v };
// Decrease ulp by 1 // Decrease ulp by 1
if v >= 0.0 { if v <= -0.0 {
bits_to_f32(f32_to_bits(v) - 1)
} else {
bits_to_f32(f32_to_bits(v) + 1) bits_to_f32(f32_to_bits(v) + 1)
} else {
bits_to_f32(f32_to_bits(v) - 1)
} }
} }
@ -58,22 +62,34 @@ pub fn robust_ray_origin(pos: Point, pos_err: Vector, nor: Normal, ray_dir: Vect
let p = pos + offset; let p = pos + offset;
// Calculate ulp offsets // Calculate ulp offsets
let x = if offset.x() >= 0.0 { //
increment_ulp(p.x()) // The additiona/subtraction of MIN_POSITIVE is to keep numbers out of the
// subnormal range when the original value is 0.0, because that seems to be
// causing issues. Not sure why. For now this seems like a reasonable
// solution because it only affects extremely small numbers near zero
// anyway. But this seems worth investigating at some point.
//
// TODO: investigate cause of subnormal numbers being a problem, and fix
// if possible. Test case: a horizontal plane at z = 0.0 and four lights
// evenly spaced apart at z = 4.0. Causes strange render artifacts.
use std::f32::MIN_POSITIVE;
let x = if nor.x() >= 0.0 {
increment_ulp(p.x() + MIN_POSITIVE)
} else { } else {
decrement_ulp(p.x()) decrement_ulp(p.x() - MIN_POSITIVE)
}; };
let y = if offset.y() >= 0.0 { let y = if nor.y() >= 0.0 {
increment_ulp(p.y()) increment_ulp(p.y() + MIN_POSITIVE)
} else { } else {
decrement_ulp(p.y()) decrement_ulp(p.y() - MIN_POSITIVE)
}; };
let z = if offset.z() >= 0.0 { let z = if nor.z() >= 0.0 {
increment_ulp(p.z()) increment_ulp(p.z() + MIN_POSITIVE)
} else { } else {
decrement_ulp(p.z()) decrement_ulp(p.z() - MIN_POSITIVE)
}; };
Point::new(x, y, z) Point::new(x, y, z)
@ -91,3 +107,54 @@ fn bits_to_f32(bits: u32) -> f32 {
use std::mem::transmute_copy; use std::mem::transmute_copy;
unsafe { transmute_copy::<u32, f32>(&bits) } unsafe { transmute_copy::<u32, f32>(&bits) }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inc_ulp() {
assert!(increment_ulp(1.0) > 1.0);
assert!(increment_ulp(-1.0) > -1.0);
}
#[test]
fn dec_ulp() {
assert!(decrement_ulp(1.0) < 1.0);
assert!(decrement_ulp(-1.0) < -1.0);
}
#[test]
fn inc_ulp_zero() {
assert!(increment_ulp(0.0) > 0.0);
assert!(increment_ulp(0.0) > -0.0);
assert!(increment_ulp(-0.0) > 0.0);
assert!(increment_ulp(-0.0) > -0.0);
}
#[test]
fn dec_ulp_zero() {
assert!(decrement_ulp(0.0) < 0.0);
assert!(decrement_ulp(0.0) < -0.0);
assert!(decrement_ulp(-0.0) < 0.0);
assert!(decrement_ulp(-0.0) < -0.0);
}
#[test]
fn inc_dec_ulp() {
assert_eq!(decrement_ulp(increment_ulp(1.0)), 1.0);
assert_eq!(decrement_ulp(increment_ulp(-1.0)), -1.0);
assert_eq!(decrement_ulp(increment_ulp(1.2)), 1.2);
assert_eq!(decrement_ulp(increment_ulp(-1.2)), -1.2);
}
#[test]
fn dec_inc_ulp() {
assert_eq!(increment_ulp(decrement_ulp(1.0)), 1.0);
assert_eq!(increment_ulp(decrement_ulp(-1.0)), -1.0);
assert_eq!(increment_ulp(decrement_ulp(1.2)), 1.2);
assert_eq!(increment_ulp(decrement_ulp(-1.2)), -1.2);
}
}