“NATURE OF CODE” in Houdini – 001 – ベクトル

長い間、仕事ではパイプライン系ツールを書くことが多くなり、幾何学や物理などの数学とはだいぶ疎遠になってしまいました。

最近になって、やっとHoudiniを業務で使用させてもらえる環境が与えられたこともあり、これも良い機会ということで、これからしばらくの間、数学の勉強や復習とリハビリを兼ね、Processingの名著 [NATURE OF CODE] の内容を、Houdiniを使って追いかけていきたいと思います。




1.1 ベクトルなしでは始まらない

・バウンドするボール

ポイントの速度と移動可能な範囲を指定。
ポイントは毎フレームで指定した速度に相当する距離移動し、範囲外に出た時に速度を反転させる。
上図では、Trail SOPとCopy SOPで軌跡を見やすくしている。

ベクトルクラスを使わないので、まとめて計算ができず、とても面倒。


1.2 Processing プログラマのためのベクトル

PVectorクラスで速度と可動範囲を定義。
Houdini上では、速度と可動範囲をベクター型の変数で置き換えた。
ついでに、3D上で動くようにした。

ベクトルクラスを使うと複数の値をまとめて計算できるようになりとても楽です。

※ベクトルの計算についてはあまりにも基本的すぎるので時短のため詳細は省きます


1.3 ベクトル加算

ベクトル同士の加算。
ProcessingでのPVector.add()を解説。


1.4 その他のベクトル演算

ベクトルクラスのメンバメソッドの解説。
Processingでは変数型にメンバメソッドが含まれる。
Houdiniでは、VEX関数を用いて同様の処理を行う。

  • ベクトルクラスのメソッド
    add(),sub(),mag(),normalize()など
  • ベクトル減算
    ベクトル同士の減算を解説。
    ProcessingでのPVector.sub()を解説。
  • ベクトル乗算(除算も)
    ベクトルとスカラーの乗除算の解説
    ProcessingでのPVector.mult()とPVector.div()を解説。


1.5 ベクトルの大きさ

ベクトルの長さはピタゴラスの定理で求める。
ProcessingでのPVector.mag()を解説。


1.6 ベクトルの正規化

ベクトルを単位ベクトルにする。
ProcessingでのPVector.normalize()を解説。


1.7 ベクトル運動:速度

速度は位置に影響を与える。
Moverクラスを定義する。

  • Moverオブジェクトはどのようなデータを持つか
    位置、速度など
  • Moverオブジェクトはどのような機能を持つか
    動く、表示する

※ここでは、Houdini標準アトリビュートの@vや@forceなどを使わず、独自のアトリビュートを用いて、最初から自前で計算します。ただし、@Pは計算結果を代入するために使います。

・コンストラクタを定義する

正しいかわからないけど、Houdinide オブジェクトをクラスととらえた時、そのオブジェクトの動作の核心は内部に含まれるSolver SOPのように見える。
Houdiniオブジェクトのコンストラクタ処理の内容は、Solver SOPの上流にあるすべての要素と言えそう。
コンストラクタの呼び出し(SolverSOPの上流にある処理)は、Solver SOPのStart Frameによって指定された時間に一度だけ行われ、キャッシュされて初期化される。

・動作を定義する

Houdiniオブジェクトが行う毎フレームの動作は、Solver SOPの中で定義する。
ある意味、Solver SOPは毎フレーム実行される関数であると考えて良いと思う。
今回作ったSolver SOP内では、現在のポイントの位置に加える変位を計算し、毎フレーム変異させる処理を行う。


1.8 ベクトル運動:加速度

加速度は速度に変化を与える。

加速度の各種アルゴリズム

  • 等加速度
    事前に加速度を設定し、変化させない。
  • 完全にランダムな加速度
    Solver SOPの中で加速度をnoise関数やRandom関数で変化させる。
  • マウスに向かう加速度
    Houdini上でマウスカーソルの3D座標が取れるかは謎なので、代わりに、特定のポイント位置へ向かう加速度を発生させるアルゴリズムで作ってみる。

速度に制限をかける

現実的に、速度が無限に高まることはないので、一定の速度を超えないように@velocityを抑制し、速度制限する。
下記のような感じ。

f@velocityLength = length(@velocity);

if( f@maxVelocity < f@velocityLength )
{
    vector normalVelocity = normalize(v@velocity);
    v@velocity = normalVelocity * f@maxVelocity;
}

@P += v@velocity;

 

1.9 static関数と非static関数

通常のプログラミングにおけるstaticメソッドに関する解説。
Houdiniオブジェクトはクラスっぽいけどクラスそのものではないので、再現できないとおもわれる。
とりあえずここは飛ばします。


1.10 加速度の対話的処理

加速度のターゲットと各Moverオブジェクトの距離に応じて加速度が変化するように設定。
結果、記事冒頭のムービーのように、なんとなく魚群風に見える映像が出来上がった。

“NATURE OF CODE” in Houdini – 000 – はじめに

長い間、仕事ではパイプライン系ツールを書くことが多くなり、幾何学や物理などの数学とはだいぶ疎遠になってしまいました。

最近になって、やっとHoudiniを業務で使用させてもらえる環境が与えられたこともあり、これも良い機会ということで、これからしばらくの間、数学の勉強や復習とリハビリを兼ね、Processingの名著 [NATURE OF CODE] の内容を、Houdiniを使って追いかけていきたいと思います。



はじめに

I.1 ランダムウォーク

オブジェクトが累積的にランダムな方向に動く動作。


I.2 ランダム・ウォーカークラス

・ランダム・ウォーカークラスを定義

Houdiniでカスタムノードを定義することはクラスを定義することと言えそう。
ただし、現時点では継承等の仕組みがあるか不明なので、あくまでもクラスもどきの定義と解釈しています。

ここでは、まずSubnetをクラスとしてとらえつつ、その内部に、ランダムウォークする点群を複数含むオブジェクトを定義する。

・擬似ランダム値

一般的に、コンピュータ上で乱数を生成するランダム関数は実際には周期性があるので擬似的なランダム関数と言える。
ただし、周期が非常に長いため、実質的に真の乱数計算関数と言って差し支えない。


I.3 確率と一様分布(離散)

HoudiniでVEX関数のrandom()を使った場合、特定の値が出現する確率はほぼ一定。
具体例として、4種類の値を生成するランダム関数があるなら、それぞれの値が出現する確率はほぼ均等で約25%になる。

bandicam 2016-06-12 22-55-14-514

random()から出力される0~1のfloat値を0~9のint値にマッピング。
乱数を1200回発生させ、マッピング後の整数値に対応するIDを持つポイントをその都度+Y方向に移動した結果、多少のばらつきはあるものの、ほぼ均等な値が生成されていることがわかる。
このように、起こりうる結果がほぼ均等に現れる確率の分布を、一様分布と呼ぶ。
他の例としては下記のようなものもある。

  • コイントスの確率
    1/2 = 0.5 = 50%
  • 全トランプ52枚からA4枚のいずれかを引く確率
    4/52 = 0.077 = 約8%
  • 全トランプ52枚からダイヤのカード13枚のいずれかを引く確率
    13/52 = 0.25 = 25%
  • 1つのサイコロを振り、1の目が出る確率。
    1/6 = 0.166 = 約17%

・確率と非一様分布

bandicam 2016-06-12 22-55-21-874

一様分布の項で作成したランダム関数を2つ足しあわせ、値が出現する回数を計測してみる。
結果、グラフは中央値が高く、山なりになるのがわかる。

bandicam 2016-06-12 22-55-26-468

今度は、3つのランダム関数を使って同様の計算をしてみる。
より顕著に、山なりになる。

サイコロを3個振る場合を考えてみると理由は簡単。
合計値が最小の3または最大の18になるのは1または6のゾロ目の時のみで非常に確率が低いのに対し、合計値が中央付近の10になるケースはより多くの組み合わせが考えられる。

・乱数に偏りを持たせる

乱数を元に、予め用意された動作を選択する際、結果に偏りを持たせるには。

  1. 乱数を元に得たい結果がリストにまとめられているケース
    結果がリスト内に登場する回数を操作して確率に偏りを持たせる

    <<擬似コード>>
    results = [1,2,3,4,5] → [1,1,1,2,3,3,4,5,5,5]
  2. 乱数の値の範囲に応じて結果が決定するケース
    結果に対応する乱数の範囲を操作して偏りを持たせる

    <<擬似コード>>
    if random < 0.1:
        return 10
    elif random < 0.2:
        return 20
    else:
        return 30

I.4 ランダム値の正規分布(ガウス分布)

ランダムな要素が平均値付近に集中する確率分布を正規分布やガウス分布(Gaussian)、ラプラス分布(Laplacian)などと呼ぶ。

[参考サイト] NtRand – 正規分布
http://www.ntrand.com/jp/normal-distribution-single/

 例として、無作為に選んだ人々の身長の分布を考える。
百歩譲っていくら確率が0ではないと言っても、身長0.1cmの人や身長10mの人はそうそういない。
平均慎重と言われている165cm付近が最も多くなるはず。

・ベル型曲線

確率の平均[μ]と標準偏差[σ]により求められる、確率分布を表すグラフ。

  • 平均:μ
    起こりうる値の平均値。
  • 標準偏差:σ
    偏差とは、値と平均値の差。
    平均偏差は全偏差の平均。
    標準偏差は、分散の平均の平方根。(※分散は偏差の2乗)

・乱数の生成(random)

HoudiniにおけるVEX関数のrandom()は、0~1の範囲で一様分布の乱数を発生させる事ができる。(その他にも、3Dノイズなども出力できる)

・乱数の生成(noise)

HoudiniにおけるVEX関数のnoise()はシンプルなパーリンノイズ。
正規分布をもとに乱数を発生させる。
ループ中で新たなポイントを作成しながら乱数を発生させ、@P.xに与えていくと、各ポイントは下図のように配置されていく。

図では色が薄く見づらいが、μ=0.5、σ=1の正規分布で乱数が生成され、x=0.5の付近でより高密度にポイントが作成されていることがわかる。
※1マス=0.25


I.5 ランダム値のカスタム分布

  • 分岐条件を乱数をもとに判定(レヴィフライト風アルゴリズム)
    乱数の発生を一様分布でも正規分布でもない独自のものにしたい場合のテクニック。正規分布の乱数中で、時折「突発的」にレンジの大きな乱数を発生させたい場合は、現在の乱数生成の機会が「突発的」なものであるか判定し、突発的であると判断された場合、結果の範囲が大きな乱数を発生させるようにする。

    ## 擬似コード
    incident = random(1)
    if incident < 0.1:
        randValue = random(-100,100)
    else:
        randValue = random(-1,1)

    上の例では、incidentが0.1以下(10%)の確率でレンジの大きな乱数を生成している。

  • 出力が条件に適合するまで乱数の生成を試行する(モンテカルロ法風アルゴリズム)
    値が大きければ大きいほど選ばれやすくしたい場合のテクニック。
    乱数を2つ使い、片方がもう片方より大きかった場合に乱数を出力する。

    def montecarlo():
        while 1:
            r1 = random(0,1)
            r2 = random(0,1)
    
            if r1 <= r2:
                return r2

I.6 パーリンノイズ(よりスムーズな手法)

有機的なものの表現には、ランダム値の中にも連続性が必要になる。
その場合、ランダムかつ連続的に変化する値を出力するnoise関数を使用するとよい。
HoudiniのVEX関数 noise()は、正規分布に近い連続的でランダムな値を返すパーリンノイズ関数で、整数を与えるとすべて同じ値(0.5)を返すようになっているので、引数は極力整数値にしないように注意する。

## noise()の使用例
f@dx = fit(noise(@Time+(@ptnum*0.339)+offset),0,1,-1,1);

・疑問

Mayaの場合、noise()関数は周期性を感じさせない上に、出力される値も-1~1の範囲にきっちり収まるようになっている。
そのため、単純なフレーム番号を引数にするだけで延々と連続的なランダム値を生成し続け、その値を係数にして角度の変化などを計算する際、変化のレンジにただ掛け合わせるだけでよかった。

Houdiniのnoise()関数は、整数値を与えると全く同じ値が返る上に、結果が正規分布で得られるため、Mayaのnoise()関数と全く同じ感覚では使えない。

正規分布のおかげで結果を有機的にしやすい反面、変化幅の上下限をきっちり決めづらくとても気持ち悪く感じてしまう。(例えば毎フレーム何らかのオブジェクトの角度を変化させたい時、0~180度の角度範囲を上下限とし、-1~1の範囲の係数をかけて使いたい場合など)
もしかしてMayaのnoise()に近い関数があるんだろうか?(誰かご存知でしたら教えて下さい)

・パーリンノイズによるランダムウォーク

・2次元のノイズ

パーリンノイズは1~4次元の値を引数に取り、同様に1~4次元の値を出力できる。

・@Cdにnoise(x,y)の結果を適用
832

・@P.yにもnoise(x,y)の結果を適用

※わかりやすくするため、頂点数とカラーのレンジを調整済み


I.7 展望

この章では、自然現象をシミュレートする際に必要となる確率や乱数を学習した。
ランダム性を取り入れることで、より自然で複雑な要素を作成できる。
だがしかし、同じアルゴリズムにばかり頼ってしまうと結果的にパターンが透けて見えてしまい、結果が退屈な仕上がりになる場合もあるので、より多くの手法を身につけ、引き出しを増やし、様々なニーズに臨機応変に対応できるスキルを磨くことが大事。


[資料]

NtRand – 確率分布Navi

確率分布 Navi


http://www.ntrand.com/jp/gallary-of-distributions/


何か間違いなどあればツッコミいただけると喜びます。
よろしくお願いします。

Houdini Crowd System – 003 / Ragdoll + Custom force テスト

また、絵的に少しどうかしている映像をアップしました。

今回は、エージェントをただラグドール化するのではなく、Stateの遷移を引き起こすボリュームにエージェントが含まれたらラグドール化し、そのボリュームから発せられる力を受けて、任意の方向に飛ばされるように設定するようにしてみました。
動画では非表示にしていますが、足元からエネルギー球がせり上がり、それに触れたUnityちゃんエージェントが吹き飛ばされているように設定してあります。

左に見えている点群がイベントボリューム兼カスタム外力発生器です。
各点から出ている黄色のラインは力の方向です。

この点群を大型キャラの四肢にアタッチするなどしておけば、自然と、大型キャラに暴れるモーションを付けるだけで、群がる群衆をを次々となぎ倒していくような絵が作れますね。

Houdiniのforループを理解する

■ループの種類と用途

  1. forループ
    入力したジオメトリデータ全体を対象に、複数回の処理を繰り返し行う

    (例)
    Copy SOPで複製されたジオメトリ全体を対象に、Mountain SOPの処理を累積的に10回行う。

  2. for-eachループ
    入力したジオメトリ内の個別のピースに対し、同一の処理を行う

    (例)
    複数のグループを複数含むジオメトリデータから、特定のルールにマッチするグループを1つずつ個別に取得し、個別にバウンディングボックスに置き換える。

  3. Fetch FeedbackとFetch Pieceの組み合わせ
    ループに入力されたジオメトリに対し、別の入力されたジオメトリのピースを使って反復的かつ累積的に処理を行う事ができる。

    (例)
    穴あきチーズ
    複数の気泡オブジェクトを入力し、気泡オブジェクトのピースごとに、チーズの本体に累積的に繰り返し削り取る。


■forループ

Tab -> For loop

オレンジ色に囲まれた部分がforループのブロック

MountainSOPを挿入しHeightを0.1に変更
Block EndのIterationパラメータを変化させると、各ボックスが累積的に変形することが確認できる。

※同じ設定のMountainSOPで同一の処理を累積的に10回繰り返すことと同じなので、CopySOPの下流にMountainSOPを10個作って直列で繋げても同じ結果が得られます。


■for-eachループ

Tab -> For-Each loop

ピース単位の処理。
ピースは、ピースを表現するアトリビュート(通例では@name)の値の同一性により表現される。

For-Each loopを作成した直後は、上流に@pieceや@nameがないためにエラーが出る場合がある。このような場合は、自分でアトリビュートを作成する。

Voronoi Fracture SOPなど、自動的に@nameを作るSOPもある。
Voronoi Fracture SOPのデフォルト設定では、破片ごとにPrimitiveクラスの@nameを作成し、piece+番号の命名規則で各Primitiveが所属するピースを表現する。

@name以外のアトリビュートを使用してピースを表現したい場合は、Block End SOPのPiece Attributeを変更し、任意のアトリビュート名を指定する。

CopySOPで複製したBoxをVoronoi Fracture SOPで分割

For-Each loopにより、各破片のピースをバウンディングボックスに置き換え


■Block Begin SOP

・Methodパラメータ

  1. Fetch Feedback
    累積的に処理を行う場合に選択。
    ループが行われる度、前回のループで出力された結果を現在の処理対象としてフィードバックし、再Fetchする。
  2. Fetch Piece
    入力されたジオメトリ内のピースごとに処理を行う場合に選択。
    ループ毎に、認識されているピースの中から新たなピースがFetchされる。
  3. Fetch Metadata
    現在のループに関する情報をDetailsアトリビュートに持つジオメトリを作成する。
    detail関数を使ってこれらの値へアクセスし、ループ内で使用することができる。

・Block Pathパラメータ

このループの終点を示すBlock End SOPへのパス

・Create Meta Import Node

Metaデータを持つBlock Begin SOPを作成する。
この実態は、ModeがFetch Metadataに設定され、Block Pathが現在のループの終点にセットされているBlock Begin SOP。


■Block End SOP

・Gather Method

  1. Fetch Each Iteration
    ループが終わるたびに結果を再Fetchする
    ループのBlock Begin SOPでMethodがFetch Feedbackの場合に使用する。
  2. Merge Each Iteration
    ループ毎にジオメトリを記録し、最後に結合する。
    ループのBlock Begin SOPでMethodがFetch Pieceの場合に使用する。
    ピースは個別に処理され最後に結合され、結果として出力される。

・Iterations/StartValue/Increment

ループ回数の指定
一般的なforループで使用する変数

・Python風に書くならこんな感じ
for i in range(startValue , iterations , increment):


■Block Begin SOP – Fetch Feedback と Fetch Pieceの組み合わせ

あるジオメトリに対し、別のジオメトリを使って複数回の処理を累積的に行う事ができる。
Block BeginのヘルプにあるSwiss Cheeseサンプルでは、チーズ本体のVDBオブジェクトを一つと、気泡のVDBオブジェクトを複数入力し、チーズ本体オブジェクトを気泡オブジェクトごとに削り取る処理を行う。

  1. チーズ本体のジオメトリを作成
  2. 気泡ジオメトリを作成
    SphereSOPをPolygonなどではなくPrimitiveモードで作成。
  3. Scatter SOP + Copy SOPでチーズ表面に気泡をばらまく
  4. For loopを作成
  5. チーズ本体を受け取るBlock Begin SOPの設定を確認
    Methodパラメータ:Fetch Feedback
    Block Pathパラメータ : Block Endへのパス
  6. 気泡を受け取るBlock Begin SOPを追加
    Block Begin SOPを作成後、以下のパラメータをセット。
    Methodパラメータ : Fetch Piece
    Block Pathパラメータ : 1で作ったBlock Endへのパスをセット
  7. Block End SOPを確認
    Iteration Method:By Pieces(またはAuto Detect from Inputs)
    Gather Method:Feedback Each Iteration
    Piece Elements:Primitive
    Piece Name:オフ
    Default Block Path/Piece Block Path:Block Begin

 


■Block Begin SOP – Fetch Metadataの使い方

  1. Block Begin SOPを作成
    Method:Fetch Metadata
    Block Path:末端のBlock End SOPへのパス

・Detailsアトリビュート

  • numiterations
    最大のループ回数
  • iteration
    現在のループ回数
  • value
    Pieceループ時、現在ループ中のピースを識別するためのアトリビュート値。
  • ivalue
    valueの整数版。
    valueがfloatの精度範囲を超える場合などに使う。
    特別な理由がなければ普段はこっちを使うのがよさそう

・Metadataを取得する

ループ内でdetailエクスプレッション関数を使う。

  1. detailエクスプレッション関数を使う
    detail(“../foreach_begin1_metadata”, “iteration”, 0)
  2. detail VEX関数を使う
  3. Import detail attribute VOPを使う
  4. Pythonで取得する
    node(“../foreach_begin1_metadata”).geometry().attribValue(“iteration”)

■ループの停止条件

Block End SOP の Stop Condition を1にセットすると強制的にループを終了できる。


■デバッグとテストループ

・Feedbackループの場合

Block End SOP の Max Iterationsパラメータで最大ループ数を設定できる。
数回のループだけである程度結果が判断できる場合は、このパラメータを有効化することでループ処理を制限し、素早く調整が行えるので便利。

・Pieceループの場合

Block End SOP の Single Passパラメータでピース番号を指定して、ピースごとの処理結果を確認できる。


■グループからピースを作成する

グループパラメータを使ってピースを作成したい場合は、Name SOPを使用して@nameを作成し、グループ名をセットしておく。


何か間違いがあれば突っ込んでいただけると嬉しいです

Houdini Crowd System – 002 / Transition + ragdoll テスト

先日から続けているHoudiniの群衆シミュレーション勉強。
今回は、状態遷移とラグドールの勉強。

正直、かなりどうかしている映像になってしまったけど、今日の勉強の成果としてアップ。

使用したエージェントの状態は、待機5種+歩行+ジャンプ+ラグドールによるシミュレーションと言った感じ。歩き出しがぎこちないのは、待機モーションと歩行モーションのポーズ差が大きすぎるからで、こういう場合は中間モーションを作ったほうが良さそう(特に回し蹴りモーションからの歩き出し)

群衆シミュレーションだと、手前のキャラは手付けで動かし、群衆は大写しになりにくい上に、もっとエージェントの密度が高い場合が多いと思うので、もう少しだけエージェントの状態遷移がスムースになればかなり見られる絵が作れると思ったりする。

Houdini 15.5 から搭載された Agent Terrain Adaptation も試してみたい。