How to get all the hits using Optix prime?

Dear forum,

I am a new user of Optix and do not have computer graphics background. So please excuse me if the answer to my question is too obvious to you.

I have a 3d, closed (i.e. watertight) obj model composed of many triangles. What I’d like to attain is that given the origin (x,y,z) and direction (u,v,w) of a ray, determine (1) whether that origin (x,y,z) is inside or outside of the obj model, (2) coordinate of the closest intersection, if it exists.

I have already done (2) using the closest hit query in Optix Prime. But to address (1), I need to know all the intersections of my ray to the model so as to implement the Jordan curve thereom, and I am not sure how to do that. I guess using the “any hit query” may be the right direction, but after reading the documentation it is unclear to me how to get the number of intersections between the ray and the obj model. What size of memory should I allocate for the hit buffer per ray? Should I use Optix prime at all, or switch to the generic Optix API?

Any advice is much appreciated.


You do not get multiple intersection results per ray with a single OptiX Prime query.

Just to understand your problem, do you only need to determine if your point is inside or outside the closed surface or do you need to prove that the surface is closed?

Proving that a model is closed would be rather complicated.

First be aware that the triangle intersection routine applied inside OptiX Prime is not watertight!
Means you can actually shoot a ray between two adjacent triangles and miss both, so more rays would be needed to make sure to arrive at a correct boolean result like in or out.

To determine if a point is inside a 3D triangle mesh which is known(!) to be closed, you could simply test if rays shot from your point always hit back faces of triangles. That is, shoot a few rays into random directions with a spherical distribution and get the closest hit result.
A point inside the closed shape would return mostly hits and you could determine if the triangle is hit on the back or front face.
Points outside the closed shape would either miss (closest hit intersection distance t < 0) or when they hit the model they would see the front faces of triangles.

The problematic case is if your point is exactly on a triangle. You would need to decide if that is inside or outside.

Doing this for a single point is going to be rather inefficient because most of the GPU cores would be unused with small queries.

I just read what the Jordan curve theorem is and as Detlef just did… There is no watertight due to the fact that rays hits positions are not computed exactly: geometry is continuous in theory and discrete on computers, resulting in that epsilon value you see when creating a ray. Supposing you know how to solve that or you have Tesla hardware to work with doubles, let’s answer the original question…

If I understand you need to trace a ray and let it pass through surfaces to count all the hits.
If so, the any_hit is the way to go. The difference between an any_hit program and a closest_hit program is that in an any_hit you need to explicitly stop the ray when you want to by calling rtTerminateRay(); otherwise it will continue after the hit and eventually hit other surfaces. In a closest_hit program you can ignore a hit by calling rtIgnoreIntersection();

Also keep in mind that in the ray generation program you can collect the result carried by the payload right after calling rtTrace(…). If you have to count surfaces, add a counter variable to you payload and increment it in the any_hit callback.

If you have to list the surfaces you hit instead of just counting, add a fixed size array to your payload and at every hit fill the next position (you’ll also need a counter to know what position to fill). If you go above the size of the array you can detect it (adding another variable to the payload for example) and handle multiple passes using a for loop in the ray generation program.

Hope it helps!

Most of your answer does not apply to the original question how to do that with OptiX Prime.

Wrong. rtIgnoreIntersection() is also only allowed in the any_hit domain.

Note that your proposed counting algorithm inside the any_hit program will fail for acceleration structure builders which split triangles into multiple BVHs. Sbvh and Trbvh are doing that. Triangles are potentially arriving in the any_hit programs multiple times then.

I was reading the same problem and in the Programming guide, it is clearly mentioned that the any_hit program can be used to count the number of hits by calling rtIgnoreIntersection() everytime, which eventually generate all the hits. I do not understand why this will not work, since the accel structures are used internally for fast traversal - why should should they affect the no. of hits on a ray?? If I understand what you are saying correctly it seems like you are modifying my geometry first and then you are treating it like split geometry and then those multiple splits are encountered though though they are the same hit. Is this is the case then I do not think it is a good way to implement it since by theory, any_hit should not return more than the actual number of hits.

Also, you mentioned it is easy to identify whether a ray hit the front-face or back-face of a triangle - can you please explain this?

There are different ways to build hierarchies around geometry data. The basic idea for bounding volume hierarchies is to build a hierarchy of axis aligned bounding boxes (AABB).
Maybe look at some of the NVIDIA GTC or SIGGRAPH presentations on OptiX.
Here is the newest from last Monday:

In OptiX you actually provide the program which calculates the individual AABB around your arbitrary geometric primitive yourself.
A ray tracer traverses the acceleration structure (AS) hierarchy to quickly arrive at a node which points to the actual geometry and does intersection tests with any geometric primitive in that AS node. It doesn’t need to be a single geometric primitive, there could be multiple, and vice versa the same geometric primitive might appear in multiple AS nodes.
For example think of a very long and thin triangle which lies diagonally in space. The AABB around that would have a big volume and most of that volume it is empty. (Standard issue with hair geometry.) It would be more efficient to split that box into two or more overlapping boxes so that they each contain only a part of that triangle, but both together contain the whole triangle.
Now most of the empty space in the original AABB would not be considered for intersections anymore => faster rendering. Mind this is 3D, halving the edge sizes of a box is an eighth of the volume.
But when hitting that triangle in the overlapping region contained in both AABBs, both would register a potential hit at the same point. Only one of them would reach the closest hit program though. That’s all.

Now some acceleration structures do that, some don’t. E.g. BVH doesn’t split, SBVH and TRBVH do.

This actually happens, for example when you use the any_hit program of the shadow ray to color and attenuate your shadows from transparent materials in a Whitted ray tracer.
For global illumination ray tracers this is normally not a problem because the visibility test of a light done with a shadow ray is a boolean decision no matter if objects between surface and light are transparent or not. The applied light transport algorithm is taking care of colored shadows and caustics.

Identifying the front side of a triangle is normally done with a dot product between the ray’s direction and the geometric normal of the primitive.

If the triangles in your model all have consistent winding, let’s say counter-clockwise in a a right-handed coordinate system, then a triangle front face is described by the triangle vertices order (v0, v1, v2) and the face normal can be calculated at runtime like this:

const float3 geometricNormal = normalize(cross(v1 - v0, v2 - v0));
// ray.direction and geometric normal not in the same hemisphere means looking at a front face:
const bool isFrontFace = (dot(ray.direction, geometricNormal) < 0.0f);

If the model triangles are not wound consistently, you would either need to make it consistent (recommended) or you would need to provide this geometric normal yourself indicating which side you consider to be the front face.

I understand the theory behind why a single piece of geometry primitive has to be split into multiple bounding boxes for faster traversal but what I am saying is that the rtReportIntersection() should be smart enough to handle identical hits and then call any_hit on only one of them. The Bvh implementation should be abstracted from the end user’s application point of view is what I think.

Anyways thanks a lot for clarifying the face calculations. I expected something similar but I actually do not have knowledge of whether the triangles are clockwise or anti-clockwise, although I think that can be computed from the data.

That’s not how it works. The intersection program itself is invoked by the AS traversal and if multiple volumes are tested along the ray, that’s what you got. The intersection program doesn’t know of other intersections along the ray so there is no mechanism available to filter out possibly identical hits.
Any of that would make intersection testing slower and since that is the most often called program, it’s important to make it as efficient as possible for performance reasons.

In the end you as the developer need to decide how you want to implement your algorithm.

I understand what you are saying, but is this definitely correct? I understand that these acceleration methods split triangles, but how that affects any_hit() programs is not noted in the documentation. So we are clear, I mean for OptiX, not Optix Prime as in the OP.

It would also create issues with accumulating opacity for a shadow ray due to semi-transparent objects. If any_hit() is called multiple times for the same primitive, the shadow will be darker than it should be.

I can see that the intersection program may be called multiple times; however, calling the any_hit program multiple times seems to break all but the most simple use-cases for an any-hit program.

By the way, in my testing of very simple scenes, it seems any_hit() is being called the correct number of times. But this is for < 10 hits per ray. I need to scale up to 1,000+ hits/ray (non-rendering application). Will report back.

Yes, multiple any_hits to the same triangle do occur in OptiX when using BVHes with splitting, currently Sbvh and Trbvh. Multiple nodes in the the BVH point to the same triangle. When all such nodes are traversed they will cause the same triangle to be intersected. It will register a hit each time and thus call the any_hit program each time.

Yes, this breaks some use cases, but works with shadows, which is by far the most common use of any_hit programs. If your algorithm can’t tolerate this behavior you can set the splitting factor to 0 for Sbvh, and not use Trbvh. We will add a split factor control to Trbvh when we can.


Okay, thanks for the clarification and list of affected builders/workarounds. I have a couple of follow-up questions:

  • Do your comments still apply if I am providing my own geometry instances?
  • How do I modify the 'splitting parameter' (I assume this is 'alpha' from the SBVH paper)? I cannot find such a thing in the API reference nor in the programming guide.
  • (Surely SBVH without the splitting is just normal BVH; do these builders also have other differences?)

Lastly, if possible, I’d really like to see this information in the docs - besides linking to the papers, I mean; the errors may be small and infrequent, but anything more complex than a fully opaque shadowing object is not guaranteed to produce correct results.