Single precision issues with custom capsule primitive for volumetric path tracing

Hello, I am attempting to write a path tracer that allows rays to take on a material ID when within an implicitly defined geometry and another background ID when outside of it.

My current implementation is to increment a counter on the ray payload when the ray is entering a primitive (front face hit), and decrement that counter when the ray is exiting (back face hit). For the default sphere intersection shader, this is working quite well. For my own implementation of a capsule, the tracking is not as reliable and I’ve narrowed the issue down to close-proximity hitT on the limits of single precision.

I wanted to ask if you can provide any resources for how this is managed with the sphere intersection shader and if you have any recommendations for minimizing single precision errors.

My capsule:

The default sphere:

Hi @AidenLewis,

Intruiging! Now I want to hear more about your project. ;) You don’t have to tell me, but it might help to understand the scale of your capsules and rays.

There are at least 3 different ways that we’ve handled sphere intersections that increase accuracy, depending on what you need.

The first one is to adjust the ray origin to be closer to the sphere, which is detailed in Ingo Wald’s article “Improving Numerical Precision in Intersection Programs”, in Ray Tracing Gems II. This idea mainly helps when your ray t gets large. The basic observation is that the error accumulated when moving the ray is linear in t, while errors in the sphere intersector are quadratic in t, therefore it’s better to move the ray origin closer to the sphere whenever t might be large.

The second way to potentially increase precision is to do root polishing/refinement on the intersection calculation. This is essentially doing the intersection twice, once with your original data, and then again with modified math that takes advantage of the first estimate in order to improve precision. We have an example of this in the old optixSphere SDK sample from OptiX 6.5 and earlier. I’m attaching the source here: sphere.cu (3.7 KB)

A third way to increase sphere intersection accuracy is to adjust the intersection math depending on the sign of the b term in your discriminant. The complete derivation is in the article by Eric Haines et. al., “Precision Improvements for Ray/Sphere Intersection” from Ray Tracing Gems. Here’s a link to the article.

Also do note that sphere accuracy of the built-in OptiX API sphere primitive might depend on which OptiX version you’re using, and we disabled the back face hit query from the OptiX sphere API in OptiX 9 in order to match the functionality of the new hardware-accelerated spheres on Blackwell GPUs. This is to allow the majority of users who don’t need back face hits to enjoy the increase in performance, while allowing users to keep the existing performance & features, or to trade off performance for even higher accuracy, using a custom intersection program.

I hope that helps! Precision is a tricky and fun topic.


David.

And for precision issues (rendering of large objects), chapter 5 of 3D Engine Design for Virtual Globes is also handy.

Thanks for the detailed and informative responses!

My (path tracing) simulations are on the scale of 1 to 100mm wide boxes (represented as floating points of 1.0 and 100.0 respectively). The step size of rays varies randomly from around 2.0mm to 33.0mm, for every new step in the simulation, an angle and new scattering length is drawn from a random distribution approximating optical physics equations.

Due to the random walk of my rays, occasionally intersections are evaluated where the ray originates only around 0.001 to 0.00001mm away from the boundaries of my capsules. Hence my quadratic equations for intersection tests are likely computing comparisons between floats of 10^5 to 10^7 differences in exponents.

Do squares and square root operations then exacerbate this floating point difference in the quadratic equation approaches?

How close would a ray need to be to the boundary of an axis aligned bounding box to trigger an additional intersection test in OptiX?

I’ve been thinking about this a little. As a vague generalization, it’s always true that when precision is an issue, squares and square roots should be considered carefully. They certainly can affect precision. On the other hand, for very short rays, I would guess those things shouldn’t be compromising your precision. It is possible that your setup is just hitting the limits of fp32 in general, but I maybe don’t fully understand your plots.

One thing I did in a capsule intersector of my own to analyze precision is to build a complete path through the intersector using double precision, and use the exact same set of intermediate calculations. I added a pixel-clicking debug mode that turns on printf for a single ray, based on the pixel I click on, that will print the intermediate results of every calculation in single precision, double precision, and then also print out the relative error of the fp32 calculation as well as count the number of mantissa bits that match when I convert the double precision result to single precision (assuming the exponent matches). This is a heavy hammer, but shines a lot of light onto the precision of every line of code, and allows you to do A/B testing of different setups. In my case, I found that I can’t really blame the squares or square roots per se, the precision simply degrades when the determinant of the calculation (the part inside the square root) approaches zero. For a capsule, this can happen not just near the surface boundary, but other places in space too, far away from the surface.

There might be some scale-specific effects in your case, but as long as you’re not hitting any overflow or underflow conditions, in general it’s usually fair to scale the entire scene and expect the same precision behavior; only the exponents will change. So, here’s a datapoint you can compare to. If I set up a sphere with a radius of 6.4e6 (radius of the earth in meters) that touches the origin at (0,0,0) and place a camera at position (0,2,0) (approximate height of a tall adult human in meters) looking down at the origin, then I don’t see any ray misses. When I start lowering the camera toward the origin, I start seeing misses at around 1.6m, and then they increase dramatically around 0.6m, and anything less than that becomes extremely noisy - equal mix of hits and misses or worse. Things don’t change after that until the camera distance is less than 1e-19 and I start getting underflow in some of the terms. Anyway, this ratio of radius to camera distance where things begin to fail is suspiciously close to 2^23 (~8.3e6), and suggests the primary precision factor is the radius/rayLength ratio. I remember there being a pretty common rule of thumb in Z-buffer raster graphics to avoid having your scene size ever exceed 8 million of the smallest unit you care about, for similar reasons.

So worth noting that the above is basically saying that ray-sphere intersection precision can be thought of (to a first approximation) in terms of a fraction of the sphere’s radius, where you want the ray to originate at least 1/(2^23) * radius away from the sphere surface. Your plots don’t fit into this category at all, which makes me assume something else might be going on, or that I am making incorrect assumptions about what plots mean. It seems like your plots are showing errors that are in the neighborhood of 0.2 * radius, is that accurate to say? The reasons that could happen might be related to accumulated numerical errors along a multi-segment path, or maybe to accidental noise introduced in the scattering step? Another thing to maybe try is translating your ray & sphere with an offset inside your intersector to place the sphere or the ray or the approximate hit point at the origin. One of those might improve precision on top of the 3 methods I first mentioned.

That was a bit of a ramble, I hope you find something valuable in there. ;) Let me know if you want to dive a little deeper into the specifics of your setup.


David.