Optix 6.5 - Wrong color assigned when using multiple intersect routines

Hello!
Am new to the Optix SDK 6.5, hope my questions are clear enough to be addressed.

Created a new project using the optixSphere and triangle_mesh.cu routines as a reference.

My scene is composed with spheres and triangle meshes objects.
I apply the material when a potential intersection is found using a struct Material, it contains every color property (different for each object).

rtDeclareVariable(Material, attrib, attribute attrib, );

Triangle meshes work fine even they are colored all differently and correctly, but for spheres I only get the first color, the red color from the first sphere that had the intersection.
I index my spheres and triangle meshes with the primIdx.
RT_PROGRAM void intersect(int primIdx)

For example: given 4 spheres that have index from 0 to 3, 0-index sphere red color, 1-index sphere blue color, etc. All the 4 spheres only get the 0-index color, therefore they all get colored with the same 0-index color.

Can you help me understanding how to properly assign a different color property for a different primIdx?

Kind regard

Hi @isaacvegan, welcome!

Have you read through the optixInstancing sample? It is an example of different colored spheres.

Also apologies if you already read and know this, but if you’re just starting out, we recommend going straight to the newest OptiX SDK version 7.3. In this case, I think material indexing is going to be more explicit and easier to understand as a result.


David.

No worries.
And sorry to ask for the old API, but am through a learning curve, and I really need to get the 6.5 working first, after that I can move to the 7th version.

Yes, I have looked at the optixInstancing sample too, and for my case it would be very simple because am only using one material no textures, just a single ambient color. Following the optixInstancing, my phong.cu has a Ka which uses a different color for every parallelogram intersection. But, for the sphere I always get a single color, it can either be the first or the last one in my sphere vector container.

I suspected on the struct alignment for my sphere and added padding to be 128bytes, my triangle struct has also the same size, no luck with it.

Creating to Materials for each GeometryInstance didn’t help either.

    optix::Material mat = context->createMaterial();
mat->setClosestHitProgram(0, programs["closes_hit_radiance"]);
mat->setAnyHitProgram(1, programs["any_hit_shadow"]);
mat["ambient"]->setFloat(0.f, 0.f, 0.f);

Then I assign to both GeometryInstances that material

    GeometryInstance tGI = context->createGeometryInstance();
t->setGeometry(tGeo);
t->setMaterialCount(1);
t->setMaterial(0, mat);
t["ambient"]->setFloat(0.f, 0.f, 0.f);

GeometryInstance sGI = context->createGeometryInstance();
sphereGI->setGeometry(sGeo);
sphereGI->setMaterialCount(1);
sphereGI->setMaterial(0, mat);
sphereGI["ambient"]->setFloat(0.f, 0.f, 0.f);

Have printed the ambient values when intersecting with the sphere and it logs the correct data, but printing in my phong.cu version the color is always the same regardless the sphere reporting the intersection.
Similar to the optixInstancing, am also introducing a rtTransform in the graph and it actually works as expected, should that be a problem?

mat["ambient"]->setFloat(0.f, 0.f, 0.f);
t["ambient"]->setFloat(0.f, 0.f, 0.f);
sphereGI["ambient"]->setFloat(0.f, 0.f, 0.f);

These code lines mean you’re creating the same variable on different scopes (material and geometry instance).

The old OptiX API will resolve that with specific variable scope lookup ordering explained in this chapter 4.1.5 of the OptiX 6.5 Programming Guide.

For this case you should only get the ambient colors declared at the geometry instance level.
If that doesn’t happen, more information would be required.

For performance reasons, it’s recommended to only declare variables ones in a scope hierarchy. You can remove the ambient variable on the material in that case.

But, for the sphere I always get a single color, it can either be the first or the last one in my sphere vector container.

If you want to have a different color per sphere in an acceleration structure with a vector of spheres, that color would need to be defined per primitive, which means you would need to return the primitive ID or the color as an attribute from the sphere intersection program and use that instead of the “ambient” variable attached to the geometry instance or material, because the latter both would be uniform for all primitives in the geometry below.

To see what happens in your code, the resp. sphere intersection and closest hit programs would need to be provided.

I suspected on the struct alignment for my sphere and added padding to be 128bytes, my triangle struct has also the same size, no luck with it.

That’s not adding any information when not showing the structures and how they are used.
A sphere primitive can be expressed with a float4 for the center position and radius. Means a buffer of floa4 will be able to represent millions of spheres in a single acceleration structure. If each of them should have a different color, you would also need to store another float4 array (faster load than float3!) for the color per primitive.

I really need to get the 6.5 working first, after that I can move to the 7th version.

The only reason to learn the 10 year old OptiX API today would be, if you need to port existing OptiX code to the new OptiX 7 API and don’t know either.
If that is not the case, it’s seriously recommended to start with the more modern OptiX 7 API for new projects.
It has been changed for a reason. It’s always faster, more flexible, and explicit.

That is helpful information. Will give it a try one last time.
I narrowed down the problem, maybe this is enough to get things working.

A collection of spheres report their right index in the intersection routine.

void intersect_sphere(int primIndex)
....
 if (rtPotentialIntersection((root1 + root11) * l)) {
        ambient = sphere.ambient;
        rtPrintf("===================%d\n", primIndex);
        if (rtReportIntersection(0))
        {
            check_second = false;
        }
    }
...

When logging the index in the phong.cu, it ALWAYS prints a primIdx with a fixed value of 5 ( the size of the sphere buffer is 6, i.e. always the index the value for the last sphere). Changing the sphere buffer to contain any arbitrary number of spheres reports same behavior.

RT_PROGRAM void closestHit(int primIndex)
{
    rtPrintf("xxxxxxxxxxxxxxxxx%d\n", primIndex);
    float3 result = ambient;
    payload.radiance = result;
}

Compared to my routines for the triangles buffer intersect program, which is very similar, this one does report the correct primIndex in the phong.cu.

What is it preventing the right index value for the spheres to be passed from the intersect program to the phong.cu?
It seems that the primIndex gets overwritten all the time (tried also declaring a new variable to save the primIndex in the intersection and then re-use it in the phong.cu, but it behaved with the same problem).
Regards

Your closest hit function signature is incorrect.
Only the intersection and bounding box programs get the primitive index as argument!

What you need to do to get the hit primitive index from the intersection program to the hit programs is to declare an integer variable with the same type (int) and identical custom attribute semantic which is written inside the intersection between the pair of rtPotentialIntersection and rtReportIntersection functions, and read inside the closest hit programs.
The attribute variable name actually doesn’t need to match for attributes, type and semantic are the relevant identifiers for OptiX to match them.

Something like this, just with a different type and attribute semantic:

  1. Declare attribute inside the intersection program and write it between the pair of rtPotentialIntersection and rtReportIntersection.

  2. Declare matching variable type and attribute semantic and read inside the closest hit program.

Also note that since OptiX 6.0 the API added so called attribute programs to be able to calculate/interpolate the final vertex attributes for built-in triangle and custom primitives in a unified way.

Note that all of this works differently inside the OptiX 7 API. There are no such variable declarations, all semantic variables have been replaced with explicit function calls, there is no variable scoping, this is replaced with the shader binding table, the intersection attributes are a limited set of 32-bit registers, etc.

Excellent! I think we are now talking about the same.

Yes, I did actually follow those steps already:

  1. Declare a integer variable for the primIndex in the intersection program on my sphere and triangle routines and write between the pair

    rtDeclareVariable(int, sIndex, attribute Index, );
    RT_PROGRAM void intersect(int primIndex)
    {

    if (rtPotentialIntersection(t))
    {
    // Pass attributes
    sIndex = primIndex;
    //rtPrintf("===================%d\n", sIndex); // <========= the values log correct
    ambient= sph.ambient;

    }

  2. Declare the matching variable type and attribute semantic , and read inside the closest hit program
    rtDeclareVariable(int, sIndex, attribute Index, );

    RT_PROGRAM void closestHit()
    {
    rtPrintf(“xxxxxxxxxxxxxxxxx%d\n”, sIndex); // <============= sIndex always logs a fixed value for spheres
    float3 result = ambient;
    payload.radiance = result;
    }

By doing that , am still getting a fixed value for the index int he closest hit program when sending that from a sphere intersection, and good values when sending from the triangle. So, yes something inside the triangle intersection programs do actually properly update the index from the closest hit program, and I guess that is missing in the sphere intersect program.

Compared the intersection routine for the triangle with mine in this link intersect_triangle_branchless, I can see how the values of ray.tmax are used, I added a condition in my sphere routing using ray.tmax and I am getting now 2 different colors, I think the primIdx gets affected using it. the optixInstancing sphere.cu does not use the ray.tmax or tmin, how do i properly use it?

The intersection routine with my buffer additions, is just a copy from the optixInstancing sphere.cu

#include <optix.h>
#include <optix_device.h>
#include "Geometries.h"
#include <optix_world.h>

using namespace optix;

rtBuffer<Sphere> spheres; // a buffer of all spheres

rtDeclareVariable(Ray, ray, rtCurrentRay, );
rtDeclareVariable(float3, ambient, attribute Ambient, );
rtDeclareVariable(int, sIndex, attribute Index, );

template<bool use_robust_method>
static __device__
void intersect_sphere(int primIndex)
{
    Sphere sphere= spheres[primIndex];
    float3 O = ray.origin - sphere.P;
    float  l = 1 / length(ray.direction);
    float3 D = ray.direction * l;
    float radius = sphere.r;

    float b = dot(O, D);
    float c = dot(O, O) - radius * radius;
    float disc = b * b - c;
    if (disc > 0.0f) {
        float sdisc = sqrtf(disc);
        float root1 = (-b - sdisc);

        bool do_refine = false;

        float root11 = 0.0f;

        if (use_robust_method && fabsf(root1) > 10.f * radius) {
            do_refine = true;
        }

        if (do_refine) {
            // refine root1
            float3 O1 = O + root1 * D;
            b = dot(O1, D);
            c = dot(O1, O1) - radius * radius;
            disc = b * b - c;

            if (disc > 0.0f) {
                sdisc = sqrtf(disc);
                root11 = (-b - sdisc);
            }
        }

        bool check_second = true;
        if (rtPotentialIntersection((root1 + root11) * l)) {
            ambient = sphere.ambient;
            sIndex = primIndex;
            //rtPrintf("===================%d\n", sIndex);
            if (rtReportIntersection(0))
            {
                check_second = false;
            }
        }
        if (check_second) {
            float root2 = (-b + sdisc) + (do_refine ? root1 : 0);
            if (rtPotentialIntersection(root2 * l)) 
            {
                ambient = sphere.ambient;
                sIndex = primIndex;
                rtPrintf("%d\n", primIndex);
                rtReportIntersection(0);
            }
        }
    }
}

RT_PROGRAM void intersect(int primIndex)
{
    intersect_sphere<false>(primIndex);
}

RT_PROGRAM void robust_intersect(int primIndex)
{
    intersect_sphere<true>(primIndex);
}

Finally found the problem!
Because am using transform nodes in the graph, the ambient color needs to be converted to world, my spheres are now colored differently as expected, the only problem is that the color looks a bit opaque.
For it to work properly I have to modify both the intersection program and the closest hit.
From the intersection program I have to
ambient = rtTransformVector(RT_OBJECT_TO_WORLD, sph.ambient);

Then in the closes hit

float3 objAmbient = normalize(rtTransformVector(RT_OBJECT_TO_WORLD, ambient));`

Normalizing it helped, but still not quite the exact color.
Once that fixed, is all the rest is good.

Wait, that sounds a bit strange. Are you assigning a color that represents the transform’s position or something like that? Color vectors aren’t typically in world or object space, they’re in color space. If you assigned a random constant value to the ambient color for each instance, then you should not transform the color vector from object to world space.

If you are using the object space position values as colors, then this does make sense. In that case to get the exact color you expect, you might want to consider the mapping from world space to color space, for example the range of possible values in each color channel, whether to deal with negative values, and whether you want to apply a gamma curve.


David.