MayaとUSDZでSceneKit（2）

<!-- <img src="sample.png" width=450 />　-->
# トラブルシューティング（Tips）


前編の[MayaとUSDZでSceneKit（1）](https://qiita.com/satoy11/private/31dc95885a180b00bd4e)に引き続き、後編では[emoc](https://whatever.co/moar/ja/emoc/)の制作を進めていく上で気付いたトラブルシューティング（Tips）をまとめています。
<br>


## アセットの配置が合わない
#### 座標系の違い

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/856236b2-a58a-0545-dbd2-816cf63489e0.png)
MayaからエキスポートしたUSDZファイルをXcodeでインポートすると、既存の背景データと向きが合わない問題が見られました。
同じ環境であれば起こりにくい問題ですが、座標系の異なるDCCツールやゲームエンジンの組み合わせだと注意が必要です。
<br>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/3278ee24-7662-e25b-c61c-10ea7a99e36e.png" width=600 />

- MayaとSceneKitの座標系はY-Upの右手座標系で同じ
- BlenderはZ-Upの右手座標系なので、そのままでは向きが合わない
- その他のソフトでも座標系が異なるので、基本的に実装する出力先に合わせるて揃える

MayaやBlender、Cinema4DなどのDCCツール、Unity、UE4、SceneKitなどの描画エンジンで扱う座標系の違いを揃えないと向きが揃いませんし、無駄に座標変換する必要が発生してしまいます。
また、スケール値についてもデフォルトがセンチメートル単位、メートル単位が混在してますし、一部のソフトではインチ単位となっているので単位系についても注意が必要です。

今回の場合は、MayaとSceneKitの座標系は同じ内容だったのですが、背景データを作成したBlenderの座標系がMayaと異なっていました。



#### トランスフォームのフリーズとリセット
座標系の違いを考慮して向きを変換しても、Blender側とMaya側で位置が合わない問題が見られました。

特に屋外でのARコンテンツの場合は、地図上の特定の地点や建物の形状と合わせる必要がありますが、位置の確認をBlenderで行なっていたため、この位置合わせやチェック用のカメラアングルが合わない不都合が発生してしまいました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/6a96fe0f-bf99-cb2f-1861-eadada7ca816.png)　
ハイライトされていない部分がBlenderデータからインポートした状態


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/37f2a017-99a4-2653-796f-617c1fc38e29.png)

Blenderデータ側の各階層でトランスフォーム情報にフリーズされていない状態でエキスポートされていたので、
**オブジェクト -> 適用 -> 全トランスフォーム**
を適切に設定した上で再エキスポートすることで解決しました。


同様にMayaで制作したキャラクターについても、ジオメトリノードは全てトランスフォームをフリーズ、リセットしてからエキスポートする必要があります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/cb9bdb99-4a86-f836-1457-d82bb4641a66.png" width=300 />

**Modify > Freeze Transformations**
**Modify > Reset Transformations**

（補足）キャラクターの場合はスキニングする前にフリーズしておく必要があります。


#### XcodeにインポートしたUSDZファイルでアセットの配置がズレる場合は次の点を確認してみて下さい。
- 使用したDCCツールと使用するゲームエンジンの座標系、単位系について違いがないか？
- 適切にトランスフォームをフリーズ、リセットされているか？

※スケルトン関係については次の項目でまとめています。
<br>


---
## スケルトン関係の注意点

#### 一番上の階層のスケルトンはワールド座標原点に配置する

ジオメトリーのトランスフォームをフリーズした状態でも、スキニングされたキャラクターだけは座標軸の向きも原点からの位置、角度もズレていました。

<!-- <img src="sample.png" width=450 />　-->
<!-- ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/77c828db-ed15-bda5-1aa7-257d4b68e02a.png) -->

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/9467fe4c-cda1-c6c9-12df-08b3ffca746e.png)

（左）　スキニングした状態のキャラをXcode上にインポートした状態だと座標軸の向きも原点からの位置、角度もズレている
（中）　JointOrientをWorldに合わせてからバインドし直すと座標軸の向きと角度は解決したけど、原点からの位置がオフセットされている
（右）一番上の階層のスケルトンが腰の位置になっていたので、ワールド座標原点にダミーのスケルトンを用意することが解決

明確な答えは見つけられませんでしたが、検証したところJointOrientはWorldに合わせ、一番上のスケルトンをワールド座標原点に配置することで解決することがわかったので、スキニングされていないダミーのスケルトンを一番上の階層に用意することで対応しています。
※既にSkin_Rootというスケルトンがあったので、Skin_Originというノード名になっています。
<br>

また、必ず発生するわけではないのですが、一番上の階層のスケルトンやその上のグループノードにスキニング情報が残っていると横倒しになることがありました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/f77fff32-12f8-a8a2-6c54-187b280894ac.png" width=300 />
<br>

#### スケルトン階層の途中にスケルトン以外のノードを含めることが出来ない

USDではスケルトン階層の途中にスケルトン以外のノードを含めることが出来ないので、オフセット用にグループやロケーターを挟む事が出来ません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/76464254-2c37-aa54-9044-324e5515d84e.png" width=300 />

シーンデータ内のリグの階層はスケルトン階層と混在させず別に用意して、USDZファイルへのエキスポート対象しないようにする事で共存可能ですが、その際エキスポート用のSetsを登録しておくと便利です。

<br>

#### SceneKitで扱える最大インフルエンス数

https://developer.apple.com/documentation/scenekit/scnskinner/1522986-boneweights
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/aab70d48-e3f2-d066-1b83-d7ff394208f3.png)

SceneKitではインフルエンス数を４以下に抑えておかないとCPU処理になるのでパフォーマンスが低下する模様です。
ブレンドシェイプの扱いについての記載は見つかりませんでしたが、ブレンドシェイプターゲットごとに１個としてカウントされる可能性が高いと思います。
<br>


## SceneKitに持って行けないアニメーション情報
Mayaでキーフレームを打っても、SceneKitで再現されないのアトリビュートが多くあります。

**Visibility**
Visibilityにキーフレームを打って表示・非表示を切り替えるアニメーションをつけても、SceneKitの表示・非表示のプロパティには反映されません。
usdviewで確認するとVisibilityのアニメーションも再現しているので、このせの制約はUSDの仕様ではなく、SceneKit側の問題だと思われます。

**カスタムアトリビュート**
USDのドキュメントを見るとMayaカスタムアトリビュートを扱う方法が見つかりましたが、こちらも同じくSceneKit側からアクセス出来ませんでした。

https://github.com/Autodesk/maya-usd/discussions/896


**マテリアルのアトリビュート**
色のパラメーターを変化させたり、点滅させたりとマテリアルのアトリビュートを変化させてもSceneKit上では確認することが出来ません。


このようなアニメーション要素をどうしても持って行きたい場合は、ダミーのスケルトンを用意して移動値などに焼き付けてSceneKit側で参照するように実装してもらう必要があります。
<br>


## ファイルサイズを抑えるためにテクスチャーを圧縮する

UnityやUnrealEngineではモバイルプラットフォーム向けのテクスチャー圧縮形式（PVRTC/ASTC/ETC2）を利用することが一般的ですが、SceneKitでの実装について確認出来なかったのでMozJPEGエンコードのJPEG形式で対応しています。

![textureComp1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/023fde7a-e7b3-4832-b83b-1e0b4524ee05.png)

このテクスチャー画像の場合、1,236KB → 164KBと約86%削減しています。


エンコーダーのインストールにはHomebrewを利用
```
$ brew install mozjpeg
```

```
$  mozjpeg -quality <圧縮率> -outfile <Resize File>.jpg <Source File>.jpg
```
<br>


## ブレンドシェイプアニメーション
とにかくブレンドシェイプ関連はトラブル続きで大変でした💦

スケルトンだけで表現出来るようにセットアップを頑張るのが正解だと思います。
それでもどうしても使わないといけない人のために、いくつか書き残しておきます。

まず、登場するキャラは１体だけなのですが、各演出によって異なる形態（バリアント）が必要となりアセットデータとしては３種類用意しています。
![MoAR記事のネタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/81276c55-cace-50ae-1469-d1a2325f7b3f.png)

|名称|コメント|
|:---|:---|
|emocS|デフォルトのキャラクター|
|emocL|ビルの屋上に登っている演出だけ巨大化している|
|slime|ビルのモニター画面から登場してくる演出はスライム状態|



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/25ec5b66-1c13-88ae-1a81-05361c943456.png)
emocS/Lは表情に合わせた口の形に対応してボディも変形する必要がありブレンドシェイプとスケルタルアニメーションを併用しています。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/d89b60ad-6dea-ae08-62f9-6358c2d75ede.png" width=600 />

slimeはコンテの段階からブレンドシェイプアニメーション対応がリクエストされていましたので、目玉以外はブレンドシェイプ対応しています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/9d907501-13b0-1646-0e0b-4ac165204c64.png" width=400 />
<br>


#### ブレンドシェイプを有効にしてUSDZファイルを出力するとフラットシェーディングになってしまう
スムージンググループが参照できためか法線情報がインポート出来ないみたいです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/d5924d23-fd97-09a0-257b-aba805e0e636.png" width=400 />

今回ブレンドシェイプを使用したスライムについては平面のスクリーンに映し出された状態から登場する演出上、コンスタントなシェーディングを使用していたのでこのままで問題なかったのですが、通常のスムースシェーディングにするにはSceneKit側で法線情報を再計算させる必要がありそうです。
<br>


#### 同じUSDZファイルに複数のキャラのブレンドシェイプアニメーションを含めることが出来ない
キャラ１体分だと問題がなくても、複数のキャラのブレンドシェイプアニメーションを１つのUSDZファイルにエキスポートしようとすると正常に動作しませんでした。
そのためブレンドシェイプアニメーションを使用するキャラは別のUSDZファイルに分けて用意する必要があります。
<br>

#### アニメーションのタイミングがズレる問題の修正箇所が増えて大変だった
後述するキーフレームアニメーションのタイミングがズレてしまう問題を修正する時、スケルトンだけでなく、ブレンドシェイプもあると修正箇所が増えてリテイク対応が大変でした。😩
<br>


#### 最終的にブレンドシェイプアニメーションしているのはスライムだけになった

アニメーションのタイミングがズレる問題のデバッグが大変だったこともあり、emocS/Lは **SSDR（Smooth Skinning Decomposition with Rigid Bones）** を利用してブレンドシェイプアニメーションをスケルタルアニメーションに変換するように方針変更することになりました。

Maya上のアニメーション作業はブレンドシェイプアニメーションのまま変わらないのですが、USDZにエキスポートする前に以下のプラグインを使用してスケルタルアニメーションに変換しています。

**SOuP　SSDR**

http://www.soup-dev.com/

**vidiaApps　vastSolver2**

https://youtu.be/prSJY4K03po

２つのMayaプラグインを検証したところ、emocS/Lでは変換結果の良好だったvastSolver2を使用しています。
<br>

![emocLSkelton.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/992f0e18-ba70-dde9-d13e-7581827bf79d.png)
トライ＆エラーが多くなり検証に時間がかかりましたが、vastSolver2のパラメーターを最適化していく事でemocS/Lについては最終的にスケルトン数の増加を微増に抑えられるようになりました。

vastSolver2は一度作成したスケルトン構成を維持しながら、アニメーションを再計算することもできるのでかなり実用的なSSDRプラグインだと思います。

*注意点としては、Maya2023に対応していなかったので、この部分だけMaya2022を使用しています。*
<br>

![slimeSkelton.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/e3f7067d-79d0-68eb-76a6-f6f462774b8b.png)
スライムについてはSOuPのSSDRコマンドの方が良好な結果が得られましたが、それでもブレンドシェイプと同等のスムーズな変形に対応できるようにするには、大幅にスケルトン数が増加してしまうため本番採用は見送りとなり、ブレンドシェイプアニメーションを使用したUSDZファイルで対応しています。
<br>

このようにUSDZファイルとSceneKitの環境ではブレンドシェイプアニメーションをスケルタルアニメーションに変換するのは有効な方法でしたが、利点と欠点について簡単にまとめておきます。

**（利点）**
**・**　ブレンドシェイプノードが含まれている複数のキャラを１つのシーンデータで扱える
**・**　ブレンドシェイプとスケルトンのアニメーションのタイミング（キーフレーム）がズレる問題が発生しない
**・**　フラットシェーディングの問題は発生しない

**（欠点）**
**・**　形状や変形によってはスケルトン数が大幅に増加する
**・**　変換作業に時間がかかる（サンプリング数とアニメーションの長さに依存する）

UnityやUnrealEngineだとこの辺りで苦労することはあまりないのですが…
**結論としてはSceneKitの場合ブレンドシェイプアニメーションの使用は推奨しません！**
<br>

---

## アニメーションのタイミングがズレる

USDZファイルをXcodeにインポートすると、アニメーションのタイミングがズレることで建物やキャラのめり込む不具合が頻発したので、Mayaから出力されたUSDZファイルの内容とSceneKit上の動作を比較して検証したところ、次のような問題が見つかりました。

### SceneKitのキーフレームの扱いについて

#### キーフレームをベイクしてもUSDZにエキスポートすると変化の無いキーフレームは省略される

**USDファイルに出力すると変化のあるキーフレームしか出力されていない**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/9fd94363-735b-5ffe-a6d1-47ea66ecf033.png)

**キーフレームをベイクしてもUSDファイルには反映されていない**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/204363b6-b852-b15b-d70c-83da40a003c5.png)

USD Toolsetに含まれていたusdcatコマンドでUSDZファイルを展開し、シーン情報をテキストエディターで確認してみると…
0Fからキーフレームをベイクしていても0F と1Fの値に変化がなければキーフレームが出力されていませんでした。

普通はこれで特に問題が起きることはないのですが、SceneKitでは最初のキーフレームを開始フレーム0.0秒として相対的な秒数でモーション時間が扱われているので、上記の場合であれば3F分タイミングがズレてしまうことがわかりました。

つまりemocSとLで最初の変化のあるキーフレームが0Fと3Fで異なっていると、開始フレームが3F分ズレたアニメーションが再生されることになります。
<br>

そして、ブレンドシェイプアニメーションのキーフレームも同様なので、同じキャラでもスケルトンとブレンドシェイプのタイミングがズレてしまいメリ込みが発生していました。

**（補足）**
- usdviewでアニメーションを確認すると問題が見られないことからSceneKitでのキーフレームの扱いの問題だと思われます。
- SceneKit上でuses​Scene​Time​Baseを適用しても解決しませんでした。



### 対応策
対処療法的な解決ですが、キーフレームの先頭にタイミングを揃えるため次のような対策を行いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58040/3940f074-0100-728c-5acd-d19a43c0df36.png)

|フレーム数|コメント|
|:---|:---|
|0F|バインドポーズでキーフレームを打つ|
|1F|ダミーのキーフレームを打つ|
|2-3F|バインドポーズでキーフレームを打つ|
|4F以降|実際に使用するキーフレーム情報|

*・スケルトンだけでなく、ブレンドシェイプアニメーションのキーフレームも同様です。*
*・ループ再生するシーンの場合ループの開始フレームまで頭出しが間に合わない不具合が見られたので、開始フレームを3F以降に調整しています。*

<br>

## SceneKitでUSDファイルで対応している機能

https://developer.apple.com/documentation/realitykit/validating-usd-files?changes=_2_8

<br>

## 最後に

USDZとSceneKitの組み合わせは、普段のゲーム開発や映像制作とは違って慣れないファイル形式と描画エンジンで苦労も多かったのですが、今後も機会があれば取り組んで行きたいと思います。

最後に、今回このような機会を頂き[Whatever Co.](https://whatever.co/moar/)ならびに[KASSEN](https://kassen.tokyo/)の皆さんに感謝申し上げます。

さとうゆーじ