Embree:流模式 - 聚集和分散如何工作以及什么是 pid 和 tid?

Embree: stream mode - how does gather and scatter work and what are pid and tid?

我正在尝试将我的应用程序从单射线相交升级到流相交。

我不太明白教程中显示的 gatherscatter functions 怎么可能起作用

该示例定义了自定义扩展光线结构 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];
};

然后它定义了这些Ray2结构的数组:

Ray2 primary_stream[TILE_SIZE_X*TILE_SIZE_Y];

这个数组在调用交集方法之前设置为userRayExt:

primary_context.userRayExt = &primary_stream;
rtcIntersect1M(data.g_scene,&primary_context.context,(RTCRayHit*)&primary_stream,N,sizeof(Ray2));

现在,对于 embree 与几何相交的每个光线束,都会调用过滤器回调:

/* 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);
    }
  }
}

这个方法的最后一行我没看懂

scatter(ray2->transparency,sizeof(Ray2),pid,rid,T);

我明白它应该做什么。它应该用 T 更新对应于跟踪光线的 Ray2 的透明度 属性。但是我没有得到 why/how 这有效,因为 scatter 的实现看起来像这样:

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;
}

我会重新表述这个函数以便更好地提出我的问题(但如果我没记错的话它应该是完全等价的):

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;
}

所以,第一行对我来说仍然有意义。构造一个指向第一个 Ray2 结构的透明字段的指针,然后递增 tid * sizeof(Ray2) - 这是有道理的,因为它将落在另一个 transparency 字段上,因为它递增 [=28] 的倍数=]

但下一行

uptr[rid] = v;

我一点都不明白。 uptr 是一个浮点指针,指向一个透明字段。因此,除非 rid 本身是 sizeof(Ray2) 的倍数,否则它根本不会指向其中一条光线的透明场。

pidrid 计算为

  const unsigned int pid = ray->id / 1;
  const unsigned int rid = ray->id % 1;

我觉得很奇怪。这不总是和

一样吗
  const unsigned int pid = ray->id;
  const unsigned int rid = 0;

?

什么是 pidrid,为什么要这样计算?

我自己没有写过这个例子,所以很难猜出它的初衷是什么,但我认为线索就在你的观察中,对于 rid 和 pid 的计算, division/modulo by '1' 没有意义。

所以,if rid 最终总是以“0”结束(因为每个值 mod 1 都将是 0 :-/),然后 uptr[rid] = ... 等同于 *uptr = ...,这实际上是正确的,因为您自己指出 uptr 始终指向有效的透明度。

现在,为什么 代码会造成这种令人困惑的 pid/rid 事情?如果我不得不从“Ray2”的命名来猜测,我会假设这个样本的不同版本可能在该 ray2 结构中使用了两条光线和两个透明度,然后使用 rid/pid 东西总是 select 右边的一对。

不过,关于“为什么这行得通”的原始问题:rid 的计算结果始终为 0,因此它总是直接写入 uptr 指向的透明度值。