論文解讀:A Fast Voxel Traversal Algorithm for Ray Tracing

今天看Refusion代碼時發現作者用的Ray Casting方法非常奇妙,查了一下方法來自87年的一篇EG。
原文鏈接: A Fast Voxel Traversal AlgorithmforRay Tracing
源碼鏈接:https://github.com/francisengelmann/fast_voxel_traversal

1、 基本理論

文章主要解決的問題爲:在一個空間體素模型中,已知起點和終點,求光線從起點到終點經過的所有體素。如下圖所示:要求路徑儘可能平滑,跟直線DDA算法的輸出確實非常相似。
在這裏插入圖片描述

作者這篇論文對傳統DDA進行了改進,同時避免了複雜的相交操作。
我們以二維爲例,已知起點 start 和終點 End, 實際就確定了光線的方向,那麼如何確定路徑上依次通過那些體素呢?

問題形式化定義:已知起點:P0P_0和終點:P1P_1 ,那麼光線方向可定義爲:v=P1P0\vec{v} = P_1 - P_0, 給定參數t[0,1]t \in [0,1],那麼, P=P0+tvP = P_0 + t\vec{v} 就可以表示線段P0P1P_0P_1上的所有點。

我們並不是要求這些連續的點,而是要求這條線段經過的所有體素。(爲了方便描述,假設體素邊長爲1,且所有光線P0.x<P1.xP0.y<P1.yP_0.x < P_1.x ,P_0.y < P_1.y ,其他情況可以直接擴展)
在這裏插入圖片描述文章是這樣說的:

Next, we determine the value of tt at which the ray crosses the first vertical voxel boundary andstore it in variable tMaxX. We perform a similar computation in y and store the result in tMaxY. Theminimum of these two values will indicate how much we can travel along the ray and still remain in thecurrent voxel.

什麼意思呢? 就是說我們從起點開始沿着光線方向v\vec{v}, 分別計算水平方向穿過一個體素邊長和豎直方向穿過一個體素邊長的tt值, 這裏分別記爲:tMaxXtMaxXtMaxYtMaxY

tMaxX=1/v.xtMaxY=1/v.ytMaxX = 1/\vec{v}.x , tMaxY = 1/\vec{v}.y, (這裏將向量v\vec{v}tt當成速度和時間其實也非常直觀.)

很顯然,如果 tMaxX<tMaxYtMaxX < tMaxY, 那麼光線先穿過右側的垂直邊界;
反過來,如果 tMaxY<tMaxXtMaxY < tMaxX, 那麼光線先穿過上方的水平邊界;

2、 案例實踐

我們以上圖中間爲例,迭代計算一下這個過程:

已知:起點P0(0,0)P_0(0,0), 終點P1(3,2)P_1(3,2), 那麼v=(3,2)\vec{v} = (3,2)
tMaxX=1/3tMaxX = 1/3,也就是說當時間 t=1/3t = 1/3時, 光線穿過了右側的豎直邊界
tMaxY=1/2tMaxY = 1/2,也就是說當時間 t=1/2t = 1/2時, 光線才穿過了上側的水平邊界

因此,我們先進入右側座標爲(1,0)(1,0)的體素,
那麼,同時我們耗掉了1/31/3的時間,下次再跟豎直邊界相交的tx=1/3+1/3=2/3tx = 1/3 + 1/3 = 2/3

光線慢慢往前移動,下一個時間點是1/21/2, 因爲1/2<2/31/2 < 2/3, 因此先跟上方的體素相交
因此我們進入上方座標爲(1,1)(1,1)的體素,
同時我們記錄光線下次再跟水平邊界相交的時間爲:ty=1/2+1/2=1ty = 1/2 + 1/2 = 1

接下來,我們繼續比較, tx<tytx < ty, 因此下一個經過的體素座標爲(1,2)(1,2)
同時記錄下一次跟豎直邊界相交的時間: tx=tx+1/3=1tx = tx + 1/3 = 1

接下來,我們繼續比較, tx=tytx = ty, 我們約定取上方的體素(2,2)(2,2)
同時記錄下一次跟水平邊界相交的時間: ty=ty+1/2=3/2ty = ty + 1/2 = 3/2

接下來,我們繼續比較, tx<tytx < ty, 我們約定取右的體素(3,2)(3,2)
同時記錄下一次跟水平邊界相交的時間: tx=tx+1/3=4/3tx = tx + 1/3 = 4/3

到達目的,迭代結束。
因此,經過的所有體素依次爲:(0,0),(1,0),(1,1),(2,1),(2,2),(3,2)(0,0), (1,0), (1,1), (2,1), (2,2), (3,2)

源代碼

#include <cfloat>
#include <vector>
#include <iostream>
#include <Eigen/Core>

using namespace std;
double _bin_size = 1;
/**
 * @brief returns all the voxels that are traversed by a ray going from start to end
 * @param start : continous world position where the ray starts
 * @param end   : continous world position where the ray end
 * @return vector of voxel ids hit by the ray in temporal order
 *
 * J. Amanatides, A. Woo. A Fast Voxel Traversal Algorithm for Ray Tracing. Eurographics '87
 */

vector<Eigen::Vector3i> voxel_traversal(Eigen::Vector3d ray_start, Eigen::Vector3d ray_end) {
  vector<Eigen::Vector3i> visited_voxels;
  //起始體素的位置
  Eigen::Vector3i current_voxel(floor(ray_start[0]/_bin_size),
                                floor(ray_start[1]/_bin_size),
                                floor(ray_start[2]/_bin_size));
  //終止體素的位置
  Eigen::Vector3i last_voxel(floor(ray_end[0]/_bin_size),
                             floor(ray_end[1]/_bin_size),
                             floor(ray_end[2]/_bin_size));
  //光線方向向量
  Eigen::Vector3d ray = ray_end-ray_start;

  //光線從起點的前進方向
  double stepX = (ray[0] >= 0) ? 1:-1; 
  double stepY = (ray[1] >= 0) ? 1:-1; 
  double stepZ = (ray[2] >= 0) ? 1:-1; 

  //光線方向的下一個邊界(x,y,z方向)
  double next_voxel_boundary_x = (current_voxel[0]+stepX)*_bin_size;
  double next_voxel_boundary_y = (current_voxel[1]+stepY)*_bin_size;
  double next_voxel_boundary_z = (current_voxel[2]+stepZ)*_bin_size;

  //tMaxX: 從起點開始,第一次跟下一個X方向的邊界相交的時間(如果沒有交點,時間設爲無窮大)
  double tMaxX = (ray[0]!=0) ? (next_voxel_boundary_x - ray_start[0])/ray[0] : DBL_MAX; //
  double tMaxY = (ray[1]!=0) ? (next_voxel_boundary_y - ray_start[1])/ray[1] : DBL_MAX; //
  double tMaxZ = (ray[2]!=0) ? (next_voxel_boundary_z - ray_start[2])/ray[2] : DBL_MAX; //

  // tDeltaX, tDeltaY, tDeltaZ --
  // how far along the ray we must move for the horizontal component to equal the width of a voxel
  // the direction in which we traverse the grid
  // can only be FLT_MAX if we never go in that direction
  double tDeltaX = (ray[0]!=0) ? _bin_size/ray[0]*stepX : DBL_MAX;
  double tDeltaY = (ray[1]!=0) ? _bin_size/ray[1]*stepY : DBL_MAX;
  double tDeltaZ = (ray[2]!=0) ? _bin_size/ray[2]*stepZ : DBL_MAX;

  Eigen::Vector3i diff(0,0,0);
  bool neg_ray=false;
  if (current_voxel[0]!=last_voxel[0] && ray[0]<0) { diff[0]--; neg_ray=true; }
  if (current_voxel[1]!=last_voxel[1] && ray[1]<0) { diff[1]--; neg_ray=true; }
  if (current_voxel[2]!=last_voxel[2] && ray[2]<0) { diff[2]--; neg_ray=true; }
  visited_voxels.push_back(current_voxel);
  if (neg_ray) {
    current_voxel+=diff;
    visited_voxels.push_back(current_voxel);
  }
  double tx = tMaxX, ty = tMaxY, tz = tMaxZ;
  while(last_voxel != current_voxel) {
    if (tx < ty) {
      if (tx < tz) {  current_voxel[0] += stepX; tx += tDeltaX;} 
      else { current_voxel[2] += stepZ; tz += tDeltaZ; }
    } 
    else {
      if (ty < tz) { current_voxel[1] += stepY; ty += tDeltaY;} 
      else { current_voxel[2] += stepZ; tz += tDeltaZ; }
    }
    visited_voxels.push_back(current_voxel);
  }
  return visited_voxels;
}

int main () {
  Eigen::Vector3d ray_start(1,1,0);
  Eigen::Vector3d ray_end(0,0,0);
  cout << "Voxel size: " << _bin_size << endl;
  cout << "Starting position: " << ray_start.transpose() << endl;
  cout << "Ending position: " << ray_end.transpose() << endl;
  cout << "Voxel ID's from start to end:" << endl;
  vector<Eigen::Vector3i> ids = voxel_traversal(ray_start,ray_end);

  for (int i = 0; i < ids.size(); i++) {
    cout << "> " << ids[i].transpose() << endl;
  }
  cout << "Total number of traversed voxels: " << ids.size() << endl;
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章