クロスシミュレーションされた布の頂点を動的に制御する方法

記事のまとめ

  • クロスシミュレーションがついているメッシュの頂点を制御する方法を紹介
  • 制御したいときだけ有効にできるような方法
  • HookモディファイアとVertex Weight Mixモディファイアを利用
  • 面倒な初期設定を行うためのスクリプトも紹介

やりたいこと

オブジェクトに被さった布をつまみ上げたり、お姫様キャラのモデルに挨拶をさせるときにスカートを摘まみ上げたり、「布をつまみあげる」操作をしたいケースって結構あると思います。

ただ基本的には常に頂点を制御したいわけではなく、布をつまみ上げている時だけ制御して、他のフレームでは普通にクロスシミュレーションに従って欲しいわけです。

やりたいことの実現例

要は、こちらの動画のようなことをやりたいわけです。

f:id:ronwall1701:20201218151709g:plain

こちらの例では、クロスシミュレーションされている布上のある頂点(片方のEmptyが追従している点)を、一時的に制御用のEmptyに追従させ持ち上げています。それ以外の時点では、頂点は他の部分と同じようにクロスシミュレーションに従っています。

どうやるのか

クロスシミュレーションで一部の頂点の位置を固定するには、ピン止めする頂点グループ(以降pinグループと呼ぶ)をあらかじめ設定しておけばよいです。

しかし今回は

  1. 制御対象の頂点を動的に変更したい
  2. (例えば今回のEmptyのように)制御対象の頂点を別オブジェクトに追従させたい

という2つの要求があるので、単にピン止めする頂点を静的に設定しておくだけではだめです。

今回は、この要求をそれぞれVertex Weight Mixモディファイアと、Hookモデイファイアで解決しました。

Vertex Weight Mixモディファイア

このモディファイアは、オブジェクトの各頂点がある頂点グループAに対して持つ重みを、別の頂点グループBの重みを用いて変更する機能をもっています。

今回のケースでは、常に固定したい頂点グループ(pinグループ)の重みに、一時的に制御したい頂点グループ(hookグループ)の重みを足し合わせるような処理を行えば良さそうです。そうすることによって、クロスシミュレーションで固定される頂点グループを動的に変更することができます。

そのような処理を行うためのパラメータは、例えば以下のようになります。ここで、pinは常に固定する頂点を含む頂点グループで、hook1が動的に制御を切り替える頂点を含む頂点グループです。(どちらも同じクロスオブジェクトの頂点グループです。)

f:id:ronwall1701:20201218151841p:plain

Hookモディファイア

このモディファイアは、オブジェクトの一部の頂点の位置を別のオブジェクトに追従させるような機能をもっています。

今回のケースでは、一時的に制御したい頂点を含む頂点グループ(hookグループ)を、(先ほどのEmptyのような)制御用のオブジェクトに追従させれば良さそうです。そうすることによって、制御用オブジェクトの位置変化を制御対象の頂点に反映させ、クロスシミュレーションに用いることができます。

パラメータ設定は、今回は以下のようにしました。

f:id:ronwall1701:20201218151845p:plain

パラメータの動的変更

以上の2つのモディファイアのパラメータを(同時に)変更することによって、対象となる頂点を動的に変更しながら位置を制御することができます。より具体的には、以下のパラメータを設定します:

  • Vertex Weight MixモディファイアのGlobal Influenceパラメータ
  • 0.0でオフ、1.0でオン
  • HookモデイファイアのStrengthパラメータ
  • 0.0でオフ、1.0でオン

ただし、このパラメータの変更速度が速すぎるとシミュレーションの結果が破綻するケースが確認されたので、これらのパラメータはある程度の余裕をもたせてゆっくり変更すると良いかもしれません。

モディファイア設定コード

布の複数の場所を別々につまみたい場合、以上の設定を場所ごとにすべて設定しなければなりません。それでは面倒なので、今回は以上のモディファイアの設定を自動で行うスクリプトを用意しました。

使い方ですが、動的に制御したい頂点を編集モードで選択した状態でScriptingウィンドウなどから実行してください。この時スクリプトの最初の方にあるNAME変数とPIN_GROUPはそれぞれ適切に変更してから実行してください。(以下の説明も参照)

  • NAME
  • 動的に制御される頂点の頂点グループ、制御・追従用のEmptyの名前に利用される。
  • _Tracer.{NAME}, _Handler.{NAME}がそれぞれ追従用、制御用のEmpty
  • 追従用のEmptyは制御対象の頂点に常に追従する
  • PIN_GROUP
  • 常に固定される頂点を含む頂点グループの名前
  • この頂点グループがクロスシミュレーションのpin止め頂点グループとして登録されていると仮定

実際に制御を開始する際には、追従用のEmptyの位置を利用するなどして、制御対象の頂点の位置と制御用のEmptyの位置があまり離れないようにした方が、破綻などが起こりづらいかもしれません。

# NOTE: 編集モードで、つかみたい頂点を選択している状態で実行すること

from functools import reduce
import bpy

NAME = "hook1"
PIN_GROUP = "pin"

# 選択頂点の座標を読み込む
mode = bpy.context.active_object.mode
bpy.ops.object.mode_set(mode='OBJECT')
selectedVerts = [v for v in bpy.context.active_object.data.vertices if v.select]
selectedVertsId = [v.index for v in bpy.context.active_object.data.vertices if v.select]
target_obj = bpy.context.active_object

# 掴む対象の頂点グループを作成
hook_group = target_obj.vertex_groups.new(name=NAME)
hook_group.add(selectedVertsId, 1.0, "ADD")

# 選択頂点のグローバル座標を計算
local2world = target_obj.matrix_world
pos = reduce(sum, [local2world @ v.co for v in selectedVerts]) / len(selectedVerts)
print(pos)

# 頂点位置追従用のEmptyと、位置設定用のEmptyを作成する
bpy.ops.object.add(type="EMPTY", location=pos)
objs = bpy.context.selected_objects
assert len(objs)==1
tracer_empty = objs[0]
tracer_empty.name = f"_Tracer.{NAME}"

bpy.ops.object.add(type="EMPTY", location=pos)
objs = bpy.context.selected_objects
assert len(objs)==1
handle_empty = objs[0]
handle_empty.name = f"_Handle.{NAME}"

# 対象のオブジェクトのトップにモディファイアを設定する関数
def add_modifier(type, obj):
initial_names = set([m.name for m in obj.modifiers])
bpy.context.view_layer.objects.active = obj
bpy.ops.object.modifier_add(type=type)
after_names = set([m.name for m in obj.modifiers])
modifier_name = list(after_names - initial_names)[0]
while obj.modifiers[0].name != modifier_name:
bpy.ops.object.modifier_move_up(modifier=modifier_name)

return obj.modifiers[modifier_name]

# Hookモデイファイアの設定
modifier = add_modifier("HOOK", target_obj)
modifier.falloff_type = "NONE"
modifier.object = handle_empty
modifier.strength = 1.0
modifier.vertex_group = NAME

# Vertex Weight Mixモデイファイアの設定
modifier = add_modifier("VERTEX_WEIGHT_MIX", target_obj)
modifier.vertex_group_a = PIN_GROUP
modifier.vertex_group_b = NAME
modifier.mix_set = "B"
modifier.mix_mode = "ADD"

# 
constraint = tracer_empty.constraints.new("COPY_TRANSFORMS")
constraint.target = target_obj
constraint.subtarget = NAME

さいごに

物理シミュレーションのキャッシュが残っていたり、制御が急すぎると破綻したりするので結構扱いが難しいです。とはいえいろいろ調べても他に良さそうな方法も出てこず…。何か別のアプローチなどご存じの方はお教えください。

スカートメッシュにボーンを自動でセットアップするスクリプト

はじめに

Vtuberが流行ってだいぶ経ってしまいましたが、最近今さらながらBlenderでの3Dキャラクターモデル作りにはまっています。せっかくスカートをモデリングしてもカチコチの剛体だと流石にアレなので、Blenderの物理シミュレーションを使ってみたいですよね。

今回は、その作業を一部自動化するときに使ったスクリプトについて書いていきたいと思います。

なお、こちらの記事は以前別の場所で公開していたものを移植してきたものです。

目的

クロスシミュレーションが適用されているメッシュがあったとき、そのメッシュの変形に追従するようなボーンを自動で設定する。

補足

そもそもなんでクロスシミュレーションに追従するボーンが必要なんじゃ、という話ですが、例えば:

  • クロスシミュレーションの結果をボーンのアニメーションに変換してBlender以外のソフトに出力する。
  • 同じクロスシミュレーションの結果を複数のオブジェクト(二重のスカートとか)に反映させる。

というような使い道があると思います。(私のケースでは後者でした。)

参考にしたサイト

https://dskjal.com/blender/skirt-setup-script.html

実際にやろうとしていることはこちらで紹介されているスクリプトと同様のタスクです。 ただこちらはBlender 2.7時点で開発されていたものらしく、Blender 2.8での使い方がよくわかりませんでした。

https://blender.stackexchange.com/questions/41235/how-to-make-rig-that-reacts-to-gravity-or-seem-to-behave-physically-correct

そこで今回は勉強もかねて、こちらのサイトで紹介されているプロセスを自動化する方針で実装しました。

コード

https://github.com/kosuke1701/add-skirt-bones

使い方

1. クロスシミュレーションを適用するメッシュを作成する。

今回のスクリプトでは以下のような円柱状の四角ポリゴンからなるメッシュを想定しています。(以後このメッシュをクロスメッシュと呼びます。) このメッシュには既にクロスシミュレーションの設定がされているものとします。

f:id:ronwall1701:20201218150802p:plain

2. パラメータを設定する

skirt_bones.pyのパラメータを直接編集します。(パネルとかでもっとかっこよくできるとは思いますが、現状はこれで…)

パラメータは8~12行目のもので、それぞれ以下のような意味があります。

パラメータ名 役割
N_STEP_HORI クロスメッシュの列(上下方向のエッジ)のうち、実際にボーンを設定する間隔
N_STEP_VERT クロスメッシュの列(水平方向のエッジループ)のうち、実際にボーンを設定する間隔
UPPER_DIR クロスメッシュの上方向。(クロスメッシュは、各列のうち最も上にある頂点が固定されるものと仮定しています。)
COL_NAME 大量のEmptyオブジェクトが生成されるため、それらをまとめておくCollection名
HEAD_NAME ボーンやEmpty、アーマチュアの名前に利用される文字列

最初の2つのパラメータは、クロスメッシュの全エッジにボーンを設定するとボーン数が多くなりすぎてしまうような場合に、間を間引きする目的で設定しています。

また3つ目のUPPER_DIRをデフォルトのZ方向でなくX,Y方向とすることで、スカートだけでなく袖などにも同じスクリプトを利用することができます。

3. スクリプトの実行

クロスメッシュをObject Modeで選択している状態でskirt_bones.pyを実行します。

(Text EditorビューのTextメニューからスクリプトを開き、同じくTextメニューのRun Scriptから実行できます)

実行すると、以下のようにクロスメッシュのエッジに沿ってボーンが作成されます。

f:id:ronwall1701:20201218150844p:plain

実際にクロスシミュレーションを実行すると、以下のようにクロスメッシュの動きにボーンが追従することを確認することができます。

f:id:ronwall1701:20201218150915g:plain

4. 設定されたボーンを使ってモデルを動かす

実際に作成されたボーンを使ってスカートモデルを動かすあたりの作業については、こちらのサイトが参考になると思います。

https://aobayu.hatenablog.com/entry/2019/03/20/111212

おわりに

BlenderPythonスクリプトを動かすのは初めての経験だったため、勝手がわからずコーディングにはかなり苦労しました。(その結果がスクリプトの汚さにも反映されています…)

ただこの処理を自動化したおかげで、スカートに対し行った処理を左右の袖に対しても簡単に行うことができ、かなり楽をすることができました。

やり方を覚えてしまえばPythonBlenderにおけるかなり強力なツールだと思うので、今後も使いどころを見つけて使っていきたいと思います。 最後まで読んでいただき、どうもありがとうございました。