おもちゃラボ

Unityで遊びを作っていきます

Unityで始めるARKit入門 環境マッピング編

ARKit2.0からは環境マッピングの機能が追加されました。環境マッピングを使うと金属などのマテリアルへの映り込みが実現できるようになります。これにより、より一層フォトリアルで映像に馴染むARの表現が可能になります。

f:id:nn_hokuson:20181023192640j:plain:w550

ここではARKitのPluginについてくるEnviroment Texturingのサンプルが微妙にわかりにくかったので、もう少し簡単に環境マッピングができる方法を紹介していきます。

環境マッッピングとは

前回の記事で書いたようにARKit1.5からは光源推定の機能が追加され自然に見えるARを表示することができるようになりました。

nn-hokuson.hatenablog.com

環境マッピングを使えば、光源推定だけでは表現できないより一層リアルなARを実現できます。環境マッピングは、ゲームで使用する空間の周りに仮想的な球(または立方体)を置き、そこに設定された環境マップ用の画像をもとに、環境光を決めるという仕組みです。

f:id:nn_hokuson:20181023193218j:plain:w500

 ARKitではこの仮想的な環境に貼り付けるテクスチャを、カメラ画像からリアルタイムで生成する機能があります。これを利用すると、カメラで映した映像をもとにして周辺の色合いを判断し、3Dのオブジェクトの映り込みに利用できるようになります。

動的な環境マップを使うことで、より映像に馴染んだARの表示ができるようになります。

タッチしたところにモデルを配置する

まずは環境マップの設定を行う前にタッチしたところに3Dモデルが表示されるようにしましょう。これがないと、正しく環境マップが設定できてるか分かりませんしね・・・

基本的には前回の記事の内容と同じなので、簡単にまとめます。まず、TouchController.csを作ってGameオブジェクトにアタッチし、インスペクタで生成したいPrefabを登録します。

nn-hokuson.hatenablog.com

このとき、Prefabモデルのマテリアルを金属っぽくなるように設定しておきます。プロジェクトウィンドウで右クリック、「Create」→「Material」を選択してMetalというマテリアルを作ります。

Metalのインスペクタで、金属っぽくなるようにMetalicを0.7程度、Smoothnessを0.8程度に設定しましょう。

f:id:nn_hokuson:20180927192304p:plain:w350

設定できたらこれをモデルにアタッチします。

f:id:nn_hokuson:20180927192336p:plain:w250

実行してみて正しくモデルが表示されるか試してみましょう。ここでは環境マッピングの設定をしていないので、まだ黒っぽいだけのモデルが表示されます。

環境マップを設定する

環境マッピングの設定するにはヒエラルキーウインドウでARCameraManagerを選択して、インスペクタのEnvironment Texturingを「Unity AR Environment Texturing Automatic」に設定します。

f:id:nn_hokuson:20180927192523p:plain:w400

Environment Texturingには次の3つを設定可能です。ここでは自動的にProbeが配置されるように「Automatic」を選択しました。

設定値 役割
Unity AR Environment Texturing None 環境マップを使用しない
Unity AR Environment Texturing Manual 環境マップのProbeを自分で設定
Unity AR Environment Texturing Automatic 環境マップのProbeを自動で設定

 
Swift+ARKitであれば、これだけで設定完了なのですが、UnityではProbeを手動で配置する必要があります。

えっ、さっきAutomaticを選んだのに・・!という方へ。ARKit内では自動で配置してくれますが、Unityには「ココにProbeを配置しますよ」というイベントが来るだけです。環境マッピングをするためにはこのイベントを受け取って自分でProbeを配置する必要があります。どこがAutomaticやねん!って感じですね。

 
このへんはSwiftとARKitでアプリを作るほうが楽だなぁ、と感じるところです。
Swiftを使ってARKitを試すには拙書の「ARKit超入門」がオススメです!

booth.pm

 
ということで、そのスクリプトを作成します。プロジェクトウィンドウで右クリックして、「Create」→「C# Script」を選択します。EnvmapController.csという名前で保存できたら次のスクリプトを入力してください。

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

public class EnvmapController : MonoBehaviour {

    private LinkedListDictionary<string, GameObject> probeAnchorMap;

    void Start () 
    {
        probeAnchorMap = new LinkedListDictionary<string, GameObject> ();
        UnityARSessionNativeInterface.AREnvironmentProbeAnchorAddedEvent += EnvironmentProbeAnchorAdded;
        UnityARSessionNativeInterface.AREnvironmentProbeAnchorRemovedEvent += EnvironmentProbeAnchorRemoved;
        UnityARSessionNativeInterface.AREnvironmentProbeAnchorUpdatedEvent += EnvironmentProbeAnchorUpdated;
    }

    void UpdateTexture(AREnvironmentProbeAnchor anchorData, GameObject go)
    {
        go.transform.position = UnityARMatrixOps.GetPosition(anchorData.transform);
        go.transform.rotation = UnityARMatrixOps.GetRotation(anchorData.transform);
        go.GetComponent<ReflectionProbe>().size = anchorData.Extent;
        go.GetComponent<ReflectionProbe>().customBakedTexture = anchorData.Cubemap;
    }

    void EnvironmentProbeAnchorUpdated (AREnvironmentProbeAnchor anchorData)
    {
        if (probeAnchorMap.ContainsKey (anchorData.identifier)) {
            GameObject go = probeAnchorMap[anchorData.identifier];
            UpdateTexture(anchorData, go);
        }
    }

    void EnvironmentProbeAnchorRemoved (AREnvironmentProbeAnchor anchorData)
    {
        if (probeAnchorMap.ContainsKey (anchorData.identifier)) {
            GameObject go = probeAnchorMap [anchorData.identifier];
            Destroy (go);
            probeAnchorMap.Remove (anchorData.identifier);
        }
    }

    void EnvironmentProbeAnchorAdded (AREnvironmentProbeAnchor anchorData)
    {
        // Reflection Probeの生成
        GameObject go = new GameObject("Probe");        
        go.AddComponent<ReflectionProbe>();
        go.GetComponent<ReflectionProbe>().mode = UnityEngine.Rendering.ReflectionProbeMode.Custom;

        if (go != null) 
        {        
            go.transform.position = UnityARMatrixOps.GetPosition (anchorData.transform);
            go.transform.rotation = UnityARMatrixOps.GetRotation (anchorData.transform);
            probeAnchorMap [anchorData.identifier] = go;
            UpdateTexture(anchorData, go);
        }
    }
}

スクリプトが入力できたら、こちらもGameオブジェクトにアタッチしておきましょう。これで環境マップを使う準備は完了です。

実行してみる

Unityでビルドして実機に転送して実行してみてください。次のように、カメラで撮影した画像がモデルに写り込んでいれば成功です!

f:id:nn_hokuson:20181023192640j:plain:w400

booth.pm

Unityで始めるARKit入門 ARKit Remote編

UnityのARKit Pluginには、作成したアプリを毎回実機に転送しなくてもデバッグできるARKitRemoteという仕組みが用意されています。今回はこのARKitRemoteを使用する方法を紹介します

今回の目次は次のとおりです。

ARKitRemoteとは

ARKitRemoteとはUnity ARKit Pluginに付属するアプリケーションで、これを使えば実機に転送することなくUnityエディタ上でARのデバッグをすることが出来ます。

このARKitRemoteは、実機側にRemote用のアプリを入れておき、ここからiPhoneで撮影した映像をリアルタイムで転送します。そして、Unityエディタ側でこの映像を受け取り、ARの計算した結果をエディタ上に表示するという仕組みです。

f:id:nn_hokuson:20181017185233p:plain:w550

UnityからARKitRemoteを利用するためには次の手順が必要になります。

  1. 実機にARKitRemoteのアプリをインストールする
  2. Unityエディタ側からアプリに接続する

実機にARKitRemoteのアプリをインストールする

まずはARKitRemoteのアプリをビルドして実機にインストールします。Build Settingsを開き、「UnityARKitPlugin/ARKitRemote/UnityARKitRemote」にチェックを入れてください。

また、Development Buildにチェックを入れます。これを忘れると動作しないので注意が必要です!

f:id:nn_hokuson:20181017183125j:plain:w400

ビルドして実機にインストールできたら起動してください。実機側で次のような画面が表示されたらOKです。

f:id:nn_hokuson:20181017183202p:plain:w200

❗❗右下にDevelopment Buildと表示されていることを確認してください❗❗
されていない場合はBuild Settingsでチェックし忘れてないか再確認してください。

Unityエディタ側からアプリに接続する

次にアプリを立ち上げた状態で、ARKitのプログラムをUnityエディタで実行してみましょう。ここではUnityARKitPlugin/ARKitRemote/EditorTestSceneを起動しました。

すると次のような緑の画面がUnityエディタに表示されます。

f:id:nn_hokuson:20181017183437p:plain:w400

次にConsoleウインドウの「Editor▼」から使用する端末を選択します。PCとスマートフォンが同一のLANに接続している場合は、Wifi経由でも接続できますが、とりあえずUSB経由での接続をおすすめします。

f:id:nn_hokuson:20181017183448p:plain:w600

正しく接続できると次のようにUnityエディタの実行画面の上側に「Start Remote ARKit Session」というボタンが表示されるのでクリックしてください。

f:id:nn_hokuson:20181017183458p:plain:w200

UnityエディタにiPhoneで撮影した画像が表示されます。また、EditorTestSceneを実行した場合は、Unityエディタの画面をクリックすると、その場所に立方体が表示されます。

f:id:nn_hokuson:20181017185556j:plain:w500

SwiftでARKitを学ぶ

ARKitRemoteは変更結果をすぐにUnityエディタ上で確認できるので非常に便利でした。
ただ、ARKitRemoteではうまく動かないARの機能もあります。そのため、ARKitの機能を個別に知るにはSwiftでの学習がオススメです。次の参考書を書きましたので、是非参考にしてみてください。

booth.pm

Unityで始めるARKit入門 影の表示編

ARKitを使って影を表示する場合、足元に丸影を置くのが一番簡単です。ただ、見栄え的にはやはりちゃんとした影があったほうがリアルですね。そこで、ARで影を付ける方法を紹介します。

f:id:nn_hokuson:20181011215215j:plain

ARで影を表示する手順

ARKitを使って綺麗な影を付ける方法は、ほとんど丸影をつける方法と同じ手順で出来ます。

  1. 影を表示するための平面を作成
  2. モデルの子要素にする
  3. MobileARShadowシェーダを使用する

3つ目の手順だけが丸影とは異なります。丸影の場合はココに影のテクスチャをセットしますが、今回はリアルな影を表示したいので、UnityのARKit Pluginに用意されているMobileARShadowシェーダを使います。
 

影を表示する平面を作成

ヒエラルキーウインドウから「Create」→「3D Object」→ 「Plane」を選択して、適当な大きさに変形して、足元に配置します。

f:id:nn_hokuson:20181011215412j:plain:w500

影の平面を子要素にする

配置できたら影用の平面をモデルの子要素にします。ヒエラルキーウインドウで平面をモデルにドラッグ&ドロップしてください。

f:id:nn_hokuson:20181011215425p:plain:w270

MobileARShadowシェーダを使用する

配置した平面にMobileARShadowシェーダを適用します。「UnityARKitPlugin/Example/Common/Materials」の中の「ShadowPlane」マテリアルを、平面オブジェクトにドラッグ&ドロップしてください。

f:id:nn_hokuson:20181011215604j:plain:w500

エディタ上でも影だけが表示されているのがわかると思います。後はこのPrefabをARで表示すれば、自動的にモデルの形の影が表示されるようになります。

f:id:nn_hokuson:20181011215215j:plain:w500

ARでタップしたところにモデルを表示する方法は次の記事を参考にして下さい。

nn-hokuson.hatenablog.com

影だけを表示するシェーダの中身を見る

折角なので影を表示するシェーダの中身を見ておきましょう。シェーダは「UnityARKitPlugin/Example/Common/Shaders」の「MobileARShadow.shader」にあります。

Shader "Custom/MobileARShadow"
{
    SubShader {
        Pass {
            Tags { "LightMode" = "ForwardBase" "RenderType"="Opaque" "Queue"="Geometry+1" "ForceNoShadowCasting"="True"  }
	    LOD 150
	    Blend Zero SrcColor
	    ZWrite On
        
            CGPROGRAM
 
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc" 
            #pragma multi_compile_fwdbase
            #include "AutoLight.cginc"
  
            struct v2f
            {
                float4 pos : SV_POSITION;
                LIGHTING_COORDS(0,1)
            };
  
            v2f vert(appdata_base v) {
                v2f o;
                o.pos = UnityObjectToClipPos (v.vertex);
                TRANSFER_VERTEX_TO_FRAGMENT(o);                 
                return o;
            }
 
            fixed4 frag(v2f i) : COLOR {
                float attenuation = LIGHT_ATTENUATION(i);
                return fixed4(1.0,1.0,1.0,1.0) * attenuation;
            }
 
            ENDCG
        }
    }
    Fallback "VertexLit"

}

このシェーダの中ではv2f構造体の中でLIGHTING_COORDSを使ってライトマップとシャドウマップ用のTEXCOORDを生成しています。フラグメントシェーダではLIGHT_ATTENUATIONを使って、先程適宜したライトマップとシャドウマップから光の減衰具合を調べています。これにより影のある場所だけが不透明になり、その他の部分は透明な画像になります。

Unityのシェーダに関しては次の記事も合わせてご覧ください。

nn-hokuson.hatenablog.com

SwiftでARKitを学習したい

SwiftでARKitを試したい場合は、ぜひ拙書の「ARKit超入門」を見ていただければと思います。
ただ、SceneKitでリアルな影を表示するのはUnityほど簡単じゃないかもです・・・(笑)
booth.pm