ClosestHit program not called anymore after upgrading to OptiX 5.1.1

I have been working on a path tracer running with OptiX for several years now in various contexts. Until yesterday I still worked with OptiX 4, but really wanted to try out that sweet denoiser, so I upgraded to 5.1.1. It now happened that the area light sampling somehow broke. Environment maps are still sampled alright, but spherical area lights don’t work anymore. In fact, the associated closestHitProgram is not even called anymore.

This is how I set the programs for the light source material:

__sm_lightMaterial->setClosestHitProgram ( ENUM_RAYTYPE_LIGHT, __sm_lightShader );
__sm_lightMaterial->setAnyHitProgram ( ENUM_RAYTYPE_OCCLUSION, __sm_lightShaderOcc );

The first program is what this is about. A ray of type ENUM_RAYTYPE_LIGHT is traced towards the light source when explicitly sampling for light contribution. This should in turn result in a call of the below closestHit program.

RT_PROGRAM void light_shader() {
    float3 normalizedNormal = optix::normalize(rtTransformNormal(RT_OBJECT_TO_WORLD, shading_normal));

    if (optix::dot(normalizedNormal, ray.direction) > 0) {
        normalizedNormal = -normalizedNormal;
    }

    prd.hitDist = t_hit;

    if (identifier == prd.queryIdentifier) {
        prd.emittance = fmaxf(optix::dot(-ray.direction, normalizedNormal), 0.0f);
        prd.occluded = false;
    } else {
        prd.emittance = 0.0f;
        prd.occluded = true;
    }
}

However, this works up to OptiX 5.0.1 and stops with 5.1.1, where the call is completely missing, all without changing anything apart from the OptiX version. The second program mentioned above, which is simply used when I need to know whether there is any occluder and is used in environment map sampling, works without any issues.

This is the code where the actual ray is traced:

prd.throughput = make_float3 ( 1.0f );
            prd.emittance = 0.0f;
            prd.queryIdentifier = sphereLights[p_lsIndex].getIdentifier();
            prd.hitDist = FLT_MAX;
            prd.occluded = false;
            prd.seed = prd_pt.seed;
            optix::Ray shadowRay = optix::make_Ray ( getAdjustedRayStart ( p_hitpoint, p_normal_geometric, scene_epsilon ), lightDir, shadow_ray_type, scene_epsilon, maxLightDist );
            rtTrace ( top_object, shadowRay, prd );
            ++prd_pt.numCastRays;

            if ( !prd.occluded ) {
                float weight = prob2 > 0.f ? balanceHeuristic ( 1, lightPdf, 1, brdfPdf ) : 1.f;
                float geometryTerm = fminf ( ( prd.emittance * fmaxf ( 0.f, optix::dot ( lightDir, p_normal ) ) / ( prd.hitDist * prd.hitDist ) ), 1.f );
                optix::float3 E = ( lightEmission * geometryTerm * brdfValue * weight ) / lightPdf;
                return ( E * prd.throughput );
            }

Here, geometryTerm becomes 0 because prd.emittance is zero.

I’d be very grateful for any hints on this; if you need more information, just let me know. As of now I still need to test this with a minimal scene like a plane with a light source.

Hi there. Nothing about your program jumps out as obviously wrong, although for a light sampling loop I’m not used to seeing a closest hit program like this containing both light geometry and occluder geometry. Correctness issues aside, wouldn’t it be more efficient to sample the light like this:

  1. determine a sample point on the light analytically or by shooting a ray at a group that only contains th light geometry. For a single quad light, for example, you could uniformly sample the quad and then weight according to incident angle to account for solid angle of the sample.
  2. given the target sample point, shoot a shadow ray at occluding geometry, with only an any-hit program in place – no closest hit. That’s enough to determine occlusion for the sample point
  3. if the sample was unoccluded, shade the point as before

The potential efficiency improvement is in (2) where you bail out of traversal as soon as the ray hits something, not necessarily finding the closest hit.

Anyway, aside from that, if you can provide a minimal reproducer as SDK-sample-style source code (preferred) or an OptiX oac trace, then we can debug from there.

Thanks for your reply. I guess you’re right about separating light and occluder geometry. I’ll give it a shot and see if maybe the issue fixes itself when doing this. If not, I’ll try to create some minimal reproducer and provide it to you.