kd-treeの構築と近傍要素の抽出

2009-10-10

kd-tree

フォトンマップを作った後、実際に描画するときに放射輝度を求めるために視点からの交点近辺のフォトンを取り出す必要がある。 この処理を高速に行うためにはkd-treeを使うといい、という話。

でもいきなり3D用のkd-treeのルーチンを書いて動かせる自信がない…なので2Dのテストプログラムを作る。

kd-treeの構築

  • 木をバランスさせるため、フォトンを記録し終わってから構築する
  • 分割する軸を選ぶ(x,y,zで一番長い軸にしてみた)
  • 分割軸に沿ってソート
  • 左右でバランスするように、左右の要素数を決める(葉が左詰になって欲しいので、あれこれ計算)
// 木を分割する中央のインデクスを求める
int select_median_index(int n) {
// 左詰にするため、あれこれ計算
int h = log2(n);
int s = 1 << h;
int d = n - s;
int s2 = s / 2;
if (s2 > 0 && d >= s2) d = s2-1;
return s2 + d;
}
  • 0オリジンの場合、ノードiの子供はi2+1とi2+2
  • 再帰的に繰り返す
  • 分割するたびにソートしなおすので、クイックソートがO(n logn)として、O(n log^2(n))?

近傍要素の探索

  • ツリーの根から始めて、含まれるほうの枝は必ず見て、含まれないほうの枝は最大距離以内だったら見る
  • 今までに見つかった近傍要素中の最大距離を更新していく
// 「フォトンマッピング」本6.4 最近傍フォトンの効率的発見、locate_photons()参照
float traverse(PhotonAndDistance[] buf, vec pos, int index, float d2) {
if (index < photons.length) {
if (index * 2 + 2 < photons.length) { // 子供がいる
float e = axis_distance(nodes[index].divaxis, pos, photons[index].pos);
if (e < 0) {
d2 = traverse(buf, pos, index * 2 + 1, d2); // 左の枝を見る
if (e * e < d2) {
d2 = traverse(buf, pos, index * 2 + 2, d2); // 右の枝を見る
}
} else {
d2 = traverse(buf, pos, index * 2 + 2, d2); // 右の枝を見る
if (e * e < d2) {
d2 = traverse(buf, pos, index * 2 + 1, d2); // 左の枝を見る
}
}
}

float e2 = photons[index].pos.sub(pos).sqlength();
if (e2 < d2) {
// フォトンを挿入
int n = buf.length;
int l, r;
for (l=0, r=n; l<r; ) {
int m = (l + r) / 2;
if (e2 < buf[m].distance2) {
r = m;
} else {
l = m + 1;
}
}
// 後ろにずらす
PhotonAndDistance temp = buf[buf.length-1];
for (int i=buf.length; --i>l; ) buf[i] = buf[i-1];
buf[l] = temp;
buf[l].photon = photons[index];
buf[l].distance2 = e2;
// 最大距離更新
d2 = buf[buf.length-1].distance2;
}
}
return d2;
}
  • バランスしてれば O(log(n))

雑多

  • フォトンマップの場合、空間にある程度ばらけて分布するのではなく、壁などの表面に偏って分布するだろうからkd-treeがうまく機能するか不安だったけど、できてるっぽい
  • 実際にフォトンマッピングで使う場合は、球じゃなくて楕円体で絞り込んだり、法線があまりにも違ってたらはじくとかする必要があるかもしれない
  • Proce55ing(Java)はどうも…
    • ソートさせるときに軸によって条件を外部から与えたい、けど関数ポインタがない、のでファンクタを渡すようにする
      • いちいちベースクラスを作って継承してオブジェクト生成して…とかメンドイ
    • KDTreeクラスがPhotonクラスに依存、分離させたい

リンク