[Maya] SeExprMeshでのwave deformer解説

VimeoにUPした動画の解説第2弾はSeExprMeshを使用したWave Deformerです。
まずは動画をどうぞ。


この波は3つのSeExprMeshノードを組み合わせて作成してあります。

  1. 平面にノイズを掛けて水面もどきにする
  2. 大波を作る
  3. 法線方向に頂点を動かす

さて今回はコードも短いですしサックリ行きます。
今回のコードはSeExprになります。言語がMELなどとはちょっと違いますのでご注意ください。

まず1つ目、水面へのノイズです。

P + N * fbm4(P*0.2, time*0.3, 6) * 2 - [0, 1, 0]

1行で終わりです。簡単です。
全体的な流れは、Pに対してN方向にノイズを掛け、最後にY方向に1動かす、という感じです。

P, N, timeはグローバル変数です。
Pは頂点のポジション、Nはノーマル、timeはカレントフレームを秒で表したものとなります。
fbm4()はノイズ関数の一つで、Fractional Brownian Motionの略でfbm、4は4Dノイズの4です。
今回はPにスケールにかけたものを第1引数に、timeにスケールをかけたものを第2引数としています。
第3引数の6は、ノイズを何回重ねあわせるかというものなのですが、詳しくはfbmに関してご自分でご参照ください、だとさすがにあまりにも不親切なので簡単に解説します。

fbm(CG分野だとfBmと書いた方が良いかも知れませんが)は、ざっくり言うとノイズを何回か重ねあわせて作られるノイズです。
例えば、MayaやMaxを使用していると、ノイズテクスチャやタービュランスフィールドにはdepthパラメータが確認できます。通常それを増やしていくとノイズの複雑さが上がっていきます。
ほぼこれをイメージしてもらえればOKです。なのでdepthが6のノイズということです。

余談ですが、fBmなどに関して詳しく知りたい方は、Advanced RenderManなどを読んで頂くとシェーダライティングなどに絡めてあれこれ興味深いことが書いてあるかと思いますので、RenderManユーザではない方にもおすすめしておきます。これじゃ物足りない!!という方にはTexturing & Modelingが良いかと思います。

また、その他SeExprのヘルプはこちらです。
SeExpr: User Documentation

話を戻して再度解説を進めます。
fbm4()で求めた結果をスケールしたものを、Y方向に1下げています。
ノイズで平面を動かした結果、上に持ち上がってしまったので原点付近に下げてノイズをかけた以外の変化があまりないようにしています。

このスクリプトでは、0.2や0.3、6など、定数でコントロールしていて後での変更がしづらくなっていますが、とりあえずなんとなくの形が作れればよかったので、そのままザザっと書いてしまっています。ホントは外に出したりとかすべきなんでしょうけど、言っても1行だしそんなに大変なものでもないので、あまり気にせずざっくりやっています。

 
次です。
このスクリプトで大波の形を作っています。
なので今回はこれが一番重要です。

rotAxis = norm(rotAxisTgtPos-origin);
_P = P - rotAxis * dot(P-origin, rotAxis);
d = length(_P - origin);
nd = pow(linearstep(d / maxDistance, 1, 0), exp);
rotate(P-origin, rotAxis, nd*rotAngle) + origin

今回は先程のものとは違って、変数がたくさん出てきます。
しかも宣言されないままに使用されているものがいくつかあります。

これは先程のグローバル変数と同じような宣言済みの変数です。
SeExprMeshでは、カスタムアトリビュートとして自分で追加したものを、SeExpr内で変数として読み込み可能になっています。
なのでこのSeExprMeshノードの場合、Attribute EditorのExtra Attributesタブを開くと以下のようにいくつかのカスタムアトリビュートが作られています。

customAttrs

これらの値を変えるとその場でメッシュに結果が反映されるのでなかなか快適です。

では上から行きます。

rotAxis = norm(rotAxisTgtPos-origin);

rotAxisTgtPosとoriginの差分をnormalizeしています。
これは先ほどの画像でも確認出来る通り、どちらもvector型のアトリビュートになっています。

この2つの値は、transformノードのtranslateがコネクトしてあり、ロケータでいじることが出来るようになっています。
originはメインのロケータの位置、rotAxisTgtPosはそのロケータの子で、tzに1オフセットしてあります。
その子のロケータでグローバルに置いたロケータをコンストレインしたもののtranslateがrotAxisTgtPosにコネクトしてあります。

これはoriginのロケータを回したりスケールしたりした場合でも、rotAxisTgtPosとの関係を変化させないことで、rotAxisを直感的に決めることが出来るようになっています。
関係が変わらないというのは、rotAxisTgtPosを決める大元のロケータ、つまりoriginロケータの子のロケータは、originのローカル座標のtz 1に存在するという事です。

しかしここではそのベクトルをグローバル座標内で欲しいが為にこういう仕組みにしています。

_P = P - rotAxis * dot(P-origin, rotAxis);

_Pは、originとPの差ベクトルをrotAxis上に投影したベクトルとPとの差ベクトルです。
今回はベクトルの内積を求めるdot()を使用します。

図解するとこんな感じです。

proj_vector

dot()を使ってP-originをrotAxis上に射影した場合の長さを取得しています。
詳しく解説したいところですが、この辺の解説は非常に骨が折れますし、僕自身何も見ないで書くのは困難極まりない\(^o^)/ので、詳しい解説は以下のページなどをご参照頂きたいと思います。
Digital Matrix – vector 2

こうしてdot()を使ってrotAxisに射影した際の長さが求められたら、それをさらに単位ベクトルであるrotAxisに掛けることで、P-originをrotAxisに射影したベクトルを取得することが出来ます。
そしてPからそれを引くことで、rotAxisに直交するベクトル_Pが取得できました。
これはrotAxisを含む直線の上の点P’からPまでのベクトルです。
後で形状をコントロールするための減衰値を作成するのですが、その際ただPとの距離を取ってしまうとロケータの点を中心に波をつくったのが丸わかりになってしまいます。しかしこの時作りたかったのは、波がある程度の長さを持って迫ってくる感じだったので、任意の直線上からの距離を取る必要があったというわけです。
実際このベクトルは次の処理でoriginからの距離を取得する際に使用しています。

しかし高校時代は何に使うの???なんて思ってた内積がこんな所で活躍するとは驚きですよねー。かつての自分に教えてあげたい。教えたところで理解も出来なかったでしょうけども\(^o^)/

d = length(_P - origin);

先程も述べた通りで、_P-originの長さを求めています。
SeExprではMELと違ってmag()ではなくlength()を使用して長さを求めることができます。

nd = pow(linearstep(d / maxDistance, 1, 0), exp);

ここでは変形の影響力を決めています。
pow()はMELと同じ、linearstep()はMELのlinstep()に相当します。
linearstep(d / maxDistance, 1, 0) とすることで、変形の最大範囲を決め(maxDistance)、それを除算することで正規化(d / maxDistance)します。
ただしこのままだと、dの値が大きいほど値が大きくなりますし、正規化と言っても0-1に収まりません。
そこでlinearstep()を使用して、最小値に1、最大値に0を設定することで値の反転とクランプを同時に行います。

それにpow()を使いexp乗してやることで、影響力の減衰をコントロールします。
正規化された値をN乗するというテクは僕はとても重宝していて、非常に多用しています。

rotate(P-origin, rotAxis, nd*rotAngle) + origin

そして実際の変形はこちらです。
rotate()は、先日のgroundImpactSystemの際も使用したrot()と同様にあるベクトルをあるベクトルに沿って回転させるための関数です。
ここでは、P-originをrotAxisに沿ってnd*rotAngle分だけ回しています。

先程から、いちいちP-originのようにoriginを引いているケースが見かけられると思いますが、こうすることでoriginを中心に回転したり、距離を計ったりなどすることが出来ています。
originはロケータで値を変更しているため、ロケータを動かすだけで波の中心や向きなどを変えることが出来るようになっています。

ndは正規化された値なので、rotAngleにかけてやることで影響力をコントロールすることが出来ます。
ちなみにrotate()関数でも、角度はラジアンで指定する必要があります。

こうして出来たベクトルにoriginを足してやると本来あるべき位置に戻り、いい感じの波が作れます。

あとの細かい調整はoriginをコントロールしているロケータをいじることで行います。
ロケータとパラメータを同時にいじりながらコントロールしていく過程はまさに通常のデフォーマのようで、軽くコードを書くだけでこんなことが出来るのはホント素晴らしい!とSeExprMeshを触りながらしみじみ思ったわけです。ナイスディズニー。

 
そして最後のノードですが、こちらはPeak、つまり法線方向への移動を行なっています。

P + N * -peak + [0, peak, 0]

ほとんど最初のノイズを掛ける処理と同じです。
ここで違うのは、Nがノイズだの波の形状だのに変形させられているため、ただの平面の際とは異なっているという点です。
通常、法線と逆方向に動かすことで少しエッジのシャープな形状が得られるようです。これはよく液体のシミュレーションをメッシュ化したものに適用したりもします。あんまりやるとメッシュが突き抜けて破綻してしまうので、ほんの少し動かす程度しか出来ないのですが、それでも印象が変わってきます。

ここでも波のエッジを少しシャープにする目的で使用しています。
またY方向にpeak分だけ足しているのは、peakをかけた際にちょっと下に下がってしまったりなど都合の悪い事があったので、それの補正として入れています。ノイズの時と基本は一緒です。

 
以上です。
今回はプラグインを使用したものの解説でしたが、考え方はどこでも使えるものだと思うので、参考にして頂けると幸いです。
GenomeやICEにトランスレートするなどの試みも大歓迎です:D

ちなみにこれを作るにあたって参考にしたのはPSYOPのMilkというCM(?)のメイキングです。

うーん素晴らしい。
まだICEとか存在してない頃の作品なので、ツールはPSYOPの自前だと思われます。
PSYOPちゃんマジ神。

 
以上です。お粗末さまでした。

“[Maya] SeExprMeshでのwave deformer解説” への2件のフィードバック

  1. うぉっこれは嬉しい!ただ、前のもまだ途中なので、嬉しい悲鳴を上げそうっすw
    おいおい学ばせて頂きまっす!

  2. >gameeffectさん
    お粗末さまです!
    少しでも参考にして頂けると幸いです!:)

コメントを残す

メールアドレスが公開されることはありません。