Embree: режим потока — как работает сбор и рассеивание и что такое pid и tid?

#c #raytracing #embree

Вопрос:

Я пытаюсь обновить свое приложение с пересечения одного луча до пересечения потока.

Чего я не совсем понимаю, так это как возможно, чтобы gather функции scatter и, показанные в учебных пособиях, вообще работали

В примере определяется пользовательская структура расширенного луча 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 = amp;primary_stream;
rtcIntersect1M(data.g_scene,amp;primary_context.context,(RTCRayHit*)amp;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*)amp;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(floatamp; ptr, const unsigned int stride, const unsigned int pid, const unsigned int rid, float v) {
  ((float*)(((char*)amp;ptr)   pid*stride))[rid] = v;
}
 

Я немного переформулирую эту функцию, чтобы лучше задать свой вопрос (но он должен быть полностью эквивалентен, если я не ошибаюсь).:

 inline void scatter(floatamp; ptr, const unsigned int stride, const unsigned int pid, const unsigned int rid, float v) {
  float* uptr = ((float*)(((char*)amp;ptr)   pid*stride));
  uptr[rid] = v;
}
 

Итак, первая строка все еще имеет для меня смысл. Указатель на поле прозрачности первой структуры Ray2 создается, а затем увеличивается на tid * sizeof(Ray2) — это имеет смысл, так как он попадет в другое transparency поле, так как он увеличивается на кратное sizeof(Ray2)

но затем следующая строка

 uptr[rid] = v;
 

Я вообще ничего не понимаю. uptr представляет собой указатель с плавающей точкой, указывающий на поле прозрачности. Таким образом , если rid само по себе не кратно sizeof(Ray2) , это вообще не будет указывать на поле прозрачности одного из лучей.

pid и rid рассчитываются как

   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;
 

?

что такое pid rid и почему они вычисляются таким образом?

Ответ №1:

Не написав сам этот пример, трудно догадаться, каково было его первоначальное намерение, но я думаю, что ключ к разгадке кроется именно в вашем наблюдении, что для расчетов rid и pid деление/по модулю на » 1 » бессмысленно.

Итак, если rid eventially всегда заканчивается как » 0 » (потому что каждое значение mod 1 будет равно 0 :-/), то uptr[rid] = ... это эквивалентно *uptr = ... , что на самом деле правильно, так как вы сами указали, что uptr всегда указывает на допустимую прозрачность.

Теперь о том, почему код делает эту запутанную вещь pid/rid? Если бы мне пришлось угадывать по названию «Ray2», я бы предположил, что в другой версии этого образца, возможно, использовались два луча и две прозрачные пленки в этой структуре ray2, а затем использовался параметр rid/pid, чтобы всегда выбирать правильный из пары.

Тем не менее, что касается первоначального вопроса «почему это вообще работает» : rid всегда принимает значение 0, поэтому он всегда записывает прямо в значение прозрачности, на которое uptr указывает.