2021年12月19日日曜日

SDFについて(その1:基礎および可視化方法について)

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

SDFについて、Houdiniを用いて説明していきます。

想定している対象読者は…

  • Houdiniはそこそこ触ったことがあり
  • VEXに興味があって習得したいが簡単な題材に困っていて
  • Houdiniの裏側でどんな概念が存在するか興味のある
そんな方を想定しています。SDF Apprentice な方向けの記事です。


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

SDFとは?

そもそもSDFと言ったとき、皆さんは何を思い浮かべるのでしょうか。

Twitter で投票を募ってみた所、以下のような結果になりました。


この結果には色々と言いたいことはあるのですが、この記事で扱いたいSDFは

  • Signed Distance Field(符号付き距離場)
  • Signed Distance Function(符号付き距離関数)
です。

Google画像検索してみる


Google で signed distance field で検索すると以下のようになります。画像を眺めると理解しやすいと思いますのでおススメです。



Signed Distance Functionとは

自分は初め、Signed Distance FunctionSigned Distance Field の誤植みたいなものかと思っていたのですが、Wikipedia の記事 [1]が存在しました。

詳しい内容はそちらに説明を投げますが、簡単に言うとある集合の境界からの距離を定義し、

  • 境界の外部では距離は正の符号
  • 境界の内部では距離は負の符号

となる関数のことです(説明を読むと正負が逆の流儀もあるようです)。

ちなみに shader-toy になじみのある人にとっては Signed Distance Function の方が通じるのでしょうか?(得票は0でしたが)


Signed Distance Fieldとは


前述の Signed Distance Function にて定義される正負の符号を持つ距離の値が空間にスカラー場として広がっているような、そんなイメージで捉えると良いと思います。

Distance Field = 距離という数値が空間に広がる場

文章が続くと分かりにくいので Houdini で可視化しながら説明をしてみます。

例:原点から広がる Distance Field


例として原点 (0, 0, 0) から広がる Distance Field を考えてみます。

a1. 原点(下図の点0)の周りに広がる距離場を考え、例えば点1での値を考える



a2. 点1での距離場は原点0からの距離となる(当たり前)

a3. しつこくもう一つの点2を考える


a4. 同様に点2の距離場は原点0からの距離となる(当たり前その2)


このように空間上のどんな任意の点を取ってもある対象(ここでは原点)からの距離であるスカラー値が設定されているような場を、距離場 (Distance Field) と呼ぶことにしたということです (signed については後述)。
前述の Signed Distance Function $f$ で距離 $d$ を表現すると以下のようになります。

\begin{align} d = f(\vec{\bf{P}}) = \rm{length}(\vec{\bf{P}}) \end{align}
空間上の任意の位置 $\bf{P}$ への相対ベクトル $\vec{\bf{P}}$ の長さをそのまま距離 $d$ としているだけです。

例:ある点から広がる Distance Field


次に、原点ではなくある任意の点から広がる Distance Field を考えてみます。これは簡単で以下のようになります。

任意のある点を $\bf{C}$ とします。
\begin{align} d = f(\vec{\bf{P}}) = \rm{length}(\vec{CP}) = \rm{length}(\vec{P}-\vec{C}) \end{align}
 
$\bf{C}$ から $\bf{P}$ への相対ベクトルの長さを距離としているだけです。Houdini で可視化してみましょう。
 

b1. 図のようにGridノード、Point Wrangleノードを配置して繋げます(パラメータはデフォルトで良いです)


b2. Point Wrangleノードでは以下のようにVEXを記述します。ベクトルパラメータを生成するために chv を記述し、右にあるボタンを押すとパラメータが下に出てきますので色々と数値を変えることができます。


b3. 可視化するために以下の distance というところをクリックします。


b4. 以下のように可視化が出来ました(余談ですが色の境目に薄っすらと白い円が見えるのは面白いですね)


b5. Point Wrangle を Primitive Wrangle に変え、数値の表示もしてみましょう。


b6. こんな感じになります。


距離というスカラー値が空間に広がって場を成している様子が分かり易いのではないでしょうか?

さて、ここではGrid SOPノードを使って2次元的な可視化を行いましたが、3次元での Distance Field の計算にもそのまま使えます。

b7. 図のようにBox, Points from Volume, Point Wrangle を配置して繋げます(パラメータはデフォルトで、Point Wrangle は前述 b2. と同じもので)


b8. 以下のようになります。ちょっと分かりにくいので


b9. Point Wrangleに以下のような二行目を追加して距離が0.5より大きいものを削除すると


b10. このようになります。計算している距離はユークリッド距離なので等値面は球となることを表しています。


Distance Field から Signed Distance Field へ

 
ココまで見てきた Distance Field は正の値を持つ距離しか出てきませんでした。何故かというとこれまではある点からの距離を考えてきた訳ですが、点には大きさが無く内部という概念が存在しませんので当たり前です。
しかしせっかくなので負の値を持つようにしてみたいですね。そこである点を中心とする球を考えてみましょう。球のように大きさを持つならば内部という概念が生まれてくるからです。

球のSDFを考える


対象ジオメトリを球としたときに、距離 $d$ を定義する Signed Distance Function $f$ は以下のようになります。

球の中心点を $\bf{C}$, 球の半径を $r$ とします。
\begin{align} d = f(\vec{\bf{P}}) = \rm{length}(\vec{CP}) - \it{r} = \rm{length}(\vec{P}-\vec{C}) - \it{r} \end{align}
     
(2) から半径 $r$ を引いただけです。簡単ですね。
この式をよく見てみると、球の中心 $\vec{\rm{C}}$ から位置 $\vec{\rm{P}}$ までの距離が半径 $r$ よりも小さい時、$d$ は負の値を持つことに注目してください。
「SDF=符号付き距離場」の「符号」の部分がようやく意味を持つようになり、正負の距離を持つようになりました。

さて可視化をして行こうと思いますが、ここでは気分を変えて Houdini の Volume を用いた可視化を行って見ましょう。

HoudiniにおけるSDF

HoudiniでもSDFを作ることが出来ますが、VDBというデータ構造でSDFを生成するノードが存在するため、SDF = VDB だと思っている人もいるかもしれません。

これはHoudiniの既存ノードでは SDF を格納するのに VDB を使っているだけであり、今まで見てきたように SDF とは VDB のようなデータ構造とは独立した概念だと覚えて頂くと良いと思います。


また今回式(3) をボリュームで可視化するにあたっては VDB ではなく Volume を使います。その理由は後述します。


c1. 下図のようにBox, IsoOffset, VolumeWrangle ノードを繋げます。IsoOffsetのパラメータは図のようにします。


c2. Volume Wrangle で以下のように記述します。半径は 0.5 と設定してみてください。


c3. 可視化されました!


c4. VDB を使う場合は以下のように Convert VDB をして Volume に変換してください。


c5. Convert VDB による Volume への変換を忘れるとこんな風になってしまいます。


Volume と VDB の違い

なぜ c5. のようになってしまうかと言いますと、VDB というのは Volume を効率よく表現したデータ構造になっているため、そのまま Volume Wrangle にて処理をするのには向いていないためです(詳しくは[2]をご参照ください)。

なので VDB から、全てのボクセルデータが存在する Volume データに展開してから Volume Wrangle を使うと良い、ということです。


長くなってきたのでその2に続きます…。


参考文献




0 件のコメント:

コメントを投稿