HoudiniでBlenderのblend形式のファイルを読み込む(?)HDAについて

Houdini Advent Calendar 2022 23日目の記事です。

Blenderのblend形式のファイルを読み込む(ように見える)SOPのHDAを作りました。今回はこのHDAについて掻い摘んで解説してみようと思います。

作ったもの

File SOPとCharacter FBX Import SOPと対応する、Blend File SOPとCharacter Blend Import SOPを作成しました。

Blend File SOP

File SOPをラップしたHDAです。

Parameterに読み込みたいObjectが含まれたblendファイルのパスとObject名を指定します。 File SOPのようにReload Geometryボタンを押すことでBlenderのObjectがHoudiniのGeometryとして出力されます。 Geometryを読み込む処理がそこそこ時間がかかるので、hipファイルの読み込み時に毎回cookが走らないようStashしています。

Character Blend Import SOP

Character FBX Import SOPをラップしたHDAです。BlenderのArmatureを読み込む用途を想定しています 基本的な仕組みはBlend File SOPと同様ですが、出力はKineFXのCharacterとして扱えるよう、Skin MeshとRest Skelton、Pose Skeltonの3つを用意しています。そのため、Blend File SOPのようにGeometryをStashする部分では、一度Character Pack SOPでこれらの3つのGeometryを一つのGeometryにまとめてからStashしています。また、Shapekeyを保持したままModifierを適応できる機能がついています。

できるようになったこと

Blenderで編集を行って保存した後、ボタンひとつでHoudini上でblendファイルからモデルデータを読み込めるようになりました(キャラモデルだと数十秒ほど処理の時間がかかりますが)。Blender上でFBXの書き出し時に行わなければいけない設定を全てHoudini側に持っていけたので書き出しの手間がだいぶ減りました。

また、通常Blender上でのArmatureを使用したキャラクターモデリングでは、書き出し前に手動でModifierの適応やObjectのMerge等の破壊的な作業を行う必要があります。今回このフローが大きく改善されたのが嬉しい点でした。このHDAの特に強力な機能が、Shapekeyを保持したままModifierを適応できる点で、後工程のHoudini側で行うObjectの合成等の処理と組み合わせることで、今までBlender上で行っていた書き出しに伴う破壊的な作業をHoudini側で非破壊的に行えるようになりました。もっと早く作ればよかった。

読み込みの流れ

タイトルの疑問符部分の種明かしです。HoudiniでOperatorのReload Geometryボタンを押したときの処理ですが、HeadlessのBlenderを立ち上げてblendファイルを読み込ませ、FBXをエクスポート、Houdini側でFile SOP等でFBXを読み込んでいます。blendファイルをHoudiniで直接呼んでいるわけではないです。まあやろうと思えば自前でbgeo形式でBlenderからHoudiniにデータを渡せるような気もします(cf. satoruhiga/blender-houdini-geo-io)。

HeadlessでBlenderを立ち上げる

HoudiniのPython経由でReload Geometryボタンを押した際にBlenderを起動します。 Blenderはそこそこ充実したCLIが用意されているため、Houdini側でHDAのParameterを元に起動用のコマンドを組み立てていい感じにBlenderを動かしています。コマンドライン引数には-bオプションを指定してWindowを生成しないバックグラウンドモードで立ち上げています。また、起動した直後に実行するPythonスクリプトも指定できるので、FBXからの書き出し処理等を行うスクリプトと、この書き出しの挙動を指定するためのHoudiniのParameterの値も追加しています。

cf. Command Line Rendering — Blender Manual

コマンドができあがったらHoudiniのPythonでsubprocessを用いてコマンドライン経由でBlenderを立ち上げます。

Blender上で行う書き出し前の処理

コマンドライン経由で受け取ったパラメータを元に書き出し前の処理を行います。 書き出し前の処理は、書き出し対象になるObjectの選択と、ObjectのModifierの適応になります。

Objectの選択

選択はHoudini側でターゲットになるObjectを名前の文字列で指定すると、Blender側でそのObjectとその子になるObjectが選択されるようになっています。なお、Renderフラグが有効になっていないObjectについては選択しないようにしています。

Modifierの適応

BlenderからFBXとして書き出す前にそれぞれのObjectのModifierを適応します。一応FBXの書き出しオプションで自動でModifierを適応してくれる項目がありますが、これを使用すると適応時にObjectのBlendshapeが消えてしまうため、自前でどうにかしなければなりません。そこで今回はSKkeeperというBlenderのAddonをPython経由で呼び出し、Blendshapeを保持したままModifierを適応する処理を行っています。

github.com

ちなみにこのSKkeeperは後発のAddonということもあり、同じ機能を持ったApply Modifier等のAddonよりも概ね高速に動作するので、普通にBlenderモデリングする場合にもオススメのAddonです。

今回は、選択したObjectについて、UIでModifierを指定しShapekeyを保持したまま適応できるSKkeeperのOperator、sk.apply_mods_choice_skを使用します。

Blenderの任意のOperatorはPython上ではbpy.ops.<bl_idname >()(bl_idnameはOperator定義時のクラス変数bl_idnameを指す cf. Operator(bpy_struct) — Blender Python API)の形式で簡単に実行できます。

ただ今回動かしたいこのOperatorは、対象になるObjectと適応するModifierのListを状態(クラス変数)として持つOperatorであるため、上記の方法では実行できません。そこで今回はOperatorのインスタンスをでっちあげてexecute()メソッドを直接叩きにいくことにしました。 Operator(bpy_struct) — Blender Python API

とりあえず以下のような形でSKkeeperのOperatorを動かしています。対象となるModifierを格納できる適切なフィールドを持ったNamedTupleとしてDummyDefを用意し、Operatorのexecute()メソッドの第一引数にインスタンスとして突っ込んで実行しています。なおobjは対象となるObject、resource_listは適応する複数Modifierを指定します

DummyDef = namedtuple('DummyDef', ['obj', 'resource_list'])
...
SKkeeper.SK_OT_apply_mods_choice_SK.execute(DummyDef(object, resource_list), bpy.context)

ちょっと無理やりですがまあ動いているので良いでしょう。

ちなみにresource_listはCollectionPropertyと呼ばれるTypeで、直接扱いやすい形で読み込めないため一旦sceneに生やした後、context経由で取得しています。Sceneが汚染されますが今回の用途ではblendファイルを保存しないので問題無いです。

bpy.types.Scene.skkeeper_resource_list = CollectionProperty(name='Modifier List', type=SKkeeper.SK_TYPE_Resource)
resource_list = bpy.context.scene.skkeeper_resource_list

現状の課題ですが、「適切な順序」でModifierを適応することは現状できていません。そのためModifierがついているObjectをAttachしたModifierを持つObjectについては正しく処理を行えない場合があります。なお、Blender自体にはObject間のDependency cycleを検出する仕組みが存在するので、Python側から何らかのAPIを叩いて得られたDependencyを元にObjectをTopological Sortしてあげれば良いとは思うのですが、未だにこのあたりを触れるAPIを見つけられていない状況です。ご存知のかたは優しく教えて頂けると嬉しいです。

Blenderからの書き出し処理

bpy.ops.export_scene.fbx()でModifier適応済みの選択したObjectをFBXとして書き出しています。

cf. Export Scene Operators — Blender Python API

オプションが大量にあるので適切に設定を行っています。普段BlenderからFBXを書き出す際に毎回行う、Apply ScalingをFBX Allにする処理やApply Transformの設定もこのオプションで指定しています。

書き出したFBXをFile SOPで読み込む

Python側からOperatorのボタンを押しています。HoudiniのOperator上ではボタンも等しくParameterです。以下のような形でボタンのParameterをpressしてあげています。

node.node('file1').file.parm('reload').pressButton()

Stash SOPに読み込んだGeometryを突っ込む

ボタンを押してStashする使い方が一般的ですが、ボタンを押さずにPython経由で直接Geometryを保存することもできます。 そもそもでこのSOPがどこにGeometryを格納しているかの話になるのですが、実はOperatorのParameterにGeometryを格納しています。

これはGeometry Dataと呼ばれるTypeのParameterで、HoudiniのParameterには、普段よく使用されるスライダーやテキストボックスといった数値や文字列等を扱うParameterと同じように、Geometryを格納する種類のParameterです。

このGeometry Dataには以下の形でgeometryを設定しています。

node.node('stash1').parm('stash').set(file.geometry())

自明な余談ですが、GeometryはParameterに格納することができるので、Stash SOPのような機能をもつOperatorを手軽に自前で作ることもできます。 Geometry Dataの形式のParameterをInterfaceに追加してあげればOperatorがGeometryを持つことができるので、あとは入出力をPythonで記述してあげれば出来上がりです。入力はボタン等何らかの処理でこのParameterにGeometryを指定してあげればOKです。出力はPython SOPあたりでParameterから値を読み取りnode.geometryに代入することで、Operatorの出力としてGeometryが得られます。

まとめ

HoudiniのACでしたがBlenderに関する記述がそこそこ多めの分量になってしまいました。今回のように他のツールと連携する等、ワークフローを改善するために様々なアプローチができる点、また、HDAやHoudini Packageとしてパッケージングすることでその可用性を高めやすいところがHoudiniの良いところだと思います。

それではみなさん良いYak Shavingを。

HoudiniでPythonの静的型検査をやってみる

この記事はHoudini Advent Calendar 2021 10日目の記事です

Calendar for Houdini | Advent Calendar 2021 - Qiita

概要

みなさんは静的型検査は好きですか?

Houdini19から、デフォルトでダウンロードされるHoudiniのPythonがPython2系からPython3系になりました。とてもめでたいですね。 この3系のPythonにはTypeHintsという仕組みがあります(Python3.5から追加)。これを用いることでPythonのコードに対して静的型検査を行うことができます。

今回はHoudini上でPythonの静的型検査ができるか試してみました。

静的型検査とは

型検査とは、記述されたプログラムの変数や式の型に矛盾が無いかを調べることです。

静的型検査とは、プログラムを実行する前に型検査を行うことです。

一般にC言語やVEX等の静的型付き言語では、この型検査はプログラムを実行する前のコンパイル時に網羅的に静的に実行されます。一方、python等の動的型付き言語では型検査は基本的にプログラムの実行時に動的に行われています。そのため、動的型付き言語では実際に処理を動かしてみるまで型の矛盾に気がつくことができません。規模が大きいプログラムの場合、実装の隅々まで処理を実行し型の妥当性を検証することはなかなか大変です。

このような問題を解決するため、動的型付き言語での開発に静的型検査を導入するアプローチがあります。

  • javascript: 静的型検査を型注釈(Type Annotation)により部分的に導入することができる漸進的型付けのtypescript。コンパイル時に静的に検査を行う。
  • ruby: 最新バージョンのruby3で型注釈が記述できるように。
  • python: 同様のアプローチでpython3.5から型注釈をの記述をサポート。外部ツールで型検査を行う。

HoudiniでPythonを記述していると、静的型検査により不安要素を取り除きたくなることがあると思います。今回はHoudini上でどうにかして静的型検査を行うことはできないか調べてみました。

どうやってHoudini上で静的型検査をするか

Pythonで静的型検査を行う際に用いられる機能、ツールについて紹介します。

TypeHints

Python3.5から導入された静的型解析のための仕組みです。

docs.python.org

Python側行ってくれるのは型注釈の構文のチェックにとどまっており、検査を行うためには外部のツールが必要になります。

mypy

github.com

Pythonのコードに記述された型注釈から静的型検査を行うための静的型検査器です。 コマンドライン上から実行します。 こちらはpip経由でインストールができます。

mypyはCLI経由で動作するため、Houdiniから後述するsubprocess moduleを用いてmypyを実行することで静的型検査を行うことにします。 また、Houdini上でPythonのコードを記述できる箇所はいくつかありますが、今回はPython SOPのpython ParameterとHDAのdefinition(PythonModule等)に記述されたpythonのscriptを検査の対象として考えていきます。

Houdini上のPython Scriptの取得

今回検査の対象となるPython SOPのpython ParameterとHDAのdefinitionからpythonのscriptを取得する方法について紹介します。どちらの場合も選択した検査対象になるscriptを持ったnode(hou.Node)からスタートして内部からscriptを取り出していきます。

Python SOP

単純にhou.Node classのインスタンスからpythonのテキストが格納されているパラメータをparm("python").eval()により取り出します。

HDADefinition

こちらはPython SOPに比べて結構深めのところにpythonスクリプトが格納されています。

まず、nodeのtype()からoperatorの雛形を表すclassであるNodeTypeを取得します。

HDAのNodeTypeはHDAの定義を表すHDADefinitionというclassを持っています。

こちらはhou.NodeType.definition()から取得できます。なおHDAではない場合はNoneが返ってくるため選択されたnodeがHDAによるものかどうかをここで判定しています。

さらにこのHDADefinitionには、HDASectionと呼ばれるHDAを構成するための様々な情報が格納されています。

HDAのscriptタブから記述できるPythonのscriptもHDASectionの形式でHDADefinitionに保持されています。このHDASectionはhou.HDADefinition.sections()からsection name -> HDASectionの辞書形式で取得ができるため、HDADefinitionを構成する複数のsectionをPythonのコードを保持するであろうsection nameでフィルタし、対象となるPythonのscriptのテキストを取得しています。なおPythonのコードを持つSection Nameは以下になります。

  • Expressions
  • PythonModule
  • BeforeFirstCreate
  • OnCreated
  • OnLoaded
  • OnUpdated
  • OnDeleted
  • AfterLastDelete
  • OnInputChanged
  • OnNameChanged
  • OnInstall
  • OnUninstall
  • SyncNodeVersion

mypyをCLI上で実行する

mypyは*.pyファイルを引数に渡すことでそのファイルに対して検査を行います。これに加えて-c(--command)optionで直接pythonのコードをテキスト形式で渡すこともできます。

$ mypy -c <PROGRAM_TEXT>

cf. The mypy command line — Mypy 0.910 documentation

今回は上記で取得したhoudini上のpythonのscriptをmypyに-coptionで渡して型検査を行っています。

subprocess module

CGWORLDクリエイティブカンファレンス2021でも紹介した、任意のコマンドを子プロセスとして実行するためのmoduleです。 subprocess.run(args)でargsに指定されたコマンドを実行します。返り値はsubprocess.CompletedProcessclassとなっており、標準出力と標準エラー出力をfieldのstdout、stderrからそれぞれ取得することができます。

今回はmypyをこのsubprocess.runを用いて実行し、型検査を行います。

command += [
    'mypy'
    '-c',
    code
]
proc = subprocess.run(command, stdout=PIPE, stderr=PIPE, text=True)
if proc.returncode == 0:
    print(proc.stdout)
else:
    print(proc.stdout)
    print(proc.stderr)
return

houpytypechecker

Houdini上でtoolshelfから型検査を行うツールを作りました。Houdini Packageの形式となっています。

github.com

機能

  • Python SOPとHDADefinitionのPythonの型検査
  • 環境変数によるmypyへ渡す追加のオプション設定 MYPY_ADDITIONAL_OPTIONSでspace区切りで指定ができます。 f:id:ttata:20211210184140p:plain

導入方法

手順

  1. [mypy]をhoudiniから実行できる場所に追加します。

    • houdiniのshellからmypyに対してpathが通っているか確認して下さい。
  2. [houpytypechecker]をhoudini packageとしてインストールします。

    • cf. Houdiniパッケージ
    • e.g. $HOUDINI_USER_PREF_DIR/packages内にhoupytypecheckerのファイルとhoupytypechecker.jsonを配置する
  3. Houdini上でToolShelfを追加します。

  4. 推奨のmypyの追加のオプションを設定します。

    • Aliaces and Variables Windowsを立ち上げます(Alt + Shift + V)
    • MYPY_ADDITIONAL_OPTIONSキーを追加し値には-v --ignore-missing-importsを指定して下さい。
      • -vはエラー時に詳細を表示するようにするオプションです。
      • ignore-missing-importsはモジュールが見つからない場合にそれを無視するオプションです。
    • 他のオプションについては以下を参照して下さい

試してみる

Python SOPに以下のscriptを記述します。

x = [] # type: List[str]
def add(lhs: str, rhs: str)-> str:
    return lhs + rhs
# ok
add('a', 'b') 

# error
# add(1, 2) 

Operatorを選択した状態でShelfのToolをクリックし型検査を実行します。 Houdini Consoleに以下が型検査の結果が表示されます。

Python SOP
========
Success: no issues found in 1 source file

次は型が矛盾している場合を試してみます。最後の行のadd(1, 2)コメントアウトを外して検査を行ってみます。

x = [] # type: List[str]
def add(lhs: str, rhs: str)-> str:
    return lhs + rhs
# ok
# add('a', 'b') 

# error
add(1, 2) 

Houdini Consoleに型の指定が誤っている箇所が表示されました。

Python SOP
========
<string>:15: error: Argument 1 to "add" has incompatible type "int"; expected "str"
<string>:15: error: Argument 2 to "add" has incompatible type "int"; expected "str"
Found 2 errors in 1 file (checked 1 source file)

課題

まだ試せていませんが、Houdini Object Model (HOM)のscriptは型定義がされていないため、このようなscriptを検査対象に含める場合は別途、以下のような手段で型情報を与える必要があります。

HOMのような型定義されていないライブラリについては、これを元に生成した型定義ファイルを環境変数MYPYPATHに対して指定し、mypyに読ませることで型検査を行うことができると思います。

また、今回作成したツールは、Houdiniで記述されたpythonすべてに対して型検査ができるわけではなく、上記にもあるようにPython SOPとHDADefinitionに記述されたもののみの対応となっています。ToolShelfやTOPのpython等他の部分に記述されたものも検査対象にできると良さそうです。

まとめ

  • mypyによるpythonの静的型検査をHoudini上で行うため実験的にツールを作りました。
  • Python SOPとHDADefinitionに記述されたpythonのみ検査することができます。
  • 型付けされていないmoduleに型定義を追加した場合の検査は未検証です。

いつかHOMに型定義が記述される日が来るのをたのしみにしています。

HoudiniによるプロシージャルUnityプロジェクト

この記事はHoudini Advent Calendar 2020 21日目の記事です

f:id:ttata:20201221225914p:plain

はじめに

HoudiniとUnityによる普通のワークフローでは,FBX等のリソースを書き出すたびにUnity上で行う作業が発生しがちです.マテリアルの割当てだったり,Prefabを構成するGameObjectの数が変わった際のPrefabの編集等,一つずつ職人の手によるぬくもりのある手作業が必要になります(UnityのEditor拡張である程度自動化できますが...).Houdiniで大量にデータを書き出せる分,Unityで扱うデータが増えて作業の時間もかかりますし,当然ヒトが手作業で頑張るのでヒューマンエラーの温床にもなります.

このあたりのツール間のギャップをどうにかしたいなと前から思っていたのでゴリっとどうにかしてみました.

(できたてほかほかのため,あらゆる部分がナイーブな実装になってます)

思いついたアプローチ

さしあたって次の選択肢を思いつきました.

  • HoudiniEngineでがんばる
  • HoudiniからUnityProject内のYAMLをゴリゴリ書き換える
  • HoudiniからUnityをどうにか操作する

HoudiniEngineでがんばる

www.sidefx.com

HoudiniEngine For Unity自体はコアのdllをUnityとの橋渡しを行うC#で包むような作りになっています.なので外側の処理を書き換えることで,あるいはイベント周りの設定で生成の度に任意の処理を実行することができそうです.ただどうしてもHoudiniそのものを用いるよりも制約が厳しいので今回は見送りです.あとHoudiniから一歩も外に出たくないので.

HoudiniからUnityProject内のYAMLをゴリゴリ書き換える

Unityの*.prefab*.unity*.meta*.asset等のアセット関係の主要なファイルはYAMLというフォーマットで記述されています. こちらをHoudini経由で書き換えていく方法もありかなと思いました.

github.com

python3で動くunity向けのYAML parserが存在します. 最近のHoudiniのPython3ビルドがそこそこ安定しているので使用できそうです.

また,アセットを新規に追加した後の*.metaの生成もHoudini経由からできます. unityをコマンドライン上で実行する際に,--batchmodeオプションを追加することでヘッドレスで起動できます.また--quitオプションによりスクリプトの実行を指定しない場合に即時に終了させることができます. HoudiniからFBXをUnityのProjectディレクトリに書き出した後に,以下のコマンドでunityを一瞬だけ立ち上げることで*.metaファイルを生成することができます.

$ Unity.exe -quit -batchmode -projectPath <ProjectPath>

このYAMLを扱う方法は,アセットデータという低レイヤを直接操作するため強力ではありますが,GUIDの取り扱いも自前でどうにかしなければいけないため今回は見送りです.

HoudiniからUnityをどうにか操作する

何らかのプロセス間通信によりHoudiniとUnityを操作する作戦です. Socketあたりが手軽で良いかなと思いました.

Socketによる方法ですとUnity上でアセット等を取り扱うことになるので,UnityEngineの機能の強力なサポートが期待できます.

できたもの

Client

f:id:ttata:20201221223556p:plain

unity_send_command ROP Houdini側のSocket Clientです. Unityで実行したい処理を表すコマンドを文字列で指定します.このROPを実行するとUnity上で動いているSocket Serverへメッセージを送信します. なお,送信するコマンドを書き換えることで,様々な処理をUnityで行うメッセージを飛ばすunity_send_command ROPを作成することができます.

Server

Unity側で動くSocket Serverです.UniTaskを使っていい感じに非同期で走らせています.

Houdini側のClientからメッセージを受け取り,メッセージで指定された任意のスクリプトを実行します.

使い方

下準備

  1. 実行したい処理を書いたscriptをUnity上に用意する
  2. Houdiniでunity_sened_command ROPを作成する
  3. unity_sened_command ROPのメッセージを入力するパラメータに,最初に用意したscriptのClassとMethodと引数を表す文字列を指定する

実行

  1. Unityでサーバーを立ち上げる
  2. Houdini側でunity_sened_command ROPを実行

動作確認も兼ねて試しにFBXの書き出し操作とPrefabの作成を行うROP Networkを作ってみました.

以下のような構成になっています. f:id:ttata:20201221203641p:plain

filmboxfbx_geo

通常のfilmboxfbx ROPはObject Nodeしか指定できないので,そこをSOPのパスを指定できるよう拡張したものです.(今回の話とはあんまり関係無いです) 今回はいつものpigheadを書き出します.

f:id:ttata:20201221205352p:plain

unity_asset_database_refresh

Unity上でAssetDatabase.Refresh()を実行するメッセージを飛ばすunity_send_message ROPです. AssetDatabase.RefreshはUnity上でのアセットの情報の更新を行います. これを実行することで,上流でUnityのプロジェクト内に書き出されたFBXを読み込み,*.metaファイルを生成,Unityのアセットとして扱えるようになります. ちなみに上記のunityをbatchmodeで一瞬だけ立ち上げる方法に比べて処理時間が圧倒的に短いです.それはそう.

cf. AssetDatabase-Refresh - Unity スクリプトリファレンス

unity_create_prefab_from_fbx

Unity上で,指定したパスのFBXからPrefabを作成する処理を実行するメッセージを飛ばすunity_send_message ROPです. ROPのパラメータにはUnityプロジェクト内のFBXを示すパスを設定します. なおUnity側の処理についてはこちらのスクリプトをお借りしてます.

unity_exit

Unity上で動いているServerを終了するunity_send_message ROPです. メッセージのパラメータには"exit"を指定しています.

実行してみる

実際に書き出し処理を行ってみます.Unity上でServerが実行されていることを確認し,Houdiniで一連のROPを実行します.

f:id:ttata:20201221205854p:plain

無事にFBXが書き出され,Prefabも一緒に生成されました.やったー.

実装

ところどころピックアップして解説します

Client

unity_send_command rop Houdini側のSocket Clientです.ごくごく普通にpythonのsocketを使って実装しています.

なおこのClientから飛ばすペイロードは,Unityで動かしたい処理を表す次のフォーマットの文字列になっています.

<TypeName>.<MethodName>:<Args>

また,Serverを終了したい場合はexitを飛ばします.

Server

Unity側で動くSocket Serverです.適当に書きましたが今の所うまく動いているのでセーフです.

[MenuItem("Tools/StartServerTask")]
static public async UniTaskVoid StartServerTask(){
    System.Net.Sockets.TcpListener listener;
    System.Net.Sockets.TcpClient client;
    var ipStr = "127.0.0.1";
    System.Net.IPAddress ip = System.Net.IPAddress.Parse(ipStr);
    var port = int.Parse("28820");
    listener = new System.Net.Sockets.TcpListener(ip, port);
    listener.Start();

    await UniTask.Run(async() =>
    {
        do
        {
            // Clientと接続
            client = listener.AcceptTcpClient();
            var ep = (System.Net.IPEndPoint)client.Client.RemoteEndPoint;
            Debug.Log(string.Format("Connect Client: ip: {0} port: {1})", ep.Address, ep.Port));

            System.Net.Sockets.NetworkStream ns = client.GetStream();

            ns.ReadTimeout = 10000;
            ns.WriteTimeout = 10000;
            System.Text.Encoding enc = System.Text.Encoding.UTF8;

            // Messageの受け取り
            var message = ReceiveMessage(ns, enc);
            Debug.Log(message);
            if (message == "exit") break;

            // Threadを一時的に切り替えてMessageが示す処理を実行
            await UniTask.Yield();
            var result = EvaluateMessage(message);
            await UniTask.SwitchToTaskPool();

            // 事後処理
            ns.Close();
            client.Close();
        } while (true);
    });
    listener.Stop();
}

以下のループでClientからのメッセージを処理します.

  1. Clientと接続します
  2. 受け取ったペイロードの文字列<TypeName>.<MethodName>:<Args>をパースし,実行したい処理とその引数に分解します
  3. EvaluateMessage(string message)でばらした情報を元にリフレクションで実行します.ここの処理については,Unity APIはMain Thread以外からは呼び出せないため前後でThreadを切り替えています.
  4. Client側から受け取った文字列が"exit"であればサーバーが終了します.
  5. 最初に戻ります

なおリフレクションについては以下のような形で文字列からクラス名,メソッド名,引数を指定して実行しています.

Type.GetType(typeName).GetMethod(methodName).Invoke(null, new object[] { argsRawStr });

cf. C#リフレクションTIPS 55連発 - Qiita

まとめ

Unityでの作業がつらかったので,UnityとHoudiniをSocketで接続し,HoudiniのROPからUnityの任意の処理を実行するしくみを作りました. ちょっと乱暴なタイトル伏線回収ですが,処理を走らせるたびにHoudiniからUnityのアセットを全部消して,再度Houdiniから生成処理を行えば,事実上,プロシージャルUnityプロジェクトの生成,みたいなこともできるなーと思います.

今後は以下のようなことができればいいなと考えてます.

  • Unity側での主要な操作を行うscriptの整備
  • Houdiniで,Unity側からの処理の返り値等の情報を受取るしくみの実装
  • Socketまわりの例外処理
  • TOPによるUnityを絡めたパイプライン構築

普段使いで便利なHoudiniのSideFX LabのOperatorピックアップ

この記事はHoudini Advent Calendar 2019 13日目の記事です.

昨日の記事はK240さんの『HDKでアニメーションをSOPに取り込む』でした.このアプローチでボーンアニメーションで動くポリゴンと干渉するようなエフェクトを柔軟に生成したりもできそうです...!

はじめに

HoudiniにはIndie以上のグレードのライセンスで利用できる,Operatorの追加パッケージ,SideFX Lab(旧GameDevTooks)が存在します.

SideFX Labs | SideFX

今回の記事では,その中でも実際に使っていて便利だなあと思ったOperatorを紹介していこうと思います.主にゲームのAssetの制作等のリアルタイム用途で使いやすかったSOPを中心に,どう応用できるか軽い具体例も挟みつつ解説していきます.

SOP

Labs Align and Distribute

f:id:ttata:20191212024259p:plain 接続されているPrimitive(Piece)ごとに重ならないよう並べてくれるSOPです.並べる順番や並べる方向,間隔を設定できます.複数パーツで構成されるモデルを,Substance Painter等でペイントしやすいような配置で書き出す用途に使えます.(TexToolsのExplodeのような感じ)

Labs Axis Align

Geometryをそのバウンディングボックスの大きさにより移動させます.移動方法は各軸ごとにバウンディングボックスの境界や中心を基準に設定できます.わざわざbbox等で計算してtransformしなくて済むので,大変便利な利用頻度の高いSOPです.(今回の記事で一番オススメかもf:id:ttata:20191213135825p:plain 左: 入力Geometry,右: Labs Axis AlignでXZ平面に丁度乗るようY軸方向へ動かしたもの

Labs Caluculate Occlusion

入力したMeshのOcclusionをattributeに書き出します.光の当たらない箇所に植物を配置したり,shader側のtextureのブレンドに用いると便利です. f:id:ttata:20191213140035p:plain

Labs Calcurate Slope

入力したMeshの傾きをattributeに書き出します.HeightField Mask by FeatureのSlope RampのPolygonバージョンです.SOPLabs Caluculate Occlusionと同様な用途で使えます. f:id:ttata:20191213140417p:plain

Labs Dissolve Flat Edges

平らな複数の面に対してDissolveをかけて一つの面にまとめるSOPです.Houdiniのハードサーフェスモデリングでお世話になっています.ちなみにこのSOPを通すとUVの情報が壊れてしまうので,UV展開前に用いるか,あとからAttribute TransferでUVを他のGeometryから渡す必要があります. f:id:ttata:20191213140453p:plain 左: 入力Geometry,右: 面を一つにまとめたもの

Labs Distance From Border

ポリゴンの縁(共有されていないエッジ)からの距離でAttributeに値を書き込みます.Rampやブラー等で調整が可能です.汚し等,地面に配置するPropの縁をAlphaで抜いてなじませたりするのに便利でした. f:id:ttata:20191213140851p:plain

Labs Edge Color

ポリゴンの縁(エッジが共有されていてもOK,NormalではなくEdgeの角度で見てるっぽい)からの距離でAttributeに値を書き込みます.擬似的なSSSのような表現に使えました.Convex/Concave(凹凸)を区別して検出してくれるので,エッジが立った場所の劣化のような表現のmaskにも使えます. f:id:ttata:20191213141056p:plain

Labs Group Curve Corners

カーブのどのpointが内側/外側かをgroup化してくれます.また角度も計測してpointに書き込みます.ハードサーフェスの,シルエットからコーナーにディティールを追加する用途に便利です.

Labs Group Expand

PointやPrimitiveのGroupの範囲をPolygon上で指定した数だけ広げます. f:id:ttata:20191213141326p:plain

Labs Min Max Average

Attributeの最小値,最大値等の計測を行い,Detail Attributeに書き込みます.Attribute Promoteを手軽に使いやすくしたSOPです.

Labs Path Deform

PolygonをPathに従って曲げるSOPです.これを用いると道路や柵等がCurveで簡単に編集できるようになります.また,入力するMeshを,入力するCurveとぴったり同じ長さにしたい場合は,arclen("../path/to/curve_op", 0, 0, 1)等で調整すると便利です.注意点として,長手方向の軸の設定(Base SettingsのAxis)によっては,指定した軸回りに形状が180度回転してしまう場合があるので要調整です. f:id:ttata:20191213141626p:plain 左: 曲げる対象となる入力Mesh(Curveと同じ長さになるよう調整済み),中央: ガイドになる入力Curve,右: Path Deformをかけた出力

Labs Soften Normals

いい感じにやわらかい表示になるようNormalを計算するSOPです.通常のNormal SOPよりも汚いハイライト,陰が出ないので使いやすいです. f:id:ttata:20191213132042p:plain 左が通常のNormal SOP,右がLabs Soften Normals SOP

Labs Quick Material

SOPとしてPrinciple Shader,GameDev PBR,GameDev MatCapのMaterialを扱うことができます.Material NetworkにアクセスせずにSOP Network内でサクっとマテリアルのセットアップができるので便利です.

Labs Remove Inside Faces

内側に存在する閉じた面を削除します.BooleanやVolumeのPolygon化の後に使うと便利です f:id:ttata:20191213141854p:plain 左: 内側にポリゴンを持つMesh,右: Labs Remove Inside Facesの出力

Labs Thicken

Meshに厚みを付けます.表と裏の両面について同時に押し出しを行うことができます.(あと一応裏面のポリゴンリダクション機能付き) 厚みをつける用途ならPolyExtrudeよりも便利かなーと思います. f:id:ttata:20191213142126p:plain

Labs Trace PSD File

PSDファイルの画像をレイヤ毎にGroup分けされたMeshとして読み込むことができます.実際の活用は以下の動画がわかりやすいです. www.youtube.com

注意点として,PSD側のレイヤー名には日本語は使えないようです.また,レイヤー名の-(ハイフン)は,Groupでは_(アンダースコア)に自動で置き換えられるようです.

ROP

Labs CSV Exporter

GeometryのAttributeをCSVとして書き出します. 用途の一例ですが,ゲームエンジン側でCSVをパースしてオブジェクトの配置を行う専用のツールを作成すると,以下のセッションの動画のように,Houdini Engine無しに柔軟にPropの配置を行うフローが構築できたりします. www.youtube.com

Labs Games Baker

Mantraでテクスチャのベイクができます.ベイクできるチャンネルはよくあるPBRのテクスチャタイプについては一通り網羅しています.

f:id:ttata:20191213142754p:plain

個人的に嬉しいのはBase ColorのBakeができる点で(Substanceだと出来ない),このROPで書き出したBaseColorのMapをSubstanceに持っていってテクスチャリングを行っています.このフローだと模様がプロシージャルに生成できるので気持ちよく作業ができますね.

あとアイコンがかわいい.(houdiniのOperatorのアイコンで一番好き) f:id:ttata:20191213142156p:plain

まとめ

SideFXLabには,もうすでに自分で作ってしまった,もっと早く知りたかった,と思うような便利なOperatorが,今回紹介した以外にも複数パッケージされています. 今まで使ってなかった方もぜひ試しに導入してみてはいかがでしょうか.

Operatorとの良い出会いがありますように🙏


明日の記事はtakavfxさんの『HoudiniをPython3でやる!!!』です.post-EOLの来たるべきPython3の時代に備えていきたいですね.

(たのしみ)

42インチ4Kモニタを買った

f:id:ttata:20190523102941p:plain

身の回りの人が声を揃えて「4Kは良いぞ」と言うので前から気になっていたが,ついにとうとうぽちってしまった.決め手はいつも通話しながら作業している@OtObOxが放った「ものを作る人はみんな42インチ4Kモニタを買っているよ」という一言だった.確かに弊社のボスもオフィスで42インチ4Kを使っている.どうやらこの「ものを作る人はみんな~」構文は自分(の財布)に効くっぽい.ちなみに買ったのはこのモニタ.

実際に使い始めて2ヶ月が経ったのでさくっとレビューを書く.

レビュー

pros

  • デカい
  • ノングレア
  • 見た目のデザインがシンプルでかっこいい
  • 画面の明るさが柔軟に変えられる
  • 42インチなので文字が読みやすい.コード書くときに楽
  • USB-Cでmacを繋げられるらしい(未検証)
    • mac book proを給電しながらは使用するのは無理っぽい
    • 標準の付属ケーブルだと微妙に給電はされるが表示はできなかった
  • Windowを内部で分割するレイアウトのUIを使っているアプリケーションは4Kの恩恵を授かりやすい
    • UnityとBlender,Houdiniがヤバイ

cons

  • ディスプレイを斜めから見ると画面の縁が見えづらい.液晶からバックライトまでの距離が大きいのが原因かも.
  • コントラスト下げた時の暗い部分の階調の表現がちょっと弱い気がする.
  • マウスカーソルが失踪する
    • カーソルをちょっと大きくした

感想とか

どうやら4Kで快適に作業するための独特なWindowの使い方があるっぽい.ブラウザやファイラは最大化せずに画面の中央に散らばらせておくやり方が楽.画面分割できないようなアプリケーションは全画面表示ではなくこっちのほうが良いのかも.これをやると首が楽,というか首が楽なのでこのアプローチに収束すると思う.

まあトータルで満足.良い買い物をしたと思う. なんというか今まで使っていたザコいモニタが自分の脳の足枷になっていたことに気がつけた.暴力的ではあるが画素が多いのは開放感があって気持ちが良い.これでまたひとつ魂のステージが上がってしまった.今後も引き続き良いものを作っていくのでよろしくお願いします,という感じ.

今後の話

未だにArchlinux(xmonad)環境で試せてないのでそのうちやりたい.タイル型WMと絶対相性が良いと思う.

木瓜

f:id:ttata:20190113171537p:plain

木瓜の鉢植えを買った.インスタで見かけて以来ずっと気になっていて,ふと近くの花屋で探してみたらいい具合の鉢植えがあったので購入してしまった.ちょうど自分の誕生日だったというのもある.樹高は14cmくらいで,花のボリュームとのアンバランスが美しい.あと名前の漢字は「ボケ」って読むらしいですよみなさん.僕は知りませんでした.

ちなみに,買う前にどんな植物なのか調べたが,どうやら屋外環境じゃないと育てるのが難しいことがわかった.室内だと光量,気温,湿度,風通し等に過不足があるらしい.盆栽的な感じっぽい.弊家環境では屋外にモノをおけるスペースが無いので諦めそうだった.

むしゃくしゃしたので最終的にお迎えした.

環境要因についてはエンジニアの端くれなのでテックでボコスカ殴ることにした.これは技術的なハードルなので可及的速やかに問題解決されねばならない.幸い手元には使われていないArduinoRaspberry Piがある.

f:id:ttata:20190113181716j:plain

まずは植物育成用のLEDライト(光合成に必要な波長の赤と青のLEDが内蔵されたピンク色のパリピ仕様)を購入した.最終的には育成を自動化したい.たのしみ.