Tutti Lab

元シリコンバレー在住のおっさん技術者、モバイルVRアプリ開発に挑戦中

ARKitを利用してGoogle Cardboardでポジショントラッキング

過去、モバイル向けARエンジンであるVuforiaKudanを利用したモバイルVRでのポジショントラッキングに挑戦してきました。
今回は、先のWWDCで発表されたAppleのARエンジンであるARKitを利用したポジショントラッキングに挑戦してみます。

youtu.be

UnityでARKitを利用するための準備

これについては、各所で詳細な説明がされているようなので、仔細は書きませんが、

  1. Apple Developer Programより、XCode 9 betaをインストールする
  2. 同じく、iOS11 betaをインストールする(こちらはMacからではなくiOS端末にて)
  3. Unityを5.6.1以上にアップデートする
  4. こちらより、unity-arkit-plugin.unitypackageをダウンロードする

となります。

ARKitでポジショントラッキング

まずは、Assets -> Import Package -> Custom Packageにて「unity-arkit-plugin.unitypackage」をインポートします。
次に、以下のスクリプトを作成し、適当なオブジェクト(MainCameraなど)に設定します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.iOS;

public class PositionTracker : MonoBehaviour {

	public Transform vrCamera;
	private UnityARSessionNativeInterface m_session;

	void Start () {
		Application.targetFrameRate = 60;
		m_session = UnityARSessionNativeInterface.GetARSessionNativeInterface();
		ARKitWorldTackingSessionConfiguration config = new ARKitWorldTackingSessionConfiguration();
		config.planeDetection = UnityARPlaneDetection.Horizontal;
		config.alignment = UnityARAlignment.UnityARAlignmentGravity;
		config.getPointCloudData = true;
		config.enableLightEstimation = true;
		m_session.RunWithConfig(config);
	}
	
	void Update () {

		if (vrCamera != null) {
			Matrix4x4 matrix = m_session.GetCameraPose();
			vrCamera.transform.localPosition = UnityARMatrixOps.GetPosition(matrix);
		}
	}
}

次に、プレイヤーが歩き回るための適当なオブジェクト群を用意します。
私は、夏に向けてホラーコンテンツを開発すべく、以下を利用しました。
https://www.assetstore.unity3d.com/jp/#!/content/18703
f:id:tuti107:20170703000902p:plain

次に、Scene上に空のGameObject(CamParent)を生成し、Main CameraをCamParentへドラッグ&ドロップします。
また、Main Cameraに貼り付けたPositionTrackerのCr Cameraに、CamParentを設定します。
Main CameraのClipping Planes->nearは0.1等としてください。これで、オブジェクトに近づき過ぎても、オブジェクトが欠けづらくなります。
f:id:tuti107:20170703001037p:plain

ビルド・実行

あとは、iOS向けにビルドし、実行するだけです。

  1. File->Build Settingsにて、PlatformをiOSにする
  2. Player Settingsにて、Virtual Reality Supportedにチェックを入れ、Virtual Reality SDKsにCardboardを追加する
  3. Bandle IdentifierやCamera Usage Description(ここが空だと、実行直後に落ちる)を適当に設定する
  4. Build and Runを押下する。しばらくするとXCode9 betaが起動してくるので、Signing -> Teamを正しく設定し、実行する

f:id:tuti107:20170703001718p:plain

終わりに

ARKitよくできていると思います。Google CardboardでVR空間を自由自在に歩き回れる日が来るとは・・感激です!
私の端末はiPhone 6S plusですが、処理落ち等は全く気にならず、精度・パフォーマンス共に素晴らしいです。
ただ、iOSでしか利用できないのがネックです・・Kudanさんがポジショントラッキング機能をサポートする日が待ち遠しい・・

Unity5.6.1f1でAndroidManifest-main.xml merging error

Unityを5.6.1f1にアップデートしたら、開発中のGearVR用のアプリが「AndroidManifest-main.xml merging error」でビルド不能に・・
f:id:tuti107:20170527190302p:plain
AndroidManifestを書き換えよ、とのことですが、何のことやら・・

こちらに解決方法がありました。

C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Apk 

のAndroidManifest.xmlをアドミンで開き(オリジナルは念のため別名で保存)、
Applicationタグの

  • android:debuggable="true"
  • android:theme="@style/UnityThemeSelector"

を削除すればよいようです。
f:id:tuti107:20170527190637p:plain

上記対応で、ビルド通るようになりました!

Enabling or adding a Renderer during rendering

遅まきながらUnity5.6へアップデートしたのですが、Unity5.5で動作していたプログラムが動かなくなりました。
gameObject.SetActive()にて、"Enabling or adding a Renderer during rendering"というエラーが発生している模様。
原因は、gameObject.SetActive()をOnBecameInvisible()内で呼び出していたため、でした。
コルーチンを使って、yield return new WaitForEndOfFrame()で、フレーム終了まで待ち、上記を実行することで、回避できました。
その他は、特段問題なく動いているようですが、Unityのアップデートは毎回ドキドキします・・

陰影表示の際のOnBecameVisibleとOnBecameInvisibleについて

現在開発中のアプリで、OnBecameVisible(), OnBecameInvisible()にて、オブジェクトがカメラに映り込んでいるかどうかを判定し・・、という処理を作っていたのですが、どうにも思い通りに動かない。
明らかにカメラからオブジェクトが外れているにもかかわらず、OnBecameInvisible()が呼ばれなかったり、逆にカメラ内にオブジェクトが映っていないのに、OnBecameVisible()が呼ばれたりと。
結論としては、オブジェクトの影がカメラ内にある場合は、表示状態となっている、というものでした。

例えば、以下のようにシーン配置し、Directional Lightを「もうじき沈むくらいの西日」にします。そうすると、シリンダの対面の壁にシリンダの影が映ります。
f:id:tuti107:20170409104740p:plain

シリンダには、以下のようなコードを貼り付けて、シリンダがカメラに映っているならVisible、映っていないならInvisibleが表示されるようにします。

using UnityEngine;

public class VisibleChecker : MonoBehaviour {

  public TextMesh text;

  private void OnBecameVisible()
  {
    text.text = "Visible";
  }

  private void OnBecameInvisible()
  {
    text.text = "Invisible";
  }
}

すると、シリンダがカメラ内なら、
f:id:tuti107:20170409104918p:plain

カメラ外なら、
f:id:tuti107:20170409104937p:plain

となり、期待通りの動きなのですが、シリンダの影がカメラ内に映っている場合も、
f:id:tuti107:20170409105010p:plain

となります。
影を付けながら、画面内にオブジェクトが映っている/映っていないときにOnBecameVisible(), OnBecameInvisible()が呼ばれるようにできないか頑張ってみましたが、結局対象オブジェクトは影がでないようにしました・・
f:id:tuti107:20170409105452p:plain

よい方法ご存知の方、ぜひご連絡ください。

KUDAN SDKを使って、Gear VRでどこでもドア(2)

前回は、Gear VRで「どこでもドアに近づく」体験を実現すべく、まずはマーカレスARが可能なKUDANをインストールしました。
今回は、KUDAN SDKを利用して、ポジショントラッキング機能を実装し、どこでもドアに近づいてみます。

www.youtube.com

Kudan Camera prefabをシーンに設置

KudanAR/Prefabsにある「Kudan Camera - Markerless Only」をドラッグ&ドロップし、シーン内に設置した上で、インスペクタより以下の通り設定します。

  • Camera→Depth = -2 (本カメラを表示対象にしないようにするため)
  • API Key = 前回KudanのWEBページより取得した開発用API Key

f:id:tuti107:20170325025958p:plain

ポジショントラッキング用のスクリプト作成

次にKudanPositionTrackerを作成します。
gist.github.com

そして、VR用カメラ(Main Camera)の位置に、空のGameObject(player)を生成し、VR用のカメラを、player配下にぶら下げます。
f:id:tuti107:20170325031447p:plain

また、playerに、上記で作成したKudanPositionTrackerを設置します。
f:id:tuti107:20170325031557p:plain
設置後、インスペクタより、以下の通り設定してください。

  • Kudan Tracker = シーンに設置した「Kudan Camera - Markerless Only」

動作解説

「Kudan Camera - Markerless Only」に設定されているコンポーネント「Kudan Tracker」が、KUDANのAR機能の制御を行います。
Kudan Trackerを利用してポジショントラッキングを行う手順は、以下のとおりです。

  1. FloorPlaceGetPose()を呼び出して、floorPosition, floorOrientationを取得(ドキュメントに詳細な記述はないのですが、恐らくAR空間におけるカメラの位置と向きをそれぞれ取得しているのだと思われます)
  2. floorPosition, floorOrientationを引数として、ArbiTrackStart()メソッドを呼び出す。これによりマーカレスARのトラッキングが開始される
  3. Update()内で、毎フレームArbiTrackGetPose()を呼び出し、トラック対象のポジションと向きを取得する
  4. トラック対象のポジションと向きを、カメラの位置に変換する
  5. playerのlocalPositionを、上記算出したカメラ位置とすることで、Gear VRの位置(向き)に応じた、VRカメラの位置を設定する

注意点など

  • ArbiTrackGetPose()で取得する、位置・向き情報は、前回サンプルアプリ実行時に表示された黒色カプセル型のオブジェクトの表示位置・向きとなります。RevRotateVector()は、この表示位置・向きから、カメラの位置を算出しています。
  • ArbiTrackは、ArbiTrackStart()呼び出し時にトラック対象を決定しそれをトラッキングし続けますが、トラッキング対象を見失う(頭の並行移動ではトラッキング対象を見失うことはあまりありませんが、頭を回転させるとあっさり見失います)と、ArbiTrackが終了してしまいます。このため、ポジショントラッキングをし続けるためには「ArbiTrackが終了時は再開する」処理の実装が必要となります
  • KudanTrackerのトラッキング開始直後に、ArbiTrackStart()を呼び出すと、例外が発生し、ArbiTrackを開始できません("KudanTrackerのトラッキング"と"マーカレスAR用のArbiTrack"は別物)。トラッキングはKudan TrackerのStart On Enableをチェックしていると、本コンポーネントの初期化のタイミングで開始されます(もしくはStartTracking()メソッドをスクリプトから呼び出すことで、トラッキングを開始できる)。そこで、直後ではなく少し時間をおいてから(上記コードでは5秒)、ArbiTrackStart()を呼び出すことで、本問題を回避しています。
  • これは、マーカレスARだから仕方がないことかと思いますが、ArbiTrackでは正確な距離の測定ができません(トラック対象の寸法情報がないのだから、当然ですね)。このため、ArbiTrackGetPose()で取得できる位置情報は所定の単位(メートルとか)ではなく、ArbiTrackのトラッキング対象の位置等により毎回異なります。上記コードでは、取得した位置情報にscaleFactor(0.0004)をかけて、Unityの座標に変換していますが、この0.0004は試行錯誤で見つけた「私の部屋ではそれっぽく見える値」であり、0.0004をかけることで、Unityの座標に正しく変換できる、というものではありません。このため、scaleFactor = 0.0004では、ArbiTrackトラッキング対象とGear VRの距離によっては、頭の動きに対して、動きすぎたり、逆に全然うごかなかったり、ということが起きます。このあたりを正確にしたいなら、試行錯誤scaleFactorの値を調整するか、マーカARにするしかなさそうですね。

おわりに

今回は、Gear VRで「どこでもドアに近づく」体験を実現すべく、KUDANを利用してポジショントラッキング機能を実装してみました。
Vuforiaで実装した際と比較し、マーカが不要であり、かつマーカがなくても結構高精度なトラッキングが実現されているため、Vuforiaよりも使い勝手のよいポジショントラッキングの実装が可能かと思います。
一方、KUDAN ARはポジショントラッキング用に作られたものでないため、トラッキング対象が視界から消えるとポジショントラッキングができなくなる等、ちゃんとしたポジショントラッキングの実現のためには、色々工夫が必要となりそうです。ただ、基本座って・前を向いて使用するアプリ、であれば、今回実装程度のものでも充分かと思います。

KUDAN SDKを使って、Gear VRでどこでもドア(1)

前回は、Unity Native VRでどこでもドア(ポータル)を実装しました。前回の予想どおり、前回開発したものをそのままGear VR向けにビルドしたところ、問題なく動作しました。
ただし、Gear VRにはポジショントラッキングがないため、どこでもドアに近づくことができず、どこでもドアの楽しさが半減してしまいます。
そこで今回は、前にVuforiaで実装したポジショントラッキングを応用し、Gear VRでもどこでもドアに近づく体験ができるようにしてみます。
tuti107.hatenablog.com

KUDAN

今回は、Vuforiaではなく、日本が誇るモバイルARエンジンである「KUDAN」を利用します。KUDANは、

  • マーカレスARが可能!つまり、マーカをプリントして貼り付ける等の面倒な手続きなしで、ARが可能!
  • 収入が100万ポンド(=1.4億円)以下の個人開発者は、なんとアプリ配布用のライセンスが無料!!(Vuforiaは有料)

ということで、今の私の中ではモバイルARは、KUDAN一択です。

KUDAN SDK Unity Pluginのダウンロード

ダウンロードページより、Free-Downloadをクリックして、Unity Pllugin "KudanARUnity.unitypackage"をダウンロードします。
購入画面が表示されますが、0ポンドなのでご安心を。メールアドレス等の情報を入力し、画面一番下のDownloadボタンを押下すれば、ダウンロードがはじまります。

KUDAN SDK Unity Pluginのインストール

ダウンロードしたKudanARUnity.unitypackageを、Assets->Import Package->Custom Packageよりインポートします。

サンプルを実行してみる

まずは、SDKに同梱のサンプルを実行してみます。

  • Assets/KudanAR/Samples/AngelScene をダブルクリックして、サンプルシーンをロードします。

f:id:tuti107:20170320130414p:plain

  • Android向けにビルドをするための設定を行います。File->Build Settings..より、Build Settingsを表示し、PlatformをAndroidへスイッチします。次に、Player Settingsを押下し、インスペクタのOther Settingsー>Identifierより、以下の通りに設定します。
    • Bundle Identifier = "eu.kudan.ar"
    • Minimum API Level = 15

f:id:tuti107:20170320131705p:plain

  • 次に、シーン内のAngel Bundle/Kudan Cameraを選択し、インスペクタのKudan Trackerコンポーネントの一番下にある「Get Editor API Key」を押下します。すると以下のページが表示されるので、"click here"を書かれている箇所をクリックします。

f:id:tuti107:20170320131922p:plain
ここで、開発用のバンドルID"eu.kudan.ar"に対応するLicense Keyが取得できます。このLicense Keyをコピーし、インスペクタのKudan Trackerコンポーネント内のAPI Keyにペーストします。なお、Editor API Keyは、上記MY ACCOUNTのページより、ログインすることで取得可能です。
f:id:tuti107:20170320132242p:plain
f:id:tuti107:20170320132346p:plain

  • 次に実行してみます。適当なAndroid端末をPCと接続し、File->Build & Runを選択します。画面右下の"Place Markerless Object"ボタンを押下すると、3Dオブジェクトが設置されます。このオブジェクトは、その設置位置に貼り付けたように、スマホを傾けたり・近づけたりすると、その通りに表示がされます。

f:id:tuti107:20170320133542p:plain

次回は、KUDANでポジショントラッキングを実装

今回は、Gear VRで「どこでもドアに近づく」体験を実現すべく、まずはマーカレスARが可能なKUDANをインストールしました。
次回は、KUDANを利用したポジショントラッキングの実装に挑戦します。

どこでもドア(ポータル)をUnity NativeのVRで実現してみる

f:id:tuti107:20170319212451p:plain

VRでどこでもドア体験、ぜひやってみたい!と思うのですが、関東在住ではないので、気軽に行けそうにありません。
www.doraeiga-vr.com

そこで、Oculus Rift向けに作ってみようと思ったのですが、RenderTextureをごにょごにょして・・と色々考えたものの、どうやったらよいのかわからず、ネットで検索したところ以下の記事を発見しました。ただし、Vive用のコードで、あちこちVive専用のメソッド等が使われています。そこで、これをUnityのNative VR向けに移植してみました。
qiita.com

RenderTextureの生成

上記のサイトでは

_leftEyeRenderTexture = new RenderTexture((int)SteamVR.instance.sceneWidth, (int)SteamVR.instance.sceneHeight, 24);

として、RenderTextureを作成していますが、

_leftEyeRenderTexture = new RenderTexture((int)VRSettings.eyeTextureWidth, (int)VRSettings.eyeTextureHeight, 24);

としました。

ポータル用マテリアルへRenderTextureを設定する処理

まず、左右カメラ視差をつくるためのオフセット設定は、

var v = _cameraForPortal.transform.localPosition;
_cameraForPortal.transform.localPosition = v + new Vector3(-VrEye.stereoSeparation/2f, 0f, 0f);

としました(左目の場合)。
また、

Valve.VR.HmdMatrix44_t leftMatrix = SteamVR.instance.hmd.GetProjectionMatrix(Valve.VR.EVREye.Eye_Left, VrEye.nearClipPlane, VrEye.farClipPlane, Valve.VR.EGraphicsAPIConvention.API_DirectX);
_cameraForPortal.projectionMatrix = HMDMatrix4x4ToMatrix4x4(leftMatrix);

として、プロジェクションマトリクスの設定を行っている箇所は、

_cameraForPortal.projectionMatrix = VrEye.GetStereoProjectionMatrix(Camera.StereoscopicEye.Left);

としました(左目の場合)。

ポータル用マテリアルのシェーダー処理

左右どちらのレンダリングか、により使用するテクスチャを切り替えている以下の処理

if (unity_CameraProjection[0][2] < 0)
{
    col = tex2D(_LeftEyeTexture, sUV);
}
else {
    col = tex2D(_RightEyeTexture, sUV);
}

は、

if (unity_StereoEyeIndex == 0)

としました。
その他は、上記サイトにて記載されているソース等の通りです。実行すると、以下のような感じに動きます。
f:id:tuti107:20170319212415g:plain

UnityのNativeのVR機能のみでの実装のため、Oculus Riftだけでなく、Gear VR、Google Cardboard、Daydreamでの動くと思われます。試していませんが。。