講義の課題でレイトレーサーを書いています。せっかくなので開発日記をつけることにしました。
リポジトリは学期期間中に公開してはいけないということなので 8 月頃に出すつもりです。

2017.04.27 [N/A]

名前 (sparkler) を考えてリポジトリを作った。

2017.05.02 [N/A]

.obj のパーサーを書いた。
v, vn, f だけ実装した。
Makefile の書き方を調べていて GNU make のパターンルールを初めて明示的に知った。
C++ で書いている。

2017.05.05 [19520 ms]

動いた。
(下図) 初めて出たもの
first output

カラフルなのは法線マップを RGB で表示しているため。
レイトレーサーは絵を出すだけなら簡単。カメラ調整がやや難しかった。
実装直後に走らせて真っ暗な画像しか出なかったときは悲しくなった (カメラ位置・方向の問題)。
(下図) 動いていそうだったので lambertian と点光源でシェーディングしたらバグっていた。

first output

典型的な頂点法線補完のバグだった。
(下図) 修正後

output

(下図) 角度調整後

output

今後このシーンをパフォーマンスの基準に使う。
今日の結果は 19.52s! (time コマンドで計測) 高速化し甲斐があるとも言えるがびっくりするくらい遅い。
ちなみに以降の結果は n 回の平均というわけではなく、適当に数回レンダリングしてみてだいたい平均っぽいな〜という値を取っている。

2017.05.06 [450 ms]

.obj の構文を拡張する形でカメラの情報をジオメトリ定義ファイルに持たせた。
Bounding Volume Hierarchy (BVH) を実装した。[11000 ms]
プロファイラをかけたところコピーコンストラクタが馬鹿にならないコストになっていた。
(下図) intersectTriangle に Face と Ray を値渡ししている。

profiler

早すぎる最適化は悪と言っても、const 参照渡しは最初からやってもいいと思った。
初めてプロファイラをまともに使ったけどとても便利だった。
[6000 ms] コピーコスト削減
[3500 ms] -O3
[540 ms] 分割アルゴリズム改善 (今までは一番長い軸を半分に切っていたが、いろんな切り方を試して一番左右均等に切れるところで切るようにした)
[450 ms] インライン化

2017.05.07 [450 ms]

光源の設定を .obj ファイルに書けるようにした。

2017.05.09 [280 ms]

コマンドライン出力をキレイにした。プロファイルっぽいものも内部で取るようにした。

before

before

after

after

[380 ms] もっと const 参照
[320 ms] Surface Area Heuristic (SAH) 実装
[280 ms] 分割をやめる要素数をチューニング

2017.05.11 [81 ms]

頂点法線が設定されていない場合の面の法線の方向が逆だった、とかいう小さなバグを潰しつつ、

[160 ms] intersectBox (BVH の各ノードボックスとレイの衝突判定) を手でループアンローリング(自作 3D ベクタ型の operator[] の実装が頭悪くて生のアクセスのほうが速かった)
[100 ms] BVH の traverse を再帰からループにした(アセンブリ見てたらスタック退避コストが大きそうだったので)
[81 ms] 不要なメモリ確保を抑制

teapot のシーンで 1Mrps (Mega rays per second) をようやく超えられたのでうれしい。

intersectBox 内を SIMD 化してみたが、[91 ms] と遅くなった。メモリアクセスコストに負けたか。

rand() をシーケンシャルに配列に書き込むコード (下囲) を -O3 でビルドすると手元の環境では 128 M loops per sec ほどになるので、きっと 100M のオーダーは無理なんだろうと思う。10Mrps / thread くらいはいけるのかもしれない。

#include <iostream>
#include <vector>

#define MS(x) std::chrono::duration_cast<std::chrono::milliseconds>(x).count()
typedef std::chrono::high_resolution_clock Clock;

int main() {
  const int N = 100000000;
  std::vector<int> xs;
  xs.resize(N);
  auto t1 = Clock::now();
  for (int i = 0; i < N; i++) {
    xs[i] = rand();
  }
  auto t2 = Clock::now();
  std::cout << (double)N / MS(t2 - t1) * 1000 / 1000 / 1000 << " (M loops per sec)" << std::endl;
  // => 127 Mlps
  return 0;
}

comments powered by Disqus