2021年12月19日日曜日

SDFについて(その2:xyzdist の利用)

 続きです。

この記事は Houdini Apprentice Advent Calendar 2021 8日目の記事になります。

その1では関数として表現できるSDFを取り扱ってきました。

それでは、関数では表しきれない複雑なポリゴンで構成されたメッシュからSDFを作るにはどうしたら良いでしょうか?


シーンファイルは以下にあります。Houdini Apprentice 19.0.475 にて動作確認しています。

Houdiniの機能でVDBを構築する


Houdiniには既に方法が存在しており、VDB from Polygons というノードがあります。

以下でTest Geometry:Pig Head ノードを使って見ていきましょう。


a1. PigHead に VDB from Polygons を繋げます。Voxel Size は適切に細かくし、Distance VDBの名前は今までの名前に倣って distance とでもしておきます。


a2. 出来ました。


特に難しい事はありません。

またその1でも言いましたがここでも改めて言及しておきますと、Houdini に慣れている人の中には SDF = VDB と思っている人も居るかもしれませんが、VDB はあくまでも SDF を格納するための手段の一つでしかないことを覚えておいてください。


さて、VDBでSDFを構築するのはノードを使えば一発ですが、この記事ではSDFへの理解を深めることを目的としていますので、自分の手で構築していって見ましょう。


xyzdist は SDF 計算に使える


VEX には xyzdist という関数があります。詳しくは xyzdistの関数リファレンス を見て頂きたいのですが、説明に「ポイントからジオメトリまでの距離を調べます。」とあるように、実はこの関数を使うことでポリゴンメッシュを対象とした Signed Distance Function を実装出来ます。

早速試してみましょう。


b1. 図のように組みます。左のルートは格納先のボリュームを確保するルートで、右のルートはVolumeWrangleから参照するジオメトリの構築です。Point Normalを設定していることに注意です(理由は後述)


b2. 初めは良く分からないと思いますのでひとまず Volume Wrangle で以下のように実装してみます。


b3. あれ!?失敗しました…(ノード名からバレてたかもしれませんが)


さて、上記の Volume Wrangle でうまく行かなかったのは何故でしょうか?

Houdini内部におけるVolumeの可視化実装を直接把握してる訳ではないですが、まずSDFというのは「符号付き距離場」の事だったことを思い出してみてください。そのことから思いつく部分として、上記の Volume Wrangle では正の距離しか設定していないことが挙げられます。

よって以下のように Volume Wrangle を変更してみます。


b4. 内部判定(後述)を行って負の距離も設定してみます。primuvを使うことで xyzdist にて求めた最近傍の点におけるアトリビュート値を補間して求めていることに注意です(ここでは位置と法線を求めています)




b5. うまく行きました。


b6. ちなみに Point Normal を設定しないとこんな風に壊れます(理由は後述)。


内外判定について


さて、上記の Wrangle 内で行っている内外判定の説明をしようと思います。

まず前提として、ジオメトリには穴が開いていないことが前提です。ここで言う「穴」というのは、トーラス(ドーナツのような形状)が持つようなトポロジ的に正当な穴ではなく、ポリゴンが欠けたような穴のことです。実際に VDB from Polygons でもポリゴンが欠けて穴が開いていると正しく処理されません。


c1. Booleanで穴を開けてみました。


c2. VDB from Polygons も失敗します。


このような「ポリゴンの欠けによる穴が開いていない」という前提条件を設定することで以下のような方法で内外判定が行えます。



上図のように、0と1の二点で考えます。0の点はジオメトリの外部にあるため、最近傍となるジオメトリ表面上の点への矢印(黄色:nearest_P - @P)は、その点におけるジオメトリの法線(緑色:nearest_N)と向き合う形になります。逆に1の点はジオメトリの内部にあり、最近傍のジオメトリ表面上の点への矢印(黄色)はその点におけるジオメトリの法線(緑色)と同じような方向を取ります。

この判定には内積を使えば良い訳で、黄色の矢印のベクトルと緑色の矢印のベクトルの内積の結果が負なら外部(つまり正の距離)正なら内部(つまり負の距離)にあると判定します。

b4. の Wrangle 内で記述した以下の部分はこのような内外判定を行っているという事です。

d = (0 < dot(nearest_N, nearest_P - @P)) ? -d : d;

三項演算子を使って一行で書いています。内積の正負と距離の正負が逆なので混乱しないように注意してみてください。


また、b1. の説明にPoint Normalを設定していることに注意ですと書きました。Vertex Normal では失敗します。何故かを見ていきましょう。


d1. 点0から二つ重なって存在する直方体への最近傍点を求め、その法線(緑の線)を見てみるとVertex Normalだとハードエッジにおいては複数の法線が存在し、どの法線が取得されるかは xyzdist の処理依存になります。右上の点では手前向きの法線、左下の点では下向きの法線が偶然取れました。そしてこのケースでは内積結果が正、つまり内部だと誤判定されてしまいました。


d2. Point Normalを設定するとハードエッジの複数の法線が無くなって平均的な向きを持つ1本の法線になり、正しく外部だと判定してくれました。


d3. 実際に Volume にしてみても、Vertex Normal だとこのようにSDF構築に失敗してしまいます。


d4. Point Normal だとうまく行きました。



続きます。


0 件のコメント:

コメントを投稿