I'm trying to upgrade my application from single ray intersection to stream intersection.
What I don't quite understand is how it's possible that the gather
and scatter
functions shown in the tutorials are even working
The example defines a custom extended ray struct Ray2
struct Ray2
{
Ray ray;
// ray extensions
float transparency; //!< accumulated transparency value
// we remember up to 16 hits to ignore duplicate hits
unsigned int firstHit, lastHit;
unsigned int hit_geomIDs[HIT_LIST_LENGTH];
unsigned int hit_primIDs[HIT_LIST_LENGTH];
};
then it defines an array of these Ray2
structs:
Ray2 primary_stream[TILE_SIZE_X*TILE_SIZE_Y];
this array is set as the userRayExt before calling the intersection method:
primary_context.userRayExt = &primary_stream;
rtcIntersect1M(data.g_scene,&primary_context.context,(RTCRayHit*)&primary_stream,N,sizeof(Ray2));
now, for each ray bundle that embree intersects with geometry, the filter callback is invoked:
/* intersection filter function for streams of general packets */
void intersectionFilterN(const RTCFilterFunctionNArguments* args)
{
int* valid = args->valid;
const IntersectContext* context = (const IntersectContext*) args->context;
struct RTCRayHitN* rayN = (struct RTCRayHitN*)args->ray;
//struct RTCHitN* hitN = args->hit;
const unsigned int N = args->N;
/* avoid crashing when debug visualizations are used */
if (context == nullptr) return;
/* iterate over all rays in ray packet */
for (unsigned int ui=0; ui<N; ui+=1)
{
/* calculate loop and execution mask */
unsigned int vi = ui+0;
if (vi>=N) continue;
/* ignore inactive rays */
if (valid[vi] != -1) continue;
/* read ray/hit from ray structure */
RTCRayHit rtc_ray = rtcGetRayHitFromRayHitN(rayN,N,ui);
Ray* ray = (Ray*)&rtc_ray;
/* calculate transparency */
Vec3fa h = ray->org + ray->dir * ray->tfar;
float T = transparencyFunction(h);
/* ignore hit if completely transparent */
if (T >= 1.0f)
valid[vi] = 0;
/* otherwise accept hit and remember transparency */
else
{
/* decode ray IDs */
const unsigned int pid = ray->id / 1;
const unsigned int rid = ray->id % 1;
Ray2* ray2 = (Ray2*) context->userRayExt;
assert(ray2);
scatter(ray2->transparency,sizeof(Ray2),pid,rid,T);
}
}
}
the last line of this method is what I don't understand
scatter(ray2->transparency,sizeof(Ray2),pid,rid,T);
I understand what it is SUPPOSED to do. It should update the transparency property of the Ray2
that corresponds to the traced ray with T. But I don't get why/how this works, since the implementation of scatter
looks like this:
inline void scatter(float& ptr, const unsigned int stride, const unsigned int pid, const unsigned int rid, float v) {
((float*)(((char*)&ptr) + pid*stride))[rid] = v;
}
I will reformulate this function a bit to better ask my question (but it should be completely equivalent if I'm not mistaken):
inline void scatter(float& ptr, const unsigned int stride, const unsigned int pid, const unsigned int rid, float v) {
float* uptr = ((float*)(((char*)&ptr) + pid*stride));
uptr[rid] = v;
}
So, the first line still makes sense for me. A pointer to the transparency field of the first Ray2 struct is constructed and then incremented by tid * sizeof(Ray2)
- this makes sense as it will land on another transparency
field, since it is incremented by a multiple of sizeof(Ray2)
but then the next line
uptr[rid] = v;
I don't get at all. uptr
is a float pointer, pointing to a transparency field. So unless rid
itself is a multiple of sizeof(Ray2)
, this won't point to a transparency field of one of the rays at all.
pid
and rid
are calculated as
const unsigned int pid = ray->id / 1;
const unsigned int rid = ray->id % 1;
which I find weird. Isn't that always the same as
const unsigned int pid = ray->id;
const unsigned int rid = 0;
?
what are pid
and rid
and why are they computed like this?
Having not written this example myself it's hard to guess what the original intention of it was, but I think the clue lies in exactly your observation that for rid and pid calculations, the division/modulo by '1' are meaningless.
So, if rid eventially always ends up as being '0' (because every value mod 1 will be 0 :-/), then uptr[rid] = ...
is equivalent to *uptr = ...
, which is in fact correct since you yourself pointed out that uptr
always points to a valid transparency.
Now as to why the code does this confusing pid/rid thing? If I had to guess from the naming of "Ray2" I would assume that a different version of this sample maybe used two rays and two transparencies in that ray2 struct, and then used the rid/pid thing to always select the right one of the pair.
Still, as to the original question of "why does this work at all" : rid always evaluates to 0, so it does always write right into the transparency value that uptr
points to.