現在、ある案件にてUnityでモーションキャプチャを行い、そのモーションをなんとかしてスマホで共有したいという要求があり、それを現実化するために空いた時間で少しずつ開発を行っていました
その際にとあるモーションの再生録画機構のおかげで作業工程を減らすことができたためメモ
EasyMotionRecorder
EasyMotionRecorderは某Vtuberで有名なDuoがシンプルなモーションの再生及び録画機構として公開しているライブラリです
元々はAnimationClipを共有しようとか考えていたのですが、モバイルでのランタイム共有においてUnityAssetの共有がかなりネックになってしまったため独自のモーション機構から共有を試みようと思ったのがきっかけでした
基本的にはモーションのあるフレームに必要なボーン情報などをシリアル化可能な状態でまとめ、配列化して共有すればいいのですがそのまとめる工数をあまり取りたくなかったのです
ざっと実装を見た感じ、シリアル化とランタイム実行を考慮したすごく都合のいい状態でまとめてくれていたこれを採用しました
システム
システムは上図のようになっています、とりあえず共有ができればよかったためクライアント以外の部分はかなり簡易的な作りになっています
モーキャプサイドが専用のシーンをエディタで叩いて、モーションキャプチャからモーションデータを生成(別にランタイムでもいいです)
サーバーに転送しユーザー情報やモーション情報と紐づけて保存
ローカルに保存しておいて任意で再生
ざっくり言うと上記のフローです
クライアントサイドとしてUnityでモーションキャプチャやモーションのダウンロードを行い、AWS上にサーバーとクラウドストレージ、RDBを用意してモーションデータや関連情報の管理をしています
実装
それでは本題であるUnityとEasyMotionRecorderでのモーションデータの共有に入ります
使用ケースで実装形式が変わるため共有のために行っている処理を抜粋して簡単に説明します
まずモーションデータの生成です、今回はモーションキャプチャーの機器としてPerception Neuronを使用しました
EasyMotionRecorderを使用することでかなり簡単にGUIをいじって生成することができます、詳しくは公式ドキュメントを読んでいただくのが早いのでここでは説明を省略します
Recordを終了させると_posesにHumanoidのモーションデータが記録されるため、このデータを使っていきます
次にモーションデータをシリアル化します、ここではDuoのモーションデータ形式をほぼそのまま使用しています
EasyMotionRecorderではHumanPosesという形式のデータで保存しており、中身はアニメーションフレーム単位でのモーション情報です
Vector3とQuaternionだけシリアル化の関係でfloatに分解しています、最近ではcsv吐き出しの機構をduoが追加したっぽいのでそちらを使うのでもよいでしょう
また、シリアライズに関してはシンプルな方法で記述しています、使用する開発環境やコストに合わせてシリアライズ形式は好きなものを選択してください
[System.Serializable] public class SerializableMotionData { // DB管理しない付随するモーション情報があればつける public string hoge; // duoのHumanPosesに含まれるモーション情報 public List<HumanoidPoses.SerializeHumanoidPose> poses; public SerializableMotionData(string hoge, List<HumanoidPoses.SerializeHumanoidPose> poses) { this.hoge = hoge; this.poses = poses; } }
// シリアライズの簡易例 /// <summary> /// バイナリシリアライズ /// </summary> public static byte[] SerializeMotionData(SerializableMotionData obj) { MemoryStream stream = new MemoryStream(); BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(stream, obj); return stream.ToArray(); } /// <summary> /// バイナリデシリアライズ /// </summary> public static SerializableMotionData DeserializeMotionData(byte[] bytes) { MemoryStream stream = new MemoryStream(bytes); BinaryFormatter bf = new BinaryFormatter(); var obj = bf.Deserialize(stream) as SerializableMotionData; return obj; }
そして投稿です、UnityWebRequestでいいんですが、今回は都合でObservableWWWを使用しています
// 任意のレスポンスを定義してやり取り public static async Task<TResponse> PostMotionData(byte[] poses) { var endpoint = ""; var form = new WWWForm(); // ここで送信するモーション情報を定義 // モーションデータのバイナリ form.AddBinaryData("poses", poses); // using UniRx var www = await ObservableWWW.Post(endpoint, form); var response = JsonUtility.FromJson<TResponse>(www); return response; }
次に共有するモバイル側の実装です、今回使用したケースでは一回モーションの関連情報をサーバーに問い合わせてモーションをダウンロードするんですが、そこは省略し直接モーションデータをURLからダウンロードする部分を記述します
投稿同様にObservableWWWでシンプルに記述しています
// URL指定してDL public static async Task<SerializableMotionData> GetMotionData(string url) { // using UniRx var www = await ObservableWWW.GetAndGetBytes(url); var motion = DeserializeMotionData(www); return motion; }
モーションデータをデシリアライズし、モーションプレイヤーに流し込みます
MotionDataPlayerという再生機構は用意されているので、適当にデータを紐づけておいてそこに代入する方式をとりました
// HumanPosesに対して追加 public void SetPoses(List<SerializeHumanoidPose> poses) { Poses = poses; }
// MotionDataPlayerに対して追加 public void SetMotion(List<HumanoidPoses.SerializeHumanoidPose> poses) { _recordedMotionData.SetPoses(poses); }
後はMotionDataPlayerで再生してやればモバイルのランタイムでモーションを見ることができます
フレーム単位でポーズをいじってるだけなのでその辺をいじる機構を作れば色々再生周りの機構も作れるでしょう
AR100Projects
最後に自分が行ってる活動の宣伝をば
この機構の開発の発端は、イワケンこと同期の岩崎謙太(@tanaka_lit)とともに行っている「AR100-Projects」という施策の下で開発中のプロダクトです
このプロジェクトはイワケンとともにARで世界にインパクトを与える、プロダクトを量産するという思想の下に、時代がAR本格化する数年後に向けてARプロダクトを100個作ろうというとてもクリエイティブなものです
ARやVRについて圧倒的な熱意を持っているイワケンやプロダクトに協力してくれる様々な人たちとともに、ARでできることを考えつつ形にしていっています
まだまだ発展途中ですが、自分も技術者としてというよりも、クリエイターとして自分を高めていくためにこのプロジェクトを盛り上げて成功させようと考えています
開発したプロダクトは勿論、ここで得た技術知見の共有などもブログで積極的に行っていく予定なので応援していただけると幸いです