2021年12月19日日曜日

SDFについて(その3:応用例)

 続きです。

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

様々な Signed Distance Function

shader-toy などでは SDF はレイマーチを行う時のモデリングの道具として良く用いられています[1]。

またこの界隈には Inigo Quilez さん(以下、iq 先生)という有名な方がおり、彼のBlog記事には Signed Distance Function の関数一覧が乗っています[2]。 このページの "Primitive combinations" というところには SDF 同士を合成する三つの方法, Union, Intersection, Subtraction が紹介されています。これらを説明していきます。

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

準備

二つの Box を下図のように重ねたケースで考えていきます。


そしてあるZ値で切り出した平面におけるSDFの、等値線を利用した可視化によって説明をしていきます。また下図のように、2点(Pt_0, Pt_1)における計算結果を矢印で表示しています。


等値線の可視化は以前 Houdini 夜会で紹介した Volume Slice に対する Volume Wrangle で以下のような実装をして表示しています。sin を使うことで縞々を可視化し、正の距離は外側を表す青で、負の距離は内側を表す赤で描画しています。




左上のボックスに対する2点(Pt_0, Pt_1)における最近傍な位置の算出は以下のようになります。


右下のボックスに対する算出は以下のようになります(2点の位置は変わっていません)。

矢印(根元の部分)と等値線は垂直になっていることにも注目です。

Union

集合で言うと和集合[3]です。二つの SDF に対して min を取ることで計算が行えます。

float sdf = min(sdf1, sdf2);

図で示すと以下のようになります。二つのSDFを和集合のように合成出来ます。各点において二つの計算のうち、距離の近い方を採用するとこうなる、というところに注目して見てみてください(ちなみにこの図に違和感を持った人は鋭いです。後述します。)。


Intersection

集合で言うと共通部分、積集合[4]です。二つの SDF に対して max を取ることで計算が行えます。

float sdf = max(sdf1, sdf2);

図で示すと以下のようになります。各点における二つの計算のうち、距離の遠い方を採用するとこうなるというところに注目して見てみてください。


Subtraction

集合で言うと差集合[5]です。二つのSDFに対して以下のような計算で行えます。

float sdf = max(sdf1, -sdf2);

これは以下のように分解して考えると分かり易いと思います。

まず左上のボックスのSDFを考えます。これはシンプルです。


次に右下のボックスのSDFを、正負を逆にして補集合にします。モデリングの観点で言うと赤い所が詰まっていて青い所が空洞だと思ってください。


この二つの Intersection (共通部分、演算で言うと max) を取ることで以下のような差の結果が得られます。


ここまでで3つの合成方法を説明してきました。HoudiniのVDBにはノードとしてこの三つが既に存在していますので簡単に利用できます(内部実装が同じとは言えないことは後述します)。


Smooth な合成

iq 先生の記事[2]の更に下には、3つの合成(Union, Intersection, Subtraction)をスムースにする方法が提示されています(どれくらい滑らかにするかという部分をパラメータ k で制御できるようになっています)。

これを実装して各々見ていきましょう。VEXで以下のように関数を定義してこれを使います。

実装方法には色々なバリエーションがありますので興味のある方はこちら[6]もご参照ください。

Tips:関数を共有する

ちなみに軽いTipsなんですが、同一シーンファイル内で使いまわしたい関数定義がある場合、自分は一つのWrangleに定義をまとめて


他のWrangleからはそれを参照するような方法を使うことがあります。結構便利です。

それでは実装した結果の可視化を見ていきます。

Smooth Union


Smooth Intersection


Smooth Subtraction


これらを VDB に対して行うノードはぱっと見では無さそうでしたので、将来的に追加されたら嬉しいですね。


応用例:パーツを組み合わせたメッシュを滑らかに

ここまで紹介したテクニックはどのような場合に使えるでしょうか?ここでは一つの例を示してみます。


a1. この豚さんに




a2. こんな角を



a3. 生やして見ました。Booleanで合成しているのですが、形状の不連続からくるブッサシ感が半端ないのでそれを緩和させてみましょう。


Volume を利用

まずは xyzdist を活用した Volume の構築をやってみます。


b0. ネットワーク準備



b1. Volume Wrangle はこう。


b2. 結果。smooth = 0.125 のとき


b3. smooth = 0.3 のとき


b4. smooth = 0.5 のとき


角と頭の接合部分の滑らかさが上がって行っている点に注目です。


Volumeを参照しながらメッシュを変形させていく

Volumeをメッシュに変換しても良いのですが、手塩にかけてモデリングしたメッシュを使いたい事ってありますよね?

なので構築した SDF Volume の内容を参照しながら元のメッシュの頂点位置を調整する方法で Smooth Union の結果を取り込んで見ましょう。


c1. ネットワークはこんな感じ。Point Wrangle で頂点を動かして Delta Mush で滑らかにするという過程を複数回繰り返します。


c2. Point Wrangle と Delta Mush はこんな感じ。表面までの距離とその方向をそれぞれ volumesample 関数と volumegradient 関数で取ってきます。それらを用いて移動することで Smooth Union の形状に近づけていきます。


c3. 適用前と適用後(smooth = 0.3)の画像です。マテリアルの境目で分かりにくいかもしれませんが、滑らかになっていることが陰影で分かりますでしょうか?



c4. 見やすいようにマテリアルを外してみます。


Volumeを参照せず計算だけでメッシュを変形させていく

次に、折角 xyzdist を使って Volume を構築しないで SDF を利用できるので、そのように Volume を利用しない方法での実装もしてみましょう。

ただ Volume の場合はそのSDFの勾配を volumegradient という関数で利用できました(c2.)。計算で行う場合にはこれと同じように勾配の方向を求める必要があります。

勾配を求めるにあたって、考え方としては比較的簡単な中心差分を用いる数値計算法があります。これは x軸 y軸 z軸の3軸方向の前後2点、合計で6点のSDFを調べることで勾配を求める方法なのですが、毎度おなじみ iq 先生の記事[7]の "Tetrahedron technique" の部分では、4点だけで良いという方法が紹介されています。詳しくはそちらをご覧ください。

シーンファイルでは泥臭く配列を活用して実装していますが、本来この実装には構造体での実装が望ましいです。



しかし構造体は外部の VEX ソースファイルを指定するような、ライブラリ実装が必要となりますが、この記事の範囲外となるので割愛させて頂きます。

d1. ネットワークはこんな感じ。Volumeへの参照を行わず、全て xyzdist を活用した計算で行っています。


d2. Point Wrangleはこんな感じ。Tetrahedron techniqueで法線を求めています。


d3. 結果です。ほぼ同じ結果になりました(微妙な差はあります)。



ボリュームだと解像度が足りないが解像度を増やすとメモリが足りなくなる、そんなケースではメモリの代わりに計算量を消費することで同じような結果が得られるので、覚えておくといつか役に立つかもしれません。

豚さん勢ぞろい。


Smooth = 0.25 で計算しており、左から順に

  1. ブッサシ
  2. Volume参照の移動
  3. SDF計算による移動
  4. VDB

となります。

不正確なSDF

最後に SDF の不正確性について触れておきます。

前述の話の中で

「この図に違和感を持った人は鋭いです。後述します。」

という記述をしましたが、その点についてのお話です。

まずこちらの Union の結果。

左下の角の部分の外側のSDFの等値線は滑らかなコーナーを描いていますが、右上の角の部分の内側はSDFの等値線が鋭角な変化になっています。左下のように滑らかなコーナーを描くのが本来は正確です。

また Intersection の結果のこちら。

こちらも角の滑らかさに違いが出来てしまっています。

最後に Subtraction の結果のこちら。

内部はとても正しいのですが、外部の空間の一部になんだか不安になる様な SDF 等値線が現れています。

VDBにも Union, Intersection, Subtraction があります。


VDBの方はどうなっているか見てみましょう。

VDBのUnion


等値線を見るとところどころ不安になるような形をしていますが、一番目立つのは内部で同じ色に染まっている部分が不安を掻き立てます。

VDBのIntersection

だいぶ良い気もしますがところどころ不思議な形になっているところが散見されます。


VDBのSubtraction(Difference)


前述の計算で求めた時に不安になった場所と同じような場所で Houdini さんも不安になっているようです。

ということで Houdini さんも苦労しているみたいです(SideFX さんゴメンナサイ)。

この症状が何か問題を引き起こすかどうかは分かりませんが、SDFという概念を理解し、可視化してみることはデバッグをする際にとても役に立つかと思います。

不正確なSDFについては、iq 先生の記事[8]をご覧ください。

追記:2021-12-19

よくよく考えると、SDFは位置によって数値が変わるため同じ情報をまとめるような最適化が出来ない性質があり、VDBとの相性は良くないことに気付きました。

詳しく調べていないので想像ですが、もしかしたらHoudiniのVDBでSDFを扱う際には境界部分から離れた距離にある空間のボクセルでは一律の情報にしてしまう最適化(つまり仕様)がされているのかもしれません。

終わりに

SDFの概念の説明とHoudiniによる可視化、xyzdist を用いた計算方法とそれを用いたメッシュの変形、そして計算にまつわる問題点などを見てきました。

SDFは shader-toy の界隈ではレイマーチによるレンダリングなどでも活発に用いられていますので Houdini 界隈の人たちにそのような世界がある事をお見せしたいというのも記事執筆の動機の一つでした。

また shader-toy に上がっているこちらの作品[9]とこちらの作品[10]をぜひ見てみてください。SDFによるモデリングの可能性を感じることが出来ると思います。

参考文献







0 件のコメント:

コメントを投稿