- 概要
- まずはCreator Portalにログイン
- UnityのプロジェクトをMagic Leap向けに設定する
- コントローラを使う
- ハンドトラッキングを使う
- レンダリング
- 開発環境についてのメモ
- その他Tips
- ハマった点
- 最後に
概要
Magic Leap Oneの開発を始めたのでそれに対する諸々をメモしておこうと思います。
モバイル開発同様、ちょっとした設定や証明書の設定など初回しかやらないことなどは忘れがちなので。
※ なお、本記事はUnity2019の、Lumin OS向けの開発がデフォルトで採用されたあとの話となります。
※ 今回追記した内奥はUnity2019.2.15f1で実行し、Magic Leapのバージョンは0.98.0です。
まずはCreator Portalにログイン
まずはMagic Leapの開発者サイトであるCreator Portalにログインします。
ドキュメントやMagicLeap向け開発のセットアップ方法などの情報、またSDKのダウンロードなどが行えるので必須です。
UnityのプロジェクトをMagic Leap向けに設定する
公式のドキュメントは以下を参照ください。
またUnityに対するドキュメントはこちらにあります。
Unity2019以降をインストール
※ Magic Leap開発をするにはUnity2019以降のバージョンが必要です。それ以降のバージョンであればサポートプラットフォームにLumin OSが含まれています。
Settingsを調整
プラットフォームをLumin OSに
新規プロジェクトを作成、Unityを起動する際、プラットフォームをLumin OSにします。
ライブラリ・ツールをインストール
The Labをインストール
以前はMagic Leap Package Managerというアプリから諸々インストールなどを行っていたのですが、現在はThe Labというアプリからインストールやダウンロードを行うようになっています。
なのでまずはこのアプリをインストールします。
パッケージをインストール
XR Managementをインストール
まず、Package Manager WindowからXR Managementをインストールします。
インストールを行うと「Project Settings」に以下のような項目が追加されます。
ここから、Magic Leap Loader
という項目を追加します。(初回はダウンロードが必要です)
また同時に、Input Handler
という項目を選択すると必要なパッケージがまだインストールされていない場合はボタンが表示されるのでそこからインストールを行います。
MLRemote用ライブラリをインポート
もし以前のものを使っている場合は新しくインポートし直したほうがいいかもしれません。
以下のように、Magic Leap > MLRemote > Import Support Libraries
から必要なライブラリをインポートします。
ここまでのセットアップが終わったら、Launch Zero Iteration
を実行することでZero Iteration
アプリが起動します。
起動後、実機を持っている人はターゲットを実機に変更する必要があります。以下の図の部分を対象デバイスに変更してください。
あとはEditor上でプレイボタンを押すと実機で確認することができるようになります。
Lumin SDKをPreferenceに設定
次に、Preference
から、Magic Leap向けのSDKを設定します。
Build Settingsを設定
Player SettingsのColor SpaceをLinear
に変更します。
次に、XR SettingsのVirtual Reality Supportedをオンにし、プラットフォームにLuminを追加します。
またStereo Rendering ModeをSingle Pass Instancedに変更します。
パッケージのインポートとPrefabの配置
Magic LeapのUnityパッケージをインポートする
Magic LeapのUnityパッケージは、上記ポータルからDownload / InstallしたMagic Leap Package Managerを起動するとパッケージのDownloadなどができます。
起動し、インストールが済むと以下のような画面にUnityパッケージが保存されている場所が表示 されるので、そこからパッケージをインポートします。
Magic Leap向けのカメラPrefabをシーン内に配置する
該当のPrefabはAssets/MagicLeap/CoreComponents/
内にあります。
アプリに署名する
Magic Leapのアプリをビルドするために、アプリに署名をする必要があります。
署名するためには証明書を作成し、適切に設定します。
Magic Leapのポータルにログインすると、証明書を作成するページがあるのでそこでIDなどを登録します。
すると、秘密鍵などがまずダウンロードされます。
その後しばらくしてページをリロードすると、上記画像のように右側のダウンロードボタンから証明書をダウンロードすることができるようになります。
それを最初にダウンロードされたフォルダに入れ、そのフォルダごとUnityのプロジェクトに追加します。
ちなみにAssets配下である必要はないので、同階層などに置いておくといいと思います。
その後、Player SettingsのPublishing Settingsで上記の証明書を設定します。
コントローラを使う
コントローラのイベントをトラッキングする
UnityEngine.XR.MagicLeap
namespaceにあるMLInput
を利用します。
以下は簡単に、トリガーのDown / Upのイベントを購読する例です。
using UnityEngine.XR.MagicLeap; private void Start() { MLInput.Start(); MLInput.OnTriggerDown += OnTriggerDown; MLInput.OnControllerButtonDown += OnButtonDown; } private void OnTriggerDown(byte controllerId, float triggerValue) { // do anything. } private void OnButtonDown(byte controllerId, MLInputControllerButton button) { // do anything. }
コントローラのバイブレーションを利用する
バイブレーションを利用するにはMLInputController
のStartFeedbackPatternVibe
メソッドを使います。
private MLInputControllerFeedbackPatternVibe _pattern = MLInputControllerFeedbackPatternVibe.ForceDown; private MLInputControllerFeedbackIntensity _intensity = MLInputControllerFeedbackIntensity.Medium; // ... 中略 ... MLInputController controller = _controllerConnectionHandler.ConnectedController; controller.StartFeedbackPatternVibe(_pattern, _intensity);
コントローラを使ってuGUIを操作する
MagicLeapのSDKの中にExamplesがあるので、それをベースにセットアップするのが早いでしょう。
CanvasのRender ModeをWorld Spaceに変更する
Magic Leapでは、uGUIを空間に配置する必要があるため、uGUIのCanvasのRender ModeをWorld Spaceに変更する必要があります。
ポイントとしては、uGUIのEventSystemオブジェクトにMLInputModule
コンポーネントを追加します。
またそのコンポーネントに、対象となるCanvasを設定します。
どうやら、Lumin SDK 0.21.0からはこの設定はいらなくなったようです。
ちなみに、0.20.0の場合は以下のように設定項目があります。
また、対象シーンにあるController
オブジェクトをシーン内に配置します。
いちおうこれだけでも動作しますが、レーザーポインタみたいなオブジェクトなどは表示されないのでちょっと操作しづらいです。
なので、同シーンに配置されているInputExample
コンポーネントを利用するとそれらが視覚化されます。
ただ、Exampleと名前がついているので、これを複製して独自にカスタムしたほうがよいでしょう。
複数Canvasを使う場合
前述のように、MLInputModule
にCanvas
を設定する必要があります。
しかし複数のCanvasがシーン内にある場合は、そららを設定することができません。
こちらも前述のように、Lumin SDK 0.21.0からは不要となりました。
0.20.0時代に調べていたら、Magic Leapのフォーラムでまさに同様なことが語られていました。(フォーラムは以下)
どうやら、Canvas
に対してMLInputRaycaster
をアタッチすることで複数Canvasでも問題なく動作させることができるようです。
このMLInputRaycaster
をアタッチするのは0.21.0でも同様に必要なようです。
ハンドトラッキングを使う
MLにはハンドトラッキングの機能も標準で搭載されています。
ハンドトラッキングを開始する
まずはハンドトラッキングを開始するためにMLHands.Start();
を実行します。
実行に失敗したかをチェックして、問題がなければハンドトラッキングが開始されます。
MLResult result = MLHands.Start(); if (!result.IsOk) { Debug.LogErrorFormat("Error: HandTrackingVisualizer failed starting MLHands, disabling script. Reason: {0}", result); enabled = false; return; }
ハンドトラッキングを検知する
まず、(必要であれば)MLHandType
型のプロパティを用意し、どちらの手のトラッキングをするかを決められるようにしておきます。
private MLHand Hand { get { if (_handType == MLHandType.Left) { return MLHands.Left; } else { return MLHands.Right; } } }
こんな感じ。
そして、対象の手の状態がenum
で取得できるので、以下のように評価します。
if (Hand.KeyPose == MLHandKeyPose.Thumb) { // do something. }
上の例ではサムズアップの状態になったら呼ばれるようにしています。
こんな感じで、MagicLeapが用意してくれている手の形を検知するとそれを知ることができるので、それに応じて処理を分岐させます。
レンダリング
MagicLeapではStereo Rendering ModeにSingle Pass Instanced
を使うことができます。
ただこれを利用すると、自作シェーダなどの場合はSingle Pass Instancedに対応した形にしないと正常に動作しなくなります。
このあたりについては凹みさんの記事に詳細が書かれているのでそれを参考にさせてもらいました。
ここではポイントだけ記述します。詳細について知りたい方は凹みさんの記事をご覧ください。
シングルパス対応のためにIDを適切に扱う
そもそもなぜ、Single Pass Instancedにすると正常に描画されないのでしょうか。
その理由は、左右の目用のレンダリングを一度、つまりシングルパスで行うためそれに対応する処理を追加しなければならないためです。
より具体的に言えば、GPU Instancingを利用してオブジェクトを1ドローコールで両目用にレンダリングします。また、レンダーターゲットアレイというものを利用してレンダーターゲットを複数(左右の目分)用意しそれを利用して描画します。
つまり左右の目それぞれのオブエジェクトごとに固有の行列などを利用する必要があり、それを適切にセットアップしないとならないのがその理由です。
シェーダ内部ではunity_InstanceID
というstatic
変数経由で、現在レンダリング中のオブジェクトの配列のインデックスを取得します。
つまりはこれを適切にセットアップし、配列から情報を取り出すことができれば正常にレンダリングされるようになる、というわけです。
コードセットアップ
なぜこれらの処理が必要なのかは上で紹介した凹みさんの記事にとてもとても詳しく書いてあるので、内部的にどういうことをやっているのかを知りたい方は凹みさんの記事を参考にしてください。
ここではベースとなるシンプルなシェーダに追記していく形で、ざっくりとだけまとめます。
ということで、まずはUnlitなシンプルなシェーダを載せます。
これは、Unityで「Create > Shader > UnlitShader」として生成されたものから、Fog関連の記述を消したものです。
Shader "Unlit/Sample" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } }
これをそのままマテリアルにして適用するとSingle Pass Instancedの設定の場合は片目になにも描画されなくなります。
pragmaを設定する
まず#pragma multi_compile_instancing
を追加します。
CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing // 追加
これを追加すると、以下のようにマテリアルのインスペクタにEnable GPU Instancing
という項目が追加されます。(当然チェックを入れます)
これでGPU Instancingを利用する準備ができました。
以下から、このインスタンシングを利用するためのコードに変更していきます。
コードをインスタンシング対応のものにする
手始めに頂点/フラグメントシェーダの入力の構造体にマクロを追加します。
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID // 追加 }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID // 追加 UNITY_VERTEX_OUTPUT_STEREO // 追加 };
これらはGPU Instancingを利用するにあたってインスタンスのIDを適切に扱うためのものになります。
そして次に頂点シェーダにもマクロを加えます。
v2f vert (appdata v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); // 追加 UNITY_INITIALIZE_OUTPUT(v2f, o); // 追加 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); // 追加 o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; }
インスタンスIDのセットアップとフラグメントシェーダへの出力を設定します。
次に、コンスタントバッファの宣言を追加し、フラグメントシェーダで利用できるようにします。
UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _Color) UNITY_INSTANCING_BUFFER_END(Props)
ちなみにコンスタントバッファ(定数バッファ)は、GLSLで言うところのuniform変数やuniform blockに相当するものです。(以下の記事を参考に)
なので、C#側から送る値だったりインスペクタで設定するプロパティだったりは、個別に必要なデータに関してはこのコンスタントバッファの定義方法を用いて適切に設定する必要があります。
具体的には、uniform
として定義する変数はほぼそれで定義しておくと考えるといいと思います。
最後にフラグメントシェーダです。
UNITY_SETUP_INSTANCE_ID(i); // ... 中略 ... col *= UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
頂点シェーダから送られてきたインスタンスIDを取り出し、適切にパラメータを扱います。
上記のコンスタントバッファのところでも説明しましたが、通常のシェーダであればuniformな変数_Color
を定義しそれを利用するだけでよかったものを、上記のようにマクロを経由して使う必要がある、というわけです。
余談
ちなみに、凹みさんが記事を書いているときのUnityのバージョンの問題なのか、Unity2019.1.4f1では以下のようにしないとエラーになっていたので書き換えました。
UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _Color) UNITY_INSTANCING_BUFFER_END(Props) // ... 中略 ... col *= UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
マクロ展開後のコード例
最後に、上記のマクロを展開したらどうなるかをコメントしたコード全体を載せておきます。
なお、以下のコードの展開例はあくまで一例です。グラフィクスAPIやその他の設定に応じていくつかの分岐が存在するため、詳細について知りたい方はUnityInstancing.cginc
やHLSLSupport.cginc
を適宜参照してください。
Shader "Unlit/Sample" { Properties { _Color ("Color", Color) = (1, 1, 1, 1) _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; // uint instanceID : SV_InstanceID;が追加される UNITY_VERTEX_INPUT_INSTANCE_ID // 追加 }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; // uint instanceID : SV_InstanceID;が追加される UNITY_VERTEX_INPUT_INSTANCE_ID // 追加 // 以下のマクロを経由して「stereoTargetEyeIndexSV, stereoTargetEyeIndex」が追加される。 // #define UNITY_VERTEX_OUTPUT_STEREO DEFAULT_UNITY_VERTEX_OUTPUT_STEREO // #define DEFAULT_UNITY_VERTEX_OUTPUT_STEREO uint stereoTargetEyeIndexSV : SV_RenderTargetArrayIndex; uint stereoTargetEyeIndex : BLENDINDICES0; UNITY_VERTEX_OUTPUT_STEREO // 追加 }; sampler2D _MainTex; float4 _MainTex_ST; // #define UNITY_INSTANCING_BUFFER_START(buf) CBUFFER_START(buf) // #define CBUFFER_START(name) cbuffer name { UNITY_INSTANCING_BUFFER_START(Props) // #define UNITY_DEFINE_INSTANCED_PROP(type, var) type var; UNITY_DEFINE_INSTANCED_PROP(float4, _Color) // #define CBUFFER_END }; UNITY_INSTANCING_BUFFER_END(Props) // これを展開して整理すると以下のような形になります。 // cbuffer UnityInstancing_Props { struct { // fixed4 _Color; // } PropsArray[2]; } v2f vert (appdata v) { v2f o; // DEFAULT_UNITY_SETUP_INSTANCE_IDはいくつか定義が分散しているので詳細は「UnityInstancing.cginc」を参照。 // #define UNITY_SETUP_INSTANCE_ID(input) DEFAULT_UNITY_SETUP_INSTANCE_ID(input) // #define DEFAULT_UNITY_SETUP_INSTANCE_ID(input) { UnitySetupInstanceID(UNITY_GET_INSTANCE_ID(input)); UnitySetupCompoundMatrices(); } UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_OUTPUT(v2f, o); // DEFAULT_UNITY_INITIALIZE_VERTEX_OUTPUT_STEREOもいくつか定義があるので「UnityInstancing.cginc」を参照。 // #define UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output) DEFAULT_UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output) // #define DEFAULT_UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output) output.stereoTargetEyeIndexSV = unity_StereoEyeIndex; output.stereoTargetEyeIndex = unity_StereoEyeIndex; UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); fixed4 col = tex2D(_MainTex, i.uv); // #define UNITY_ACCESS_INSTANCED_PROP(arr, var) arr##Array[unity_InstanceID].var col *= UNITY_ACCESS_INSTANCED_PROP(Props, _Color); return col; } ENDCG } } }
開発環境についてのメモ
Magic Leap Remote for Unityを使って実機でプレビューする
まずはドキュメント。
Magic Leap Remoteを起動する
Remoteでの確認をするのに、専用のアプリを利用します。
ドキュメントは以下。
適切にセットアップが終わっていれば、Magic Leap Package Manager
がインストールされているはずなので、その中のLumin SDK
をリストから選択、さらにその後Use ML Remote
ボタンを押してアプリを起動します。
また、Unity Editorから実機に転送できるようセットアップを行います。
Player Settingsから、Windows向け設定のAuto Graphics API for Windows
のチェックをはずし、OpenGL Core
をリストに追加します。
続いて、必要なパッケージをインポートします。
Magic Leap向けのプラグインをインポートしている場合は、メニューにMagic Leap用のものが追加されているので、そこから必要なパッケージをインポートすることができます。
Magic Leap > ML Remote > Import Support Libraries
に該当のライブラリがあります。
そして同じメニュー内にMagic Leap > ML Remote > Launch MLRemote
と、MLRemoteを起動する項目があるので起動します。
起動すると以下のようなウィンドウが表示されるので、Start Device
ボタンを押下して実機に接続します。
あとはUnity Editorのプレイモードに入れば、自動的に描画結果が実機に転送されプレビューできるようになります。
コマンドラインを扱う
Magic Leap OneのSDKにはコマンドラインツールも含まれています。
利用するのはmldb.exe
です。
コマンド自体は以下のようなパスに保存されています。(デフォルト設定の場合)
C:\Users\{USER_NAME}\MagicLeap\mlsdk\{VERSION}\tools\mldb\
接続されているデバイスのリストを表示する
$ mldb devices
コマンドラインからmpkファイルをインストールする
mpk
ファイルをコマンドラインからインストールするには以下のようにします。
$ mldb install /path/to/any.mpk
また、すでにインストール済のものを上書きインストールする場合は-u
オプションを使用します。
$ mldb install -u /path/to/any.mpk
コマンドでできることをまとめてくれているサイトがあったので紹介しておきます。
UnityのProfilerにつなぐ
こちらのフォーラムの質問にありました。
引用させてもらうと、以下のようにすることでUnityのプロファイラに接続することが出来ます。
Here’s my steps to profile:
その他Tips
Lumin OSを選択している場合のPlatform Dependent Compilation
PLATFORM_LUMIN
を利用する。
#if PLATFORM_LUMIN // for lumin #endif
Magic Leap Oneの映像をモバイルのコンパニオンアプリにミラーリングする
まだbeta版のようですが、コンパニオンアプリを使うことでミラーリングすることができます。
(ただし、コンパニオンアプリはアメリカのStoreでしか落とせないのでちょっとごにょごにょしないと手に入りません。無料です)
ハマった点
ARKit関連でLinker error
ARKitを使っているARプロジェクトなどをMagic Leapに移植しようとして、ARKit関連のSDKが残ったままだと以下のようなエラーが出てしまいます。
In function `UnityARVideoFormat_EnumerateVideoFormats_m1076262586' : undefined reference to `EnumerateVideoFormats'
利用している箇所で#if UNITY_EDITOR
のみとなっている箇所が、Luming OS
プラットフォームだとDLLを参照しにいこうとしてコケるやつです。
なので、分岐を追加することで回避できます。
こちらの記事にも似たようなことが書かれています。
(ただ、バージョン違いなのか自分の環境ではARVideoFormat.cs
というファイル名でした)
PlacenoteなどのライブラリをSymboliclinkで参照を作る
上記と似たような問題ですが、今回の開発では元々ARKit向けに作っていたものを改修する形で対応しました。
なので元々ARKit用のプラグインなどが入っていて、いくつかのライブラリに関してはそのままでも大丈夫だったのですが、場合によってはビルドがまったくできなくなってしまいます。
そこで、各プラットフォームごとに必要なSDKなどをSymboliclinkにして読み込ませる、という方法を取りました。
もっとスマートなやり方がある気もしますが、ビルド時に対象フォルダを外す、などはあまり簡単にできそうではなかったのでこの方法を選択しました。
(もし他の方法を知っている人いたら教えてください)
$ new-item -itemtype symboliclink -path D:\MyDesktop\UnityProjects\078_ar_city\Assets -name Placen ote -value D:\MyDesktop\UnityProjects\078_ar_city\Placenote
ネットワークで通信ができない
最初にビルドしたときはできていた気がしたんですが、途中からなぜかネットワークに接続できない現象が。
調べてみたら、以下の記事がヒット。
ただ、最終的にはマニフェストファイルで解決したんですが、ランタイムで個別に権限の確認などが行えるスクリプトが最初から用意されているらしく、メモとして残しておきます。
簡単に説明しておくと、Privilege Requester
というコンポーネントをつけてLocal Area Network
を追加するだけでいいようです。
OSやSDKのupdateに伴う変更
Magic LeapにはPackageManagerがあるので、それでSDKなどのバージョン管理などを行います。
なのでアップデートがあったときはそこからインスールし、さらにバージョン管理も(フォルダ分けも)自動的に行ってくれるので非常に楽です。
が、Unity側で指定しているSDKのパスは、エディタ上で設定を変更しないとなりません。
設定自体は特にむずかしいことはないのですが、しばらく開発をしていて久々にアップデートした際に忘れがちになるので、メモとして残しておきます。
設定自体はAndroid SDKなどと同様に、Editor > Preferences
から開く設定画面で、以下の箇所に設定項目があります。
イベントを登録しているとuGUIへのイベントが発火しない?
ちょっとまだしっかりと調査していないのですが、ちょっとハマったのでメモ。
MLInput
の各種イベントを購読して処理をしていたら、その処理を追加したあとなぜかuGUIへのイベントの発火が止まってしまい、uGUIを操作できなくなった。
処理終了後にそれらのイベントを購読解除したら正常に動いた。
最後に
ARKitを用いたモバイルARと、いわゆるARグラスを用いたAR体験はだいぶ色々なものが異なるので、プロジェクト的に一緒に開発していくのはややきびしいかもなーというのが正直な感想でした。
一番感じた点としては、ARKitなどのモバイルAR開発であったとしても、ARグラス向けを意識して開発しておくと後々幸せになれるかもしれません。(Hololens2も控えているし)