
Post by ascotti on Jan 10, 2019 23:13:44 GMT
I wanted to test some optimizations based on bounding boxes and found that a very basic scene was already not working. Here's the scenario: basic sphere translated by (1, 0, 0) basic sphere translated by (+1, 0, 0) ray from (0, 0, 5) direction (0, 0, 1) this ray should intersect both spheres exactly at the origin (with a "double intersection" from each sphere) but I was getting only one. After some debugging and quite a few head scratches, I found a bug in my boxray intersection (which is also the cuberay intersection). I had implemented this pseudocode: tmin ← max(xtmin, ytmin, ztmin) tmax ← min(xtmax, ytmax, ztmax) using library functions math.Max and math.Min. It seems ok until you get a situation like above, which creates a 0 by 0 division, resulting in tmin or tmax being NaN. And when NaN is used as an operand, everything becomes NaN too! The fix I found is based on using explicit comparisons, something like: tmin := math.Inf(1) if xtmin > tmin { tmin = xtmin } if ytmin > tmin { tmin = ytmin } if ztmin > tmin { tmin = ztmin } This way, only the invalid value will be skipped but tmin (or tmax) will be still checked against the others. What a strange bug! With bounding working (I hope), the Sphere of Spheres challenge takes 33 seconds to render a 1000x1000 image with 1219 spheres, using 4 threads. The standard code takes 93 seconds to do the same.



Post by Jamis on Jan 11, 2019 2:02:11 GMT
Oh, good catch on that edge case with 0/0. I'm embarrassed that I missed that in the book! Probably, if both numerator and denominator are zero in check_axis, it should return infinity and infinity for tmin/tmax.... Thanks for pointing this out.
And your sphere of spheres looks great!



Post by ascotti on Jan 11, 2019 22:42:34 GMT
The book doesn't have to contain everything though... leave some fun for us too! :D
I'm now down to slightly more than 4 seconds for that image, getting some good results from acceleration structures!


fs
New Member
Posts: 28

Post by fs on Jan 19, 2019 21:53:53 GMT
I'd borrowed a tip from www.cs.utah.edu/~awilliam/box/box.pdf to handle 0.0 denominators, so I found converting zero numerators to small, appropriately signed values prevented the NaNs, so min & max won't trip on it. func check_axis(origin, direction float64) (float64, float64) { // Convert zero numerators to small, appropriately signed value to avoid a // NaN result below if denominator is zero. tmin_numerator := 1  origin if tmin_numerator == 0 { tmin_numerator = math.SmallestNonzeroFloat64 } tmax_numerator := 1  origin if tmax_numerator == 0 { tmax_numerator = math.SmallestNonzeroFloat64 }
// Capture the sign of direction (and handle 0.0). // See http://www.cs.utah.edu/~awilliam/box/box.pdf improved method (end pp2) // We're relying on IEEE fp rules to produce +Inf, Inf if appropriate. div := 1 / direction if div >= 0 { return tmin_numerator * div, tmax_numerator * div } return tmax_numerator * div, tmin_numerator * div // swap }



Post by ascotti on Jan 20, 2019 20:17:39 GMT
Thanks for the paper fs, it's very interesting! Besides handling the negative zero case it seems to also contain an early exit which should happen quite frequently, I'll definitely test that part and see if it helps speed up things a little.

