Houdiniプラグイン開発備忘録1 ~ hcustomでのビルド編

最近どうしても自作したいSOPがあり、Windows環境でのHoudiniのプラグイン開発を少しずつ学び始めたので、忘れないようにメモしていこうと思います。

HDKリファレンスによるとHoudiniプラグインは、hcustomを使うことで簡単にビルドできるらしかったので、まずは初歩中の初歩として、最も簡単そうなこの方法から試してみました。

今回使用するコードは、サンプルにある「SOP_Star」です。


■環境の準備

まず、手元の環境ですが、以下のような状態です

  • Houdini 18.0.532 Indie
  • Visual Studio 2019 / 2017 / 2015 Community

■ビルド手順

・ソースコードをコピーする

以下のフォルダを、任意の場所にコピーします
今回は、以下のようにコピーしました

コピー元:$HFS\toolkit\samples\SOP\SOP_Star
コピー先:D:\temp\SOP_Star

※$HFSは、Houdiniのインストールフォルダで、デフォルトでは以下のようなパス

C:\Program Files\Side Effects Software\Houdini 18.0.532

・Command Line Toolsを起動する

スタートメニューで「Command Line Tools」と検索し、起動します。
「Command Line Tools 18.0.532」というタイトルの、Houdini関連の環境変数が一通り自動的にセットされたコマンドプロンプトが立ち上がります。

※どんな環境変数がセットされているかは「set」と打ち込んでEnterを押すことで一覧表示、確認できます。


・ソースコードのあるフォルダをカレントにします

cd /d D:\temp\SOP_Star

・protoヘッダファイルを作成します

generate_proto.pyを利用し、SOPプラグインのコード内で、ノードのパラメータを定義している文字列(theDsFile)の記述をもとに、パラメータへのアクセスを簡単にしてくれるprotoヘッダファイルを自動生成しておきます。

hython %HH%/python2.7libs/generate_proto.py SOP_Star.C SOP_Star.proto.h

・hcustomでビルドを実行します

hcustom SOP_Star.C

・ビルドの成功を確認

成功していたら、以下のフォルダにビルドで生成されたファイルができているはずです

%USERPROFILE%\Documents\houdini18.0\dso

SOP_Star.dll
SOP_Star.exp
SOP_Star.lib
SOP_Star.pdb

・Houdiniでプラグインの動作を確認する

・Houdiniを起動
・Geometryを内でTABメニューから[star]と検索し[Star]ノードを作成
・無事ビューポート内に星型のジオメトリが現れればOKです。


注意点


・protoヘッダファイルに関して

古い文献を探すと、protoヘッダファイル不使用の解説が見つかったります。
また、18.0.532 では、protoヘッダファイルを作らずにいきなり

hcustom SOP_Star.C

を実行すると、protoヘッダファイルが見つからずにビルドが失敗するようでした。

現時点では、protoヘッダファイルを使うことでプラグインのパラメータアクセスが簡単になるので、必ず用意するものと考えて良さそうだと考えています。

※2020/08/30 追記
サンプルのソースコードを覗くと、もともとprotoヘッダファイルをincludeするように指定されていました。
protoヘッダファイルを作らないとビルドがコケるよね、というオチ。

恐らくprotoヘッダファイルを使うやり方は新しいやり方なのだと想像できるし、何より便利な方法なので、積極的に利用するのが良さそうという見方は変わらず。

protoヘッダファイルの作成と、ソースコードでincludeする、ということをお作法としておけば間違いないのではないかと思いました。

Houdini – Python(HOM)によるパラメータ操作

 前回は、HOMによりノードを選択する操作に関する記事を書きました。
 ノードを探して選択するだけではあまりにも寂しいので、今回はそれに引き続き、ノードのパラメータを操作する部分に関するメモを書き残したいと思います。
 Houdiniでは、ノードパラメータへの操作パスが幾通りも存在しており、とても柔軟な作りになっていますが、今回は、自分がよくやる方法を取り上げます。
「もっと便利な方法があるよー」などの情報があればぜひ教えてください。
 ここで紹介する各種メソッドはあくまでも一例で、実際には更に多くのメソッドが用意されているので、ぜひマニュアルも参照してください。
http://www.sidefx.com/docs/houdini/hom/hou/Node.html
http://www.sidefx.com/docs/houdini/hom/hou/Parm.html
http://www.sidefx.com/docs/houdini/hom/hou/ParmTuple.html


パラメータ名の確認

 まず最初に、パラメータへアクセスする際はパラメータ名が重要になるので、名前の確認方法から。

Parameter Spread Sheetで確認

 Parameter Spread Sheetで、ノードとパラメータをツリー表示で確認できる。
 各項目名の右端にある、カッコで囲まれた名前が実際にアクセスする際に使用するパラメータ名。パラメータにはUniform Scaleのような単一の値を持つパラメータと、位置や回転など、複数の値をひとまとめにして持つ配列パラメータがある。
 下図を例にすると、Axis Divisionsという「ラベル」が付けられたパラメータは、実際の名前が「divrate」であり、3つの子要素(divratex/y/z)を持っている配列パラメータである事がわかる。

ドラッグ&ドロップで確認


 Python Source Editorに、パラメータラベルをドラッグ&ドロップすることで正しい配列パラメータのパスと名前が確認できる。

ポップアップウインドウで確認

 Parametersパネルなどでパラメータ名ラベルの上にマウスカーソルを置き、少し待つと、パラメータ情報がポップアップで表示される。この中の、Parametersの部分にある文字列が、このノードにおけるパラメータの正式な名前になる。

↑↑↑
 この場合、tx ty tz がパラメータ名であると確認できるが、あくまでも配列要素の単一パラメータ名のみがわかる。
 正確な親の配列パラメータ名が知りたければ、Parameter Spread Sheetを確認するなど一手間必要。(大体の場合、配列要素パラメータ名の末尾を削ったものが配列パラメータ名になっているっぽい)


おおまかなパラメータアクセスの方法

パラメータへのアクセス方法

  1. パスを指定して直接パラメータオブジェクト(hou.Parm)へアクセス
  2. 任意のノードのパラメータオブジェクト(hou.Parm)へ、名前を指定してアクセス

パラメータの操作方法

  1. ノードオブジェクト(hou.Node)を直接操作
  2. パラメータオブジェクト(hou.Parm)を通して操作

単一パラメータオブジェクトの取得

import hou

# パラメータオブジェクトを直接取得
parm_direct = hou.parm('parm_path')

# ノードオブジェクトからパラメータ名を使用してパラメータオブジェクトを取得 
node = hou.node("node_path")
parm_from_node = node.parm('parm_name')

配列パラメータオブジェクトの取得

 translateやrotateのような複数要素で構成されるパラメータは、配列パラメータとしてまとめて各要素へアクセスすることができる。
 基本的に、これは単一のパラメータへのアクセスと同じように行うが、その際は、parmTupleやevalParmTupleといった配列パラメータを扱う専用のメソッドを使う。

import hou

# 配列パラメータオブジェクトの子要素配列を取得
tupleParm_direct = hou.parmTuple('tupleParm_path')

# 配列パラメータの値を取得
# ノードオブジェクトをつかまえておく
node = hou.node("node_path")
tupleParm_from_node = node.parmTuple('tupleParm_name')

# 子要素へは、tupleParmの配列要素へアクセスして行う
tupleParm_x = tupleParm_from_node[0]
tupleParm_y = tupleParm_from_node[1]
tupleParm_z = tupleParm_from_node[2]

単一パラメータの値を取得する

 eval~と名づけられたメソッドを使う。
 型を指定して型変換しながら値を取得するメソッドや、時間指定で値を取得するメソッド、それらを組み合わせて取得するメソッドなどもある。

import hou

# 単一パラメータオブジェクトから現在の値を取得
parm = hou.parm("parm_path")
parmValue = parm.eval()

# 型を指定ながら現在の値を取得
parmIntValue = parm.evalAsInt()
parmFloatValue = parm.evalAsFloat()
parmNodeValue = parm.evalAsNode()

# 指定時間での値を取得
parmAtFrameValue = parm.evalAtFrame( FRAME_NUMBER )

# 型指定と時間指定の組み合わせ
parmIntAtFrameValue = parm.evalAsIntAtFrame( FRAME_NUMBER )
parmFloatAtFrameValue = parm.evalAsFloatAtFrame( FRAME_NUMBER )

配列パラメータの値を取得する

 配列パラメータの値取得も、単一パラメータとほぼ同様。

import hou

# 配列パラメータオブジェクトから現在の値を取得
tupleParm = hou.parmTuple("parm_path")
tupleParmValue = tupleParm.eval()

# 型を指定ながら現在の値を取得
parmIntValues = tupleParm.evalAsInts()
parmFloatValues = tupleParm.evalAsFloats()
parmNodeValues = tupleParm.evalAsNodes()

# 指定時間での値を取得
parmAtTimeValue = parm.evalAtTime( TIME )
parmAtFrameValue = parm.evalAtFrame( FRAME_NUMBER )

# 型指定と時間指定の組み合わせ
parmIntAtFrameValues = parm.evalAsIntsAtFrame( FRAME_NUMBER )
parmFloatAtFrameValues = parm.evalAsFloatsAtFrame( FRAME_NUMBER )

パラメータをセットする

 値のセットは、set系メソッドを使う。
 事前に組み立てたパラメータ辞書をsetParmsメソッドに与え、一括で値をセットするのがとても楽なのでおすすめ。
 その際、setParmsメソッドでは配列パラメータ名は認識できないので、各要素を個別の単一パラメータとして辞書に値を用意します。

import hou

# 単一パラメータに値をセットする
parm = hou.parm('parm_path')
parm.set( value )

# 配列パラメータにシンプルな値の配列をセットする
tupleParm = hou.parmTuple('parm_path')
tupleParm.set( (value1, value2, value3, ...) )

# ノードオブジェクトを使い、複数のパラメータをまとめてセットする
# セットしたいパラメータを辞書に溜め込み、setParms()に与える
parmDict = { 'parm_name1':value1, 'parm_name2':value2, ... }
node = hou.node('node_path')
node.setParms(parmDict)

 以上、何か不明点などあれば気軽にご質問ください。
 間違いがあればツッコミも大歓迎です。

Houdini – Python(HOM)によるノード選択(改題)

 あるシーンを開いた時、膨大なオブジェクト数とグループ数により階層が深く、なおかつすべてのノードに命名規則が全く見当たらないという、ツール作成者視点でシーンを眺めた時、ついつい汚い床の上をのたうち回りたくなるような状況は往々にしてあることなのですが、そのような状況下でのHoudiniの選択操作は、他のDCCツールよりも遅れを取っている気がします。

 例えば、ビューポートで選択しているすべてのノードの親を簡単にまとめて再選択したかったり、同様に、選択アイテムのすべての下流ノードをまとめて選択したかったりと言った状況です。

 ノードを右クリックして表示されるポップアップメニューから、Inputs/Outputsサブメニューを駆使してみるも、単一の選択アイテムのみがノード検索の起点になるので、複数のノードツリーをまとめて選択できなかったりします。

 このように、意外とHoudiniデフォルトの選択機能に痒いところが多かったので、選択補助スクリプトを書いてみました。

 ついでと言ってしまうと何だか申し訳なく感じてしまうのですが、簡単なメモを書き残したいと思います。


基本的なノード検索

import hou
nodeObject = hou.node("node_path")
import hou
selectedItems = hou.selectedItems()

ノードの親子関係

import hou
node = hou.node('node_path')

# parent()は自身を含む外側のノードの取得
outerNode = node.parent()
print( outerNode )

# children()は自身が含む内側のノードリストを取得
innerNodes = node.children()
print( innerNodes )
import hou
node = hou.node('node_path')

# inputs()は自身へ入力している直接の上流ノードリストの取得
inputNodes = node.inputs()
print( inputNodes )

# outputs()は自身が直接出力しているノードリストを取得
outputNodes = node.outputs()
print( outputNodes )

# inputAncestors()は直系の祖先をまとめて取得
ansectorNodes = node.inputAncestors()
print( ansectorNodes )

かなり基本的なところではこんな感じ。
他にもたくさんあるので、その他のメソッドは要マニュアル参照。

出力の全子孫を取得するメソッドがパット見見つからない感じだったので、再帰的に子孫をたどる関数を作ってみる。

import hou

def getDescendents(node, *args, **kwargs):
    """
    DESCRIPTION : 引数で与えたノードから再帰的に子孫を辿って返す
      ARGUMENTS : node : 探索を開始するノード
         RETURN : descendents : 見つかった子孫ノードリスト
    """
    descendents = []

    direct_outputs = node.outputs()
    if direct_outputs:
        for direct_output in direct_outputs:
            descendents += [direct_output]
            descendents += getDescendents(direct_output)
   
    return descendents
    
node = hou.node('node_path')
descendents = getDescendents(node)
for descendent in descendents:
    print( descendent )

選択操作

import hou
node = hou.node("node_path")

# 選択 setSelectedメソッドにブール値をセット 1で選択0で非選択
node.setSelected(1)

# 選択解除
node.setSelected(0)

 上記を組み合わせて、現在のノードから1つ上流のノードを探し、再度全下流ノードを探して全選択を行うなどの便利なコマンドを作成できます。

Interactive Houdini

Houdini Advent Calendar 2016 18日目に投稿させていただいた記事です


■はじめに

Houdiniには外部デバイスからインタラクティブにデータを入力する機能があります。

今回は、その機能を利用して外部に接続した各種フィジカルデバイスの操作から、Houdiniへデータを入力する方法を色々と書いてみたいと思います。

手頃に試せそうな例として、ここでは以下のデバイスからの入力を試してみます。

・マウス
・ペンタブレット

・キーボード
・MIDIコントローラ


■環境

・Windows10 64bit
・Houdini Indie 15.5.673


■まずは今回取り上げるCHOPに共通する予備知識

今回取り上げるCHOPは以下の通りです

Mouse CHOP
Keyboard CHOP
MIDI In CHOP

・CHOPデータの在り処について

CHOPのデータは、「CHOPチャンネル」が持っています。
CHOPチャンネルのデータへアクセスする際は、CHOPノードだけではなく、CHOPノード内にあるCHOPチャンネル名まで指定する必要があります。

・CHOPデータの詳細確認

CHOPノードを中ボタンでクリックするとCHOP内のチャンネル数や、開始/終了フレーム、サンプルレートなどの詳細情報が表示されます。

赤く囲んだ部分には、このCHOPに含まれるチャンネル名と値が表示されています。
この例の場合、ステレオのオーディオファイルを読み込んでいるので、このCHOPには左右2チャンネルの波形データが含まれていることがわかります。
そして、チャンネル名は左右でそれぞれ chan0 chan1 が割り当てられています。

・CHOPチャンネルへのパス

CHOPチャンネルへのパスは、CHOPノードへのパス+CHOPチャンネル名で指定します

~/chop_name/channel_name

上記の例で言うと、WAV_FILE(File CHOP) の chan0 チャンネルへのフルパスは以下のようになります

/obj/driver_chopnet/WAV_FILE/chan0

・CHOPチャンネルへのアクセス

CHOPチャンネルの各情報は、chop系エクスプレッション関数を使って取得します。
これらの詳細はマニュアルを参照してください。

  • chop : 現在時間でCHOPチャンネルの値を取得
  • chope : CHOPチャンネルの終了インデクスを取得
  • chopf : 指定フレームでCHOPチャンネルの値を取得
  • chopi : 指定サンプルポイントでCHOPチャンネルの値を取得
  • chopl : CHOPチャンネルの長さをサンプル数で取得
  • chopn : CHOP内のチャンネル数を取得
  • chopr : CHOPのサンプルレートを取得
  • chops : CHOPチャンネルの開始インデクスを取得
  • chopt : 指定した時間でのCHOPチャンネルの値を取得
  • chopcf : 指定フレーム、指定チャンネルインデクスでCHOPチャンネルの値を取得
  • chopci : 指定サンプルポイント、指定チャンネルインデクスでCHOPチャンネルの値を取得
  • chopct : 指定時間、指定チャンネルインデクスでCHOPチャンネルの値を取得
  • chopstr : 現在時間でのCHOPチャンネルの文字列データを取得

先の例で登場したCHOPチャンネルが持つ現在時間のデータが欲しい場合は、データを呼び込むパラメータボックス内で以下のエクスプレッションを使用します。

chop( “/obj/driver_chopnet/WAV_FILE/chan0” )

・CHOPチャンネルの波形データを確認する

CHOPの波形データは、CHOPノードの右端にある[Graphフラグ]をオンにすることで、Motion FX Viewに表示されます。

Motion FX View

・CHOPデータのサンプルレート

CHOPでは様々な波形データを扱うことができます。
波形データは自由に再生時のサンプルレート(一秒あたりのデータ数)を指定できるようになっています。

サンプルレートの高いオーディオデータを30fps設定のシーンでアニメーションカーブとして使う場合、サンプルレートを30にリサンプルしてから使用するのが負荷軽減につながって良いのではないかと思います。

サンプルレートは、File CHOPのChannelタブ内またはResample CHOPにあるSample Rateパラメータで指定します。

・上で読み込んだWAVデータの波形を30fps化したところ

・外部から入力されたデータのレコーディング

外部から入力されたデータを記録しておき、再生することができます。
MIDIデータを外部から入力するMIDI In CHOPなどは、入力されたMIDIデータを自分自身に記録する事ができるようになっていますが、Record CHOPを下流に接続してそちらで記録するほうがデータの取り回しの面で良いと思います。

レコーディングは、データ入力CHOPの下流に Record CHOPを接続し、Record パラメータをオンにした状態で行います。


■というわけで、早速やってみよう

CHOP入力→レコーディング→再生の流れは、今回紹介するどのCHOPでも変わらないので、まずはMouse CHOPの簡単なチュートリアルで作業の流れを掴んでみます。


■Mouse CHOPチュートリアル

1・CHOPコンテキストにCHOP Networkを作成します

2・CHOP Network の中に Mouse CHOPを作ります

3・Mouse CHOPのGraphフラグをオンにしたあと、Motion FX Viewを表示します

4・Mouse CHOPの下にRecord CHOPを接続します

5・Record CHOPのRecordパラメータをOnにし、シーンを再生します

この時、シーンのリアルタイム再生モードをオンにしておくと実際のフレームレートでレコーディングできます。

また、再生モードを Play Onceにしておくことでリピート再生をオフにし、うっかり開始フレーム付近を書き換えてしまうのを防げます。

6・マウスカーソルを自由に動かし、グラフが描かれていくのを確認します


各カーブの上には、対応するカラーでチャンネル名が表示されています。(geo1:txgeo1:ty

7・記録できたら、Record CHOPのRecordをOffにします

8・/obj/に新たにBoxオブジェクトを作ります

9・boxオブジェクトのTranslateX/Yにエクスプレッションを設定します

・TranslateX
chop(“../../ch/ch1/record1/geo1:tx”)

・TranslateY
chop(“../../ch/ch1/record1/geo1:ty”)

10・シーンを再生して結果を確認します

boxオブジェクトが、マウスが動いたとおりに移動するのが確認できます。

11・ペンタブレットをお持ちの場合

Mouse CHOPのUse Tabletをオンにして、同様の操作をすることで、筆圧やペンの傾きなどを検知し、記録できる事がわかると思います。
Pressure CHOPチャンネルを boxオブジェクトのUniform Scaleパラメータにアサインすると楽しいかもしれません。


■データ入力手順のまとめ

これが外部入力系のCHOPを使うワークフローの基本的な手順です。
手順をまとめると以下のようになります。

1・外部入力を受け付けるCHOPを作成
2・CHOPにデータの窓口となるCHOPチャンネルを作成
3・任意のパラメータからchop関連のエクスプレッション関数でCHOPチャンネルのデータを参照する

これはその他のデータ入力CHOPを使った場合も同様です。
これだけ理解できれば、あとは応用して色々できるはず。


■Keyboard CHOPチュートリアル

Mouse CHOPに続いて、Keyboard CHOPチュートリアルです

1・CHOPコンテキストにCHOP Networkを作成します

2・CHOP Network内にKeyboard CHOPを作成します

3・Keyboard CHOPの設定をします

Name 1

どんな名前でもいいのですが、ここでは [key_a] と記入します。

Type 1

そのままにします。

Type 1の右側にあるプルダウンメニュー

 [A] を選択します。

同様に、Name 2~Name 4まで、それぞれ同様に s,d,w キーを割り当てます。


4・Interceptモードをオンにします

キーボード上にあるScroll Lockキーを押し、Interceptモードをオンにします。

現在時間がオレンジ色になります。

5・キーを押して反応を確かめます

 a,d,wを同時押しした様子

あとは、Mouse CHOPのチュートリアルと同じ方法で、下流にRecord CHOPを繋いでレコーディングできます。

Interceptモードではキーボード入力が無視されるのでマウスでシーンの再生ボタンを押してください。

chopエクスプレッション関数による値の取得も同様に行えます。


■MIDI In CHOPチュートリアル

最後に、MIDI In CHOPを使用してMIDIコントローラから入力するチュートリアルです

1・CHOPコンテキストにCHOP Networkを作成します
2・CHOP Network内にMIDI In CHOPを作成します

3・MIDI In CHOPの設定をします

・Sourceタブ

MIDI Source : MIDIIN2 (Launchpad Pro)
MIDI Channels : 1

※MIDI Sourceは、各々がお持ちのMIDIデバイス名を選択してください。

・Noteタブ

Note Scope : 0-127
Aftertouch Name : af

4・鍵盤を叩き、反応を確かめます

・ch1n60 (音階で言うとど真ん中の[ド])を押したところ

今回使用したLaunchpad Proはアフタータッチ対応のMIDIコントローラなので、打鍵後に鍵盤を更に押しこむ事でafチャンネルを変化させることができます。

あとは、Mouse CHOPのチュートリアルと同じ方法で、下流にRecord CHOPを繋いでレコーディングできます。

chopエクスプレッション関数による値の取得も同様に行えます。


■おわりに

今回ご紹介したテクニックを使うことで、直感的なデータ入力ができるようになります。

例えば、特定のキーを押したタイミングでパーティクルを発生させたり、ジオメトリの頂点カラーを変化させたり、キーボードを叩くたびに乱数のSeed値を変化させて、次々と予想だにしないビジュアルを作り出すこともできるでしょう。

また、キーを押す強さが強いほどパーティクルの発生量を多くしたり、色を濃くしたりと言った、複雑な表現もできるようになります。

Joy To Keyのようなツールを併用することで、手に馴染んだゲームコントローラを操作してHoudiniへのデータ入力を行うことも簡単にできます。

また、今回はNetwork CHOP や Pipe In CHOPは使用しませんでしたが、これらも使いこなせばなかなかに楽しいことができそうなCHOPなので、いずれ折を見て記事にしたいと考えています。

ここまで読んでいただき、ありがとうございました。
何か不明点や間違いがあれば、記事へのコメントやTwitterなどで質問、ツッコミください。


■おまけ

以降は、今回使用したCHOPのマニュアルみたいなものです。


■マウスからの入力 : Mouse CHOP

マウスのカーソル位置を取得することができます。
タブレットを使用している場合は、筆圧やペンの傾きなども取得できます。

・PositionX/Y

ここで指定した文字列が、それぞれに対応するCHOPチャンネル名になります。
画面左下から右上に向かって値が増えていきます。
Xの範囲は-1~1、Yの範囲は-0.8~0.8

・Use Tablet

オンにするとPressureやAngleなど、タブレット向けパラメータが有効になります。
PositionX/Yと同様、ここで入力した各文字列が、それぞれに対応するCHOPチャンネル名になります。


■キーボードからの入力 : Keyboard CHOP

指定したキーが押されたとき、Typeで指定した方法で値を入力します。

このノードを動作させるためには Interceptモード をオンにする必要があります。
Interceptモードをオンにするには、Scroll Lockキーを押しScroll Lockを有効化します。

Interceptモードが有効になると現在フレームがオレンジ色に着色されます。
この状態でキーイベントを取得するよう設定したキーを押すことで、値が入力されます。

・Modifier Keys

使用する修飾キーを指定します。

上記画像の例では、[A]を押している間だけCHOPチャンネル[a1]の値が1になります。

・Name

対応するキーが押されたときにオンになるCHOPチャンネル名を指定します。

・Type

キーを押したときの動作

Momentaly:押しているときだけ1を送出
Toggle:押すたびに0と1を切り替え
Count:押すたびに1ずつ値が増える
Pulse:押した瞬間だけ1を送出
Time:シーン再生中にキーが押され続けた時間

・Key

このチャンネルに値を入力するキーを選択


■MIDIデバイスからの入力:MIDI In CHOP

MIDIキーボードやフェーダーコントローラなどを使用して、Houdiniに外部から直接MIDIデータを入力することができます。
また、MIDIファイルを直接読み込む事もできます。

・Sourceタブ

MIDI Source

MIDI信号の入力ソースを指定します。
MIDIファイルやMIDIデバイスを選択できます。
インタラクティブな入力を行いたいなら、接続済みのMIDIデバイス名を選択します。
すでに作られた楽曲などを入力したいならMIDI Fileを選択します。

MIDI Channels

入力するMIDIチャンネルの番号を指定します。

入力するMIDIチャンネル数が多いと入力データの転送に時間がかかります。
例えば、使用するノート番号の範囲が0-127の場合、使用するMIDIチャンネル1つごとに128個のデータを解釈することになります。
そこでアフタータッチをポリフォニックモードで解釈させるとなると、さらに128倍の負荷がかかります。
そのため、インタラクティブに操作したい場合は入力するMIDIチャンネルを一つに絞ったほうがいいと思います。
複数のMIDIチャンネルを入力するのは、MIDIファイルを入力する時のみで良いでしょう。

入力を許可するMIDIチャンネル番号は以下のように指定できます

・スペース区切り(個別指定)

1 2 10 11

・ハイフンでつなぐ(範囲指定)

1-10

Channel Prefix

入力するMIDIチャンネル番号を識別するために付加される接頭辞を指定します。
ここで指定した文字列のあと、入力されたMIDIチャンネル番号が付加されます。
この値が[ch]の時、CHOPチャンネル名は以下のような書式になります。

ch1
ch2

Echo Messages to Textport

オンにすると、受信したMIDIメッセージの内容がHoudini Consoleに表示されます。
デバッグに便利なので必要に応じてオンにします。

・Recordタブ

レコーディングには、MIDI In CHOPの下流に接続するRecord CHOPを使用するので、ここでは何も変更しません。

・Noteタブ

主にMIDIコントローラの鍵盤部分とピッチベンドコントローラからの入力信号の解釈方法を決定するタブです。

・チャンネル名パラメータ

チャンネル名パラメータは、対応するMIDI信号を受けとるCHOPチャンネルを作成します。

Note Name : ノートナンバー
Velocity Name : ノートのベロシティ
Aftertouch Name : ポリフォニックキープレッシャー
Pressure Name : チャンネルプレッシャー
Pitch Wheel Name : ピッチベンド

ここで指定した文字列が、MIDIチャンネルを示す文字列のあとに続きます。
例えば、Note Nameの値が[n]なら、MIDIチャンネル1のノート番号60の値を受け取るCHOPチャンネル名は以下になります。

ch1n60

これらのCHOPチャンネル名パラメータに何か文字列が入力されると、その時点でCHOPチャンネルが作成され、データの入力待ちが開始されます。
使用しないMIDI信号の入力については負荷軽減のため何も入力しないのがベターです。

Note Scope

入力を受け付けるMIDIノートナンバーの範囲を指定します。
0-127の範囲が使えます。
Sourceタブの MIDI Channels と同様の書式で指定できます。

Note Output

・One Multiplexed Channel

ノート信号を番号ごとに分けず、一つのチャンネル内で押されたノート信号をすべてまとめて使用します。

この時、CHOPチャンネル名にノート番号は付かず、以下のような名前になります。

ch1n : チャンネル1のノートすべて

・Separate Channels

ノート信号をノート番号ごとに個別のチャンネルとして入力します。
この時、CHOPチャンネル名は以下のようになります。

ch1n60 : チャンネル1のノート番号60番
ch1n127 : チャンネル1のノート番号127番

Velocity

MIDI Note信号のVelocityをどのように受け取るか指定します。

・Off

Velocityを入力しません
ノートを入力するCHOPチャンネルの値はオンオフの2値になります。

・Note Amplitude

ノートを入力するCHOPチャンネルの値が、Velocityの値になります。

・Separate Channels

Note信号の値はオンオフの2値になります。
同時に、Velocityは次のVelocity Nameで指定された接頭辞とノート番号で表されるCHOPチャンネルの値として取得されます。

Normalize

入力されるVelocityデータの値の範囲を指定します。

・None

値は 0-127 の範囲で入力されます。

・0-1

値が 0-127 を 0-1 にマッピングした状態で入力されます。

・Controlタブ

MIDI CCメッセージに関する解釈方法を決定するタブです。
MIDIキーボードにスライダやツマミが付属している場合がありますが、そのような鍵盤以外のコントロールから送信されるデータに関する入力設定です。

・チャンネル名パラメータ

チャンネル名パラメータは、対応するMIDI信号を受けとるCHOPチャンネルを作成します。

Controller Name : CCメッセージ
Program Change : プログラムチェンジメッセージ

Controller Type

一般的なMIDI音源やDAWなどで共通してMIDI CCメッセージへ割り振られているコントロールを、わかりやすくプルダウンメニューから指定できます。

Controller Index

取り扱うMIDI CCメッセージのコントロールナンバーを直接指定します。
0-127の範囲が使えます。
Sourceタブの MIDI Channels と同様の書式で指定できます。

Controller Format

・7 bit Controllers

扱うCCメッセージがLSB/MSBに対応していない場合はこちらを選択します。

・14 bit Controllers

扱うCCメッセージがLSB/MSBに対応している場合はこちらを選択します。

Normalize

入力された値の範囲をリマップします。

・None

リマップしません

・0 to 1

0~1にリマップします

・-1 to 1

-1~1にリマップします

・On / Off

ブール値化します

Unwrap

これはちょっとよくわかりませんでした。
ラベルから察するに、ターンテーブルのような値をインクリメント/デクリメントするコントローラ向けに、値が0-127の範囲外にセットされたときに値をループさせるか否かを決めるオプションのような気がします。

 ・Sysタブ

MIDIクロックメッセージやシステムメッセージに関する解釈方法を決定するタブです。
今回は、手元で検証できる環境がないので端折りますが、この辺をちゃんと設定すれば外部のDAWとHoudiniの間で内部時間や、再生操作や停止操作などを同期できるはず。

KINGSGLAIVEセミナーおさらい プロテス – サンプルhiplcファイルあり

先週と同様、Indyzone主催のHoudiniセミナー「KINGSGLAIVE FINAL FANTASY XV メイキングストーリー」のおさらいをしていました。

というわけで、今日はプロテスのエフェクトです。

今回もサンプルファイルを付けてみました。
興味のある方はぜひ覗いてみてください。

■ダウンロード(One Drive)
MYAM_imitation_FF_KINGSGLAIVE_Protes.zip

zipファイル解凍後に出てくるHoudiniProjectフォルダにSetProjectしてからシーンを開いてください。


■シーンファイルに関して

うろ覚えではあるのですが、セミナーの説明では、@Cdアトリビュートを利用してプロテスエフェクトの「生成、再生、破壊」の振る舞いを決める部分で、ベースになるプロテス障壁の形状を分流してそれぞれを個別のグループに入れながらPaintSOPなどで@Cdの設定を行い、最後にMergeSOPで各データフローを統合してから、最終的に使用しないグループに含まれる成分を削除するという解説だったように思います。

今回は、この部分を極力簡略化し、カラーを独自アトリビュートに変換したあとでAttributeTransferSOPでレンダーモデルに転送する方法を試してみました。
空間転写は処理負荷が高いので、もう少し上手に軽い方法を使えばよかったと思っていたりします。


何か間違いなどあればご遠慮無くツッコミください。
とても喜びます。

Houdini – 基本的なデータの取り回し方(サンプルファイルあり)

Houdiniを触り始めた頃、アトリビュートや変数のデータの取り回し方がわかりにくいと感じていました。
中でも特にローカル変数の挙動がよく理解できずに、正しく値を操作できても何だか狐につままれたような気持ちになっていました。

最近はそのようなこともなくなり、意図したとおりに値を操作できるようになってきたので、実践している値の取り回し方を備忘録的な意味で書いてみます。

今回もまたちょっとしたサンプルファイルをつけてみます。

■ダウンロード(One Drive)
MYAM_attributeAccess_exsample


Houdiniで扱うデータのスコープ

Houdiniの中で主にユーザーが直接扱うデータには、以下のようにいくつかの種類とスコープがあります。

  • グローバル変数
  • ローカル変数
  • パラメータ
  • アトリビュート

Houdiniに触り始めた頃、わかりにくいと感じていた部分が、ローカル変数とパラメータとアトリビュートの相互の関係性です。

※グローバル変数は、環境に依存しすぎる気がするので、最近でも極力手を付けないようにしています。(パス文字列の簡略化などに使えばとても便利ですが)
よって、今回の記事では扱いません。


なにがわかりにくかったのか?

さまざまなチュートリアルを見ると、Attribute Create SOPを使い、アトリビュートを作成すると同時にローカル変数も定義して、下流のパラメータエクスプレッションの中でローカル変数を参照して処理をするやり方が散見されます。

これは勝手な印象ですが、特に古いバージョンのチュートリアルで顕著だったように思います。

チュートリアルの後、自分なりに実践してみてうまく値を扱えない時に感じたのが

  • データフローの上流で作ったはずのローカル変数が下流で参照できない・・・
  • と思ったらなぜか使える場合があったりする・・・
  • オペレータの性質から想像するに、マニュアルを引くまでもなく明らかに使えそうに思えるローカル変数が使えない・・・
  • オペレータのリファレンスマニュアルに記載されてるローカル変数が使えない・・・
  • アトリビュートを直接操作したい・・・ローカル変数を参照できない理由がわからない・・・まるで異次元に迷い込んだようだ・・・地獄だ・・・

といったあたりです。
平たく言うと

「ローカル変数が値の操作を混乱させる諸悪の根源」

のように感じていたわけです。


デジタルアセットとローカル変数の罠

また、このローカル変数というやつは、デジタルアセット上で使用できないという、恐るべき制約も手伝って、更に話をややこしくしています。
※ColorSOPのマニュアルに記載あり。
もしかしたら、全デジタルアセットの性質ということではなく、ColorSOPに限った話かもしれません

例えば、Color SOPはデジタルアセットで、Colorパラメータ内ではローカル変数を使用できません。上流で独自アトリビュートを作成し独自のローカル変数を作っても参照できません。

紛らわしいことに、そもそもColorSOP自体、上流で@Cdアトリビュート操作があることを想定していません。あくまでも@Cd操作の開始点として使用する前提です。

このような場合、Color SOPではなくPoint SOPを使用し、その中でローカル変数を参照することで問題なく@Cdアトリビュートを直接操作できます。

このような紛らわしさもまた、一つの地雷と言えそうです。


ローカル変数よ、さようなら

ではどうすればいいのか。

結論を言うと、Attribute Wrangle SOPを介してアトリビュートを直接管理や操作をするのがベストだと考えています。

最近のバージョンでは、VEX周りがかなり進化しています。
Attribute Wrangle SOP内だけでなく、各オペレータのパラメータエクスプレッションでも、VEXのように@記法で直接アトリビュートを参照したり、それができない場合でもpoint関数などを使ってアトリビュートを直接参照する事ができます。

値の管理と操作を、一貫したVEXの世界で行えるのは、この上なくシンプルで理想的です。
今のところこの方法で困ったことは一度もありません。

よって、もう、ローカル変数の出番は無いとすら思います。


まとめ

というわけで、今考えているわかりやすくデータを取り回すための方策は以下の通りです

  • アトリビュートの作成にはAttribute Wrangle SOPを使う
  • アトリビュートの操作にはAttribute Wrangle SOPやAttribute VOP SOPを使う
  • アトリビュートの参照は@記法を使って直接参照する
    またはpoint関数などのアトリビュート値を直接取得する関数を使う
  • ローカル変数は使わない
    $CEXなど便利なローカル変数はあるが、同様のVEX関数もあるのでそちらを使うようにする。

これが同じ悩みを持つ方の一助となれば幸いです。

何か間違いなどあれば遠慮なくツッコミをください。

Houdiniセミナーおさらい – サンプルhiplcファイルあり

先日、「Houdini プロシージャルモデリングテクニカルセミナー」が開催されました。

セミナー中に、CG World 2016年09月号のHOUDINI Cook Bookで掲載された作品「Spine」がどのように作られたかを、著者の秋元氏が自ら解説するという貴重なセッションがありました。

そこで感銘を受け、自分なりの解釈も踏まえつつ真似してみたくなり、早速作ってみました。
記事と解説をすべてトレースするのではなく、VOPで処理されていた場所をVEXで処理するなど、ちょっとだけアレンジしています。
シェーダーまでは手を付けられなかったので、簡易モデルと動きのみです。

今回は実験的に、Houdini Indie 15.5.523 で作成した hiplcファイルを公開してみます。
ファイルには、簡単なモデルと本体モデルとシリンダーの挙動を組み込んであります。
時間経過に伴いウネウネ動く仕込みもしてあるので、再生してみると楽しいかもしれません。

■ダウンロード(One Drive)
MYAM_imitation_spine_hiplc

“NATURE OF CODE” in Houdini – 002 – 力

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

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




 

■動画内容の御品書

  1. シンプルな力
    単純な力を与えて動かす。
  2. 力と質量
    質量を考慮しながらシンプルな力(重力+風)を与えて動かす。
    小さいものほど風の影響を受けやすく水平方向によく動く。
  3. 力と摩擦
    摩擦による減速。
  4. 力と流体抵抗
    Y=0の位置から流体があると仮定し、ポイントが抵抗を受ける。
    この流体の範囲に入った時、ポイントは赤くなります。
  5. 引力(固定アトラクタ)
    原点の位置に引力を発生するポイントを配置し固定。
    その他のポイントは原点のポイントと引き合う。
  6. 引力(ポイントの相互作用)
    各ポイントはそれぞれ相互に引き合う力を持っている。
    質量や引力の設定にもよるが、小さな塊ができ、それぞれの塊が引き合い徐々に大きな塊になっていく。(ポイント半径に合わせたコリジョンは設定していないので、実際には、塊は大きくなりません。)

2.0 Houdiniの単位系

Houdiniは標準的なSI単位を採用している。
https://www.sidefx.com/docs/houdini15.0/dyno/about
http://www.cranenet.or.jp/tisiki/si.html

2.1 力とニュートンの運動の法則

力とは、質量を持った物体に加速度を生じさせるベクトル。

・ニュートンの運動の第1法則(慣性)

静止している物体は静止状態を続け、運動している物体は不平衡力の影響を受けないかぎり、一定の速さで一定の方向へ運動を続ける。

・ニュートンの運動の第3法則(作用・反作用)

力は常に反作用が対になって生じ、その強さは等しく、向きは正反対である。

-F[N]

 押し合う物体の質量が違う場合や、接地面の摩擦力が違う場合、同じ力が加わってもそれぞれの物体が静止し続けるとは限らない。

2.2 力とProcessing

・ニュートンの運動の第2法則(運動方程式)

質量(m)に加速度(A)を掛けあわせると力(F)になる。

・重量と質量

  • 質量(m):物体内の物質の量[kg]
  • 重量(W):物体にかかる重力[N]
  • 密度(Density):単位体積(m^3)あたりの質量(kg)

2.3 力の積算

書籍では質量を1として計算し、F=Aとして簡単化する。

通常、物体にはいくつかの外力が同時に作用する。
そのような場合、全外力の合計値を使用する。
物体に働く力は、特殊な状況を除いて常に変化し、積算されないので、毎フレームで新たな値を計算して使用する。

windForceやgravityForceなどを追加してみる。

2.4 質量

先に計算された外力を質量で割ることで、各ポイントごとに設定された質量を考慮したシミュレーションが行われる。

2.5 力の作成

・力の作り方

  • 直接指定
    自由に直接指定する
  • シミュレーション
    物理公式などを利用し、計算する。

2.6 地球と重力と力のシミュレーション

この時点では、質量が小さいものほど重力の影響を強く受けるようになっている。
物体の落下速度は、その質量にかかわらず一定。
重力加速度の式から正しい加速度を求めて適用するため、重力に質量を掛けあわせてる。

2.7 摩擦

摩擦は散逸力(非保存力)。

※散逸は、抵抗力によって運動エネルギーを熱エネルギーに変換する現象。
散逸力とは、摩擦力などのエネルギーを減少し、別の力に変換させる力。
車のブレーキなどは運動エネルギーを熱エネルギーに変換。

・摩擦の種類

  • 静止摩擦
    接触面に対し静止している物体が受けている摩擦
  • 動摩擦
    動いている物体が接触面から受ける摩擦

摩擦の方向は速度方向の逆。

・摩擦の公式

・公式を使用する際のポイント

  • 右辺を計算し、左辺に代入する。
  • 変数がベクトルかスカラーかを見極める。
  • 記号が隣り合っている場合は乗算を表す。

■各項の意味

  • F friction:摩擦により働く力

  • μ:摩擦係数

    特定の表面上に生じる摩擦力の強さ。

  • N:垂直抗力
    物体が接触面から受ける反作用で接触面に対して垂直方向に物体を押し返す力。
    物体が接触面から受ける垂直抗力の大きさは物体の質量に比例する。
  • v:正規化済みの速度ベクトル
    式中の-1と掛けわせることで摩擦による力の発生する方向を取得する。
    この場合、速度ベクトルの真逆方向に摩擦力による力が発生する。

2.8 空気抵抗と流体抵抗

物体が空気や液体(流体)の中を通り抜けるとき、抵抗力が働く。
これは、粘性力(Viscous Force)や抗力(Drag Force)、流体抵抗(Fluid Resistance)などと呼ばれる。

・流体抵抗の公式

■各項の意味

  • F drag:流体を物体が通り抜ける際に働く抗力
  • 1/2:定数
  • ρ(rho):流体の密度
  • v^2:速度ベクトルの長さの2乗
  • A:進行方を向いている面の表面積
  • Cd:抗力係数、自由に決定する
  • v:正規化された速度ベクトル

2.9 重力

■重力の公式

■各項の意味

  • F gravity:重力
  • G:万有引力定数、独自の値を使っても面白い。
  • m1とm2:相互に作用する2つの物体が持つそれぞれの質量。
  • r:物体間の距離ベクトルを正規化したベクトル。物体同士が引き合う方向。
  • r^2:物体間の距離の2乗。
    物体同士が引き合う力は距離が長いほど弱くなり、近いほど強くなる。

2.10 相互引力と反発

2.9の計算を、各ポイント間で計算し、毎フレーム全ポイントの影響を合計して適用する。


何か間違いがあればツッコミをいただけると助かります。

“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/


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