0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VRM のポーズを別のモデルに当てたら顔が消えた話

0
Posted at

デスクトップに VRM アバターを常駐させるアプリを作っていて、「利用者が自分の
VRM を持ち込める」ことを前提にした。そのときポーズの持ち回りで一日溶かしたので、
何が互換で何が互換でないかを実測つきで残しておく。

結論から言うと、モデルをまたいで使い回せるポーズ形式は実質 .vrma しかない

何が起きたか

自作アバター用に用意した立ちポーズを、別の VRM(配布されているサンプル)に
当てたら、頭が真下を向いて顔が消えた。腕もあらぬ方向に曲がった。

ポーズのデータ自体は壊れていない。元のモデルでは正しく立つ。

原因:ボーンには「2 種類の座標系」がある

VRM を扱うライブラリ(three-vrm など)には、同じ「右上腕」に対して
2 つの見え方がある。

生のボーンノード

glTF のシーングラフにそのまま入っているノード。回転値は
そのモデルのバインドポーズ(初期姿勢)を基準にした絶対値になる。

モデリングした人が「腕を下ろした状態」で作れば、その下ろした状態が原点。
別の人が「T ポーズ」で作れば、T ポーズが原点。基準が統一されていない。

だから、あるモデルで「頭をまっすぐ前」にする回転値を、別のモデルに
そのまま入れると、そのモデルの基準からさらにその角度だけ回る。
結果、頭が真下を向く。

正規化ヒューマノイドボーン

VRM 仕様が定義している方。こちらは T ポーズを基準にした相対回転で、
どのモデルでも同じ意味になることが仕様で保証されている。

three-vrm なら vrm.humanoid.getNormalizedBoneNode('head') で取れる。
getRawBoneNode() の方が生ボーン。

ポーズを配る/使い回すなら、必ず正規化側で書く。

// これは他のモデルに持っていける
const bone = vrm.humanoid.getNormalizedBoneNode('rightUpperArm');
bone.rotation.z = 0.16;

// これはこのモデル専用
const raw = vrm.humanoid.getRawBoneNode('rightUpperArm');
raw.quaternion.set(...);  // バインドポーズ依存

.vrma が答えだった

VRM Animation (.vrma) は VRM のために作られた
アニメーション形式で、ヒューマノイド基準で記録される。仕様として
モデル非依存が保証されている。変換も推測もいらない。

VRoid が無料の .vrma を配っているので、動作確認にはそれが使える。

three-vrm-animation で読める。

import { createVRMAnimationClip, VRMAnimationLoaderPlugin } from '@pixiv/three-vrm-animation';

loader.register((parser) => new VRMAnimationLoaderPlugin(parser));
const gltf = await loader.loadAsync(url);
const clip = createVRMAnimationClip(gltf.userData.vrmAnimations[0], vrm);
mixer.clipAction(clip).play();

createVRMAnimationClipVRMLookAtQuaternionProxy が無いと警告を出す。
動くが、視線を扱うなら自分で作って渡した方がいい。

Unity の .anim は変換できない

「Unity Humanoid のアニメーションを変換すればいいのでは」と考えて、
かなり粘ったができなかった。理由を残しておく。

Unity Humanoid の .anim は「マッスル値」で記録される。
Left Upper Leg Front-Back のような名前で、-11 の正規化値が入る。

名前は公開されているので、どの関節のどの軸かは分かる。しかし
マッスル値 0 が何の姿勢を指すのかが公開されていない。

実際に手元の「普通に立っているポーズ」を開くと、こうなっている。

Left Lower Leg Stretch: 0.999

膝を伸ばして立っているだけなのに、値は上限近く。
つまり 0 は T ポーズではない。基準姿勢が非公開なので、
-11 を角度に戻す変換式が作れない。

ここは諦めて、.anim を読み込もうとしたら
「この形式は変換できない。.vrma を使ってほしい」と理由つきで
表示するようにした。黙って失敗するより、何をすればいいか分かる方がいい。

クォータニオンとオイラー角を混ぜない

もう一つ踏んだ罠。

初期姿勢をクォータニオンで作り、そこにオイラー角の成分を足していく、
という書き方をしていた。これは合成として成立しない

同じポーズを両方の方法で作って角度差を測ったら 22.8度 ずれた。
小さい角度なら気づかないが、腕のように可動域が大きい部位で破綻する。

回転の合成はクォータニオンで統一すること。

ひねり(twist)は腕を曲げてから効く

これは知らないと詰まる。

upperArm の y 軸回転(腕の長軸まわりのひねり)は、
腕がまっすぐ下りているときはほとんど見た目が変わらない。
長軸まわりに回しても、腕の位置は動かないため。

ところが肘を曲げると、前腕が体の前を横切るように大きく動く。

「腕を組む」ポーズが作れなかったのは、これに気づいていなかったから。
肩を回そうとしていたが、正解は「ひねってから肘を折る」だった。

// 腕を組む
{ bone: 'rightUpperArm', axis: 'y', fn: (t) => hold(t) * 0.85 },  // ひねる
{ bone: 'rightUpperArm', axis: 'z', fn: (t) => hold(t) * 0.16 },
{ bone: 'rightLowerArm', axis: 'y', fn: (t) => hold(t) * 1.42 },  // 肘を折る

衣装によって「作れないポーズ」がある

最後に、技術というより設計の話。

腕を大きく動かすポーズは、袖の広い衣装だと角度が正しくても破綻する。
袖が平らな塊になる。これはスキニングの限界で、ポーズ側では直せない。

「どの VRM でも成立させる」という前提を置くなら、
体幹と頭で作るポーズに寄せるしかない。重心の移動、視線、上体の傾き。
腕を使うものは、肘を折るだけの小さいものに留める。

地味だが、これが一番効いた。

まとめ

やりたいこと 使うもの
モデルをまたぐアニメーション .vrma 一択
コードで書く立ちポーズ 正規化ヒューマノイドボーンへの相対回転
そのモデル専用の作り込み 生ボーンでもよい(配布はできない)
Unity の .anim の変換 できない。基準姿勢が非公開
  • 回転はクォータニオンで統一する
  • ひねりは肘を曲げてから効く
  • 衣装によって作れないポーズがある。腕より体幹

同じところで溶かす人が減れば。


こういう検証をしながら、VRM アバターをデスクトップに常駐させるアプリを作っています。
ここで書いた「正規化ボーンで書く」「.vrma を使う」はそのまま実装に入っています。

Wisp — デスクトップに、体を持ったAI

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?