メタセコイアで保存した.glbからVRMのモデルデータを作る

[VRM](https://vrm.dev/)で遊んでみたいと思いましたが，Unityを起動するのが面倒だったのとファイルフォーマットを理解するついでに変換ツールを書いてみました．
VRM は [glTF](https://www.khronos.org/gltf/) ベースなので比較的簡単に変換できます．

![vrm.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58515/8625ad27-ec6e-48d6-9f69-73fb29d2096b.gif)

# 注意

変換したファイルの扱いは特に制限しませんが，おそらく実用には耐えません．

# 準備

### 変換ツール https://github.com/binzume/modelconv

VRMファイルを出力するために書いた3Dモデルフォーマットを変換するコマンドラインツール．
[Go](https://golang.org/)が入っていればビルドできると思います．Windowsの場合は[Releases](https://github.com/binzume/modelconv/releases/latest)にビルド済みのものも置いてあります．

### メタセコイア https://www.metaseq.net/

マテリアルを編集したり，.glbファイルを出力するのに使います ~~(無料版だと.glbで保存できないのでStandard以上が必要です)~~ (2020/2/8 .mqoから.vrm に直接変換できるようにしました)

### ココニ立体ちゃんの3Dモデル https://3d.nicovideo.jp/works/td14712

確認用に使います．(もともとVRMファイルも用意されてるので，完全に確認と説明目的です)

# 変換する

.pmx から .vrm への直接変換もできますが，微調整のために編集できたほうがよいので，.pmx → .mqo → .vrm という順序で変換します．

## .pmx → .mqo

ダウンロードしたニコニ立体ちゃんのデータにはPMXとFBXが入ってますが，メタセコイアでは読めないっぽい(Standard版だとFBXも読めない)ので変換しておきます．

```bash
modelconv -rot180 -scale 80 -autotpose "右腕,左腕" Alicia_solid.pmx alicia.mqo
```

エラーメッセージ等が出ずに `alicia.mqo`と`alicia.mqx`が生成されれば成功です．

VRMの座標系に合わせるために，ここで180°回転して80倍してます．スケールとかはここで指定しなくても，メタセコイア上で調整しても良いです．
あと，`-autotpose` オプションで腕のボーンを指定してTポーズにしていますが，この記事を書いたあとに実装したので，あとの説明ではTポーズになっていません．．．

## 材質などを編集

メタセコイアで`alicia.mqo`を開いて，変換できているのを確認します．
本来は，ちゃんとTポーズになるように修正したりしないとダメですが，今回はこのまま変換してしまいます．

![mqo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58515/223830f7-5c16-ae51-5db6-ab117b53e6f8.png)

`hairShadow`だけ不透明のままだと目立つので，マテリアルの設定でglTFのAlphaモードをBlendにしました．

あと，下記のボーンの対応も確認しておきます．

.mqo または .glb ファイルに保存します．(当初はメタセコイアで.glbで保存していましたが，無料版だと使えないのと変換時にモーフの名前などが失われるので.mqoのほうが良さそうです)

## .mqo → .vrm

VRMはglTFの glb フォーマットがベースになっているので，glbファイルにVRM用の情報を埋め込みます．
以下のような，VRMのメタデータとVRMのボーンの対応をjsonファイルを作ります．UTF-8で保存してください．

```json:alicia.vrmconfig.json
{
    "meta": {
        "version": "0.0.0",
        "title": "Hello, VRM!",
        "author": "Test"
    },
    "preset": "mmd",
    "boneMappings": [
        {"bone": "hips", "nodeName": "センター"},
        {"bone": "spine", "nodeName": "上半身"},
        {"bone": "chest", "nodeName": "上半身2"},
        {"bone": "neck", "nodeName": "首"},
        {"bone": "head", "nodeName": "頭"},
        {"bone": "leftShoulder", "nodeName": "左肩"},
        {"bone": "leftUpperArm", "nodeName": "左腕"},
        {"bone": "leftLowerArm", "nodeName": "左ひじ"},
        {"bone": "leftHand", "nodeName": "左手首"},
        {"bone": "rightShoulder", "nodeName": "右肩"},
        {"bone": "rightUpperArm", "nodeName": "右腕"},
        {"bone": "rightLowerArm", "nodeName": "右ひじ"},
        {"bone": "rightHand", "nodeName": "右手首"},
        {"bone": "leftUpperLeg", "nodeName": "左足"},
        {"bone": "leftLowerLeg", "nodeName": "左ひざ"},
        {"bone": "leftFoot", "nodeName": "左足首"},
        {"bone": "rightUpperLeg", "nodeName": "右足"},
        {"bone": "rightLowerLeg", "nodeName": "右ひざ"},
        {"bone": "rightFoot", "nodeName": "右足首"}
    ],
    "materialSettings": {
        "*": {"forceUnlit": true}
    }
}
```

`"preset": "mmd"` を指定すると自動的にMMDのボーンやモーフの名前を認識するので，上のboneMappingsの中身は省略可能です．

マテリアルは，とりあえず強制的にUnlitにする設定だけ用意しました．元データがVRM用のボーン名になっていればboneMappingsは不要です．

この設定でmqoからvrmに変換します．

```bash
modelconv -vrmconfig alicia.vrmconfig.json alicia.mqo alicia.vrm
```

必須なボーンが無かったり上記のjsonが読めなかったりするとその旨のエラーメッセージが出力されます．

エラーメッセージ等が出ずに `alicia.vrm`が生成されれば成功です．

# 表示してみる

最後に[VRM Live Viewer](http://fantom1x.blog130.fc2.com/blog-entry-309.html) で読み込んでみました．

![vrm.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58515/8625ad27-ec6e-48d6-9f69-73fb29d2096b.gif)


口が動いてないですが，とりあえず踊ってくれました．肩が下がり気味なのはおそらくデータがTポーズになってないせいです．あと試しに髪の毛も揺らしてみました．

# まとめと感想

とりあえず動いたので満足しました．

やったのは，

- モデルの座標系やスケールを合わせる
- ボーンの軸を書き換える(.glb→.vrm時にやっている)
   -  Skinが持つinverseBindMatricesの回転成分とNodeのRotationを削除して，それにあわせて位置を計算し直す
   -  ここの計算は雑なので移動や回転以外の変換は考慮してません．行列やクォータニオンもベダ書きしてるのでバグってそう…
- `UNSIGNED_BYTE` になっているバッファを `UNSIGNED_SHORT` に変換して追加しなおす
   - 仕様には特に制限の記述がみあたらないけどJOINT_0をUNSIGNED_BYTEにすると動かなかったので
- VRM用に必要な情報をglTFの`extensions.VRM`以下に入れる (ボーン，マテリアル，メタデータ等)

くらいです．

VRMのデータを扱う上で少し微妙だと感じたのは

- マテリアル周りが実質Unity専用になってて使いにくい (glTF拡張も分離してmaterial.extensionsに入ってたほうが実装しやすそう)
- ベクトルがarrayじゃなくてxyzを持ったオブジェクトになってる箇所がある
- いくつかはまるポイントがあった(UniVRMではUNSIGNED_BYTEなBufferが使えない箇所がある？とか)
- 今の所 https://kcoley.github.io/glTF/extensions/Prefixes.html でVRMは予約されてなさそう
- ~~立体ちゃん，かわいいのだけどぱんつ見えないように動画とるの難しい~~


# おまけ

人体錬成に失敗したときの気持ち．．．

### Component Type

glTFとしてはそれっぽく動いたので，VRM Live Viewerで読み込んだら，悲惨な状態の立体ちゃんが表示されました．よくみると微妙に面影があって動いてます．

ダウンロードしてきたAliciaSolid.vrmと比較すると，JOINTS_0のComponent Typeが5121 (UNSIGNED_BYTE)になってるあたりが違います．
glTFの仕様上は問題ないはずですが，たぶんUniVRMが対応してないんだろうと判断してUNSIGNED_SHORTにすると表示されました．

### マテリアルとか

VRM.materialPropertiesにマテリアル情報を入れなければいけないらしいです．
空にしておけばglTFのマテリアル情報を読んでくれるんじゃないかという淡い期待もあったのです．

https://github.com/vrm-c/UniVRM/blob/master/specification/0.0/schema/vrm.material.schema.json

Unityのシェーダのパラメータ埋め込むの嫌だなと思っていたら，シェーダに`VRM_USE_GLTFSHADER`を指定すれば，glTFのマテリアルを見てくれるらしい．良かった．こまで来たら，これをデフォルトにしておいてほしいところ．

### モーフとか揺れものとか

モーフ対応は，glTF内に名前の情報が残ってないのと，VRM仕様のblendShapeMasterの説明が少なくてよく分からなかったので見送り．
一応，動作確認用に実装した部分は残ってるので，vrm変換時の設定に以下のように書けば，まばたきするようになります．
髪の毛を揺らしたりもできますが，コライダーを追加しないと体を突き抜けます．

```json
"morphMappings": [
    {"name": "Blink", "nodeName": "MorphBase1", "targetIndex": 25}
],
"animationBoneGroups":[{
    "stiffiness": 0.7,
    "dragForce": 0.4,
    "center": -1,
    "nodeNames": ["左髪１","右髪１"]
}]
```