Flood Fillのエラーについて

Substance Designerの便利ノード [Flood Fill] はアイランドに分かれている形状にランダム性をもたせるのに重宝します。

f:id:monsho:20180906215410p:plain

このノードには3種の処理方法があり、[Safety/Speed trade-off] パラメータで変更が可能です。

  • Simple or small shapes:高速ですがエラーが発生しやすく、オススメできません
  • Complex or big shapes:デフォルトです。多くの場合で正常に処理できますが、失敗もあります
  • No failure mode:最も遅いですが、失敗はほぼありません

デフォルトの [Complex or big shapes] を選択し、[Flood Fill to Random Color/Grayscale] に接続した場合、多くの場合で成功するのですが、一部で失敗が発生します。

f:id:monsho:20180906220339p:plain

赤枠で囲った部分に横線が入っているのが見えるでしょう。
このようなエラーを回避するには [No failure mode] を選択するのが最も正しいやり方ですが、私の環境ですと、このモードを選択すると20msほど余分に処理がかかってしまいます。
大きな問題になる時間ではないとはいえ、軽く処理できるならそれに越したことはないわけで、そもそもこのようなエラーが何故発生するのか疑問に思いました。
そこで、ちょっと調べてみた結果を記事として公開することにした、という次第です。

さて、そもそも [Flood Fill] ノードから出力されているカラーの値は一体どのような値なのでしょうか?
これは、出力ピンにマウスドラッグすると判明します。

f:id:monsho:20180906220905p:plain

RGには各形状のUV値が格納されます。
このUV値は、各形状の軸並行境界ボックス(AABB)の左上を0、右下を1としたUV座標が入っています。
BAはこのAABBのサイズです。サイズ1.0はテクスチャ全体を示しています。

例えばRGの値を利用すれば各形状にテクスチャを貼り付けるようなことも可能です。

f:id:monsho:20180906221718p:plain

さて、[Flood Fill to Random Color/Grayscale] についてですが、これらはRGの値とBAの値を利用して、形状ごとに一意の値を生成、ここからランダムに色やグレースケールを求めるという手法を採っています。
とりあえず、[~ to Random Grayscale] の中身を見てみましょう。

f:id:monsho:20180906222054p:plain

[Pixel Processor] を2つ使っていますが、左下のノイズが表示されているものは [White Noise Fast] と近い処理を行っています。
重要なのはもう1つの方。こちらの中身を見てみましょう。

f:id:monsho:20180906223226p:plain

形状ごとに一意の値を求める、と前述しましたが、その部分は前半部分です。コメントで「upper-left corner」と書かれている部分までがその処理です。
あるピクセルの値を取得した際、そのピクセルが所属する形状のサイズ(BA)はその形状内では全て同一の値です。
違いがあるのはUV値となるRGの値ですが、RG * BAの計算で何が求められるかというと、そのAABBの左上からそのピクセルまでのXY軸の距離となります。
このピクセルの座標からこの値を引き算すると、求められるのはAABBの左上の座標、ということになります。
つまり、コメントどおりにこの段階で所属する形状のAABBの左上座標という、形状ごとに一意の値が求まることになるわけです。

ここで一意の値が求まっているわけですので、ノイズからサンプリングする座標をこのAABBの左上座標にしてみましょう。

f:id:monsho:20180906224837p:plain

左がオリジナルの [Flood Fill to Random Grayscale]、右がAABBの左上座標からノイズサンプリングをしたバージョンです。
サンプリングする座標が変わっているため、各形状のグレースケール値は変化していますが、縞模様は出ていません。
形状のサイズを様々に変更してみましたが、この手法で縞模様は出ませんでした。
つまり、後半の計算が縞模様を出す原因になっていると言えるわけです。
なお、[Flood Fill to Random Color] も同様の処理を行っていますが、やはり同じ改造で縞模様を消せます。

ここからはあくまでも私の想像ですが、デフォルトの [Flood Fill] 処理ではUVの値、もしくはAABBのサイズ、もしくは両方に微量の誤差が出ているのではないかと思います。
AABBの左上を求める計算でもその誤差が出ていて、同じ形状内だけど小数点以下の小さな値に微量の誤差が埋まっているのだろうと思われます。
そのままの座標をノイズサンプリングに利用した場合は誤差は切り捨てられて無視できるわけですが、65535 という大きな値をかけてしまったために誤差が顕在化しているのではないでしょうか?
[Flood Fill] ノードの中身を追ってみれば正確な原因もつかめるのかもしれませんが、軽く開いて絶望したので追いません。
なので、これらはあくまで予想ですが、そう外れてもいないんじゃないかと思っています。

というわけで、[Flood Fill] のエラー対策としては以下の手法のどれかを選ぶのがおすすめです。

  • No failure mode を使用する(簡単だけど処理時間大)
  • AABBの左上座標を用いてノイズサンプリングする(ノード化しておけばコストは安い)
  • to Random ~ の後に [Flood Fill to Color/Grayscale] を用いる(コスト安め)
  • [Flood Fill to Color/Grayscale] にノイズテクスチャを刺す(コスト安め)

3つ目と4つ目について簡単に解説します。

[Flood Fill to Color/Grayscale] は形状のAABBの中心座標の色を [Color/Grayscale Input] に刺したイメージからサンプリングします。
to Random ~ の後にこのノードを利用することで、AABB中心の色をサンプリングできます。
縞模様になっている形状の場合は縞のどの部分が選択されるか微妙ではありますが、縞模様になっていない形状についてはほぼ同じ色が取得できます。

f:id:monsho:20180906233814p:plain

ほぼ、と書いたのは、複雑な形状のためにAABB中心がその形状の外に行ってしまっているという稀有な状態の場合にうまくいかないという問題があるためです。

4つ目の手法を用いる場合の注意点としては、to Random ~は形状の境界部分が0、それ以外は0以上の値が入るように設定されている、という点です。
つまり、形状部分と境界部分が明確にされなければ、下手すると形状と境界が合体してしまうことがあるということです。
ノイズ生成時には注意しましょう。