ヘアーレンダリング3

髪の毛のレンダリングにおいて前回までの円柱+球ではなく、カーブとしてレンダリングしてみた。
カーブのレンダリングの手法はRAY TRACING FOR CURVES PRIMITIVEを実装することで解決した。
また、髪の毛の一本一本をカーブとみなすために、点列間をcatmull-romカーブでつなぎそれを動的にベジェカーブに変換する仕組みを入れた。
以上により髪の毛が形状も陰影もなめらかに変異するようになった。

差し当たり結果画像のみアップしておく。


以上では、どこが変わったかわからないので、一部を拡大した画像を置く。
上が前回まで円柱+球、下が今回のカーブ


速度は前回までの円柱+球での画像が2400*2400で30秒程度だったのに対し、今回の手法では約90秒ほどの速度になった。

ヘアーレンダリング2

前回のシーンの影を半透明にしてレンダリングしてみた。



あと今後できることは以下のようなことが考えられる。

  1. もっと複雑なシェーディングモデルの採用
  2. 半透明シャドウの高速化
  3. ヘアーセグメントのスプライン曲線による補間・細分化
  4. GIへの対応

ヘアーレンダリング

自作レンダラにおいて髪の毛のレンダリングに取り掛かっている。
具体的にどうするべきか、実験中であるが、とりあえずここ(HAIR Model Files - Cem Yuksel)からhairモデルを取得して単純なレイトレースにてレンダリングしてみた。
物体形状としては髪の毛一本一本を円柱と球をつなげたものを使った。

シェーディングモデルにはKajiya-Kayの物*1をそのまま使った。
以下は2400*2400でレンダリングして800*800に縮小したもの。

いまいち立体感に欠けるのは、

  1. 透明度を考慮していない。
  2. 影がレイトレーシングシャドウ
  3. 内部散乱を考慮していない。
  4. サンプル数が足りない。

などが挙げられるだろう。
速度に関しては、レイトレーシング、スポットライト1灯、解像度2400*2400でそれぞれ1分程度である。
今後どうするかだが、透明度を考慮して、シェーディング&影付けすればリアリティが増すものと思われる。



*1:Rendering fur with three dimensional textures

QBVHを実装した


空間構造の一種であるQBVH(Quad-tree Bounding Volume Hierarchy)を実装した。
元の論文はhttp://www.uni-ulm.de/fileadmin/website_uni_ulm/iui.inst.100/institut/Papers/QBVH.pdf
QBVHはレイトレーサの交差判定を高速化する。
以前syoyoさんがイイヨとおっしゃっていたので実装してみた。


この論文で語られるQBVHの要点は以下。
1.4分木構造
木構造においてひとつのノードに4つの子ノードを格納する。
2.ノードのAABB(バウンド)を親が持つ
一つ一つのノードがそのAABBを持つのではなくその親が4ついっぺんにもつ
3.SIMDによる判定
4つの子ノードのAABBの交差判定をSIMD演算を利用していっぺんに行う
4.面情報を格納するノードは作らない
面情報を格納するノードは実際には作らない。その代わり、ノードの子が枝ノードなのか面なのかをインデクッスの一部をフラグとし格納する。

■実装

普通BVHといえば2分木構造を用いるがQBVHでは4分木構造を用いる。これはSIMD(インテルCPUではSSE)ではfloat*4の演算を一度にすることができるため、4分木であることが最適であるからだ。
木構造のあるノードのAABBはそのノード自身が持つのではなく、その親のノードが持つ。ゆえにひとつのノードはその子の4つのAABBを持つ。
枝ノードはSSEでの実装では以下のような構造体で記述できる。

struct SIMDBVHNode{
    __m128 bboxes[2][3];//4 float min-max xyz
    size_t children[4]; //4 children
    int axis_top;       //top axis
    int axis_left;      //left axis
    int axis_right;     //right axis
    int reserved;       //padding 
};

SIMDによる判定はAABBに対して行う。
今回はその先の面の交差には用いない。(論文では実装はしてるようなのだ・・・けど・・・the original triangle layout is left untouched.となってるがはてはて・・・)
SSEでは以下のような関数となる。

inline int test_AABB(
    const __m128 bboxes[2][3],  //4boxes : min-max[2] of xyz[3] of boxes[4]
    const __m128 org[3],        //ray origin
    const __m128 idir[3],       //ray inveresed direction
    const int sign[3],          //ray xyz direction -> +:0,-:1
    __m128 tmin, __m128 tmax    //ray range tmin-tmax 
)
{
    // x coordinate
    tmin = _mm_max_ps(
        tmin,
        _mm_mul_ps(_mm_sub_ps(bboxes[sign[0]][0],org[0]), idir[0])
    );
    tmax = _mm_min_ps(
        tmax,
        _mm_mul_ps(_mm_sub_ps(bboxes[1 - sign[0]][0], org[0]), idir[0])
    );

    // y coordinate
    tmin = _mm_max_ps(
        tmin,
        _mm_mul_ps(_mm_sub_ps(bboxes[sign[1]][1],org[1]), idir[1])
    );
    tmax = _mm_min_ps(
        tmax,
        _mm_mul_ps(_mm_sub_ps(bboxes[1 - sign[1]][1], org[1]), idir[1])
    );

    // z coordinate
    tmin = _mm_max_ps(
        tmin,
        _mm_mul_ps(_mm_sub_ps(bboxes[sign[2]][2],org[2]), idir[2])
    );
    tmax = _mm_min_ps(
        tmax,
        _mm_mul_ps(_mm_sub_ps(bboxes[1 - sign[2]][2], org[2]), idir[2])
    );

    return _mm_movemask_ps(_mm_cmpge_ps(tmax, tmin));//tmin<tmaxとなれば交差
}

これで各枝ノードでの判定は終わり、面との交差判定となる。面と枝ノードはインデックスの一部をフラグとする。
元の論文では以下のようにレイアウトしている。

今回の実装では最上位ビットのみフラグとし、面の数はインデックスに含めないこととした。その代わり、面の最後にNULLを置くレイアウトとした。


以上のように実装した。

■性能
QVBHの性能を以下に示す。交差判定をSSEを使うもの(SIMD)とそうでないもの(SISD)をそれぞれ実装した。またQBVHではfloat*4なのでdouble*2のBBVHも実装してみた。
比較対象として、通常のBVH(ノード自身がAABBを持つ),BIH,KD-Tree,Octreeを用意した。
構築時間

交差判定時間(1000000rays)

消費メモリー


グラフも示す。


QBVHを含め、BVH系構築時間は長めになっている。これは今回の実装が構築するとき空間で振り分けたのではなく、sortで並べることによるものだ。
QBVHでは他の構造と比べ消費メモリーも少なく、肝心の交差判定も非常に速いことがわかる。自分の環境(Core2Duo T8100 2.1GHzh)で約300000rays/sec程度処理できている。
SIMDとSISDではQBVHで約2倍、BBVHで約1.5倍の高速化が実現できていることがわかる。
QBVHではfloat精度なのだが、今回のQBVHではAABBの交差判定のみSIMD化を行った。そのため、最終的な精度は面での交差判定が行うため、こちらがdouble精度の場合、AABBには適切なマージン(最低でもstd::numeric_limits::epsilon()*2)分膨らませてあげれば精度については問題なくなるはずだ。(マージンより狭い範囲に面が集中するようなメッシュだと交差判定が遅くなることはあるがまあ到底考えなくていいだろう)

■結論
QBVHのSIMD化はメッシュの空間構造のみ手を入れればいいため、既存のシステムにマージしやすい。
交差判定スピードは非常に速いため、SSEのようなSIMD命令が使える環境では実装してみる価値はあるはずだ。


■追記
以前実装したときそんなに速くないと吹聴しましたが、参考にした実装(lux render-qbvhaccel.cpp)が間違っていました。
具体的にはAABBの交差判定後、交差するノードをスタックに積む順番が逆になっていました。

■ソース等
以下のURLからソース等の一部をダウンロードできるようにしておきました。参考になるかどうかはわかりませんが。
http://kaze-renderer.googlecode.com/files/acc_test20090925.zip
また全ソースは以下のURLにホスティングしてあります。
http://code.google.com/p/kaze-renderer/

ヒルベルト曲線順

ヒルベルト曲線は空間充填曲線のひとつだ。このヒルベルト曲線でより高次の空間をたどるとキャッシュ効率がよくなるらしい。

さっそく3次元のモデルの面をヒルベルト曲線順で並び替えてみる。
以下はstanford bunnyのそれぞれの面の順番をHSVのH(色相)に対応させて表示させたものだ。
デフォルトの順番は次のようになる。

かなりめちゃくちゃだ。位置が近ければ近い色に表示されるのが望ましい。
コレを3次元空間をヒルベルト曲線順でたどってみると次のようになる。

だいぶ位置が近い順になった。ただ境目(順番が飛ぶ場所)が結構多い。そこでヒルベルト曲線の階層を1階層にとどめると次のようになる。

なんだか僕にはこちらの方が自然に思える・・・
ヒルベルト曲線順がどういう効果をもたらすか2次元画像をヒルベルト曲線順で色付けして確認してみよう。

どうやらヒルベルト曲線順にすると

  • 大きな境目は少なくなる
  • 境目の数は多くなる

これは大きく順番が飛ぶ場所を無くす変わりに順番が飛ぶ場所を増やして分散させるといった効果があるようだ。

でキャッシュ効率なんだけど、どうもいまひとつ効果の違いを見出す実験が発見できなかった。自作レイトレーサとOpenGLレンダラでやってみたんだけれども。
そんなわけでここには結果は載せない。
ただ、キャッシュ効率なんてのはプラットフォームしだいで全然変わってくるものだから、プラットフォームが変わるたびに実験して計測してみるといいかもしれない。

以下にソースを示す。

続きを読む