めーぷるのおもちゃばこ

- アイドルになりたいエンジニア女子の制作日記 -

【Unity】AR Foundationで平面の検出をする

🐼はじめに

UnityのAR Foundationを使用して平面を検出し、平面にオブジェクトを出すまでの一連の流れについてです!

まずAR Foundationの導入方法については以下の記事をご覧ください!
UnityにAR Foundationを入れて基本のセットアップとビルドのセッティングをするまで書いてあります!今回の記事はその続きから書きます。
www.wwwmaplesyrup-cs6.work


  
今回のサンプルプロジェクトはこちら↓↓
github.com


 

🐼開発環境

・Unity 2019.3.15f1
・AR Foundation 2.1.8
・ARKit XR Plugin 2.1.9

  

🐼平面を検出する

まずは平面の検出をできるようにします。
ヒエラルキーのAR Session OriginにインスペクタからAR Plane Managerコンポーネントを追加します。

f:id:maplesyrup-cs6:20200804093522p:plain
AR Plane Managerの追加

    
Ditection Modeは平面検知の種類を水平方向、垂直方向、両方、から選択します。
使用用途で選びましょう。今回は水平方向だけにします。

f:id:maplesyrup-cs6:20200804100140p:plain
Ditection Mode


ちなみに、AR Foundationのドキュメントには以下のように書かれています。

Some platforms require extra work to perform vertical plane detection, so if you only need horizontal planes, you should disable vertical plane detection.
参照元:AR plane manager | AR Foundation | 4.0.2

いくつかのプラットフォームでは垂直平面の検知をするために特殊な追加作業が必要になるため、垂直平面の検知が必要出ない場合は水平方向の検知のみにしておくと良いとのことです。

  

🐼平面検出のプレーンを出す

せっかくなので平面検出のプレーンをだしましょう!(出したくない場合はこの工程はとばしてください!)

f:id:maplesyrup-cs6:20200804094053p:plain
AR Default Planeの追加

  
 
ARのプレーンはGameObject>XR>AR Default Planeを選択します。
ヒエラルキーに追加されたら次にこれを、プロジェクトウィンドウにドラッグ&ドロップでプレファブ化し、ヒエラルキーにある元のやつを削除します。

f:id:maplesyrup-cs6:20200804100451p:plain
プロジェクトウィンドウにドラッグアンドドロップ

  
次にこれを先ほどのAR Session Originに追加したAR Plane ManagerのPlane Prefabのところにセットします。

f:id:maplesyrup-cs6:20200804101303p:plain
Plane Prefabにセット

  
ちなみにこれはプレファブ化しなくても使えるのですが、AR Foundationのドキュメントではプレファブ化することを推奨しています。シーンにプレーンを残すとゼロスケールのプレーンアーティファクトが残ってしまうとのこと。アーティファクトってなんだろう🥺 ぴえん(古物って意味らしい)

It is recommended to save the AR Default Plane as a prefab first, delete the AR Default Plane GameObject, and then use that in the prefab field as leaving the plane in your scene will leave a zero scale plane artifact in the scene.
参照元:AR plane manager | AR Foundation | 4.0.2


これで平面検知の設定完了!
これで一旦ビルドしてみると、こんな感じで床を認識しました。

f:id:maplesyrup-cs6:20200804103526g:plain
床認識してる!


   

🐼タップした場所にオブジェクトを出す

床認識したら次はオブジェクトを出したいですね!
タップしたらその平面にオブジェクトを出しましょう。
まず、

f:id:maplesyrup-cs6:20200804105643p:plain
AR Raycast Managerの追加

  

つづいてプロジェクトウィンドウで右クリックで Create>C# Script からスクリプトを作成します。
ここでは ObjectDeployer という名前にします。
以下のスクリプトを入力します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;   //忘れないように
using UnityEngine.XR.ARSubsystems;   //忘れないように

[RequireComponent(typeof(ARRaycastManager))]
public class ObjectDeployer : MonoBehaviour
{
    [SerializeField] GameObject _objPrefab;
    
    private ARRaycastManager _raycastManager;
    private List<ARRaycastHit> hits = new List<ARRaycastHit>();


    void Start()
    {
        _raycastManager  = GetComponent<ARRaycastManager>();
    }

    void Update()
    {
       if(Input.GetMouseButtonDown(0))
       {
           if(_raycastManager.Raycast(Input.GetTouch(0).position, hits, TrackableType.All))
           {
                Vector3 pos = new Vector3(hits[hits.Count - 1].pose.position.x, 
                hits[hits.Count - 1].pose.position.y + _objPrefab.transform.position.y, 
                hits[hits.Count - 1].pose.position.z);

                Instantiate(_objPrefab, pos, Quaternion.identity);
           }
       } 
    }
}

  
スクリプトがかけたらこれをAR Session Originにセットします。
スクリプトの解説は後に書きます。

f:id:maplesyrup-cs6:20200804113623p:plain
AR Session Originにセットする

 

先ほどセットしたスクリプトに、出したいオブジェクトのプレファブをインスペクタからセットします。
キューブとかをプレファブ化してそれをセットするとかでも大丈夫です。

f:id:maplesyrup-cs6:20200804114126p:plain
プレファブのセット

  
これでビルドしてみます!
わーい!タップしたとこにオブジェクトでましたん!

f:id:maplesyrup-cs6:20200804123120g:plain
わーい

  
※でないよ!って人はもしかしたらオブジェクトがでかすぎて自分がうもれている可能性があるので、オブジェクトは小さめにしておくとよかです。
  
   

🐼コードの解説

コードの解説をしていきます!

[RequireComponent(typeof(ARRaycastManager))]

RequierComponentARRaycastManagerを指定しています。
今回の記事では手動でARRaycastManagerコンポーネントをつけましたが、ここでこう書いておくとこのスクリプトをセットしたときに一緒にARRaycastManagerもセットされるので付け忘れ防止になります。

   
つづいて、

if(_raycastManager.Raycast(Input.GetTouch(0).position, hits, TrackableType.All))
{
    Vector3 pos = new Vector3(hits[hits.Count - 1].pose.position.x, 
    hits[hits.Count - 1].pose.position.y + _objPrefab.transform.position.y, 
    hits[hits.Count - 1].pose.position.z);

    Instantiate(_objPrefab, pos, Quaternion.identity);
}

タップした位置にオブジェクトを出すため、AR Raycast ManagerクラスのRaycastメソッドを使用します。引数は以下の通りです。

public bool Raycast(Vector2 screenPoint, List<ARRaycastHit> hitResults, TrackableType trackableTypeMask = TrackableType.All);

public bool Raycast(Ray ray, List<ARRaycastHit> hitResults, TrackableType trackableTypeMask = TrackableType.All, float pointCloudRaycastAngleInDegrees = 5f);

画面上のタップした座標からRayを飛ばし平面とぶつかった座標をARRaycastHitのリストに格納して返してくれます。
このARRaycastHitのリストに格納されている衝突情報は距離によってソートされます。
0番目が最も近い場所でヒットした情報になります。

今回は、

hits[hits.Count - 1].pose.position

とすることで一番遠い位置(高さが一番下の位置)を平面としています。つまり、机の上と床をよみこむと机の上にだそうとタップしても床の位置にオブジェクトがでます。
逆にhits[0]とすると一番近い位置を平面とするので、その場合机と床を読み取ったら机の高さにオブジェクトがでることになります。
  
また、オブジェクトの出すポジションについて、hits[hits.Count - 1].pose.position.y_objPrefab.transform.position.yを加算しているのは、オブジェクトの原点が中心にあるとプレーンの位置に出した時に半分下が埋もれてしまうので、プレファブ側で少し高さを上げておいてそれをプレーンのy軸に加算することでプレーンの上にうまくでるようにしています。

f:id:maplesyrup-cs6:20200804130820j:plain
原点がまんなかなのでプレーンにうもれちゃう

  
ちなみに、プログラムがわかる方はこの部分をもうすこし簡潔に書きたい場合Linqを使って以下のようにかくこともできます。hits.Last()と書けるので読みやすいです。
こちらの書き方をする場合 using System.Linqを忘れずに。

//Linqを使用してわかりやすく書く場合はこっち
Vector3 pos = new Vector3(hits.Last().pose.position.x, 
hits.Last().pose.position.y+_objPrefab.transform.position.y, 
hits.Last().pose.position.z);

 
第三引数で指定しているTrackableTypeはRayが当たる対象の指定です。
他にも以下のものから選択できます。

[Flags]
public enum TrackableType
{
    None = 0x0,     //検出しない
    PlaneWithinPolygon = 0x1,    //形状に沿った平面
    PlaneWithinBounds = 0x2,    //矩形平面
    PlaneWithinInfinity = 0x4,     //無限遠の平面 
    PlaneEstimated = 0x8,         
    Planes = 0xF,                         //全ての種類の平面
    FeaturePoint = 0x10,             //得頂点
    Image = 0x20,                      
    Face = 0x40,            
    All = 0x7F                             //すべてのタイプを検出
}


以上です!🐼

   

【めも】

平面検出のプレーンを消したい時はSetActivefalseにするのが良いみたいです。
ARPlainManagerが有効になっている間はDestroyするべきでないとAR Foundationのドキュメントに書かれていました。

foreach (var plane in planeManager.trackables)
{
    plane.gameObject.SetActive(false);
}