読者です 読者をやめる 読者になる 読者になる

Tutti Lab

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

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

Gear VR どこでもドア ポジショントラッキング KUDAN モバイルAR

前回は、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で実現してみる

Oculus portal どこでもドア

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での動くと思われます。試していませんが。。

シーン内GameObjectの初期化の順序などについて

シーン内GameObjectの初期化の順序について、私はごく最近まで「シーン内のGameObjectは入れ子構造の親から順に初期化、子はシーンの上にあるものから順に初期化」される、と勘違いしていました。

+GameObject1
|    |
|    +---GameObject2
|    +---GameObject3

なら、GameObject1, GameObject2, GameObject3の順で・・
ただ実際は「初期化順は不定」なのですね。今開発中のアプリで、起動時は期待した順にAwake()が呼び出されるのに、SceneManager.LoadScene()するとAwake()の呼び出し順序が入れ替わってしまう、という問題が発生し、この事実を知りました。

そうすると、GameObjectの初期化時に他のGameObjectを参照する必要がある場合、どうするのだとうと。例えばGameObject2の初期化時に、GameObject1のなんらかを参照する場合、GameObject1が初期化されていないと期待の動作となりません。
これについては、

  • 自身の初期化はAwake()で
  • 他のGameObjectの参照を伴う初期化処理はStart()で

行う、と徹底すればよいようですね。

初歩的なことなのですが、いままでずっと勘違いをしていました。これまで開発したアプリは「たまたま動いていた」ようで・・

VR広告SDK「Immersv」を組み込んでみました

Gear VR Daydream immersv VR広告SDK モバイルVR

モバイルVRアプリ開発において「マネタイズ」は大きな課題です。一般的にモバイル向けアプリ(特に個人開発者)のマネタイズ手段は広告であり、各種提供されている広告SDKをアプリに組み込むことで、アプリ内で広告を表示し、実績に応じて広告収入を得る、というものです。
しかし、これらモバイル向けアプリの広告SDKは、VR空間上に貼り付けて使用するような使用形態が想定されていないため、モバイルVRアプリにそのまま適用することはできません。
今回は、モバイルVRに特化された広告SDKである「Immersv」を、開発中のアプリに組み込み、動作確認をしてみました。
f:id:tuti107:20170307200728p:plain

SDKの請求

まずはこちら(注:2017年3月7日現在、本サイトに接続できなくなっております。一時的なものかと思いますが。。)にアクセスし、SDKを請求します。フォームに必要情報を入力・送信すると、数日で先方から応答のメールがあります。
その後、私のプロジェクト(今、Gear VR、Daydream向けにモバイルVRアプリを開発しています)の説明をし、何度かのインタラクションを経て、SDKを入手することができました。なお、Immersvは米国の会社であり、全編英語を覚悟したのですが、対応いただいた日本法人の担当の方は日本語が堪能で、問題なくコミュニケーションをとることができました。

SDKのダウンロード、組み込み

SDKは、Gear VR、Daydream/Cardbord用に、これらOculus SDK/Google VR SDKやUnityのバージョン毎に用意されており、自分の開発環境に合わせて取得・インポートします。
私は、Daydream向けビルドを考慮し、Unity Daydream Preview 5.4.2f2-GVR12で、かつ(まずは)Gear VR向けに開発をしているので、本条件に相当する「ImmersvSDK-1.31-GearVR-1.0.3.unitypackage」を使用しました。
f:id:tuti107:20170223073524p:plain

実装

Immersv SDKは非常にシンプルです。基本、SDKに同梱のPDFドキュメントに記載されているサンプルコードをそのまま使えば、ほぼ問題ありません。一応、処理の流れを記しておくと、

  • ImmersvSDK.Init(APPLICATION_ID)を呼び出す。APPLICATION_IDはSDK利用許可時に通知される。
  • ImmersvSDK.OnInitSucces()コールバック(初期化成功)にて、ImmersvSDK.Ads.LoadAd(PLACEMENT_ID)を呼び出し、広告を読み込む。なお、PLACEMENT_IDはSDK利用許可時に通知される。
  • ImmersvSDK.Ads.OnAdReady()コールバック(広告準備完了)を受け取った後、なんらかのユーザ操作(「広告を見る」ボタン押下など)を受け付けた後、ImmersvSDK.Ads.StartAd()を呼び出して、広告を再生する
  • ImmersvSDK.Ads.OnAdComplete(result)コールバック(広告再生完了)にて、ユーザが広告を最後まで見る等の条件を満たした場合(result.BillableCriteriaMetがtrue)、アプリ内仮想通貨を与える等の、ユーザに対する報酬処理を行う

という形になります。

なお、以下の公式ビデオ(英語)にて、SDK請求から実装までの詳細が公開されています。
www.youtube.com

ちなみに私の開発中アプリにSDKを組み込んだものは、このような感じです。ビデオ画質が悪いため細かな部分、特に広告閲覧後の報酬の様子がわかりづらいですが、一通り動作しています。
www.youtube.com
チケット券売機の右中央辺り(ビデオでは解像度が低くてよく見えませんが、いわゆる「広告を見てコインをゲット」を表示しています)をGAZEすることで、Immersvの広告が始まります。
広告は、2D広告を映画館風に再生するものや、360度動画等様々であり、充分楽しめる(?)内容です。
広告が終わると、コインが手に入ります(こちらも見えづらい・・)

GearVRでは、私がいつも利用しているキャプチャーアプリLollipop screen recorderが動作せず、MirrorOP for Galaxy+OBSを使用しました。ただこれだと録音ができず、解像度が低く、微妙な感じです。。

getterのケアレスミスでUnityクラッシュ

以下のコードですが、

private int _curNum;
public int curNum {
  get {
    return curNum;
  }
  set {
    _curNum = value;
  }
}

正しくは、「return _curNum;」としなければなりません。
なお、上記の間違いコードを実行すると、Unityがクラッシュします。
f:id:tuti107:20170306192256p:plain
スタックがえらいことになっています。循環参照をしまくったためでしょうね。
この不具合の検証をさらに困難にさせるのが、このgetterにアクセスしなくても、例えば本ゲッターを持つクラスの別の変数にアクセスしても落ちます(ただ、落ちない変数もある・・謎・・)。まさかgetterのミスとは気づかず、小一時間解析に要してしまいました。

Quadで円形プログレス

Quad fillAmount

uGUIのImageは、fillAmountを使って簡単に円形のプログレスができるのですが、SpriteやQuadにはfillAmountに相当する機能がないようです。
こちらこちらに、Quadを使った円形プログレスの実現方法があるのですが、予め0→360度でα値を1→0とした画像を用意する必要があるようで、面倒だし汎用性がないな、と思いました。
そこで、角度(0~360度)を指定して円形プログレスをするための簡単なシェーダーコードを書いてみました。

f:id:tuti107:20170204193817p:plain

Shader "Tuti/Reticle"
{
  Properties
  {
    [NoScaleOffset] _MainTex("Texture", 2D) = "white" {}
    _Degree("Degree", Float) = 360.0
  }

  SubShader
  {
    Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }

    ZWrite On
    Blend SrcAlpha OneMinusSrcAlpha

    Pass
    {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag

      #include "UnityCG.cginc"

      #define PI 3.14159

      struct appdata
      {
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
      };

      struct v2f
      {
        float2 uv : TEXCOORD0;
        float4 vertex : SV_POSITION;
      };

      sampler2D _MainTex;
      float _Degree;

      v2f vert (appdata v)
      {
        v2f o;
        o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
        o.uv = v.uv;
        return o;
      }
			
      half4 frag(v2f i) : SV_Target
      {
        half4 col = tex2D(_MainTex, i.uv);
        float xx = i.uv.x - 0.5;
        float yy = i.uv.y - 0.5;

        float deg = (atan2(yy, xx)) * 180.0 / PI;
        if (yy >= 0) {
          if (deg > _Degree) {
            col.a = 0.0;
          }
        }
        else {
          if (deg + 360.0 > _Degree) {
            col.a = 0.0;
          }
        }

        return col;
      }
      ENDCG
    }
  }
}

時計の12時の位置から時計回りにする場合は、z軸90度回転+Y軸で反転してください。
ちゃんとテストはやっていませんが、とりあえず動いているようです。

Oculus Touchをつかってみる

Oculus Touch

Oculus Touchが出荷され、約一月が経ちました。Oculus StoreにはOculus Touch対応コンテンツが多数並び、Oculus Touchの凄さを体験できるわけですが、一方で開発向きとなるとまだ情報が足りない状況のようです。
今回は、Oculus Touchの練習用に作成したコンテンツを取り上げ、開発のポイントを挙げていきたいと思います。
youtu.be
コンテンツは、今年大流行したアレを、Oculus Touchで体験する、というものです。

情報収取

Oculus Touchの技術情報については、以下が必読です。

準備

Oculus Touchにて「手やアバターを表示し、なにかをつかむコンテンツを作る」ためには、以下のインストールが必要です(詳細は上記情報収集をご参照)

  • Oculus Utilities for Unity 5(こちらからダウンロード可能)
  • Oculus Avatar SDK(同じくこちらからダウンロード可能)
  • Avater Grab Sample(こちらからダウンロード可能)、なおUnity 5.5でのみ動作(Unity 5.4ではクラッシュ)とのこと
  • その後適当なプロジェクトを作成し、上記ダウンロードしたパッケージ(OculusUtilities.unitypackage, OvrAvatar.unityPackage, AvatarGrabSample.unitypackage)をそれぞれインポートします

サンプル実行

Assets/Samples/Content/AvaterWithGrab をダブルクリックして、シーンをロードします。あとは、PlayerSettings→Other SettingsのVirtual Reality Supportedをチェックし、実行してみます。
f:id:tuti107:20161231102951p:plain
つかむ・離す・投げる、一通りのことができます。

つかめるものを追加

まずは、サンプルで「つかめるもの」がどのようになっているかを見てみます。
f:id:tuti107:20161231103341p:plain
ポイントは、Grabbableというスクリプトです。これはAvater Grab Sampleに含まれるスクリプトで、これを追加したオブジェクト(かつ、Collider, Rigidbodyも追加する必要あり)がつかめるようになります。
つかんだ時、離した時の振る舞いは、Grabbable#GrabBegin()、Grabbable#GrabEnd()メソッドにそれぞれ記述します。これらメソッドはvirtualメソッドですので、Grabbableクラスの子クラスを作ってこれらメソッドをオーバーライドすれば、任意のつかむ・離す際の処理を作成することができます。

Oculus Touchのバイブレーション

OVRHapticsClipを使用します。AudioClipを引数としてOVRHapticsClipを作成することで、そのAudioClipの音量に基づいた振動を作成できます。
作成したOVRHapticsClipを引数としてOVRHaptics.RightChannel.Mix()(=右側コントローラ、左側の場合はOVRHaptics.LeftChannel.Mix())を呼び出すことで、Oculus Touchが振動します。なお、振動させるためのメソッドにはMix()以外に幾つかあり、それぞれ挙動が異なります(詳細は情報収集で挙げたサイトを御覧ください)。
以下は、スタティックメソッドSE.PlaySE()を呼び出すと、効果音鳴動+振動するサンプルです。

using UnityEngine;

public class SE : MonoBehaviour {

  public AudioSource audioSource;
  public OVRHapticsClip hapticClip;

  private static SE thisObj;

  void Start () {
    thisObj = this;
    hapticClip = new OVRHapticsClip(audioSource.clip);
  }
	
  public static void PlaySE() {
    if (thisObj != null) {
      thisObj.audioSource.Play();
      OVRHaptics.RightChannel.Mix(thisObj.hapticClip);
      OVRHaptics.LeftChannel.Mix(thisObj.hapticClip);
    }
  }
}

No Oculus Rift App ID has been provided

エディタで実行すると、問題なく実行できますが、上記のエラーがでます。Oculusサイトにて、アプリを登録してIDを取得することで、本エラーを回避できるようです(私がつくったコンテンツをエディタ上で実行する上では特に問題がなかったため、試していません)。詳細は、上記「情報収集」で挙げたサイトを御覧ください。

PP◯Pっぽい素材

終わりに

Oculus Touchは本当にすごいです。CG空間に触る、というユーザ体験は先行していたHTC Viveをも超えるものでは、と個人的には思います。