RoomAliveToolKitで任意形状の投影面へのプロジェクションマッピングを行う

#初めに
この記事は[「Unity #2 Advent Calendar 2017」](https://qiita.com/advent-calendar/2017/unity2)の22日目の記事です。
先日の記事は、[gam0022](https://qiita.com/gam0022)さんの「Unityでメガデモ制作に挑戦する」が掲載されるはずです。やっぱ年末だと記事書く時間つくるの大変ですよね。
今回自分が記事にするものはUnityで任意形状の投影面にプロジェクションマッピングするのにRoomAliveToolKitが便利だったという話になります。

#概要

RoomAliveは以下の動画にあるように、部屋の内部へのプロジェクションマッピングとkinectによる人体認識を組み合わせて部屋全体でゲームが遊べるツールです。
https://www.youtube.com/watch?v=cMzM0LqIOg4

UnityでRoomAliveを行う為にRoomAlive Toolkit for Unityが提供されていますが、本記事ではRoomAlive Toolkitの機能の一部である任意形状の投影面へのプロジェクションマッピング機能を紹介します。

[RoomAliveToolkit](https://github.com/Microsoft/RoomAliveToolkit)

#動作環境
Windows10
Unity5.6f1

RoomAliveToolkitはUnity2017以降では動作保証がないです。

#RoomAlive Toolkitによるプロジェクションマッピング
プロジェクションマッピング方法としてよく利用されるのが、MapMapperを経由して投影する映像を投影する物体に合わせて加工するやり方があります。下記の記事にとても分かりやすい説明が乗っています。
[初心者も簡単！プロジェクションマッピングの作り方(MadMapper and openFrameworks, VDMX5)](https://qiita.com/g45i/items/40a0cb27f14e89b550dd#_reference-3c9438967110edd79f68)

それに対してRoomAlive Toolkitでは次のリンク先にある方法にかなり近い手法を使って、Unity内で投影面の形状に合わせてプロジェクタから投影する映像を変形することによって、任意形状の投影先へのプロジェクションマッピングをしています。
[Unity によるプロジェクションマッピング入門](https://github.com/keijiro/unity-pm-tutorial)


ざっくり手法を箇条書きするとこんな感じになります。
1. プロジェクタのFOVを測定、投影面とプロジェクタの位置関係を測定
2. 投影面の形状を深度情報から3Dモデル化する
3. Unity内に現実空間で測定した投影面と、プロジェクタ（カメラ）を設置
4. Unity内にプロジェクターで投影したいオブジェクトを、投影面とプロジェクタの間に配置する
5. Unity内の観客視点カメラからオブジェクトを撮影し、プロジェクタから投影すべき画像に加工する。

それぞれ個別に解説していきます。

# プロジェクタのFOVを測定 + 投影面の形状と、投影面とプロジェクタの位置関係を測定
下記のツールで行います。
https://github.com/Microsoft/RoomAliveToolkit/tree/master/ProCamCalibration

このツールではプロジェクタから投影面にテストパターンを照射しそれをkinectのRGBカメラで撮影した画像を解析して最終的な出力として下記のパラメータを得ます。

1. プロジェクタのカメラ行列
2. プロジェクタ位置
3. プロジェクタのレンズ歪み行列
4. kinectのRGBカメラ行列
5. kinect RGBカメラ位置
6. kinectのレンズ歪み行列

これらのパラメータを利用して、投影面とkinect位置、プロジェクタのFOV、プロジェクタの位置をUnityの中に再現できます。
RoomAliveToolKitのキャリブレーションツールの使い方に関しては以下のブログが詳しいので、利用する機会があったら読むといいかもです。
http://hiroyky.blogspot.jp/2015/05/kinect-v2roomalive.html

その他、カメラキャリブレーションについて参考にした文献です。
https://www.slideshare.net/TakuyaMizoguchi/ss-65111488
https://qiita.com/nn_tok/items/852e1a7d4d9a1e7d5075

#投影面の形状を深度情報から3Dモデル化する
キャリブレーションツールはkinectで測定した深度情報から3Dモデルを作成します。この3DモデルはUnityにインポートして投影面の3Dモデルとして利用します。

#Unity内に現実空間で測定した投影面と、プロジェクタ（カメラ）を設置
キャリブレーションツールの測定結果が書かれたファイル（XMLファイル）と3DモデルをインポートしてRoomAliveを初期化すると下記のように投影面とプロジェクタ（画像の左にある黄土色の直方体のモデル）位置、kinectの位置が現実世界そのままの位置に配置されます。

![alt](https://github.com/Microsoft/RoomAliveToolkit/raw/master/RoomAliveToolkitForUnity/docs/images/TestScene1x1NoUser.png?raw=true)

#Unity内にプロジェクターで投影面に投影したいオブジェクトを、投影面とプロジェクタの間に配置する
投影面にプロジェクションマッピングしたいオブジェクトをプロジェクタと投影面の間に配置します。
![alt](https://github.com/Microsoft/RoomAliveToolkit/raw/master/RoomAliveToolkitForUnity/docs/images/TestScene1x1.png?raw=true)

#Unity内の観客視点カメラからオブジェクトを撮影し、プロジェクタから投影すべき画像に加工する。

ここからは、RoomAliveでどうやってプロジェクションマッピングをしているかの説明になります。
下の図は以下のようなオブジェクト構成になっており、観客視点カメラから撮影した映像をプロジェクタで青い球に投影する際に、プロジェクタからどのような画像を投影すればよいのかを説明する図になっています。

* 観客視点カメラが左側の赤いカプセル
* 観客視点カメラから撮影した映像が、赤いカプセル前方の黒い板の中の画像
* 白いオブジェクトがプロジェクションマッピング対象のオブジェクト
* 青い球が投影対象のオブジェクト

![usercamera.PNG](https://qiita-image-store.s3.amazonaws.com/0/116884/00fb6f85-cd0b-c10d-d85a-486f65779bc9.png)

先にプロジェクタから投影する画像をRoomAliveがどう変形させているか見てもらいますと下図のようになります。下図の映像をリアル空間のプロジェクタから投影すると、ユーザー視点の位置から見たときにあたかもユーザーにはオブジェクトが違和感なく投影されているように見えるはずです。
![projection.PNG](https://qiita-image-store.s3.amazonaws.com/0/116884/ac8aa64c-554f-9f1f-1323-a21ffb98b647.png)

#RoomAliveがユーザー視点からの画像をどうやって変形させているか
以下のような手順で変形させています。

1. 観客視点カメラの映像をRenderTextureに書き出す
* プロジェクターの位置にプロジェクタのFOVが同じカメラがある
* プロジェクターカメラには投影対象の青い球のレイヤーがカリングとして設定されている
* camera.RenderWithShaderで、カメラに映った青い球に対して観客視点カメラのRenderTextureをマッピングしている。

camera.RenderWithShaderがミソでここが変形処理の全てです。
この処理自体はRoomAliveのRATProjector.csスクリプトの中で行っており、
ProjectionMappingMinimal.shaderをRenderWithShaderの引数に設定しそのような処理を行っています。
ProjectionMappingMinimal.shaderをみると観客視点カメラの画像をカメラに映った対象物に対してマッピングしている様子が分かると思います。

# まとめ
RoomAliveToolkitで任意形状の投影対象にプロジェクションマッピングする方法を説明しました。
この方法では投影対象をkinectでした3Dモデルを利用しますが、まあ深度のノイズ込みのモデルなので形状はガタガタにはなってしまうとは思いますし、深度を利用してしまうとkinectで深度がとれる領域の範囲でしか無理になってしまいます。
キャリブレーションさえ適当な板をkinectの深度とれる範囲に設置して、プロジェクタの位置,FOVを取得してしまえば、実際にUnityに置く投影対象のものは別にkinectで作成した3Dモデルを使わなくてもMaya等の外部のツールで作った3Dモデルでもよいし、Unity内でプリミティブなモデルを組み合わせた3Dモデルでも良いと思います。

#最後に
明日は[「Unity #2 Advent Calendar 2017」](https://qiita.com/advent-calendar/2017/unity2)の23日目で[jhorikawa_err](https://qiita.com/jhorikawa_err)さんの「Unity + Archimatix Pro でリアルタイム・パラメトリック・モデリング」の記事になります！