-
Notifications
You must be signed in to change notification settings - Fork 852
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AABB hit and NaNs & Infinities #927
Comments
I'm leaving the above message as original, including the mistakes, but here's some errata: division by zero causes an infinity, not a NaN. Operations with infinities work in different ways than operations with NaNs. I managed to find at least one reproducible example case where the two methods produce different outcomes: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=48a920a2604206baab6f555e1759fe44 Note that in the old method, I think this means that the new method can cause false negative hits for the AABB hit method. |
@trevordblack — I'm inclined to punt this to post v4.0.0. |
Just wanted to mention that I hit this exact problem (and as it turns out I'm also using rust.) My code is slightly different from the book but it's based on the "optimized" example in section 3.5 of book 2: let ray_direction_in_axis_inv = 1.0 / ray_direction_in_axis;
let mut t_min = (axis.min - ray_origin_in_axis) * ray_direction_in_axis_inv;
let mut t_max = (axis.max - ray_origin_in_axis) * ray_direction_in_axis_inv; ray_direction_in_axis_inv is +Inf which in theory is fine, but if the ray origin is exactly on axis.min or axis.max there will be a NaN, causing the comparison to fail and the AABB to incorrectly report that it was not hit by the ray. I addressed this by changing the code to offset the axis bounds by an epsilon. If NaN occurs the function returns no hit, which would be correct in this case. const EPSILON: f32 = 1e-4;
let mut t_min = (axis.min - EPSILON - ray_origin_in_axis) * ray_direction_in_axis_inv;
let mut t_max = (axis.max + EPSILON - ray_origin_in_axis) * ray_direction_in_axis_inv; |
BTW, I found it very helpful for debug purposes to hack the BvhNode to always check if the children return a hit, and if it does assert that the AABB for the node also reports a hit. This made it very easy to find/reproduce this issue. Don't know if it's worth mentioning this in the book though. |
Ok, in reviewing the
As a quick explanation, the new The key issue here is the interval
In the case that the ray lies between the two planes, then
Also note that I tried being more explicit about the zero denominator, intercepting such values and treating them specially, but the result is more complicated code that runs a bit slower. Better to understand and use the defined IEEE-754 semantics instead of regarding infinities and NaNs as errors. |
The question now is how much of this to add to our text. |
Ref: #1270 |
Discovered that sometimes `std::fmin()` and `std::fmax()` can be performance bombs. Likely this is because these functions have special handling for NaNs. Instead, I switched to using ternary expressions like `a < b ? a : b`, which had a large performance impact. This particularly impacts the `aabb::hit()` function. Update book for latest AABB changes: - Add clarifying comment for corner cases of ray-aabb intersection. - Fix some listings. - Update code listings. - Deprecate section "An Optimized AABB Hit Method" Resolves #927
Discovered that sometimes `std::fmin()` and `std::fmax()` can be performance bombs. Likely this is because these functions have special handling for NaNs. Instead, I switched to using ternary expressions like `a < b ? a : b`, which had a large performance impact. This particularly impacts the `aabb::hit()` function. Update book for latest AABB changes: - Add clarifying comment for corner cases of ray-aabb intersection. - Fix some listings. - Update code listings. - Deprecate section "An Optimized AABB Hit Method" Resolves #927
Finally. Done. |
(Disclaimer: my implementation of this project is in Rust, and my experience with C++ is limited. I am linking to reference docs just to make sure I have verified my own understanding - not to imply that the authors of this book & maintainers of this repo wouldn't know! Feel free to point out any inaccuracies and mistakes - I'm here to learn. Thanks!)
Looking at
dev-major
branch, to be most up to date.Given the function
raytracing.github.io/src/common/aabb.h
Lines 61 to 73 in 62ce2e6
and an example ray with an origin of
[0.0, 0.0, -1.0]
and direction[0.0, 0.0, 1.0]
- or anything similar for that matter, where any component of the direction vector is zero - you will end up getting aNaN
from the division byr.direction()[a]
.On a quick look at the reference,
fmin(NaN, other)
andfmax(NaN, other)
calls will returnother
, unless both operands areNaN
, in which case aNaN
is returned. This will mean thatt0
andt1
will get set toNaN
. Additionally,ray_t.min
andray_t.max
will get set to what they already are, and the comparison ofray_t.max <= ray_t.min
will be falsy so the loop continues to the next iteration.This could potentially lead to unintended results, with false positives on whether the AABB can be hit with the ray (todo: are false negatives possible?)
Sidenote: also pondering whether this could potentially be one explaining factor for the open question of why one implementation is 10x faster compared to the other one - or not, this is just a random guess.
Note that even the other implementation below it is susceptible:
raytracing.github.io/src/common/aabb.h
Lines 78 to 88 in 62ce2e6
In this function
invD
will beNaN
due to the division by zero. The later multiplications will maket0
andt1
set toNaN
. TheinvD < 0
comparison will always be falsy with a NaN so the values won't get swapped. Thefmin
andfmax
function calls will returnother
, the resultingray_t.max <= ray_t.min
call will always be falsy so the loop continues.There's also the version that currently exists in the published book:
This should be identical to the one above.
The text was updated successfully, but these errors were encountered: