はじめに
まずは以下の動画を御覧ください。
本稿では、この動画で使用されている糸の動きについて、実現するまでのプロセスを紹介します。なお、あくまで自分が試行錯誤して得た結果なので、もっと簡単な方法があるかもしれません。もし他の方法を知っている方がいましたら、コメントで教えてください。
サンプルシーンのダウンロード
仕組みをすっ飛ばしてシーンだけ見たいという方は、github にシーンを配置していますので、ダウンロードしてみてください。
利用するツール
- Maya 2017 Update5
要件の整理
実現方法を検討する前に、要件を整理します。
糸を動きを作る上で、以下の要件を満たす必要があります。
- 糸の柔らかさの表現
- 毛糸玉の軌跡への追従
- 他のモノ(机や椅子など)との衝突
- 工数がかからない方法
あとで説明するように、実現するまでにいくつかの方法を試しています。これらの要件と照らし合わせながら最適解を探っています。
実現方法の検討
いくつかの方法を検討しました。
- クラスタデフォーマを使う
- 押し出しのパスカーブを使う
- nHair を使う
クラスタデフォーマ(Cluster Deformer)を使う
方法
- NURBS カーブの CV をクラスタ化にする
- クラスタにアニメーションを手付けする
問題点
- アニメーションは全て手付けになり工数がかかる
###押し出し(Extrude)のパスカーブを使う
方法
- メッシュに対して押し出し(Exclude)を適用する
- 参考動画
問題点
- UV も一緒にアニメーションしてしまうため表現力に乏しい
- アニメーションは手付けになるので工数がかかる
###ヘアー(nHair)を使う
方法
- NURBS カーブにヘアーを適用する
- 糸のリアルな表現が工数をかけずに実現できる
問題点
- 手付けできないので細かい手直しができない
試行錯誤の結果、ヘアーが求めている表現に近く、工数がかからないと判断したので、ヘアーを採用することにしました。しかし、いくつかの問題をクリアする必要がありました。
- どのように糸を毛糸玉に追従させるのか
- 地面に落ちていない糸はどこに隠すのか
- NURBSカーブをどのようにレンダリングするのか
上に挙げた問題を1つずつ解決していきます。
実現方法
螺旋状のNURBSカーブの作成
ヘアーを使うとなったときに、一番悩ましいのが、まだ地面に落ちていない糸をどのように隠すかということでした。
糸を一箇所にまとめてしまうと、糸自身に衝突してしまい不具合の元になります。
そこで NURBS カーブをコイルのように螺旋状に巻いて、毛糸玉の中に収まるようにしました。
螺旋のNURBSカーブを作り方を説明します。
Helix のプリミティブを作成します。
Helix の外周のエッジを選択します。
Modify → Convert → Polygon Edges to Surface
を適用します。
これで螺旋の NURBS カーブが作れます。
しかし、ここで重要なのは NURBS カーブの長さです。NURBS カーブの長さが糸の長さになるからです。そして NURBS カーブの長さを決めるのは、Helix のアトリビュートです。必要な糸の長さからアトリビュートに入力する数値を決めていきます。すごく雑に計算すると、以下の式になります。
糸の長さ \fallingdotseq 円周 \times 螺旋を巻いた回数
糸の長さは 2m にします。毛糸玉のサイズは直径 10cm なので、螺旋はその中に隠れる必要があります。ということで直径 6cm 程度にします。螺旋を巻く回数は、
200 = 6 \times 3.14 \times x
x = 200 \div (6 \times 3.14)
x \fallingdotseq 10.615
となり、11回程度巻いておけば 2m になる計算です。Helix のアトリビュートを調整し、約2mのNURBSカーブを作ります。
Freeze Transform
を適用して、ヒストリを切っておきます。
NURBSカーブのCV数の変更
次に考慮するのは、NURBS カーブのCVの数です。CVの数が多ければ、それだけ柔らかい糸の形状を表現できますが、今度は計算量が増えてしまいます。適切なCV数になるように修正します。今回は 1cm 毎にCVを入れることにします。
NURBSカーブを選択し、 メニューから Curves → Rebuild
を選択します。
1cm 毎にCVを入れるので、オプション画面で Number of spans
の値を 200 に変更して適用します。
これでCV数が203(200+3)の NURBS カーブができます。
(なぜ3足されるのかよく分かってないです…すいません…)
NURBSカーブのヘアー化
螺旋状の NURBS カーブにダイナミクスを適用します。NURBSカーブを選択した状態で nHair → Make Selected Curves Dynamic
を実行します。
初期状態ではカーブの両端が落下しないので、アトリビュートの内容を編集します。
Follicle Attributes
の Point Lock
を No Attach
に変更します。
もう一つ、 nucleus
の Space Scale
を調整します。Maya の規定の作業単位はセンチメートルで、重力はメートル単位で解釈されるため、Space Scale
は 0.01
にしておく必要があります。
試しにアニメーションを再生してみます。
当然ながら、糸はすぐに自由落下します。糸を毛糸玉に追従させるには、CVに対してダイナミクスを反映するかしないかを制御する必要があるのです。
Transform Constraint の適用
ダイナミクスのオン/オフを制御するために、 Transform Constraint
を使用します。言葉だと説明が難しいので、Transform Constraint
の効果の分かりやすい動画があるので紹介します。
https://www.youtube.com/watch?v=TwhTxlFaEO0
以下の対応を行います。
- CV の親になるノードを作成する
- 出力カーブのCV毎に
Transform Constraint
を適用する -
Transform Constraint
を親ノードにParent Constraint
させる - 入力カーブを親ノードに
Parent Constraint
させる
まず、親ノードを作成します。
空のグループを螺旋の中心に配置します。ノード名は YarnTrailParent
としておきます。
次に、CV毎にコンストレイントを適用していくのですが、 203個もあるので手動で行うのは大変です。そこで、ここでは MEL
を使います。
以下のスクリプトを Script Editor
に貼り付けて実行します。親ノード(YarnTrailParent
)を選択した状態で実行してください。
$select = `ls -sl`;
$parent = $select[0];
for($i = 0; $i <= 202; $i++)
{
// create dynamic constraint
selectKey -clear ;
select -r curve1.cv[$i];
createNConstraint transform 0;
// create parent constraint
$number = $i + 1;
$constraint = "dynamicConstraint" + $number;
select -cl;
select -r $parent;
select -add $constraint;
doCreateParentConstraintArgList 1 { "0","0","0","0","0","0","0","0","1","","1" };
parentConstraint -mo -weight 1;
// disable rest position
$parentConstraintName = $constraint + "_parentConstraint1";
string $enableRestPositionName = $parentConstraintName + ".enableRestPosition";
select -r $parentConstraintName;
setAttr $enableRestPositionName 0;
}
実行すると、大量のコンストレイントがアウトライナ上に表示されます。
邪魔なのでグループにまとめて非表示にします。
最後に入力カーブに対して Parent Constraint
をかけます。親ノード(YarnTrailParent
)を選択してから、入力カーブを選択します。
Constrain → Parent Constraint
を実行します。
この状態でアニメーションを実行すると、ダイナミクスは適用されず、糸は静止したままの状態になります。このコンストレイントのオン/オフを制御すれば、糸の軌跡を描くことができそうです。
糸制御用コントローラの作成
螺旋がダイナミクスの影響を受けず、親ノードに追従するようになりました。
次に糸制御用のコントローラを作成します。このコントローラには以下のパラメータを持たせます。
- 現在の角度 (
Angle
) - 初期状態の角度 (
Initial Angle
) - CV の個数 (
Cv Count
) - CV 1つの角度 (
Cv Angle
) -
Angle
とInitial Angle
を加算した角度 (Trail Angle
)
コントローラの形状はなんでもいいのですが、螺旋を覆う感じで円を描きます。ノード名を YarnTrailCtrl
とします。
Freeze Transform
してヒストリを切ります。そして、親ノード(YarnTrailParent
) をコントローラの階層に移動します。
これでコントローラの移動・回転が螺旋に反映されるようになります。
コントローラのアトリビュートを以下のように編集します。
Scale
と Visibility
は使用しないので Lock and Hide Selected
を適用します。追加分のアトリビュートは Add Attribute
で追加します。追加したアトリビュートの名前は、後でスクリプトで使用するので厳密に決めておく必要があります。
まだ何も制御はできていませんが、これで糸制御用のコントローラができました。
糸の排出を制御する
球の回転に応じて、頂点毎のコンストレイントのオン/オフを制御します。球の回転角はフレーム毎に変わるので、フレーム単位で値を評価する必要があります。
そこで Expression
を利用します。以下のような処理をフレーム毎に行うExpression
を記述します。
- コントローラの回転角(
Angle
)と初期角度(Initial Angle
)を加算し現在の角度(Trail Angle
)を取得する - 回転角を単位角(
Cv Angle
)で除算する - 除算で得た値の数から、対象となるCVを取得する
- CVの
Transform Constraint
及びParent Constraint
を無効にする
Expression Editor
を開きます。
Expression Name は適当に YarnTrailController
とします。
以下のスクリプトを Expression Editor
に貼り付けて、Create
ボタンを押します。
$cvAngle = YarnTrailCtrl.CvAngle;
$cvCount = YarnTrailCtrl.CvCount;
$trailAngle = YarnTrailCtrl.Angle + YarnTrailCtrl.InitialAngle;
YarnTrailCtrl.TrailAngle = $trailAngle;
$count = $trailAngle / $cvAngle;
for($i = 0; $i < $cvCount; $i++)
{
int $number = $i + 1;
string $dynamicContraintShape = "dynamicConstraintShape" + $number;
string $parentContraintWeight = "dynamicConstraint" + $number + "_parentConstraint1.YarnTrailParentW0";
$enableConstraint = $i < $count ? 0 : 1;
setAttr($dynamicContraintShape + ".enable", $enableConstraint);
setAttr($parentContraintWeight, $enableConstraint);
}
エラーが発生したときは、コントローラのアトリビュートの命名が間違っている可能性があります。
次にコントローラにパラメータを入力します。CV Count
には CV の総数を入れるので 203
、CV Angle
には19.5
を入力します。
CV Angle
の 19.5
という値は、以下の大雑把な計算で求めています。厳密な値ではありません。
x = 円の角度 \times 螺旋を巻いた回数 \div CVの数
x = 360 \times 11 \div 203
x \fallingdotseq 19.5
Angle
にキーを打って動作確認します。ここでは1フレーム目を 0
、120フレーム目を 720
にしました。アニメーションを実行してみます。
### 回転運動との同期
糸の排出の制御は完成したので、次は糸の排出とコントローラの回転運動を同期させます。
YarnTrailCtrl
の Rotate X
と Angle
を同期させます。Angle
に打ったキーを全削除します。再度 Expression Editor
を開いて、最初の行に以下を追加します。
YarnTrailCtrl.Angle = -YarnTrailCtrl.rotateX;
これでコントローラのX軸回転と糸の排出が同期するようになりました。
動作確認のため、ヘアーを床に衝突させます。 nucleus
の Use Plane
を有効にします。
位置を調整して、X軸回転と移動方向にキーを打ち、アニメーションを再生させます。
Curve warp デフォーマの適用
糸のカーブが物理で制御できるようになりました。ここからNURBSカーブにメッシュを適用させてレンダリングします。NURBSカーブにメッシュを割り当てる方法として、今回は Curve Warp
デフォーマを使用します。Curve Warp
デフォーマの分かりやすい解説動画を貼っておきます。
https://www.youtube.com/watch?v=_Ey8TSduH4U
まずは糸のモデルとなるメッシュを用意します。Cylinder を作成し、アトリビュートを編集します。縦の長さは 2m、半径 1mm の糸を作成します。 縦の割りが必要になるので、Subdivision Heights
を 200 に指定します。
メッシュが用意できたら、 Freeze Transform
をかけてヒストリを切っておきます。モデルを選択してから続けて出力カーブを選択します。
Deform → Curve warp
を選択します。
メニューに項目がない場合は、 Plug-in Manager
でチェックが入っているか確認してください。
螺旋にメッシュが巻きついたら成功です。
ここでレンダリングを開始したいところですが、 CV数の多い NURBS カーブに対する Curve Warp
は重い処理で、容易にレンダリングの調整を行うことができません。そこでダイナミクスのキャッシュ化を行います。
ダイナミクスのキャッシュ化
キャッシュ対象の NURBS カーブを Outliner で選択します。
nCache → Create New Cache → nObject
でオプション画面を開きます。
Cache Format
は mcx
、 File Distribution
は One File
に変更して書き出します。
レンダリング
毛糸のリグに球のメッシュを追加し、床とライトをセットアップして Arnold でレンダリングしてみます。
Arnold で書き出した動画を gif に変換しているので階調は粗いですが、球から排出される糸の動きが表現できていることが分かります。
問題点と対策
今回の方法の大きな問題点は、シーンが重くなることです。シーンが重くなるとアニメーション作業に支障をきたします。
シーンが重くなる原因は、Curve Warp
デフォーマの変形対象となるメッシュ量が大きいためです。しかし、メッシュ量を減らすと今度は糸の滑らかな形状を失います。なお、ダイナミクスに関しては、アニメーションをつけるときは nucleus
を切っておけば、それほど重いシーンにはならないです。
解決案(暫定)
自分が作業しているときは、レンダリング用のシーンとアニメーション用のシーンを分けて管理しました。
アニメーションのデータは ATOM
を使用し、ダイナミクスのデータは nCache
を使用して、アニメーション用のシーンからレンダリング用のシーンにアニメーションデータを流し込んでいました。
nCache
はファイルパスへの参照なので、アニメーション変更の際はキャッシュを書き出すだけの手間で済むのですが、 ATOM
は毎回レンダリングシーン側で読み込み作業が発生するので工数がかかります。ここらへんは他に良い方法がないか模索している最中です。
おわりに
なかなか応用範囲の狭いマニアックなリグが完成しました。
github に今回作成したシーンを上げておきます。シーンを見たい人はダウンロードしてみてください。