VRM10のLookAtの仕様によってVMCProtocolの送信時に困った話

VRM10のLookAtと結構仲良くなれました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">VRM1.x系のLookAtチョットデキルになったのでVMCProtocolでBlendShape(Expression)やボーンどちらでも目線送れるようになったゾ、VRM1.x系のLookAtはExpressionやControlRigのボーンの値が0のままだからちょっと工夫したゾ <a href="https://t.co/OuwPLaBnk1">pic.twitter.com/OuwPLaBnk1</a></p>&mdash; 大福らな🧷ぱふぇらむ公式VTuber (@daifuk_lana) <a href="https://twitter.com/daifuk_lana/status/1753711869871952267?ref_src=twsrc%5Etfw">February 3, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

# はじめに
VRM1.x系のLookAtの仕様では目線制御について、Expression制御の場合はLookUp～LookRightに直接値が入るわけでは無くWeight値が生成され、ボーン制御の場合はControlRigのボーンが直接操作される訳では無くlocalRotationが生成されます。

そのため、LookAtで視線制御された表情をVMCProtocolで送信する際、単にExpressionの値を取得したり、`Animator.GetBoneTransform(hogehoge)`で取ってきたボーンの回転を送ろうにも、値が0であるために目線が送信出来ない、ということが起こります。

詳細なVRM1.x系のLookAtの仕様は以下に記載されています。

https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0/lookAt.ja.md


今回私が解決した方法をメモがてら残しておきます。
上記前提含め間違っていたらごめんなさい。

# Expressionの場合

LookAtを色々触っているうちに、以下のコードでLookAt時の目線のピッチとヨーを取得出来ることが分かりました。

```cs
Vrm10Instance.Runtime.LookAt.Pitch
Vrm10Instance.Runtime.LookAt.Yaw
```
そのため、このピッチ及びヨーの値から、ExpressionのLookUp～LookRightに相当する値を生成することにしました。
Expressionの値に比べ方向や桁が異なるため、適切な値となるよう計算します。
私の場合は*-0.01fしました。

そして、ピッチとヨーがそれぞれ0以上である場合、未満である場合でLookUp～LookRightを切り分けていきます。
2値であるため、上下左右がプラスマイナスで表現されています。
VRM0.x系のBlendShapeにSetする場合、左右なら例えば以下でしょうか。
```cs
if (yaw > 0)
{
 VRMBlendShapeProxy.ImmediatelySetValue(BlendShapePreset.LookLeft, yaw);
}
if (yaw <= 0)
{
 VRMBlendShapeProxy.ImmediatelySetValue(BlendShapePreset.LookRight, -yaw);
}
```

なお、私の場合はさんたーPさんにVRM1.x系と0.x系の表情互換APIを作って頂きましたので、同様のコードでExpressionにもSet出来ます。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">とりあえず作りかけてみた<a href="https://t.co/y8caRdRCGv">https://t.co/y8caRdRCGv</a></p>&mdash; さんたー (@santarh) <a href="https://twitter.com/santarh/status/1707448932920655929?ref_src=twsrc%5Etfw">September 28, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

https://github.com/Santarh/SthVrmUtil

これで、Expressionの値は生成出来ました。

なお、LookAtの仕様を読む限り、LookAtから生成されたWeight値がExpressionにSetされており、そのWeight値を取ってくれば良いのでは？と考えたのですが`Vrm10Instance.Runtime.Expression.GetWeight(hogehoge)`しても0でした。何故……

# ボーンの場合

こちらは単純で、LookAtからlocalRotationが生成されており、仕様の中にも記載の通り`vrm.humanoid.leftEye.localRotation = Quaternion.from_YXZEuler(yaw, pitch, 0);`とあるため、以下のコードで左右の目の回転が取得出来ます。
```
Vrm10Instance.Humanoid.LeftEye.localRotation
Vrm10Instance.Humanoid.RightEye.localRotation
```
簡単です。

# 最後に

ここまでで取得出来た値をVMCProtocolで送信すれば、LookAtのターゲット座標を転送せずとも目線情報が送信可能です。
Expressionの方は、もう少し賢いやり方が、というかもう少し正しい値の取得方法があるかもしれません。
ボーンを触ってても思いますが、VRM1.x系は、まずはVrm10Instanceなんだと感じて面白いです。

なお、これらの知見を活かして開発を進めているのがVMCProtocolミキサーです。
VRoid Hubにも対応している都合上、VRM1.x系でも使用可能に仕上げています。
<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="ja" dir="ltr">最新 <a href="https://twitter.com/hashtag/VMCProtocol%E3%83%9F%E3%82%AD%E3%82%B5%E3%83%BC?src=hash&amp;ref_src=twsrc%5Etfw">#VMCProtocolミキサー</a> v.0.1.8のデモ映像こんな感じ<br><br>アウトライン、シャドウ両方オンです<br>LuppetXの上半身とVRMポージングデスクトップの下半身を混ぜつつ、足先だけはLuppetXを活かして生感を持たせてます<br>waidayoから直接表情を受信出来るので、アイトラッキングや舌出しなども混ぜています <a href="https://t.co/obv0B6zBOm">pic.twitter.com/obv0B6zBOm</a></p>&mdash; 大福らな🧷ぱふぇらむ公式VTuber (@daifuk_lana) <a href="https://twitter.com/daifuk_lana/status/1752602625227579769?ref_src=twsrc%5Etfw">January 31, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


以上です。