2022/11/03 追記:
VRM 1.0 のリリースおめでとうございます。所感や、特に周知の足りなさそうな VRM 1.0 における変更点についてまとめた記事を投稿しましたので、SNS での記事共有などによる周知・拡散の協力をお願いします。
VRM 1.0 で決められた『親指の定義』とまたリターゲットの話
2022/02/08 追記:
VRM 1.0 ではローカル軸を保持するという決定がなされたので、その影響について検証記事を投稿しました。
VRMに正規化がいらない理由と関節ルールの必要性(glTFリターゲット覚書)
2021/12/14 追記:
この問題については解決しそうな方向に進んでいるようです。
https://github.com/vrm-c/vrm-specification/issues/337#issuecomment-993074170
互換性について心配される方もいるかも知れませんので、「なぜローカル軸が完全に揃っていなくても大丈夫なのか」という記事を、アニメーションや IK についての実装を交えて 2022 年初頭に投稿する事を予定しています。
▼ここから記事本文▼
VRMのデータ的でのローカル軸の維持 · Issue #34 · vrm-c_vrm-specification
この記事は VRM 1.0 の仕様策定リポジトリの上記 issue に対して、正規化でローカル軸の方向を破棄してしまう実装に対する問題点の洗い出しと、その解決案についてまとめたものとなります。
VRM 1.0 は現在の VRM 0.x との互換性を持たない設計である事に注意してください。
簡潔に要点を知りたい方はこちらのスライドの方を先に見ることを推奨します。
前説
VRMはUnityにかなり依存している
皆さん、VRM 使ってますか? 僕は Godot Engine という非 Unity 環境で VRM を使う事にグループで取り組んでいるのですが、現在、いくつかの問題に直面しています。
VRM は glTF 拡張フォーマットなので、glTF に対応したアプリ上で楽々使えるかと思いきや、全然そんな事はありません。今の VRM は、glTF ネイティブな Godot Engine や three.js(three-vrm)のような環境で非常に扱いづらい形式となっています。モデルを表示するだけならさほど問題はありませんが、モデルをアバターとして動かすためには、一般的な人型の glTF モデルであれば持っている「ある情報」が VRM には欠けているのです。
その情報とは『ボーンのローカル軸の方向』です。
結論から言いますが、『VRM 1.0 では、ボーンのローカル軸の方向を保持すべき』と考えます。
現在の VRM は仕様によって「正規化の段階でボーンのローカル軸の方向を破棄」しており、これは非 Unity 環境で VRM を扱うにあたって大きな障害となっています。その一方で、具体的にどのような問題が発生しているのか十分に共有されておらず、これまでのところ、改善に向けた大きな進展はありませんでした。
VRM 1.0 ではプラットフォームを超えて使える汎用性の高いフォーマットとすることを目指しているといいます。しかしその新たな仕様策定の場でも、この『ボーンのローカル軸の方向』にまつわる議論は停滞しており、この調子でいくと VRM は、たとえ 1.0 になっても Unity 以外の環境ではまともに扱えないままなのではないかという危機感を募らせています。
たしかに、「ボーンのローカル軸の方向」というものがなぜ必要なのか、多くのユーザーに理解してもらうのは容易ではありません。なぜなら VRM は Unity アプリ上ではなんとなく正常に動いているように見えてしまい、問題が潜んでいる事に気付くのが難しいからです。この件について GitHub 上で1年半近くも議論が行われているのがその複雑さを物語っています。
しかし Unity 以外の環境で VRM を扱おうとする際、実際にどのような困難に直面することになるのかを周知することは非常に重要です。でなければ VRM はいつまで経っても Unity 専用フォーマットのままです。
以下より、現在の正規化の問題点について、改善案の提案も含め説明していきます。
正規化(ローカル軸の方向の破棄)には多くの問題がある
VRM の「ボーンがローカル軸の方向を持つ事」を許さない仕様による、一番分かりやすい問題は VRM を glTF として Blender に読み込んだ時の状態でしょうか。
VRM モデル:アリシア・ソリッド
極端な話、VRM を一般的な glTF として扱うのであれば、この状態のものを操作する必要があります。これは Blender でなくても表示上こうなっていないだけで内部的にはこれと同じ状態になっています。
そしてその状態は、斜めのボーンの回転軸が完全に死にます。例えば親指をオイラー角を用いて曲げる場合、画像で言えば X 軸回転と Z 軸回転の中間辺りに回転用のギズモが欲しいのですが、そうはなっていません。
「VRM は編集する為のフォーマットじゃないんじゃ〜」という方に釘をさしておくと、記事中で Blender に VRM を読み込ませているのはあくまでボーンを操作する為であって、モデル自体の編集を行う意図は全くありません。ポージングやアニメーションを行えなくするのが VRM の目的ではない筈です。
また、「VRM Addon 使え!」とか「Unity では動いてるじゃん!」という方に対しては、それらが何を行っているのかについて後ほど説明します。
ではまず、ローカル軸の方向とは何かについて説明しましょう。
ボーンのローカル軸の方向
「レスト」と「ポーズ」
ボーンには「レスト」と「ポーズ」の2つの状態があり、ポーズによる変形がない状態を一般的にレストとします。ローカル軸の方向とはレスト回転、いわゆるレスト状態のボーンのローカル軸の方向の事を指します。この「ローカル」は「親からの相対値」というより「ボーン個々が持っている」という意味合いである事に注意してください。
個人的には「レスト回転」という呼称の方がパッとするのですが、GitHub 上でもローカル軸とよく言われているので、本記事では「ローカル軸の方向」という呼び方で統一します。
厄介な事に、Blender とその他の一般的な編集ソフトではローカル軸の方向の決め方が異なります。
Node形式(glTF等)
ジョイントがノードとして存在し、親子関係を持つ2つのジョイント間にボーンが生成されます。一般的な編集ソフトはこれに準じた形式を用いています。
枝分かれする場合はこうなります。
Node 形式では、親と子を繋ぐようにボーンを表示します。これを「ボーンの見た目上の方向」と呼ぶ事にします。ただし、「見た目上の方向」はあくまで親と子を繋ぐ線をそれっぽく表示しているだけの物であり、モデルがその方向のデータを数値として持っている訳ではない事に注意してください。実際に数値として存在し、操作するのは「ローカル軸の方向」になります。
Node 形式においては、ボーンのローカル軸の方向 = 親ジョイントの方向となります。
図で示したように、末端のジョイントはローカル軸の方向を持っていますが、それより先に子が存在しないので、実体としてのボーンは存在しません。とはいえウェイトを割り当てることはできるので、仮想的なボーンが欲しくなる場合もあり、その場合はローカル軸の +Y 方向の延長線上に適当な長さのボーンを配置する事が多いです。
ジョイントが個別にローカル軸の方向を持ち、角度を直接設定する事が可能である為、一見正常に見えても、ローカル軸のいずれもがボーンの見た目上の方向と一致しない状況が発生します。
ボーンが1本に定まっている場合(二股などになっていない場合)において、ボーンの「ローカル軸の方向」と「見た目上の方向」が一致しない状況は、最初に提示した「回転軸が死んでいる画像」のように、オイラー角での制御が困難になるという問題が発生します。
Head-Tail形式(Blender)
そこで、ボーンの「ローカル軸の方向」と「見た目上の方向」のズレを防ぐようになっているのが Blender の Head-Tail 形式です。この形式では Head と Tail の2座標と1つのロール値からローカル軸の方向が決定されます。
枝分かれする場合はこうなります。
Blender 上ではローカル軸の Y 軸 = ロール軸と決められており、Head から Tail に向かう方向が必ずローカル軸の +Y 方向になります。Y 軸の方向が決定した状態で Y 軸をロール値により回転させると残りの X 軸と Z 軸の方向が自動的に決定します。
しかし、Blender はこれにより決定されたローカル軸の方向を数値としては表示しません。それ故、Blender ユーザーに「ローカル軸の方向っていうのがあるんだよ」と言っても伝わらない事も多く、とはいえ僕自身も Blender 上で数値として見れたほうがいいよねと思ったのでアドオンを作りました。
このようにボーンのローカル軸の方向が表示されます。下記リンク先にフリーで公開していますので、ローカル軸の方向を意識したい時に利用して下さい。
blender-addons_bone_rest_info at main · TokageItLab_blender-addons
Head-Tail 形式の利点として、見た目通りリギングすればローカル軸の1つであるロール軸が必ずボーンの見た目上の方向と一致するのですが、ボーンのロール軸が Y 軸でない場合にボーンが立ってしまうという欠点があります。VRM のボーンが全て立った状態になるのはこの為です。
では、「ボーンのローカル軸の方向」というものがあると理解した上で、元のボーンのローカル軸の方向を無視してボーンを制御する「2つの技術」について説明していきます。
リターゲット
リターゲットとは
ローカル軸の方向を無視してボーンを制御する技術の1つが「リターゲット」です。Unity や Unreal Engine の Humanoid 周辺の機能の一端がこれに相当します。一部の編集ソフトでもアニメーションのためにリターゲット機能を持っているものがあります。
一つ注意しておくと、本来、リターゲットはローカル軸の方向が破棄されたモデルの為の機能ではありません。
リターゲットとは、例えば基準となるボーン群 A とインポートされたボーン群 B がある場合に、ボーン群 B のローカル軸の方向をボーン群 A と同じになるように揃えるという機能です。つまり、モデルによって異なるボーンのローカル軸の方向を揃える為のものです。どこかで聞いた事があるような...?
UnityHumanoidとVRM正規化の関係
Unity の Humanoid はブラックボックスなのであくまでも予想になってしまうのですが、内部的に何が行われているかと言われると、まず「インポートしたモデルのボーンのローカル軸の方向」を「Unity が基準としている Humanoid ボーンのローカル軸の方向」へ一致させます1。
その後はローカル軸の回転と役割を対応づけたパラメータである Muscle 値を操作させる事で、各関節の制御が可能という作りになっています。
Muscle の制御では数値を角度として直接見せない事により、リターゲット後のローカル軸の方向をユーザーに意識させません。
VRM の場合はランタイムロードを前提とした作りとなっているのでこの辺りのパラメータは Unity エディタ上で気軽に扱えるようにはなっていないのですが、内部的にはちゃんと存在しています。
この時2つ注意して欲しい事があります。
- Unity Humanoid は「元のローカル軸の方向」を「Muscle 値で制御するローカル軸の方向」とは別で保持している
- Unity が基準としている Humanoid ボーンのローカル軸の方向は VRM が定義したものではなく、Unity Humanoid 化に VRM の正規化は必須ではない
つまり、Unity Humanoid はローカル軸を利用するアプリやプラグインの為に元のボーンのローカル軸の方向を残す作りになっているのですが、VRM の正規化はローカル軸の方向を破棄するのでそれらのプラグインが正常に使えなくなります。
また、これは VRM が出るより前から Unity が使われていた事からも明らかなのですが、Unity Humanoid のリターゲットを行う上で、VRM の正規化処理は必要ありません。
VRM の正規化処理も一応リターゲットと言えるでしょう。しかし、VRM の正規化処理は Unity Humanoid の Mecanim 制御ほどその後の制御方法が考えられておらず、元のローカル軸の方向も保持していません。
Implementation guide about Humanoid · Issue #12 · vrm-c_vrm-specification
つまり、VRM の正規化は Unity 上では役割が重複しているのに Unity 外においては大きな足枷になっているというのが現状です。
リターゲットも全能ではない
ボーンの制御方法をブラックボックスである Unity Humanoid のリターゲットに頼っている状態で脱 Unity を謳うのはあまりにも無責任なように思えます。では、VRMSpringBone のように内製のリターゲットシステムを提供すれば良いのかというと、そういう訳でもありません。
広く知られているところだと、Unity Humanoid では指の曲がる方向が不自然である(特に親指)という問題があります。また、リターゲット前後で「実際に利用するローカル軸の方向」は変化します。その為、ポージングの際にデザイナーの意図した通りの変形が行われるとは限らず2、リターゲットシステムに適応したリギングや設定をユーザー側に要求する事になってしまいます。
もし、内製のリターゲットシステムがあったとしても、移植の手間も発生する上、環境に既に存在するリターゲットシステムと競合する事が考えられます。Unity で Humanoid を使わないという事はまず考えられません。
方向推定
方向推定とは
ローカル軸の方向を無視してボーンを制御する技術のもう1つが「方向推定」です。3次元空間にある2点において、一方の点からもう一方の点へ向かうベクトルを算出する事は可能であり、それは方向についてもある程度は同じ事が言えます。(後ほど説明します)
編集ソフトを触った事がある人は聞いた事があるかもしれませんが、「IK のために関節を少しだけずらす」という話には、この方向推定が絡んでいます。
BlenderのglTFimporterにおける推定
ところで、先ほど説明した Head-Tail 形式を思い出してください。ボーンの Head から Tail へ向かう軸が必ず +Y になると説明しました。これは VRM に限らない話なのですが、前述した Node 形式においてはロール軸方向が必ず +Y になるとは限らないので、Blender の glTF importer には方向推定オプションがあります。
また、これとは別に VRM Addon には Humanoid を前提とした独自の決め打ちベースの推定が実装されています3。比較してみましょう。
Blender で推定した物を glTF として出力した結果です。左二つに関しては、VRM 0.x は glTF としては逆を向いている(1.0 では修正される)ので 180 度回転後、トランスフォームを適用して向きを揃えています。
そもそも Head-Tail 形式が方向推定と似たようなものなので、どこからどこまでを推定と呼んでいいかは線引きが難しいのですが...少なくとも方向推定されたものはそれなりの見た目を保っているように見えます。
では方向推定があれば問題はないのでしょうか。
もちろんそんな事はありません。推定された方向は、正規化前の方向とは異なる物なので、正規化前のボーンによる変形を再現する事はできません。
アリシアのローカル軸を修正した物を正規化前のモデルとして用います。
VRM Addon でエクスポート(正規化)後、再インポートした結果がこちらです。
繰り返しになりますが、このようなローカル軸の方向の強制的な変更は、ポージングの際にデザイナーの意図とは異なった変形が生まれてしまう原因2となります。
方向推定はあくまで推定でしかない
議論の中で、どうも VRM は方向推定を推奨しているというような発言がありました。
引用:VRMのデータ的でのローカル軸の維持 · Issue #34 · vrm-c_vrm-specification
こういう事を言われてしまうと、VRM 0.x は Head-Tail 形式に近そうに思えるのですが、Tail がないので 1 対 n ではロール軸方向が不明であり、ロール値がないので X 軸と Z 軸の方向が制御不能です。推定で得られる方向を定義と言うには、明らかに必要なものが欠けています。
3次元空間にある2点において、一方の点からもう一方の点へ向かうベクトルを算出する事は可能であり、それは方向についてもある程度は同じ事が言えます。
ある程度というのは、3次元空間上では一方の点からもう一方の点へ向かう回転は1つだけでは無いという事を意味していました。X 軸と Z 軸の方向を確定させる為に Head-Tail 形式ではロール値を用いていたという訳です。
正規化により発生するVRM自身の機能の問題点
VRMSpringBoneの問題
回転方向の制御ができない
VRMSpringBone の問題として、回転方向の制御ができない事が挙げられています。表現の幅が狭くなるという問題もありますが、回転方向の制御ができない事は VRMSpringBone でスカートが貫通しやすい原因の一つでもあります。
VRMSpringBoneを布モノに適用すると突き抜けてしまう · Issue #83 · vrm-c/vrm-specification
ダイナミックボーン(DB)を使った貫通に強い揺れるスカートの作り方
VRChat でよく用いられている DynamicBone には FreezeAxes という、子ジョイントの指定軸への移動を禁止する、即ち回転方向を制限するパラメータが存在します。しかし、VRM にはローカル軸の方向が存在しないため VRMSpringBone では回転方向の制御が難しくなっているのです。
自転車のチェーンのような、いわゆるヒンジジョイントを例として考えてみましょう。
図のような場合、DynamicBone で言えば FreezeAxes に X を指定すれば誤った方向への回転を防止する事ができます。
ところが VRM はローカル軸の方向を破棄してしまうので、
この状態では X Y Z のような軸指定での回転方向の制御が不可能というのは一目瞭然です。
方向推定の為にモデルのボーン数が増える
また、VRMSpringBone はジョイント末端において、末端の親と末端の2座標を使った方向推定を行っています。
引用:UniVRM_VRMSpringBone.cs at master · vrm-c_UniVRM
もしローカル軸の方向を保持するのであれば、この方向推定が不要になります。任意の方向を向いた仮想ボーンを方向推定で生成する為にはジョイントが2つ必要という事を考えれば、ミニスカートやショートヘアのような短いボーンにおいてはジョイント数(ボーン数)を削減する事が可能であると言えます。
スカートに1節だけ揺れるボーンを入れたい場合を例に考えてみましょう。
Blender でスカートにボーンを入れるとこうなります。Head-Tail 形式から Node 形式にするにあたって、Tail は Head のローカル軸の方向を残してなくなります。
しかし VRM はローカル軸の方向を破棄するので、上の図に VRMSpringBone を適用するとこうなります。
このように親からの方向を用いると、正規化前の末端のボーンの曲率が大きい場合に仮想 Tail の方向がおかしくなります。現在の VRM ではこうならないために、あらかじめ末端へボーンを追加しておく必要があります。
これにより、以下のように仮想 Tail が自然な方向へ配置されます。
現在の VRMSpringBone では仮想的な Tail は延長線上にコライダを置く事を目的にしているのではなく、親にローカル軸の方向を持たせたいという意図が強いと思われます。(長さ 0.07 というのは殆ど Head と被っているので)
もし、モデルがローカル軸の方向を保持しており、仮想 Tail までの長さが分かるのであれば、末端ジョイントのロール軸の延長線上に仮想 Tail を配置する事ができるので、最初の図から以下の図の状態を作り出すことが可能になります。
つまり、末端の長さのパラメータを必要とする代わりにボーン数を削減できるという訳です。このように、ローカル軸の方向の保持は、昨今で話題となっている軽量化問題にもボーン数の削減という点において良い影響をもたらすと考えられます。
VRMConstraintの問題
移動・回転方向の指定ができない
VRM 1.0 ではコンストレイントの実装が行われる予定があります。
WIP_ [1.0] Constraints_ add explainer by FMS-Cat · Pull Request #188 · vrm-c_vrm-specification
コンストレイントについて簡単に説明すると、例えば親子ではないボーン A とボーン B が存在する場合に、ボーン B がボーン A の変形を見張るように設定し、ボーン A が変形したときにボーン B もそれに合わせた変形を行う事ができるようにする機能です。
説明よりも実例を見た方が理解しやすいです。
上の図では、右のボーンの X 軸回転を左のボーンの X 軸回転へと適用しています。気づいた方もいるかもしれませんが、VRMSpringBone 同様、コンストレイントにおいて、移動・回転方向の制御のためにローカル軸の方向は非常に重要です。
コンストレイントがよく用いられるケースとして、関節を曲げたときにポリゴンが破綻するのを防ぐための補助ボーンという物があります。
Unity では Mecanim がこの辺りを勝手に補完しますが、Unity 外の環境で自然な関節の変形を行うためには補助ボーンが必要です。とはいえ、この補助ボーンについては基本的に関節と同じローカル軸を用いるのでそれほど問題はありません。
問題になるのは、上の画像のように、ローカル軸の方向が異なる場合にコンストレイントを適用できない事です。つまり、サブアーム4や機械部品、獣耳・尻尾・羽のような独立したボーンにコンストレイントを適用する場合、ローカル軸の方向がないのでそれらが動く方向を制御できないという事になるのです。このように、VRM がローカル軸の方向を破棄するせいでコンストレイントの用途が著しく限定されてしまいます。
一応、この問題については VRM コンソーシアムの方でも認識されているようですが...。
引用:WIP_ [1.0] Constraints_ add explainer by FMS-Cat · Pull Request #188 · vrm-c_vrm-specification
もしかしたら、これらの問題については下記 issue のような形でなんとかしようとしているのかもしれません。
引用:ボーン回転バックアップ、ボーン回転復旧機能 · Issue #731 · vrm-c_UniVRM
「ローカル軸の方向の手動での再設定」というのは完全に二度手間であり、いたずらにデータの量と作業量を増やすことになってしまいます。また、機能開発の際に手動での再設定があるかないかで前提条件がブレるというのは、保守性や信頼性を著しく下げます。このような実装をするのであれば、むしろローカル軸の方向の破棄のほうをアプリケーション側で必要に応じて行うようにすべきではないでしょうか。
ローカル軸の方向の破棄という仕様はあまりにも可用性に欠けています。「自分で捨てたローカル軸の方向という情報がやはり後から必要になり、推定や再設定のような手段でその情報を取り戻そうとしている」と言えば、VRM の正規化処理が引き起こしている「無駄」や「二度手間」に気づいて頂けるでしょうか。
各編集ソフトにおける汎用性を考慮した定義の提案
問題点のおさらい
ローカル軸の方向を破棄するというのは正規化時の実装が非常に楽で、単純で明確な定義なのですが、実装上の都合で失う物があまりにも大きく、その分のツケがデザイナーやアプリ製作者に回ってきています。Unity を利用する分には Unity がある程度のツケを払ってくれる事もあるのですが、Unity への依存を本当に減らしたいのであれば、正規化の仕様について考え直す必要があります。
ローカル軸の方向が破棄された場合の問題点のおさらいです。
- リターゲットシステムのない環境でポージングやアニメーションの作成が困難
- ボーンを曲げる方向が正確に分からないのでデザイナーの意図した変形ができない
- 揺れものや IK、コンストレイント等における回転軸の制限が困難になる
- どうしてもローカル軸の方向が必要になり、推定や再設定を行う二度手間が発生している
これらは全て、ボーンの回転方向が正確に分からない故に引き起こされている問題です。これらの問題が起こらない定義・実装があるのか考えていきましょう。
各回転軸の役割を統一した定義を行う
まず、VRM 1.0 では回転軸の役割を定義しようとしています。
ヒューマノイドのローカル軸を決める · Issue #176 · vrm-c_vrm-specification
一応、ボーンの表に Orientation というロール軸らしき項目もあるのですが...(策定途中?)
引用:vrm-specification_specification_VRMC_vrm-1.0_draft at master · vrm-c_vrm-specification
しかし、現在の定義では回転軸による役割が X Y Z で混在しています。特に、斜めになる親指や足に関しては2軸が割り当てられている上に数値での指定もないので、それらのボーンを回転させる方向が分かりません。
引用:ヒューマノイドのローカル軸を決める · Issue #176 · vrm-c_vrm-specification
親指を曲げる方向をはっきりさせたいのであれば、間違いなく現在の親指のロール軸に2軸が割り当てられているような定義は避ける必要があります。
そこで各回転軸の役割を固定した Azure Kinect の例があります。
引用:Azure Kinect body tracking joints _ Microsoft Docs
この図では関節の捻り(Roll)を X 軸、開閉(Spread)を Y 軸、屈伸(Bend)を Z 軸としているように見受けられます。個人的には肩の屈伸と開閉については逆のように考えていますが...ともかく回転軸の X Y Z によって役割は固定されています。ロール軸の方向が所々反転しているのは、左右における各関節の回転の正負の対称性のためでしょう。これをベースとして考えます。
まず、ロール軸を Blender や VRMSpringBone の推定と同じように Y 軸、親から子へ向うのが +Y 方向とします。そうするとロール値が 0 の状態では腰の屈伸軸が X 軸になるので、屈伸軸を X 軸で統一します。この時、関節を曲げる動きを左半身右半身とも正の回転、伸ばす動きを負の回転とします5。これにより Y 軸と X 軸の方向が明確に決まったので、Z 軸の方向は右手正規直交系という前提において Y 軸と X 軸から自動的に決まります。
- Y 軸:捻り(基本的に親ジョイントから子ジョイントを向く)
- X 軸:屈伸(筋収縮が正の回転5)
- Z 軸:開閉(右手正規直交系にて X 軸と Y 軸より一意に決定)
という原則に基づいたローカル軸の方向の定義になります。上に貼った現在の策定途中の表に合わせる場合、Orientation の項目は全て Y+ になるので記述するまでもなくなります。代わりに、基準となるローカル軸の方向を「ローカルレスト」もしくは「グローバルレスト」6のような何らかの形で数値として記載すべきという事になります。
腰や肩は大きく動かすと大変なことになるので、とりあえず手足のボーンのローカル軸を複数同時に回転させた時の挙動です。全てのボーンで +X 回転を行うと、胎児のように縮こまるポーズになります。ちなみにこれにより最悪 X 軸だけである程度のポージングが可能です(そのような環境があるとは思いませんが)。
以下のリンクで公開しておきます。
元の VRM からの変更点として、ウェイトが塗られておらず、必要のなさそうなボーンは削除しています。また、hips の角度がかなり急ですが、これは恐らくアリシアの元が MMD 形式を意識した物7であることに由来するものではないかと考えられます。
このような編集ソフト上で再現できる定義であれば、ガイドラインやサンプルプロジェクト等を用意し、リギングの時にそれに従って貰うという事が可能になります。VRM 0.x の定義は、編集ソフト上で再現することが難しく、ガイドラインがあったとしてもそれに従ってリギングすることは出来ません。明確なガイドラインがあれば、今のような「正規化のせいでユーザーが知り得ない所で何かが起きる」という混乱を減らす事ができるのではないでしょうか。
懸念事項としては、ロール軸を親から子へ +Y 軸と定めた結果、X 軸以外の回転については左右での正負の対称性が保証できないという事が挙げられます。Unity の Humanoid Muscle のようなスライダーを実装する場合には、それらの正負のリストが必要になると考えられます。
自動正規化は必要なのか
「自動」という言葉の意味が「元のローカル軸の方向を破棄して書き換える」という事を意味しているのであれば、オススメはしません。先ほども言ったように、ポージングの際にデザイナーの意図と異なった変形が行われてしまうのは、VRM の自動正規化による元のローカル軸の方向の破棄が大きな原因です。
ライトユーザー向けとして、ローカル軸の方向を書き換えて強制するリターゲット機能をどこかに用意しておくのはいい事だと思いますが、必須にすべきではありません。
基本的には、デザイナーの意図を最大限尊重して、「書き換え」ではなく「推奨範囲内にあるかどうかというチェック」にとどめるべきではないでしょうか。
例として、グローバルレストの値を基準としたバリデーションを行う事は可能でしょう。
推奨値の定義には、クォータニオンよりも直感的な基底変換行列を用いることを推奨します。
例えば、上図のような左上腕が X-Back Y-Left Z-Down というのは、
\begin{pmatrix}
0 & 0 & -1 \\
1 & 0 & 0 \\
0 & -1 & 0
\end{pmatrix}
と表せます。
クォータニオンと同様にジンバルロックがないので親指のような斜めのボーンの定義がしやすく、基底変換行列には特異点8が存在しないので、推奨範囲を±のような形で表すことも可能です9。
とはいえ、この辺りはプログラマーが分かっていれば良い話なのでユーザーには推奨範囲外になっているという事を何らかの方法で警告すれば良いと考えています。この定義はあくまで推奨値であり強制してはいけないという事を強調しておきます。
例えば、逆関節のアバターであれば脚のロール軸が 180 度回転するような場合、推奨範囲外であるという警告は出しても、推奨範囲に収めなければ書き出せないというような実装はすべきでないという事です。
ついでに Humanoid 以外のボーンについてはチェックする必要はないと考えています。
提案する定義とVRM0.xの定義の比較
提案する定義と VRM 0.x の定義では具体的に何がどう違うのかという事を分かりやすくする為、数値と回転軸の役割を表にして比較してみましょう。
VRM 0.x の定義では、グローバルレスト回転の数値を強制した結果、各回転軸の役割が曖昧になってしまっている事がわかります。アバターモデルという前提においては、数値が完全に一致しない事よりも、回転軸の役割の曖昧さにより意図しない変形が引き起こされる事の方が問題ではないでしょうか。
異なるモデル間でボーンを曲げる方向を揃えたいという目的は分かりますが、骨格を無視してまで関節を曲げる方向を数値的に一致させる必要があるのか(そしてそれを要件としてエクスポート時にやる必要があるのか)という点について、今一度考え直すべきであると提言いたします。
ローカル軸の定義と正規化実装を改善した際のメリット
デザイナーとプログラマーそれぞれにメリットがある
さて、ローカル軸の方向を破棄することをやめ、回転軸の役割に基づいた定義を行った場合のメリットとしては、
デザイナー
- 多くの編集ソフト上で定義をそのまま利用できる
- リギングの指標が分かりやすくなる
- 意図した変形をアバター利用者へ伝える事ができる
- アニメーションの作成や共有がリターゲットがなくても容易になる
プログラマー
- リターゲットや方向推定が必須ではなくなる
- ローカル軸を参照した移動や回転方向の制御が可能
- ボーン削減による負荷軽減
- ボーンを制御するパラメータの実装が容易になる
というような事が挙げられます。
先に挙げた問題点がすべて解決された上で、という事になるので、問題点が改善されただけの項目はメリットといっていいのか怪しい所ですが、これらの事が実現出来ていないのが VRM の現状です。
アニメーションの共有が容易になる
最初にも言った通り、現状の VRM を glTF として扱いアニメーションを作成する場合、例えば Blender では全てのボーンが立っている状態のモデルを操作してアニメーションを作れという事になります。
もし Rigify のような IK を使うとしてもこの状態のボーンをセットアップするのは不可能であり、どこかでリターゲットが必要になってしまいます。
VRM Addon を使ったとしても、VRM Addon での方向推定後のモデルは VRM の定義とローカル軸の方向が異なるので、方向推定後のモデルで作ったアニメーションを Blender の外で VRM モデルに割り当てると壊れてしまいます。
Unity であれば、VRM は Unity Humanoid なので、アニメーションを Unity Humanoid アニメーションとしてインポートすればリターゲットが行われて、それなりに上手く適用できるのですが、そのようなリターゲットのない環境では壊れてしまうという訳です。
たとえ、VRM Addon がアニメーションを出力時に再度ローカル軸の方向を破棄するような実装を行う、あるいは Blender 以外の編集ソフトを利用する場合10でも、斜めのボーンのローカル軸の方向が死んでいるというような問題は依然として存在しているので、デザイナーが想定していた変形が行われるという保証はありません。
何も変なことをしようとしている訳ではありません。デザイナーが編集ソフトでモデルのボーンを操作してアニメーションを作成し、それをプログラマーがアプリ内で適用するというだけの事ですが、たとえいくつかの制約があったとしても一般的な人型の glTF モデルで容易に出来ることが VRM では出来ないというのは、glTF 拡張フォーマットを謳う上でどうなのかなと考えてしまいます。
そしてリポジトリの方には、アニメーション用のファイルを定義したいという issue があります。
VRM向けモーションデータの標準化 · Issue #118 · vrm-c_vrm-specification
しかし、何度も言いますが、現在の定義では親指や足のような斜めのボーンの回転方向を一意に決める事は絶対にできません。きちんと回転軸の定義を行えば、少なくとも glTF として、方向推定やリターゲットに頼らないアニメーションの共有が可能になります。体型による誤差は生じますが、それはリターゲットでも同じ事が言えます。
恐らく、Mecanim のようにモデルごとの差異を吸収できるような曲げ具合や捻り具合といったパラメータを用いたアニメーション用のファイルを定義するというのが最終目標としてあると思うのですが、その前段階として、せめて一般的な glTF としてアニメーションを共有できるようにすべきではないでしょうか。
また、もしローカル軸の方向の破棄をするとするなら、ここで行うべきだと考えます。言い換えると、「モデルのデータ」に対してではなく、アニメーションのような「ボーンの操作というアクション」に対してローカル軸の方向の破棄を行うのであれば、モデル自体への影響を抑える事ができるという事です。
具体的には、アニメーションのトラック毎に、ローカル方向を破棄する、即ち回転軸を無視した変形を行うという選択項目をつける事で、ローカル軸の方向に由来する変形の不具合をある程度回避する事ができると考えられます。もっとも、ローカル方向に対するパラメータしか設定できない既存のアニメーションシステム(Unity に限らず)との兼ね合いが難しいという問題11はありますが...。
こちらの記事も読んでみると参考になるかもしれません。
モーションとグローバル軸とローカル軸_いろはんのブロマガ - ブロマガ
まとめ
長くなりましたが、VRM の正規化周りについて言いたい事は大きく2つです。
- 正規化処理でボーンのローカル軸の方向を破棄するような実装は避けるべき
- 回転軸の役割が X Y Z で一意に決まるような定義を行うべき
最低限、この2つがあれば推定に頼らない変換が可能であり、glTF に対応したプラットフォームであれば、意図した通りのボーンの操作を行う事が可能です。
再定義を行うにあたって、もちろん UniVRM の中身をいくつか書き直す必要はありますが、方向推定等に頼らずローカル軸をそのまま用いることができるため、実装はシンプルになる筈です。
VRM 0.x から 1.0 への移行は互換性を考えなくても良いという滅多にない機会です。Unity 上であれば途中で定義が変わっても Humanoid がリターゲットを行うのでそれほど問題はありませんが、Unity 外の環境にとっては死活問題になりますので、この機会にしっかりと再定義を行うべきだと強く提言致します。
後書き
散々指摘しましたが無差別に粗探しをしているわけではありません。
- こういう設定項目が欲しい
- MToon 以外のシェーダーも使えるようにしたい
- プラットフォームごとに容量制限されるのは問題
とかそういう事についてはあまり議論する気はありません。それらは VRM の実装とは切り離してでも最悪なんとか出来る事だと考えています。
VRM のコンセプトには同意できますし、Unity 前提の独自フォーマットとしてなら文句はありません。
しかしプラットフォームを超えた glTF 拡張の汎用フォーマットを謳うのであれば、一般的な glTF として扱うには明らかに欠陥がある点について、glTF ネイティブのアプリを開発している側としては、
このような glTF としての汎用性を破壊するような定義を見過ごす事が出来ませんでした。
この記事を読んでいる皆さんも、脱 Unity や VRM をもっと多くのプラットフォームで使えるようにしたいという事を少しでも考えているのであれば、GitHub での議論に参加してみては如何でしょうか。リアクションとしての絵文字を付けるだけでも反応としては十分だと思います。
VRM コンソーシアムの方々の胃を痛めるかもしれませんが...。
ここまで読んで頂きありがとうございました。もう少しだけ続きがあります。
余談:VRMにはTポーズの定義がない?
正規化の問題とはあまり関係がないのですが、まったく関係ないという訳でもないので余談とさせて頂きました。どうも現在の VRM の定義では「ローカル軸の方向の要件」と「T ポーズの要件」が同じになってしまっているように見受けられます。
引用:vrm-specification_specification_VRMC_vrm-1.0_draft at master · vrm-c_vrm-specification
見たところ、T ポーズの要件に関して軸の方向の定義しかありません。T ポーズ、則ちジョイントがどこに存在しているかというのは座標の話になると思うのですが...。現状を簡単に言うと、左手首のボーンは左肘より左に配置されなければいけないというような定義がありません。
つまり極端な話、この T ポーズの定義にはあまり意味はなく、Humanoid のボーン構造さえ保っていれば、A スタンスだろうが 4 足歩行だろうが、ローカル軸の方向を破棄した時点で T ポーズであると VRM は認めているのです。これは、先述した「方向推定を定義とするのは不可能」という理由の一つにもなります。
Unity の T ポーズに通らない物が VRM として存在可能であるというのは、VRM が独自の定義であるという裏付けの一つになっているとは思いますが、T ポーズを推奨しつつ T ポーズの定義がほぼ無いに等しいのは如何なものかと感じます。
ただし、この問題に関してはあくまで見た目上の問題であり、破壊的であるとかそういう問題でもないのでそこまで固執する事ではないとも思っており、むしろ今の状態であれば T ポーズの要件に関しては撤廃してもいいのでは? というような事を考えてしまいます。
あくまでも「正規化の問題はローカル軸の方向の話」で「T ポーズの問題はジョイント同士の相対位置の話」なので、議論や定義の際にはこの2つの問題を混同しないように注意する必要があります。
とりあえずこんな所で現在の VRM の正規化周りの問題点についての説明は終わりです。正規化の仕様が見直され、VRM 1.0 が一般的な glTF モデルと同等に扱えるようになった暁には、どこかの Unity ではない glTF ネイティブのアプリケーション上でお祝いしましょう。
お疲れ様でした。
謝辞
情報提供、調査協力、意見交換、ファクトチェックその他諸々に携わって頂きました以下の方々に感謝致します。
- Ernest Lee (Godot Member, Core Contributor 3D pipeline of Godot)
- Saracen (V-Sekai)
- Lyuma (V-Sekai)
- lox9973 (ShaderMotion)
- Silent (Starlit Wanderings, Silent's Cel Shading Shader)
- fuka mizno (sirokiri)
-
厳密には一致ではなく体型ごとに少々異なるようです、この辺りは完全に Unity のブラックボックスなので正確な処理は分かりません。 ↩
-
Unity Humanoid であれば筋肉をシミュレートしたウェイトの分散などもあるようなので、完全にローカル軸の方向の変更だけが原因という訳ではありません。ただしそういった補完の無い状況ではこれが単一の原因となります。 ↩ ↩2
-
執筆中に VRM Addon のアップデートがあり、最新版は Blender 2.83 以降の環境であれば Fortune アルゴリズムを利用するようになりました。Blender 2.83 未満の環境では引き続き独自アルゴリズムを用いるようです。 ↩
-
モデルと回転軸が全く同じであれば何とかなりますが、サブアームを斜めに付けたいとかになってくると破綻します。 ↩
-
オイラー角で X 軸のみを回転させた時、基本的に大きく曲げる側、即ち筋肉を収縮する側の回転が正の値となるようにします。 ↩ ↩2
-
ローカルレスト、グローバルレストにおける「ローカル / グローバル」は「親からの相対値 / ワールド軸からの相対値」という意味合いです。 ↩
-
MMD 形式では腰の回転を意識した構造として、上半身と下半身とその2つを束ねる親という構造があります。兄弟関係だった Spine(UpperBody) と Hips(LowerBody) を Humanoid に適合する為に親子関係にした結果、妙な位置関係になったのだと推測されます。 ↩
-
オイラーはジンバルロックの影響で特定の角度を跨いだ時に値が大きく変化してしまいます。クォータニオンにジンバルロックは存在しませんが、特定の角度を跨いだ時に各要素の正負が突然反転します。分かりやすく言うと、基底変換行列内の一つの要素が 0.001 -> -0.001 と変化すると、それらを乗算を使ってまとめたものであるクォータニオンの値は 0.6 -> -0.6 というように突然正負が反転してしまうのです。 ↩
-
基底変換行列の要素ごとに直接数値を入れて範囲を定義するのは直交の遵守や正規化(数学的な意味)の影響で難しいです。基底変換行列は行ごとに X 軸 Y 軸 Z 軸がそれぞれどの方向を向いているかという事を表している為、各軸ごとに基準の X Y Z 軸となす角度の大きさの上限を定義する事で、逆説的に基底変換行列の要素ごとの値の範囲を決定することが出来ます。特に範囲を±で記述する必要がないのであれば、クォータニオンでの定義と各軸のなす角の大きさによる推奨範囲の定義でも問題ないとは思いますが、どちらにしても X Y Z 軸ごとのなす角を取るためには基底変換行列を経由する必要があると考えています。 ↩
-
Blender 以外では VRM をインポート出来る編集ソフトがまともに無いので何とも言えませんが...。Maya には VRM の「エクスポートのみ」プラグインが存在しているようです。これも現在の正規化の破壊的な仕様のせいで、出力は容易く、利用は困難となっている現状を物語っているのではないでしょうか。 ↩
-
変換は容易ですが、その変換後のパラメータをエンジンにどう伝えるかの問題です。モデルが変更された際に毎回アニメーションデータを書き換える(変換する)手間が発生しうるので、アニメーション中にモデルをシームレスに変更するのが難しくなると考えられます。 ↩