Imaginantia

思ったことを書きます

シェーダで具体的な動きを描く

メモを兼ねて。シェーダで何か絵とかを作ろうとする場合の考え方みたいな話。

因果の逆転

feedbackなどをしない普通のシェーダの出力は、必ず入力となるパラメータのみから決定する。 そのパラメータを私達は操作できないから、いわゆるプログラミング言語で行うような「変数を用意して」「だんだん移動する」みたいなことはできない。

だから、「ある瞬間に於いて」「この変数の値はこうである」という書き方をする。適当なことを言うと、「ある瞬間から微小時間後を得ていく / 微分的」な考え方ではなく、「任意の瞬間における値を得る / 積分的」な考え方をする。 命令型言語と宣言型言語の違いに似ている。「なにをするか」ではなく「なにであるか」を書くのだ。

これによってもちろん大きな制約が出てくる。物理的な挙動は大体微分方程式でできているわけで、それをシェーダでやるには一般解を求めることになってしまう。できるとしても例えば放物運動が限度である。 だけど、私達は逆に「未来が確定している」世界である。全空間を同時に決めちゃっていいから、実質的に未来を先読みできる。それを最大限利用すれば違和感の無い自由な動きを構成できる。はず。

周期

例えばseamlessなテクスチャを作ることを想定したとする。左端から埋めていくような「微分的な考え方」をすると、右端に到達するときに左端と同じ値になるように調節する、みたいなことになると思う。でもそれはまさしく「境界」という概念を導入していることに他ならない。「不連続になった場所を適当に埋める」のは大体うまくいかない。気がする。

ノイズみたいなものは何らかの値を元に計算している。その値は場所によって決まっているが、全点においてランダムならばそれは綺麗な連続的ノイズになるわけがない。ある秩序を持ってランダムな値を決めているのだ。特に一般的に使われているノイズの実装は離散的な格子点上の値をベースに作られている。それらの間を補間することでなめらかにする。そして離散的ということは、空間の形は比較的自由ということだ。周期的にしたいなら、その参照する離散点の位置を周期的にすればよい。何かを周期的にするのではなく、周期的であることから全てを始めるのである。

f:id:phi16_ind:20190429153400p:plain

離散的な点は自由にできる。そしてそれらの間を繋げば連続である。

非周期的周期

微分的変化のような運動が作りたい。もちろん非周期。積分できれば万々歳だが、ランダムな動きを入れたいとなればうまくいくわけがない。

そこで、先程のノイズの話と同様に「ある瞬間における積分値を確定させる」のだ。そうすればその2点間を補間すればよくなる。だんだん変化させるよりも変化量を確定させたほうがはやい。 特に、周期で分割して各々をランダムに動かす、みたいなことができる。分割した各々は区画の境界で連続になるようにうまくやれば周期性はまったく見えないし、各々の空間はコンパクトなので秩序をあわせることだけに気をつければ自由に動きを描くことができる。

これは結構気に入っていて、横との干渉みたいなものをいれたことでかなり微分的な動きっぽいものができたと思う。仕組みはコレ。

f:id:phi16_ind:20190429154411p:plain

これは1周期に属す板の位置を表していて、上から下に時間が進む。これは次のようにどんどん空間を分割することでできている:

  • まず横方向にランダムに区画を区切る。最小1、最大4。これで更にコンパクトになった。
  • 同じ区画に属する線は高々4つなので、線を千切るために必要な交換も4回で十分。そこで時間レベルで4つに区切る。(実際の実装では3つになってる)
  • 全体で全ての線が必ずどこかで切れるように、各々の時間区画に赤い箱を描く。(この図は設計段階なので同時発生している箱があるが、実装では存在しない)
    • どこかで切れてないと過去の因果を引き摺ってしまう可能性があるので。そうなったらどのパネルだったかがわからなくなってしまう。秩序を保てなくなってしまう。

空間のいい感じなランダム分割、みたいなのもまた周期をベースに行える。周期で区切った後、各々の空間に新たな区切りを配置する

f:id:phi16_ind:20190429161205p:plain

区切りの配置方法によって、生まれる新たな区画の間隔や分布は自由に調節できる。結構自然に見せるのに便利だと思う。

その他

結構前に見たコレがそこそこ興味深かった。

相変わらずiqさんの作品で、「物体を動かす」という考え方無しにこれを実現している。さて。

これの興味深い点は「連続空間をベースに連続空間を生み出す」ところ。やり方としてはこうだ:

  • まずランダムに球を配置する。正確に言えば、各々の球は時間レベルでは連続になめらかに動く。もちろん衝突する。
  • 衝突を解決する!物理的計算を行うことによって各々の球が被らないくらいになるまで移動させ、これを全球に対して行う。(完全に衝突が解決される保証はないと思う)

この「衝突の解決」は連続的に行われているので、この工程において不連続な結果が得られるわけがない。だから時間レベルで見ると各球は連続的に存在しているように見える。 球が急激に動いたときはきっと中心が近い位置に居て衝突の解決の方向が激しく変化するからだと思う。

連続空間を元に「ランダム性」を作り出すという目的では、こういう物理的挙動は近いものを与えてくれるのかもしれない。ただまぁ完全に制御できるものでもない。使い道はありそう。

というわけで

こういうシェーダを書くのは難しそうな感じはある。けどとりあえず問題に対するアプローチ方法が一般的プログラミングとはだいぶ異なると思うのでそういう意識で見れば見えるものもあると思う。 がんばっていきましょう。

余談

「不連続になった場所を適当に埋める」のはちょこちょこあって、triplanar mappingもそうだとおもうし、あとコレもそう。

ただ基本的な考え方は「境界で0になるように重みを消す」(窓関数みたいなもん) なので情報はちょっと落ちる。その辺が悩ましいとこ。


あと余談