オブジェクトの親子関係と座標

オブジェクトどうしで親子関係を付けることができます。
親子関係を付けたオブジェクトの親を動かすと、子も動く…までは直感的に理解しやすくて何も難しいところは無いのですが…

位置や回転のパラメーターの数値を直接弄ってよくよく考えてみると、Blender の親子関係の実装が直感的ではないという評判をよく聞きます。
いや、実は確かにそうなのです。
そこで、オブジェクト同士の親子関係を付けた場合の、位置や回転のパラメーターの挙動について確認してみましょう。

子供の位置や回転の情報の正体は何?

原点に置いたスザンヌを親に、(0,0,-2)に移動したデフォルトキューブを子供にしました。
それぞれ、名前は Parent と Child にしました。
「順番に選んで、Ctrl-P で親子関係を付ける」というのが標準的な操作です。
その他にも、アウトライナー上で Shift を押しながらドラッグ&ドロップ で親子関係を付けることもできます。

親子関係を付ければ、親のスザンヌを動かしたり回転したりすると、それと一緒にキューブも移動します。
このあたりは、ごく普通に理解できるところです。
スザンヌを選択しながら、キューブのパラメーターを3Dビュー上のパネルで見ることはできません。
ということで、プロパティパネルでキューブをピン止め機能を利用して、こちらでキューブのパラメーターを見てみます。
スザンヌを選択して、動かしたり回したりしてみます。

ギズモの表示をローカル座標基準にすれば、スザンヌの座標の向きもギズモで直接見えます。

スザンヌを動かしてもぐるぐる回しても、キューブの座標は、(0,0,-2)のままで、あたかもパネルには
「親のスザンヌの座標系での、キューブの位置などの情報」
が表示されているように見えます。
この理解が直感的に分かりやすいのですが、実は違うのです。
一旦親子関係を解除して、位置や向きを整理します。

スザンヌを(2,0,0)と原点以外に置き、90度回してみました。
次いで、デフォルトキューブをスザンヌの下(2,0,-2)に置きます。
この状態から、スザンヌを親にキューブを子供にします。
スザンヌから見て赤い矢印(x軸)方向にキューブがあるわけですから、
キューブのスザンヌから見たローカル座標は(2,0,0)のはず…素直に考えればそうなります。

しかし、キューブの位置パラメーターは元のグローバル座標(2,0,-2)になっていて、
スザンヌを動かしても回してもそのままです。

パネル上に表示されている子供の位置情報は実は親から見たローカル座標ではないのですね。 どうもここで混乱が生じやすいようです。

実は、親子関係を付けた直後、親(今回はスザンヌ)を動かしたり回したりする前は、
「親子関係を付ける前まで使っていたグローバル座標でそのまま」子供の座標を考えることができるような仕組みになっています。
なので、今の例でキューブの位置情報が親子関係を付けた後も(2,0,-2)のままになっていたわけなのですね。

ですから、子供の位置情報パネルに表示されているのは「親子関係を付けたタイミングでの」「子供のグローバル座標」に相当するものです。

このあたり、素直に考えると「親から見たローカル座標」かと思うので、混乱の元になっているわけです。
特別な場合として、親が原点で回転0スケール1のデフォルト状態であれば、「グローバル座標 イコール 親から見たローカル座標」ですから、
親がデフォルト状態の時に親子付けをした場合は、ローカル座標と考えても良いというあたりが、混乱に拍車をかけている気がします。

次のセクションでは少し理屈に深入りをします。
理屈は飛ばして、結論だけ見たい人はその次のセクションにどうぞ。

親子関係とMatrix

ここでよく考えてみると、子供の位置情報が「親子関係を付けたタイミングでの」親の位置関係に依存する…ということは、
もしかして「親子関係を付けたタイミングでの位置関係」も情報として保持しているという事?
と疑問がわいてきます。その通りです。
matrix_parent_inverse という名前で、子供のオブジェクトは、(親子関係を付けたタイミングでの)親の位置情報の行列(の逆行列)を保持しています。

ここから先は行列(Matrix)の話があり少々難しいのですが、
行列による座標変換などの知識があって、詳細に興味のある人だけ進んでください。
オブジェクトの位置や回転、スケールの情報は、16個の数字の並び(4x4の変換行列)によって保持することが出来ます。

グローバル座標における情報は、matrix_world という行列になっています。 例えば、原点にあって回転 0 スケール1 のオブジェクトの状態は、
このように奇麗な行列で表されます。
左上の3x3 の部分が回転とスケールを表します。
4列目の3つの数字は位置に対応します。
例えば(1,2,3)の位置にあるオブジェクトであれば、3x3の部分はそのままで、
4列目に位置情報が入りこのようになります。
(4行目は常に0,0,0,1になっていますが、ここの部分の情報は使いません。)
matrix_world の他に、オブジェクトは
matrix_local, matrix_basis, matrix_parent_inverse の3つの行列を持ち
計 4 種類の行列で、親子関係などを表現しています。

行列同士の掛け算をすると親子関係に相当する座標変換することが出来ます。
(コンソール上で、行列の掛け算は"@"で表します)
親を PARENT, 子を CHILD で表現するとして、

PARENT.matrix_world @ CHILD.matrix_local = CHILD.matrix_world になります。

実際にこれらの行列を表示させてみてみると
確かにそのような関係になっていることが分かります。
(数値誤差があるので、2ではなく1.999... となっていますが、そうした誤差は仕方が無いので無視します)

あれっ?ローカル座標の情報、もってるじゃん
先ほど見た、スザンヌの右側にあるのだから、ローカル座標は(2,0,0)ではないのかな?
というサンプルで確認してみます。

誤差があるので、0 であるべきところが厳密に 0 にはなっていませんが、
位置(2,0,0)となっており、実はローカル座標も情報も持っていることが分かります。
では、このローカル座標の情報をパネルに表示すれば良いじゃん、という声も聞こえてきそうですが、
何らかの理由で(どこかで読んだ記憶があるのですが、今覚えていません)このローカル座標の情報を使いづらい理由があり、先ほど見た(親子関係を付けた瞬間の)グローバル座標を使っているはずです。

この「CHILD.matrix_local」は実際には2つの Matrix の掛け算になっていて、

matrix_local = matrix_parent_inverse @ matrix_basis

の関係があります。
この matrix_parent_inverse が、「親子関係を付けたタイミングでの位置関係」に相当するものになっています。
先ほどの関係から式を展開してみると…

CHILD.matrix_world = PARENT.matrix_world @ CHILD.matrix_parent_inverse @ CHILD.matrix_basis

ここで、matrix_parent_inverse は、(親子関係を付けた瞬間の)親の変換行列の逆行列なので、
親を動かしたりして親の Matrix が変化しなければ、打ち消しあって単位行列になり

CHILD.matrix_world = CHILD.matrix_basis

この matrix_basis の情報が、パネルに表示されてる位置や回転の情報になります。
親子関係を付けた瞬間は、matrix_basis と matrix_world が等しいので、結局先ほどの結論
子供の位置情報パネルに表示されているのは「親子関係を付けたタイミングでの」「子供のグローバル座標」ということになっているのですね。

単純なローカル座標

と、ややこしい話をしてきましたが、この matrix_parent_inverse があるので
直感的な「親の座標系」「子の座標系」という理解ができないことが分かりました。
(グローバル座標を使えるという点で、ある意味では便利だったり利点があるはずなのですが)

直感的な「親の座標系」「子の座標系」の単純な考えで行きたい場合には、
この matrix_parent_inverse を間に挟まないでくれれば良いことになります。
それを行うのが、ctrl-P のメニューに、単純な親子付けの下にある幾つかの項目になっています。

Keep Transform Without Inverse(逆行列無しでトランスフォーム維持)
は、仕組みを知らずに字面だけ見るとすぐには分からないのですが、
matrix_parent_inverse を無し(単位行列)にして親子付けをします。

こちらのメニューで親子付けをすれば、
直感的に理解しやすい形で、子供の位置情報が(2,0,0)、つまりスザンヌの右側と設定されます。

(スザンヌを90度回した状態で、回っていないキューブを親子化したので、
キューブは-90度回ったことになっていることに注意です)

matrix_parent_inverse が効いていないので、
子供の位置を(0,0,0)に移動すれば、親と完全に重なります。

直感的な「親の座標系」「子の座標系」という理解で考えられることが分かります。
では、既に原点以外の場所で matrix_parent_inverse 付きで親子付けをした場合に、
この状態にするにはどうすればよいでしょう。

2手間かかってしまうのが面倒なのですが、
一旦 Alt-P で子供の位置を維持したまま親子関係を解除、
そして改めて(Keep Transform Without Inverse)で親子関係を組みなおせば良いことになります。

スケールは例外

さて、(Keep Transform Without Inverse)で親子関係を組めば、matrix_parent_inverse 抜き、
厳密には matrix_parent_inverse が単位行列になって、単純な親子関係になる、と書きましたが、
実は移動と回転に関してはそうなのですが、スケールの処理は例外になっています。

このように引き延ばしてスケールで歪めたスザンヌにたいして、
(Keep Transform Without Inverse)で親子関係を組んでみましたが、Scale のパラメーターが、
親のスケールに関係なく1のままです。
しかし、本来は親が拡大されれば子も拡大されるはずなので…
親のスケール変換を打ち消すための matrix 情報がどこかに残っているはずです。

子の matrix_parent_inverse を見ると、単位行列ではなく、スケールの情報が残っていることが分かります。
このあたり、移動回転とスケールで処理の仕方に差がある理由は私も理解していないのですが、スケールも含めて親子関係を組むときには気を付けます。

最終手段としては、スクリプトで強引に matrix_parent_inverse を単位行列に書き直す手もあります。

子供のキューブも、スケールも含めて完全に親の座標変換に影響されて、
引き延ばされたキューブになりました。

inserted by FC2 system