Hi @crow, good question. Sounds like a fun & interesting ray tracing application to me.
Does your volume have any co-planar triangles, and do you need to guarantee that you process all the hits along a ray, including potentially multiple hits for coindicent surfaces at the same t value? I could imagine if you’re traversing irregular cells and need to be guaranteed to get both the enter & exit hits, the need to handle coincident surfaces might be on the table. If so, then you’ll want to use an anyhit program, instead of closest-hit, and a sorting process similar to what you described with your CUDA project. Otherwise, you may have the option to use a closest-hit program while re-casting the ray after every hit, in order to avoid the sorting process.
Anyhit will give you all the hits along a single ray, and not necessarily in sorted order. (Note in OptiX a single primitive is allowed to call anyhit more than once by default. There is an OptiX enum to prevent multiple anyhit calls, if that’s something you need.) The usual difficulty with this approach is that you need to have enough memory already allocated to store the results, and it’s preferable to have a simple static indexing scheme for storing the data vs trying to manage anything dynamic. Presumably you’ve already solved those problems in your previous project, but the performance snag is that saving results to memory and sorting is quite expensive, and possibly more expensive than the ray tracing phase.
Another approach is to do what you’ve started trying: use a closest-hit program, and shoot a new ray after every hit. (Note you may be aware, but just in case: make sure to shoot the new ray in your raygen program after the
optixTrace() call returns, and not in your closest hit program, otherwise you’ll have a recursion and use a lot of stack space unnecessarily.) I would think it’s not necessarily slower or less efficient to keep calling
optixTrace(), compared to capturing out-of-order hits and sorting them. Using closest-hit is probably going to overall be generally easier and less code. In fact I might recommend starting with the closest-hit & casting new rays approach and see if perf is acceptable, unless you need to guarantee traversal through every primitive along the ray.
With closest hit, you’ll only be able to get one hit for any given t value, and the hit order for any coindicent surfaces is undefined. On top of that you might have to do something to avoid hitting the same primitive twice, or missing the next primitive when it’s very close, or both. The first thing I would try is to always re-cast the exact same ray, e.g., pass the original ray origin, and do not use the previous hit location as the new origin, instead advance the tmin value for the next ray to be the t value of the current hit plus the smallest possible delta (like maybe interpret the t bits as int, add one, then go back to float). I don’t know if this is guaranteed to work, but I’d expect that it will. Otherwise you could look into using a more general Self Intersection Avoidance solution.
OptiX is a decent choice for this problem when you’re traversing through polygon soup. Just for completeness, I should mention that one question to consider is whether you might want or need to handle sampled or voxelized volume data, because OptiX is not really designed for that, and there are useful volume rendering algorithms for structured grids that perform better without involving a BVH.