見出し画像

シェイプ・キーを調べる

シェイプ・キーを設定すると、頂点移動によりメッシュを段階的に変形することができるので、キャラクタに表情をつけたりするようなこともできる。

シェイプ・キーは別名をメッシュ・モーフィングの呼ぶことがあるらしい。画像処理では、ある形状から別の形状へ、連続的に、滑らかに変形させることをモーフィングと呼ぶけれど、これをメッシュで行うためのようだ。

シェイプ・キーへのアクセス

まずは適当にシェイプ・キーを設定してみる。Basisは変形の開始位置を保存するための特別なキーということらしい。

BasisとBasisの派生キー「Key 1」を定義すると、Basisの位置から変形が始まり、Key 1に定義された頂点位置へ向かって変形が行われる。

データの内容がどのようになっているのかを調べたいので、変形は簡素なものにし、頂点を1個だけ動かしたものを定義してみた。

シェイプ・キーはオブジェクト・データとメッシュ・データのどちらに紐づいているだろうか。調べてみると、メッシュ・データのほうへ紐づいていた。

データ型を調べてみるとKey型と表示されていた。bpy_prop_collection型ではないので複数の要素を持っていない。試しにlen関数を使って要素数を表示させてみようとしたらTypeErrorになった。

>>> type(bpy.data.meshes["Cube"].shape_keys)
<class 'bpy.types.Key'>

>>> len(bpy.data.meshes["Cube"].shape_keys)
Traceback (most recent call last):
 File "<blender_console>", line 1in <module>
TypeError: object of type 'Key' has no len()

shape_keysプロパティ配下のプロパティを調べてみると、それらしいプロパティはkey_blocksしかない。

key_blocksプロパティはbpy_prop_collection型なので、このプロパティを掘り下げてみる。

>>> type(bpy.data.meshes["Cube"].shape_keys.key_blocks)
<class 'bpy_prop_collection'>

追加したキーは「Basic」「Key 1」の2つなので、nameプロパティを使って表示される名前がキー名と一致すればいい。次のスクリプトを叩いて、キー名にアクセスできることを確認した。

>>> bpy.data.meshes["Cube"].shape_keys.key_blocks[0].name
'Basis'

>>> bpy.data.meshes["Cube"].shape_keys.key_blocks[1].name
'Key 1'

シェイプ・キーは、頂点データへのアクセスを行うデータ構造になっているはずなので、key_blocksプロパティには頂点データが紐づけられていると考えられる。

頂点データを紐づけているプロパティをインタラクティブ・コンソール上で探してみると、dataプロパティを見つけることができる。

dataプロパティの要素数を調べてみるとCubeオブジェクトを構成する8個の頂点と同じ数の値を得ることができた。

>>> len(bpy.data.meshes["Cube"].shape_keys.key_blocks["Basis"].data)
8

そこで、dataプロパティにサブスクリプト0を指定して、自動補完をかけてみる。するとcoプロパティを見つけることができた。

coプロパティはCoordinate(コーディネート、座標)の略なので、つまりそのまま座標を表している。変形後の座標をcoプロパティに保存するというデータ構造のようだ。

>>> bpy.data.meshes["Cube"].shape_keys.key_blocks["Basis"].data[0].co
Vector((1.0, 1.0, 1.0))

dataプロパティをイテレーションしてみて、座標を表示させてみると、次のような結果となる。

いまシェイプ・キーは2つ用意してあるので、両方表示させてみた。各シェイプ・キーにはすべての座標が保存されているので、変形させたくない頂点は、移動させない変形という扱いをしていることになる。

>>> for vertex in bpy.data.meshes["Cube"].shape_keys.key_blocks["Basis"].data:
...     print(vertex.co)
...     
<Vector (1.0000, 1.0000, 1.0000)>
<Vector (1.0000, 1.0000-1.0000)>
<Vector (1.0000-1.0000, 1.0000)>
<Vector (1.0000-1.0000-1.0000)>
<Vector (-1.0000, 1.0000, 1.0000)>
<Vector (-1.0000, 1.0000-1.0000)>
<Vector (-1.0000-1.0000, 1.0000)>
<Vector (-1.0000-1.0000-1.0000)>

>>> for vertex in bpy.data.meshes["Cube"].shape_keys.key_blocks["Key 1"].data:
...     print(vertex.co)
...     
<Vector (1.2631, 2.0911, 1.4018)>
<Vector (1.0000, 1.0000-1.0000)>
<Vector (1.0000-1.0000, 1.0000)>
<Vector (1.0000-1.0000-1.0000)>
<Vector (-1.0000, 1.0000, 1.0000)>
<Vector (-1.0000, 1.0000-1.0000)>
<Vector (-1.0000-1.0000, 1.0000)>
<Vector (-1.0000-1.0000-1.0000)>

シェイプ・キーのデータ構造をイメージにまとめると次のようになる。頂点位置は各シェイプ・キー内のキー・ブロック内に保存されているので、関連性があるのは頂点の並びということになる。

Relativeモード

シェイプ・キーのプロパティには、Relative(リラティブ、相対的)というチェック・ボックスがあって、デフォルトではチェックされている。これを便宜上、Relativeモードと呼ぶことにする。

Relativeにチェックされていると変形量を相対的に表現するようになっているらしいのだけど、何に対して相対的なのか、ちょっと調べてみることにした。

頂点の動きそのものはBlender Pythonの範疇ではないけれど、Blenderの機能を知っておくことはプログラミングのために役立つだろうと思う。

Relativeをチェックするとどのような変形をするのか、あるいはRelativeではない場合はどのような変形をするのか。比較を行ってみた。

何パターンか試してみた感じでは、変形の違いを明確にするにはBasis、Key 1、Key 2という3つのシェイプ・キーを設定したほうがよかった。

Key 1はX軸方向に+1m、Key 2はZ軸方向に+1mだけ1個の頂点を動かす設定をしている。3Dのツールなのに、一次元落として2Dの移動しているのは、頂点の動きを把握しやすかったからだ。

まずはBasisを見てみる。これは変形開始の頂点位置を保存しているシェイプ・キーなので、指定できるプロパティがない。

Key 1とKey 2を選択すると、いくつかのプロパティが表示されるようになる。

この内、Valueが変形の適用度に当たる。Key 1のValueを0.5だけ指定すれば、X軸方向に+1m×0.5=0.5mだけ頂点が移動する。

Blenderの設定はデフォルトのままなので、グリッドの1マスが1mになるように描画されているみたい。

Relative Toプロパティ

Relative Toプロパティについて調べてみる。Relative Toプロパティはメッシュを変形させる際に頂点移動量を計算する対象を指定するものらしい。

試しに上の状態からKey 2のRelative ToプロパティをBasisからKey 1に変更してみると、不思議な位置に頂点が移動した。

このような妙な変形をされて、どういう過程を経て変形処理が行われているのか、最初はよくわからなかった。

そこで、移動後の座標から移動量を逆算して当てはめてみたら、やっと理解できた。シェイプ・キーはベクトルを定義するための仕組みだった。

つまり、BasisとKey 1、Key 1とKey 2という組み合わせからそれぞれのベクトルを求め、Valueプロパティという適用度をもってベクトルの合成をしているということらしい。

シェイプ・キーをベクトルに変換し、図にして合成したら自分的には理解しやすかったのでイメージを作成してみた。算出されるベクトルは適用度1.0での移動量になる。いまはKey 1もKey 2も適用度が0.5ずつなので、ベクトルは半分になる。それを合成すると最終的な頂点の位置が求まる。

最初に出てきたシェイプ・キーの動きも同じようにベクトルで考えることができる。こちらの場合、すんなり理解していたのではなく、不自然さを感じない変形が行われていたので、変形に疑問を持たなかったのだと思う。

Range Min/Max

メッシュ・モーフィングが関連性のある2つのシェイプ・キーによって、ベクトル計算されるということがわかっているので、Range MinプロパティとRange Maxプロパティはベクトルの倍数を設定できるプロパティだというのは容易に想像できた。

試しにKey 1のRange Maxを2に設定してみると、予想通りに2倍の移動量を持つシェイプ・キーになった。

そこからKey 2の変形を加えると、ベクトルの合成により頂点は上へ移動していく。

Range Minプロパティを-3.0に設定した。ベクトルはマイナスのスカラー値を乗ずると向きが反転するので、頂点は反対方向へ移動するようになった。

Rangeの限界は-10.000~10.000、メモリは0.001刻みで設定することができる。Range MinプロパティとRange Maxプロパティの値は逆転させることはできず、同一の値を取ることもできない。

自分なりにわかりやすい表現をすると、Range Maxプロパティに設定できる最小値はRange Minプロパティに0.001を足した値となる。逆に、Range Minプロパティの最大値はRange Maxプロパティから0.001を引いた値となる。

まとめると次の範囲で値を設定することができる。

Range Minは-10.000~9.999
Range Maxは-9.999~10.000

頂点グループ

Relativeモードでは、各シェイプ・キーに頂点グループを設定することができる。おそらく頂点グループに含まれる頂点だけ変形させると予想を立て、変形している頂点以外に頂点グループを設定してみた。

予想通り、頂点グループに含まれない頂点は変形しなかった。

この機能は、無用な頂点の変形を防ぐために存在しているのだと思う。もしかしたら、Pythonスクリプトを使ってシェイプ・キーを生成する場合、安全のために頂点グループを設定したほうが事故を防げるのかもしれない。

Absoluteモード

次にAbsolute(アブソリュート、絶対的)なシェイプ・キーを見てみる。Relativeチェック・ボックスを外すとAbsoluteモードになる。

Relativeモードではメッシュの変形をValueプロパティで設定していたけれど、AbsoluteモードではEvaluation Time(イバルエーション・タイム、評価時間)プロパティを使って変形させる。

Evaluation Timeは、シェイプ・キーを上から順に10区切りで自動設定されていく。10まではBasisからKey 1までの変形が行われ、20まではKey 1からKey 2までの変形が行われる。つまりシェイプ・キーの適用が連続的に行われることになる。

連続的にシェイプ・キーを適用していく関係上、各シェイプ・キーの適用度が100%になってから、次のシェイプ・キーの適用が始まる。したがって、頂点の動きはBasisからKey 1までの変形が完了してから、やっとKey 1からKey 2の変形が始まる。

この動きはちょうど、Key 1のRelative ToプロパティをBasis、Key 2のRelative ToプロパティをKey 1に設定している場合に似ている。異なるのは各シェイプ・キーの適用度を50%ずつにする、ということができない点にある。

ところで、シェイプ・キーは定義の順序を入れ替えることができるので、その場合にAbsoluteではどのように頂点が動くようになるのか?という疑問が出てくる。

実際にやってみると、プロパティ画面では次のイメージのように表示された。どうやら定義順に連続的な変形をしていくようになるようだ。Key 1の位置からBasisの位置へ頂点が移動し、その後、Basisの位置からKey 2の位置へ頂点が移動していくように変わる。

なぜシェイプ・キーの頂点座標が絶対座標なのか

シェイプ・キーは、関係性を持った2つのシェイプの頂点座標からベクトルを算出することがわかった。そのベクトルを算出するための頂点座標はメッシュ・データに対して絶対座標で記述されている。

なぜ絶対座標で記述する必要があるのだろうか?と考えたけど、たとえばRelative Toを動的に変えたい場合や、シェイプ・キーの定義順を動的に変えた場合のことを考えると、メッシュ・データの絶対位置にしておかなければならないことは明白になる。

しかし、絶対座標で記述することに問題がないわけではない。たとえば、シェイプ・キーのプロパティをシリアライズしたいと考えた場合、絶対座標では不都合が生じる。

シェイプ・キーをシリアライズしたいケースは、Armatureモディファイアを適用したいが、シェイプ・キーは保持させたい場合などが考えられる。ポーズを変形させた後、ポーズを確定させたい場合、ポーズを変形させてから、何かしらの形でシェイプ・キーをシリアライズしておき、Armatureモディファイアを適用してポーズを確定させ、再びシェイプ・キーをデシリアライズしたいことがあると思う。

絶対Basis

Basisは特別なシェイプ・キーだと書いたけれど、調べてみると正確には先頭にあるシェイプ・キーが変形の基本となるメッシュの形状を保持することになっているようだ。

試しにKey 1を先頭に持ってくると、Relativeモードで設定できるはずのプロパティが一切表示されなくなる。これを便宜上、絶対Basisと呼ぶことにする。

ここでやっとPythonスクリプトに戻る。

シェイプ・キー・ブロックの先頭にKey 1がある状態が絶対Basisになるなら、絶対どこかに管理情報が存在しているのだと考えられる。

先ほどのデータ構造をまとめたイメージを見返してみると、シェイプ・キー・ブロックをまとめているshape_keyプロパティが存在するのを思い出すことができる。

インタラクティブ・コンソールでそれらしいプロパティを探すと、reference_keyなるものを見つけることができた。プロパティの値を表示させてみるとKey 1を参照していることがわかった。

>>> bpy.data.meshes["Cube"].shape_keys.reference_key
bpy.data.shape_keys['Key'].key_blocks["Key 1"]

今度はBasisを絶対Basisに設定して同じスクリプトを叩いてみると、Basisへの参照に切り替わった。どうやらreference_keyプロパティが絶対Basisを決めているようだ。

>>> bpy.data.meshes["Cube"].shape_keys.reference_key
bpy.data.shape_keys['Key'].key_blocks["Basis"]

絶対Basisは便宜上で作った適当名なので、reference_keyプロパティにならって参照キーと呼んだほうが適切なのかもしれない。自分では絶対Basisと呼ぶけど。

シェイプ・キー削除のお作法

ところで、この絶対Basisは変形の基本となるメッシュ形状を保持しているとみなされるので、シェイプ・キーを削除する順序を気をつけないと、シェイプ・キーが適用されたままの状態でメッシュ形状が固定されてしまう。

これは、メデューサに睨まれ、その姿勢のまま石になってしまうのに似ている。石になる刹那、気をつけ!のポーズになったりはしない。

Pythonスクリプトを使ってシェイプ・キーを消していく場合、無用な変形が適用されないようにreference_keyプロパティを参照して、最後に削除するようにするのがお作法になってくる。

ただ、Blenderの場合は0番目のシェイプ・キー・ブロックが絶対BasisになるようなUIとなっているし、reference_keyは読み取り専用プロパティなので、単純に下から消していけば問題は起きない可能性のほうが高い。

いいなと思ったら応援しよう!