凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

SDF テクスチャを uRaymarching で描画してみる

はじめに

uRaymarching 関連の話題になります、TwitterSDFr と uRaymarching を試されている方から質問をもらいまして、調査をはじめました。前回の記事はこちら:

tips.hecomi.com

これまでは物体表面までの距離を距離関数で記述してレイマーチングする手法を紹介してきましたが、この距離は別に関数でなくても問題ありません。ある地点から物体までの距離がどれくらいの場所にあるのかを符号付き(外部なら +、内部なら -)で示せればよいわけで、距離情報を 3 次元的に格納した 3D テクスチャでも実現できます(ちなみに TextMeshPro では 2 次元的に文字までの距離を格納したテクスチャでフォント描画を行っています)。キューブの中に入ったレイのある地点において 3D テクスチャをサンプリングし、その距離分だけ再度レイをすすめる、を繰り返していくことによって格納されたオブジェクトの表面へ到達することができます。本エントリではこの 3D テクスチャを SDF テクスチャと呼び(SDF: Signed Distance Field、符号付き距離場)、この描画を uRaymarching で実現することについて見ていきます。

デモ

ポリゴンはキューブですがこんな画が出ます。

f:id:hecomi:20200627233030g:plain

また従来どおり smin 計算などでこういった表現もできます。

f:id:hecomi:20200628151441g:plain

テクスチャを Repeat や Mirror にすれば繰り返しもできます。

f:id:hecomi:20200628152745g:plain

環境

  • Unity 2019.4.1f1
  • uRaymarching v2.1.0
  • SDFr (1cfba97)

今回のために少し uRaymarching を更新したので最新版のリリース(執筆時点では v2.1.0)を以下よりダウンロードしてください。

github.com

SDFr

SDF テクスチャは Unity では SDFr というプロジェクトを利用することで得ることができます。これは単一または複数の Mesh を RHalf 形式で -1 ~ +1 の範囲で書き込んだ SDF テクスチャを生成してくれるアセットです。VFX Graph とも組み合わせてパーティクルの衝突計算にも使えるようです。

github.com

Raymarching や VFX Graph のサンプルも付属しています。

f:id:hecomi:20200627232645p:plain

Bake ボタンをSDFData という Texture3D を含む ScriptableObject を生成してくれるので、そこから色々情報を得ることができます。

f:id:hecomi:20200627233004p:plain

f:id:hecomi:20200627235127p:plain

今回は生成された 3D テクスチャだけを利用します。SDFr の Example > Data > sdfData_stanfordLucy64Stanford Lucy のデータがあるのでこれで色々試していきます。

uRaymarching の設定

設定

f:id:hecomi:20200628002527p:plain

ビルトインパイプラインのフォワードでやっていきます。また、カメラがキューブポリゴン内部にも入り込めるように Camera Inside Object にチェックを入れておきます。またオブジェクトの拡縮ができるように Follow Object Scale にもチェックしておきましょう。

Properties

3D Texture を読み込むためにマテリアルのプロパティにスロットを追加します。

[Header(Additional Properties)]
_Volume("Volume", 3D) = "" {}

f:id:hecomi:20200628001457p:plain

Distance Function

SDF テクスチャに距離情報が入っているのでとてもシンプルになります。

Texture3D _Volume;
SamplerState sampler_Volume;

inline float DistanceFunction(float3 pos)
{
    return _Volume.SampleLevel(sampler_Volume, pos + 0.5, 0).r;
}

f:id:hecomi:20200628001514p:plain

Conditions のところで World Space にチェックを入れない場合は、ここでやってくる pos は -0.5 ~ +0.5 のローカル座標になります。なので、これを 0 ~ +1 の範囲になるように 0.5 だけ足してあげた座標のテクスチャの値をサンプリングします。形式は RHalf なので r にその地点から物体表面までの距離が入っているので、これを距離関数の戻り値として採用しています。

Post Effect

簡易的なオクルージョンと、法線色をうっすらと乗せてみます。

inline void PostEffect(RaymarchInfo ray, inout PostEffectOutput o)
{
    float a = 1.0 * ray.loop / ray.maxLoop;
    o.Occlusion *= pow(1.0 - a, 2.0);
    o.Albedo *= (o.Normal + 1.0) / 2;
}

f:id:hecomi:20200628001530p:plain

マテリアルの設定

Properties のところで追加した _Volume を設定するスロットに Stanford Lucy のテクスチャを入れます。

f:id:hecomi:20200628001639p:plain

また、今回 v2.1.0 からアップデートで追加した NormalDelta を 0.01 ほどの値に設定します。これは法線情報は距離関数の微分によって得ているのですが、その微分を行うための微小距離を定義するものです。以前は 1e-4 を固定値として使っていましたが、この値だとテクスチャは数式ほどなめらかでないので次のようにノイズが発生してしまいます。

f:id:hecomi:20200628010530g:plain

おわりに

空間解像度はそんなに高くないので、まだブロックノイズ的な小さいキューブが見えてしまっています。3次元的な勾配情報を保持した RGBHalf な画像にしてクオリティを上げるみたいなことも必要そうです。

github.com

また、smin 計算などで他のオブジェクトと結合しようとした際に、バウンディングボックスの外ではテクスチャ境界面の距離が適用されてしまうのでちょっと変に見えてしまいます(デモのボールとの結合はばれないようにキャプチャしています)。

f:id:hecomi:20200628152450p:plain

距離関数内でレイとバウンディングボックスの距離計算なども必要そうです(SDFr ではやっているようです)。