クラフト・マンの屋根裏部屋

HoloLens/Mixed Reality/Unity/3D Scanner, @sike_dev

SpatialMapping プレハブの処理に関して

ライブラリ内部の振る舞い・状態を知ることはアプリを作る上でより高いパフォーマンスが出せる、ローリスクハイリターンな投資だと考えます。

目的

今回は Hololens の主要機能の一つである SpatialMapping(環境スキャン) にフォーカスを当ててみようと思います。 SpatialMapping の実装は SpatialMappingCollider と SpatialMappingRederer で行うのが簡単なのですが、SpatialMapping で何をやっているかは理解しておいたほうがよいと思いましたので今回は Hololens の環境センサが現実世界の形状を取得した後、どのように処理されるか?に注目して掘り下げたいと思います。

D.YAMA さんのこちらのブログでも SpatialMapping について詳しく書かれていますが、自分なりの理解の記録という意味でこの記事は書いています。よって新たに何かがわかったという情報が載っているわけではありませんのでご了承ください。

早速ですが、おそらく HoloLens の開発者が最も利用するであろう、Microsoft が提供している MixedRealityToolkit-Unity(以降、MRTK) というライブラリの中にある SpatialMapping を紐解いてみます。 SpatialMappingCollider と SpatialMappingRederer も内部では同じようなことをしているようですし、なにより MRTK は github でソースも公開されているので動作が追いかけやすいです。

Spatial Mapping Prehub

SpatialMapping は prehub 化されており、導入しやすいという点でここからスタートしましょう。 Unity のプロジェクトに MRTK を追加すると Project ビューに /HoloToolkit というフォルダが追加されます。SpatialMapping の prehub はこの下の Prefabs にありますのでそれをシーンに追加すれば SpatialMapping の機能は使えるようになります。

f:id:sike_dev:20180319134507j:plain

では prehub の構成を見てみましょう。

SpatialMapping の prehub は単純で、空の GameObject と3つの Component から構成されています。 オブジェクト図は以下のようになります。

f:id:sike_dev:20180319143044p:plain

ここでの赤い線は GameObject と Component の繋がりを示しており、継承関係はありません。

それぞれの Component の機能は以下のとおりです。

コンポーネント 説明
SpatialMappingManager スキャンのトリガーとSpatialMappingSourceへのインターフェースを持つ。(今回はあまり注目しない)
SpatialMappingObserver HoloLensの環境スキャナから取り込んだ情報を受け取り、保持する
ObjectSurfaceObserver プロパティのRoomModelに設定されたオブジェクトを表示する。主にデバッグ時に利用する

また、クラス図としては以下のようになります。

f:id:sike_dev:20180319163220p:plain

SpatialMappingManager はシングルトン、SpatialMappingObserver と ObjectSurfaceObserver はそれぞれ SpatialMappingSource から派生しています。

注目したいのは SpatialMappingObserver です。このクラスはメンバに UnityEngine.XR.WSA.SurfaceObserver を持ちます。このクラスが HoloLens でスキャンされた物体(壁や床等)の位置情報をアプリに運んできます。 運ばれてきた情報は基底クラスである SpatialMappingSource のメンバの surfaceObjects(List)に格納され、アプリから参照できるようになります。また、このコンテナに格納される SurfaceObject はヒエラルキーからも見える形になっていますのでご注意を。

SpatialMappingObserver の挙動を決める主なステートは以下の5つです(すべてメンバ)。こいつらを押さえて SurfaceObserver で何をしているかに注目すれば理解は早いと思います。

メンバ名 説明
ObserverStates ObserverState スキャン中か否か
Queue surfaceWorkQueue スキャンで見つかった Surface の ID が格納される処理待ち行列
SurfaceObject? outstandingMeshRequest ベイク(Mesh 化)処理中の Surface オブジェクト
SurfaceObject? spareSurfaceObject アロケートコスト節約のためのリサイクル用オブジェクト
float updateTime スキャン頻度

SpatialMappingObserver の動作をソースから読む上でちょっとややこしいのは、メンバにベイク処理中の Mesh 情報(outstandingMeshRequest)とメモリアロケートコスト節約のためにリサイクル用 Mesh オブジェクト(spareSurfaceObject)があることです。最初はこの2つのメンバはあまり意識せず読み解くと理解が早いと思います。

SpatialMapping シーケンス

次に動きを追います。以下の図は SpatialMapingObserver に注目した起動時のシーケンスです。

f:id:sike_dev:20180319163324p:plain

Awake() / Start()

Unity フレームワークから Awake, Start が呼ばれます。 Awake 呼び出し時、SpatialMappingObserver は初期化処理として ObserverState を Stopped に、SpatialMappingManager は SpatialMappingObserver のインスタンスを保持します。

次に Start呼び出しでは SpatialMappingManager は SpatialMappingObserver に対してスキャン開始を呼び出します。 SpatialMappingObserver はスキャン SurfaceObserver を生成し、BoundingVolume(物体の包含種類。ref. )を設定します。この時点ではまだ環境スキャンは始まっていません。

Update()

次に Update です。 以下はまだメッシュが登録されていないときのシーケンスです。実際はこの順番通りに呼ばれることは保証されていません. 説明のための理想的な図であることをご了承ください。

f:id:sike_dev:20180319163409p:plain

現実世界に沿って HoloLens にメッシュが表示されるには以下のシーケンスを通ります(だいぶ乱暴な説明です)。

  1. SpatialMappingObserver から SurfaceObserver へメッシュが見つかったら教えてねと依頼する(Update ハンドラの登録)
  2. SurfaceObserver はメッシュを見つけたら ID と共に SpatialMappingObserver のハンドラを呼び出す
  3. SpatialMappingObserver は ID をキューに保存する
  4. SpatialMappingObserver はキューに ID があったらメッシュのベイク(作成) を SurfaceObserver に依頼する
  5. SurfaceObserver はメッシュのベイクが終わったら SpatialMappingObserver に終わったことを通知する
  6. SurfaceObserver はメッシュオブジェクトを保管する

SpatialMappingObserver の Update はキューに ID が溜まっているかどうかで処理が別れます。

HoloLens にメッシュが表示されるためには最低でも2回 Update が呼ばれなければなりません。つまりメッシュの出現には2フレーム以上かかるということです。ただし体感的な感覚ではありますが、 HoloLens が Surface を見つけるにはかなり時間がかかるようですので、この2フレームというのはあまり気にならないかと思います。

最後に

今回はプレハブの追跡はここまでにします。 この記事では Unity の SDK である SurfaceObserver が現実世界の Surafec を見つけた後、どのように処理するかというところに注目しました。

個人的に疑問に思ったこととしては、

疑問
SurfaceObserver が Surface を発見したときに呼ぶ SurfaceObserver_OnSurfaceChanged の中でベイク依頼をしても構わないのでは?なぜIDをキューへ保存する処理とベイクの処理が別れている?
この質問への答えが、マルチスレッドで呼ばれるから、なら、キューのコンテナはスレッドセーフなのか?

これに関して調査は継続しますのでなにかわかったらまた書きたいと思います。

次回も MRTK にある SpatialMapping まわりを調べたいと思います。

はじめに

このブログでは Unity を使った xR のアプリ開発で調べてわかったこと、作成中のアプリの進捗メモ、あるいは3Dスキャナに関する情報の覚書を記していきます。

最近は xR(AR/MR/VR)、3D スキャナに関する技術が進んでおり、そろそろ昔で言うところのプラモ狂四郎の世界、最近ですとビルドファイターズでしょうか、あの世界が再現できる環境がそろってきたと思います。 私が作るアプリの基本コンセプトとしては、自分で作ったプラモデルを3Dスキャナでスキャンし、それをxRの世界で動かそうというものです。

この動画は以前作ったアプリの様子です。

バンダイビークルモデルというスターウォーズ関連を出しているシリーズのX-Wingをフォトグラメトリックでモデル化し、Unity に組み込んだ後、Microsoft の HoloLensというデバイスで動かしています。 現実世界を飛び、壁等にレーザーが当たっている様子がわかるかと思います。

こんな感じで作ったアプリを複数人で遊ぶバトルゲームを作っています。 開発系の仕事をしていますが本業はxRとは関係ないので、このブログに書くことは趣味の範囲でやっております。なので更新は遅めです。月に2回位記事を書けていけたらなぁと思っています。