首页 > 解决方案 > Embree: stream mode - how does gather and scatter work and what are pid and tid?

问题描述

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 Ray2structs:

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?

标签: c++raytracingembree

解决方案


我自己没有写这个例子,很难猜出它的初衷是什么,但我认为线索恰恰在于你的观察,即对于 rid 和 pid 计算,除以 '1' 的除法/模数是没有意义的。

因此,如果rid 最终总是以“0”结束(因为每个值 mod 1 将是 0 :-/),则uptr[rid] = ...相当于*uptr = ...,这实际上是正确的,因为您自己指出它uptr总是指向有效的透明度。

现在至于为什么代码会做这个令人困惑的 pid/rid 事情?如果我不得不从“Ray2”的命名中猜测,我会假设这个样本的不同版本可能在那个 ray2 结构中使用了两条光线和两个透明度,然后使用 rid/pid 东西来始终选择正确的一个一对。

尽管如此,关于“为什么这完全有效”的原始问题:rid 总是评估为 0,所以它总是直接写入uptr指向的透明度值。


推荐阅读