Extend optix-toolkit selfIntersectionAvoidance to curve and sphere

selfIntersectionAvoidance works perfect on triangle, so I’d like to also use it on curve and sphere.

For sphere, I am using a minor adjusted objPos, while objErr is derived from center and radius. Is that correct?

    float c0 = 5.9604644775390625E-8f;
    float c1 = 1.788139769587360206060111522674560546875E-7f;
    float c2 = 1.19209317972490680404007434844970703125E-7f;

    float4 q;
    // sphere center (q.x, q.y, q.z), sphere radius q.w
    optixGetSphereData( gas, primIdx, sbtGASIndex, 0.0f, &q );
    float3& sphere_center = *(float3*)&q;
    objPos   = optixTransformPointFromWorldToObjectSpace(P);
    objNorm  = normalize( ( objPos - sphere_center ) / q.w );

    objPos = sphere_center + objNorm * q.w;

    float3 objErr = FMA( float3( c0 ), abs( sphere_center ), float3( c1 * q.w ) );
    objOffset = dot( objErr, abs( objNorm ) );

    SelfIntersectionAvoidance::transformSafeSpawnOffset( wldPos, wldNorm, wldOffset, objPos, objNorm, objOffset );

For curve, I found use curve center pos at u=optixGetCurveParameter() actually works in most case.

    auto curveAttr = CurveAttributes( optixGetPrimitiveType(), primIdx );
    objPos = curveAttr.center;
    objNorm = curveAttr.normal;

    float3 objErr = FMA( float3( c0 ), abs( curveAttr.center ), float3( c1 * curveAttr.radius ) );
    objOffset = dot( objErr, abs( objNorm ) );
    
    SelfIntersectionAvoidance::transformSafeSpawnOffset( wldPos, wldNorm, wldOffset, objPos, objNorm, objOffset );

Here is the confusing part, do we use objPos at hit surface or center for sphere / curve?

Hi @iaomw,

First are you actually having self-intersection problems? If so I’d like to find out why. The OptiX built-in curves and sphere primitives are generally “backface culled” : https://raytracing-docs.nvidia.com/optix9/guide/index.html#curves#back-face-culling

For curves and spheres, “backface culling” is the idea is that a ray should register a hit when entering a curve or sphere, but not register a hit when exiting those primitives. So when a shadow ray starts slightly underneath the surface due to floating point rounding issues, it will generally escape the primitive without accidentally intersecting. For the most part, this seems to prevent most self-intersection problems, and as a result you should not need to adapt or even use the Self Intersection Avoidance code. I am under the impression that this has been working as intended for all the commercial renderers that have incorporated OptiX curve and sphere prims.

We don’t guarantee that rays exiting curves will miss the curve. There are legitimate cases where the ray exits the curve and then re-enters the curve later, and the expected result is a hit:

As the programming guide notes, there are some minor caveats. There might be minor differences between the OptiX curve types, i.e., Phantom vs Rocaps, and there might be some interactions with the different ray flags and build flags that could cause unintended internal hits. It’s also possible to hit internal endcaps in between curve segments. If you’re experiencing any of these, please let me know.

With the sphere primitive, because spheres are convex, the backface culling means that a ray originating inside the sphere should always escape.

One way to help prevent self-intersections with curves and spheres is to avoid casting rays inside the primitive. You can do this by computing the geometric surface normal nearest to your ray origin, and then only cast your secondary ray if the dot product of your ray direction and the surface normal is positive, i.e., the ray is pointing away from the surface.

Let me know if you still need self-intersection avoidance on top of backface culling, or if you’re seeing unexpected hits not explained by the programming guide.

To answer the question you asked more directly, the selfIntersectionAvoidance function we offer only applies to triangles, and does not apply to spheres or curves. That code and the associated constants need to be derived from floating point precision error that might accumulate in the intersector, and so it very much depends on the primitive type and does not transfer to other primitives. If you need self intersection avoidance for curves and spheres, one of us would need to do the requisite precision analysis and derive some new code.


David.

Thanks, I am more than happy to realize the backface culling feature you mentioned.

There is another case I want confirm beside the inside point. Is that possible to get P on another side of the curve along the ray, where P = ray_orign + ray_dir * dist.

Here is another question on parameteru=optixGetCurveParameter()

Is u from hit surface point or nearest center along infinite thin curve relative to ray?

Yes it is possible to have the diagram you show, with Error P being on the other side of the curve, when computing P = O + tD, where t is the hit t. It is somewhat unusual for that to happen. It depends on the magnitudes of P, O, D, and t, and it depends on your curve radius and control points as well. Note that this scenario is guaranteed to happen whenever your radius is smaller than 1 ULP, and your Real P’s mantissa rounds toward the curve center. This might happen if your ray hit the small end of a curve segment that tapers to zero radius. Hits very near the zero radius end can easily have rounding error that is larger than the radius. There’s no real alternative when using floating point, and this is known/expected/accepted behavior when working with curves. This won’t happen the vast majority of the time, when using “normal” cameras and curves, so it typically isn’t a problem in practice.

For the optixGetCurveParameter() question, the u value corresponds to the hit or surface u, and not to the closest point of approach of the ray to the curve’s center.


David.

Hi, after re-thinking, there is another case to consider.

On curves such as hair or curved glass, we are not always doing reflection, sometimes we want transmission. Which requires the next ray origin must below the curve surface or on another side (error but ok) along ray.
The naive P = o + r *d cannot guarantee this condition.

Well, in practice how to make sure my transmission ray origin is good without introduce too much error?
Can we simply use the curve center interpolated from parameter u?

Hey this is a good question. The very short answer is yes, you may be able to use the curve center as your transmission ray origin and relying on backface culling. One downside of that strategy is that it lacks the ability to estimate the ray’s travel distance inside the curve, which might matter depending on your shading. Another option is to evaluate the curve at u, and try to move the hit point slightly inside the curve (use 0.9 radius or something) and handle the surface interaction. That way you’ll have a smaller gap in the ray path.

The long answer depends heavily on your goals and the scale of your scene data and your error tolerance. If your curves are very thin on-screen, for example no more than 1 or 2 pixels wide, then the precision of your transmission ray’s origin point shouldn’t matter very much. If your curves are large on screen, where the curve radius projects to more than a few pixels wide, then I’m sure you’ll want something more accurate.

OptiX curves are primarily designed for high performance hair & fur, the kind you might see in games and films. I say that because it impacts our design choices, and it means that if your use-case is very different from what OptiX offers, then it’s important to review the tradeoffs and consider your options.

If you’re rendering thick glass tubes, and you want to see a detailed refraction through the tube, then you might want to consider other options, such as tessellating the geometry (for example using cluster to make it fast & highly detailed), or writing a curve intersector that can optionally return exit (backface) hits.

We’ve tried quite a few strategies for transmission rays, and they have differences when rendered in extreme close-up (curves more than a few pixels wide), but all they look very similar and acceptable when rendered from a distance (curves are less than a couple of pixels wide).


David.